arcane-os 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +86 -117
  3. package/bin/arcane-test.mjs +170 -46
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
  5. package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
  7. package/browser-runtime/ai/browser-wasm.mjs +24 -35
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
  9. package/browser-runtime/ai/model-controller.mjs +584 -181
  10. package/browser-runtime/ai/speech-worker-client.mjs +8 -148
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
  12. package/browser-runtime/dom-event-instrumentation.mjs +55 -147
  13. package/browser-runtime/event-manager.mjs +239 -624
  14. package/package.json +5 -6
  15. package/runtime/arcane/components/app-bar.html +3 -15
  16. package/runtime/arcane/components/assistant-panel.html +10 -10
  17. package/runtime/arcane/components/calculator.html +1 -1
  18. package/runtime/arcane/components/chat.html +1359 -135
  19. package/runtime/arcane/components/conversation-view.html +2 -2
  20. package/runtime/arcane/components/document-inspector.html +11 -17
  21. package/runtime/arcane/components/file-manager.html +13 -56
  22. package/runtime/arcane/components/markdown-document.html +82 -281
  23. package/runtime/arcane/components/markdown-editor.html +7 -10
  24. package/runtime/arcane/components/media-embed.html +6 -6
  25. package/runtime/arcane/components/screen-capture.html +4 -4
  26. package/runtime/arcane/components/source-explanation.html +2 -2
  27. package/runtime/arcane/components/speech.html +112 -68
  28. package/runtime/arcane/components/terminal-workspace.html +4 -4
  29. package/runtime/arcane/components/theme-editor.html +1 -1
  30. package/runtime/arcane/components/unified-inbox.html +2 -2
  31. package/runtime/arcane/components/voice-transcription.html +31 -21
  32. package/runtime/arcane/entities/Calculation.js +2 -3
  33. package/runtime/arcane/entities/Chat.js +228 -43
  34. package/runtime/arcane/entities/Preference.js +3 -5
  35. package/runtime/arcane/entities/Weather.js +5 -5
  36. package/runtime/arcane/modules/AI.js +1042 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
  38. package/runtime/arcane/modules/AIResponseLength.js +9 -19
  39. package/runtime/arcane/modules/AIRuntimeState.js +109 -72
  40. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
  41. package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
  42. package/runtime/arcane/modules/CalculatorEngine.js +9 -9
  43. package/runtime/arcane/modules/CommunicationAppController.js +3 -7
  44. package/runtime/arcane/modules/ComponentContracts.js +30 -32
  45. package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
  46. package/runtime/arcane/modules/ConversationActionItems.js +26 -59
  47. package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
  48. package/runtime/arcane/modules/ConversationTimebox.js +27 -15
  49. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
  50. package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
  51. package/runtime/arcane/modules/HTMLImport.js +54 -1
  52. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
  53. package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
  54. package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
  55. package/runtime/arcane/modules/MD.js +1 -45
  56. package/runtime/arcane/modules/Mail.js +51 -103
  57. package/runtime/arcane/modules/MailOutbox.mjs +95 -193
  58. package/runtime/arcane/modules/MailTransport.mjs +36 -57
  59. package/runtime/arcane/modules/ModelDefinition.js +22 -106
  60. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
  61. package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
  62. package/runtime/arcane/modules/PreferenceStore.js +102 -30
  63. package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
  64. package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
  65. package/runtime/arcane/modules/ScreenCapture.js +175 -128
  66. package/runtime/arcane/modules/SpeechPlayback.js +46 -149
  67. package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
  68. package/runtime/arcane/modules/ToolCallRouter.js +25 -12
  69. package/runtime/arcane/modules/YouTubeMedia.js +6 -5
  70. package/schemas/arcane-app-bundle.schema.json +13 -78
  71. package/schemas/arcane-app.schema.json +9 -25
  72. package/schemas/arcane-lock.schema.json +18 -151
  73. package/schemas/arcane-package.schema.json +2 -16
  74. package/schemas/native-build-plan.schema.json +119 -122
  75. package/src/app-descriptor.mjs +75 -132
  76. package/src/application-tests.mjs +200 -0
  77. package/src/cli/main.mjs +27 -46
  78. package/src/constants.mjs +3 -4
  79. package/src/dev-server.mjs +30 -324
  80. package/src/doctor.mjs +92 -154
  81. package/src/dom-event-instrumentation.mjs +55 -147
  82. package/src/errors.mjs +2 -3
  83. package/src/event-manager.mjs +239 -624
  84. package/src/event-queue.mjs +3 -3
  85. package/src/import-map.mjs +273 -1028
  86. package/src/index.mjs +14 -16
  87. package/src/installed-sdk-runtime.mjs +27 -67
  88. package/src/integrated-provider-loader.mjs +53 -382
  89. package/src/mail-api.mjs +0 -2
  90. package/src/mail-server.mjs +224 -580
  91. package/src/mail.mjs +4 -10
  92. package/src/native-plan.mjs +163 -598
  93. package/src/native-provider-loader.mjs +104 -1063
  94. package/src/packager/core.mjs +485 -3229
  95. package/src/process.mjs +5 -10
  96. package/src/release-bundle.mjs +292 -2405
  97. package/src/runtime.mjs +76 -396
  98. package/src/scaffold.mjs +30 -80
  99. package/src/sdk-browser-runtime.mjs +70 -626
  100. package/src/source-server.mjs +588 -0
  101. package/src/targets/index.mjs +78 -188
  102. package/src/templates/workspace-template.mjs +19 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +131 -544
  106. package/src/update-check.mjs +26 -64
  107. package/src/workspace-operation-lock.mjs +139 -430
  108. package/src/workspace-runtime.mjs +109 -1558
  109. package/src/workspace.mjs +40 -302
  110. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
  111. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
  112. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
  113. package/browser-runtime/ai/internal/sha256.mjs +0 -166
  114. package/docs/architecture.md +0 -344
  115. package/docs/compatibility.md +0 -36
  116. package/docs/event-manager.md +0 -294
  117. package/docs/platform-targets.md +0 -108
  118. package/docs/publishing.md +0 -201
  119. package/docs/reference/README.md +0 -185
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1295
  122. package/docs/reference/ai/browser-wasm.md +0 -530
  123. package/docs/reference/arcane-ollama.md +0 -288
  124. package/docs/reference/availability-and-normalization.md +0 -183
  125. package/docs/reference/behavioral-testing.md +0 -133
  126. package/docs/reference/cli.md +0 -779
  127. package/docs/reference/core/README.md +0 -62
  128. package/docs/reference/core/arcane-ai-contracts.md +0 -907
  129. package/docs/reference/core/arcane-api.md +0 -601
  130. package/docs/reference/core/arcane-entities.md +0 -65
  131. package/docs/reference/core/arcane-events.md +0 -134
  132. package/docs/reference/core/ollama-module.md +0 -181
  133. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
  134. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
  135. package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
  136. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
  137. package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
  138. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
  139. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
  140. package/docs/reference/event-manager.md +0 -1511
  141. package/docs/reference/inventory/package-api.json +0 -3284
  142. package/docs/reference/inventory/runtime-components.json +0 -1011
  143. package/docs/reference/inventory/runtime-entities.json +0 -26
  144. package/docs/reference/inventory/runtime-modules.json +0 -1431
  145. package/docs/reference/mail.md +0 -316
  146. package/docs/reference/protocols.md +0 -719
  147. package/docs/reference/runtime-components.md +0 -1366
  148. package/docs/reference/runtime-entities.md +0 -303
  149. package/docs/reference/runtime-modules.md +0 -2965
  150. package/docs/reference/sdk-api.md +0 -6698
  151. package/docs/roadmap.md +0 -79
  152. package/docs/work-amplification.md +0 -129
  153. package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
@@ -1,50 +1,37 @@
1
- import {randomBytes} from 'node:crypto';
2
- import {lstat,realpath} from 'node:fs/promises';
1
+ import {lstat,readFile,realpath} from 'node:fs/promises';
3
2
  import path from 'node:path';
4
- import {
5
- appDescriptorSha256,
6
- projectNativeDescriptor,
7
- projectPackageManifest,
8
- validateAppDescriptor
9
- } from './app-descriptor.mjs';
10
- import {
11
- authenticateAppReleaseAuthority,
12
- parseSemver,
13
- readVerifiedAppReleaseFile
14
- } from './packager/core.mjs';
3
+ import {validateAppDescriptor} from './app-descriptor.mjs';
4
+ import {normalizeRelativePath} from './packager/core.mjs';
15
5
  import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
16
6
 
17
7
  export const NATIVE_BUILD_PLAN_PROTOCOL='arcane-native-build-plan/1';
18
8
  export const NATIVE_BUILDER_PROTOCOL='arcane-native-builder/1';
19
9
 
20
- const NATIVE_TARGETS=Object.freeze({
21
- portable:Object.freeze({
10
+ const NATIVE_TARGETS={
11
+ portable:{
22
12
  platforms:new Set(['windows','linux']),
23
13
  architectures:new Set(['x64','arm64']),
24
14
  format:'portable',
25
15
  signingMode:'unsigned-local-test',
26
16
  signingProfileId:null
27
- }),
28
- 'windows-x64':Object.freeze({
17
+ },
18
+ 'windows-x64':{
29
19
  platforms:new Set(['windows']),architectures:new Set(['x64']),format:'exe',
30
20
  signingMode:'unsigned-local-test',signingProfileId:null
31
- }),
32
- 'linux-x64':Object.freeze({
21
+ },
22
+ 'linux-x64':{
33
23
  platforms:new Set(['linux']),architectures:new Set(['x64']),format:'deb',
34
24
  signingMode:'unsigned-local-test',signingProfileId:null
35
- }),
36
- 'linux-arm64':Object.freeze({
25
+ },
26
+ 'linux-arm64':{
37
27
  platforms:new Set(['linux']),architectures:new Set(['arm64']),format:'deb',
38
28
  signingMode:'unsigned-local-test',signingProfileId:null
39
- }),
40
- 'android-arm64':Object.freeze({
29
+ },
30
+ 'android-arm64':{
41
31
  platforms:new Set(['android']),architectures:new Set(['arm64']),format:'apk',
42
32
  signingMode:'development',signingProfileId:'arcane-android-development-v1'
43
- })
44
- });
45
- const SHA256_PATTERN=/^[a-f0-9]{64}$/u;
46
- const issuedPlans=new WeakMap();
47
- const attemptedPlans=new WeakSet();
33
+ }
34
+ };
48
35
 
49
36
  function fail(message,code=ERROR_CODES.policyDenied,details){
50
37
  throw new ArcaneError(code,message,{details});
@@ -54,469 +41,140 @@ function isObject(value){
54
41
  return value!==null&&typeof value==='object'&&!Array.isArray(value);
55
42
  }
56
43
 
57
- function assertOnlyKeys(value,allowed,label){
58
- if(!isObject(value))fail(`${label} must be an object.`);
59
- for(const key of Object.keys(value)){
60
- if(!allowed.has(key))fail(`${label} contains an unsupported key: ${key}.`);
61
- }
62
- }
63
-
64
- function immutable(value){
65
- if(Array.isArray(value))return Object.freeze(value.map(immutable));
66
- if(isObject(value))return Object.freeze(Object.fromEntries(
67
- Object.entries(value).map(([key,item])=>[key,immutable(item)])
68
- ));
69
- return value;
44
+ async function emit(onEvent,event){
45
+ if(typeof onEvent==='function')await onEvent(event);
70
46
  }
71
47
 
72
- function compareSemver(leftValue,rightValue){
73
- const left=parseSemver(leftValue);
74
- const right=parseSemver(rightValue);
75
- for(const key of ['major','minor','patch']){
76
- if(left[key]!==right[key])return left[key]<right[key]?-1:1;
77
- }
78
- if(!left.prerelease.length&&!right.prerelease.length)return 0;
79
- if(!left.prerelease.length)return 1;
80
- if(!right.prerelease.length)return -1;
81
- const length=Math.max(left.prerelease.length,right.prerelease.length);
82
- for(let index=0;index<length;index+=1){
83
- const leftPart=left.prerelease[index];
84
- const rightPart=right.prerelease[index];
85
- if(leftPart===undefined)return -1;
86
- if(rightPart===undefined)return 1;
87
- if(leftPart===rightPart)continue;
88
- const leftNumeric=/^\d+$/u.test(leftPart);
89
- const rightNumeric=/^\d+$/u.test(rightPart);
90
- if(leftNumeric&&rightNumeric){
91
- if(leftPart.length!==rightPart.length)return leftPart.length<rightPart.length?-1:1;
92
- return leftPart<rightPart?-1:1;
93
- }
94
- if(leftNumeric!==rightNumeric)return leftNumeric?-1:1;
95
- return leftPart<rightPart?-1:1;
96
- }
97
- return 0;
98
- }
99
-
100
- function canonicalContractList(value,label,{maximum=4096}={}){
101
- if(!Array.isArray(value)||value.length>maximum||value.some(item=>typeof item!=='string'||!item)){
102
- fail(`${label} must be a bounded, unique, sorted string array.`,ERROR_CODES.integrityFailed);
103
- }
104
- const sorted=[...value].sort((left,right)=>left.localeCompare(right,'en'));
105
- if(new Set(value).size!==value.length||value.some((item,index)=>item!==sorted[index])){
106
- fail(`${label} must be a bounded, unique, sorted string array.`,ERROR_CODES.integrityFailed);
107
- }
108
- return value;
109
- }
110
-
111
- function assertRequirementsAvailable(required,available,label){
112
- if(!Array.isArray(available)){
113
- fail(`The native toolchain receipt does not bind supported ${label}.`,ERROR_CODES.integrityFailed);
114
- }
115
- const missing=required.filter(value=>!available.includes(value));
116
- if(missing.length){
117
- fail(
118
- `The native toolchain does not provide required ${label}: ${missing.join(', ')}.`,
119
- ERROR_CODES.targetUnavailable
120
- );
121
- }
122
- }
123
-
124
- export function assertNativeToolchainCompatibility({
125
- appDescriptor,
126
- toolchainReceipt,
127
- minimumCoreVersion
128
- }={}){
129
- const descriptor=validateAppDescriptor(appDescriptor,{appId:appDescriptor?.id});
130
- const requirements=descriptor.requirements;
131
- if(!isObject(toolchainReceipt)||typeof toolchainReceipt.version!=='string'){
132
- fail('Native compatibility inputs are incomplete.',ERROR_CODES.integrityFailed);
133
- }
134
- if(toolchainReceipt.protocolVersion!==requirements.arcaneProtocol){
135
- fail(
136
- `The app requires ${requirements.arcaneProtocol}, but the native toolchain provides ${String(toolchainReceipt.protocolVersion??'unknown')}.`,
137
- ERROR_CODES.targetUnavailable
138
- );
139
- }
140
- const requiredVersions=[requirements.minimumCoreVersion];
141
- if(minimumCoreVersion!==undefined)requiredVersions.push(minimumCoreVersion);
142
- for(const requiredVersion of requiredVersions){
143
- try{
144
- if(typeof requiredVersion!=='string'||compareSemver(toolchainReceipt.version,requiredVersion)<0){
145
- fail(
146
- `Arcane Core ${toolchainReceipt.version} does not meet the required minimum ${String(requiredVersion??'unknown')}.`,
147
- ERROR_CODES.targetUnavailable
148
- );
149
- }
150
- }catch(error){
151
- if(error instanceof ArcaneError)throw error;
152
- fail('Native compatibility contains an invalid Arcane version.',ERROR_CODES.integrityFailed);
153
- }
154
- }
155
- assertRequirementsAvailable(requirements.features,toolchainReceipt.features,'features');
156
- assertRequirementsAvailable(
157
- descriptor.permissions.capabilities,
158
- toolchainReceipt.supportedCapabilities,
159
- 'capabilities'
160
- );
161
- assertRequirementsAvailable(
162
- descriptor.permissions.methods,
163
- toolchainReceipt.supportedMethods,
164
- 'methods'
165
- );
166
- return descriptor;
167
- }
168
-
169
- function highestCoreMinimum(versions){
170
- let highest=null;
171
- for(const version of versions){
172
- if(version===undefined)continue;
173
- try{
174
- parseSemver(version);
175
- }catch(error){
176
- fail('Native compatibility contains an invalid minimum Core version.',ERROR_CODES.integrityFailed);
177
- }
178
- if(highest===null||compareSemver(version,highest)>0)highest=version;
179
- }
180
- if(highest===null)fail('Native compatibility has no minimum Core version.',ERROR_CODES.integrityFailed);
181
- return highest;
182
- }
183
-
184
- function sameOrDescendant(parent,candidate){
185
- const relative=path.relative(parent,candidate);
186
- return relative===''||Boolean(relative&&!relative.startsWith('..')&&!path.isAbsolute(relative));
187
- }
188
-
189
- function overlaps(left,right){
190
- return sameOrDescendant(left,right)||sameOrDescendant(right,left);
191
- }
192
-
193
- async function canonicalDirectory(value,label){
194
- if(typeof value!=='string'||!value.trim())fail(`${label} is required.`,ERROR_CODES.usage);
195
- const requested=path.resolve(value);
48
+ async function realDirectory(location,label){
49
+ if(typeof location!=='string'||!location.trim())fail(`${label} is required.`,ERROR_CODES.usage);
50
+ const requested=path.resolve(location);
196
51
  let info;
197
52
  try{info=await lstat(requested);}
198
53
  catch(error){
199
- if(error?.code==='ENOENT')fail(`${label} does not exist: ${requested}.`);
54
+ if(error?.code==='ENOENT')fail(`${label} does not exist: ${requested}.`,ERROR_CODES.prerequisiteMissing);
200
55
  throw error;
201
56
  }
202
- if(info.isSymbolicLink()||!info.isDirectory())fail(`${label} must be a real directory.`);
203
- return realpath(requested);
57
+ if(info.isSymbolicLink()||!info.isDirectory()){
58
+ fail(`${label} must be a real directory.`,ERROR_CODES.policyDenied);
59
+ }
60
+ const canonical=await realpath(requested);
61
+ const canonicalInfo=await lstat(canonical);
62
+ if(canonicalInfo.isSymbolicLink()||!canonicalInfo.isDirectory()){
63
+ fail(`${label} must be a real directory.`,ERROR_CODES.policyDenied);
64
+ }
65
+ return canonical;
204
66
  }
205
67
 
206
- async function canonicalFutureDirectory(value,label){
207
- if(typeof value!=='string'||!value.trim())fail(`${label} is required.`,ERROR_CODES.usage);
208
- const requested=path.resolve(value);
209
- let cursor=requested;
210
- const missing=[];
211
- while(true){
212
- try{
213
- const info=await lstat(cursor);
214
- if(info.isSymbolicLink()||!info.isDirectory())fail(`${label} has a linked or non-directory ancestor.`);
215
- const canonical=await realpath(cursor);
216
- return path.join(canonical,...missing.reverse());
217
- }catch(error){
218
- if(error?.code!=='ENOENT')throw error;
219
- const parent=path.dirname(cursor);
220
- if(parent===cursor)throw error;
221
- missing.push(path.basename(cursor));
222
- cursor=parent;
223
- }
224
- }
68
+ function inside(root,candidate,{allowEqual=false}={}){
69
+ const relative=path.relative(path.resolve(root),path.resolve(candidate));
70
+ return (allowEqual&&relative==='')
71
+ ||Boolean(relative&&!relative.startsWith('..')&&!path.isAbsolute(relative));
225
72
  }
226
73
 
227
- export function validateNativeBuilder(provider){
228
- const methods=['describe','doctor','prepare','authenticateToolchainReceipt','build','verify','run'];
229
- if(!isObject(provider)||provider.protocol!==NATIVE_BUILDER_PROTOCOL
230
- ||methods.some(method=>typeof provider[method]!=='function')){
231
- fail(`Native builder must implement ${NATIVE_BUILDER_PROTOCOL} and its complete operation contract.`,ERROR_CODES.targetUnavailable);
232
- }
233
- return provider;
74
+ function overlap(left,right){
75
+ return inside(left,right,{allowEqual:true})||inside(right,left,{allowEqual:true});
234
76
  }
235
77
 
236
- function validateTargetRequest(value){
237
- assertOnlyKeys(value,new Set(['target','platform','architecture','format','signing']),'targetRequest');
78
+ function selectedTargetRequest(value){
79
+ if(!isObject(value))fail('targetRequest must be an object.',ERROR_CODES.usage);
238
80
  const definition=NATIVE_TARGETS[value.target];
239
81
  if(!definition)fail(`Unsupported native target: ${String(value.target)}.`,ERROR_CODES.targetUnavailable);
240
- if(!definition.platforms.has(value.platform)||!definition.architectures.has(value.architecture)
241
- ||value.format!==definition.format){
242
- fail(`Target ${value.target} does not support ${value.platform}/${value.architecture}/${value.format}.`,ERROR_CODES.targetUnavailable);
82
+ if(!definition.platforms.has(value.platform)){
83
+ fail(`Target ${value.target} does not support platform ${String(value.platform)}.`,ERROR_CODES.targetUnavailable);
243
84
  }
244
- assertOnlyKeys(value.signing,new Set(['mode','profileId']),'targetRequest.signing');
245
- if(!Object.hasOwn(value.signing,'mode')||!Object.hasOwn(value.signing,'profileId')
246
- ||value.signing.mode!==definition.signingMode
247
- ||value.signing.profileId!==definition.signingProfileId){
248
- const profileRequirement=definition.signingProfileId===null
249
- ?'a null profileId'
250
- :`profileId ${definition.signingProfileId}`;
251
- fail(
252
- `Target ${value.target} requires ${definition.format} with ${definition.signingMode} signing and ${profileRequirement}.`,
253
- ERROR_CODES.targetUnavailable
254
- );
85
+ if(!definition.architectures.has(value.architecture)){
86
+ fail(`Target ${value.target} does not support architecture ${String(value.architecture)}.`,ERROR_CODES.targetUnavailable);
87
+ }
88
+ if(value.format!==definition.format){
89
+ fail(`Target ${value.target} requires format ${definition.format}.`,ERROR_CODES.targetUnavailable);
90
+ }
91
+ if(value.signing!==definition.signingMode){
92
+ fail(`Target ${value.target} requires signing mode ${definition.signingMode}.`,ERROR_CODES.targetUnavailable);
93
+ }
94
+ if((value.signingProfileId??null)!==definition.signingProfileId){
95
+ fail(`Target ${value.target} has a different signing profile.`,ERROR_CODES.targetUnavailable);
255
96
  }
256
- return immutable({
97
+ return {
257
98
  target:value.target,
258
99
  platform:value.platform,
259
100
  architecture:value.architecture,
260
101
  format:value.format,
261
- signing:{mode:value.signing.mode,profileId:value.signing.profileId}
262
- });
263
- }
264
-
265
- function releaseApp(receipt){
266
- return isObject(receipt?.app)?receipt.app:null;
102
+ signing:value.signing,
103
+ signingProfileId:value.signingProfileId??null
104
+ };
267
105
  }
268
106
 
269
- function releaseSummary(receipt){
270
- const app=releaseApp(receipt);
271
- if(!app||typeof app.id!=='string'||typeof app.version!=='string'
272
- ||!SHA256_PATTERN.test(receipt.policySha256)
273
- ||!SHA256_PATTERN.test(receipt.contentSha256)){
274
- fail('The app release receipt is missing its verified identity.',ERROR_CODES.integrityFailed);
275
- }
276
- return immutable({
277
- canonicalLocation:receipt.canonicalLocation,
278
- policySha256:receipt.policySha256,
279
- contentSha256:receipt.contentSha256,
280
- fileCount:receipt.fileCount,
281
- totalBytes:receipt.totalBytes
282
- });
283
- }
284
-
285
- async function authenticateReleaseAuthority(
286
- receipt,
287
- releaseRoot,
288
- signal,
289
- expectedPackageConfig,
290
- expectedDescriptor
291
- ){
292
- try{
293
- return await authenticateAppReleaseAuthority(receipt,{
294
- releaseRoot,
295
- expectedPackageConfig,
296
- expectedDescriptor,
297
- signal
298
- });
299
- }catch(error){
300
- if(error?.code===ERROR_CODES.cancelled)throw error;
301
- throw new ArcaneError(
302
- ERROR_CODES.integrityFailed,
303
- `App release authentication failed: ${error instanceof Error?error.message:String(error)}`,
304
- {cause:error}
305
- );
306
- }
307
- }
308
-
309
- async function authenticatedRelease(item,targetRequest,{signal}){
310
- assertOnlyKeys(
311
- item,
312
- new Set(['appDescriptor','appReleaseRoot','appReleaseReceipt']),
313
- 'application release input'
314
- );
315
- const presentedDescriptor=validateAppDescriptor(
316
- item.appDescriptor,
317
- {appId:item.appDescriptor?.id}
318
- );
319
- const root=await canonicalDirectory(
320
- item.appReleaseRoot,
321
- `Release root for ${presentedDescriptor.id}`
322
- );
323
- const authority=await authenticateReleaseAuthority(
324
- item.appReleaseReceipt,
325
- root,
326
- signal,
327
- projectPackageManifest(presentedDescriptor),
328
- presentedDescriptor
329
- );
330
- const receipt=authority.receipt;
331
- const descriptor=validateAppDescriptor(authority.descriptor,{
332
- appId:presentedDescriptor.id
333
- });
334
- if(appDescriptorSha256(descriptor)!==authority.descriptorSha256){
335
- fail('App release descriptor authority hash is invalid.',ERROR_CODES.integrityFailed);
336
- }
337
- projectNativeDescriptor(descriptor);
338
- if(!descriptor.targets.includes(targetRequest.target)){
339
- fail(`App ${descriptor.id} does not declare target ${targetRequest.target}.`);
340
- }
341
- const app=releaseApp(receipt);
342
- if(app?.id!==descriptor.id||app?.version!==descriptor.version){
343
- fail(`Release identity for ${descriptor.id} does not match its canonical descriptor.`,ERROR_CODES.integrityFailed);
107
+ export function validateNativeBuilder(provider){
108
+ if(!isObject(provider))fail('Native builder must be an object.',ERROR_CODES.usage);
109
+ if(provider.protocol!==NATIVE_BUILDER_PROTOCOL){
110
+ fail(`Native builder protocol must be ${NATIVE_BUILDER_PROTOCOL}.`,ERROR_CODES.targetUnavailable);
344
111
  }
345
- return Object.freeze({
346
- descriptor,
347
- descriptorSha256:authority.descriptorSha256,
348
- releaseRoot:root,
349
- releaseReceipt:receipt,
350
- readFile:(relativePath,{signal}={})=>readVerifiedAppReleaseFile(receipt,{
351
- releaseRoot:root,
352
- relativePath,
353
- signal
354
- }),
355
- summary:immutable({
356
- id:descriptor.id,
357
- version:descriptor.version,
358
- descriptorSha256:authority.descriptorSha256,
359
- release:releaseSummary(receipt)
360
- })
361
- });
362
- }
363
-
364
- function assertDependencyClosure(application,dependencies){
365
- const byId=new Map(dependencies.map(item=>[item.descriptor.id,item]));
366
- if(byId.size!==dependencies.length)fail('Dependency releases must have unique app ids.');
367
- if(byId.has(application.descriptor.id))fail('The selected app must not also be supplied as a dependency.');
368
- const reachable=new Set();
369
- const visiting=new Set();
370
- function visit(descriptor){
371
- if(visiting.has(descriptor.id))fail(`Bundled app dependency cycle includes ${descriptor.id}.`);
372
- visiting.add(descriptor.id);
373
- for(const id of descriptor.native.bundledApps){
374
- const dependency=byId.get(id);
375
- if(!dependency)fail(`Bundled app dependency ${id} is missing its verified release.`);
376
- if(visiting.has(id))fail(`Bundled app dependency cycle includes ${id}.`);
377
- if(!reachable.has(id)){
378
- reachable.add(id);
379
- visit(dependency.descriptor);
380
- }
112
+ for(const method of ['describe','doctor','prepare','build','verify','run']){
113
+ if(typeof provider[method]!=='function'){
114
+ fail(`Native builder must implement ${method}().`,ERROR_CODES.targetUnavailable);
381
115
  }
382
- visiting.delete(descriptor.id);
383
- }
384
- visit(application.descriptor);
385
- const extra=[...byId.keys()].filter(id=>!reachable.has(id));
386
- if(extra.length)fail(`Unrequested dependency releases were supplied: ${extra.sort().join(', ')}.`);
387
- }
388
-
389
- function toolchainSummary(receipt,toolchainRoot){
390
- if(!isObject(receipt)||receipt.canonicalLocation!==toolchainRoot
391
- ||typeof receipt.kind!=='string'||!receipt.kind
392
- ||typeof receipt.version!=='string'||!receipt.version
393
- ||typeof receipt.protocolVersion!=='string'||!/^arcane\/[1-9][0-9]*$/u.test(receipt.protocolVersion)
394
- ||!SHA256_PATTERN.test(receipt.contentSha256)){
395
- fail('The native builder returned an invalid authenticated toolchain receipt.',ERROR_CODES.integrityFailed);
396
- }
397
- const features=canonicalContractList(receipt.features,'toolchainReceipt.features',{maximum:256});
398
- const supportedCapabilities=canonicalContractList(
399
- receipt.supportedCapabilities,
400
- 'toolchainReceipt.supportedCapabilities'
401
- );
402
- const supportedMethods=canonicalContractList(receipt.supportedMethods,'toolchainReceipt.supportedMethods');
403
- return immutable({
404
- kind:receipt.kind,
405
- version:receipt.version,
406
- protocolVersion:receipt.protocolVersion,
407
- features,
408
- supportedCapabilities,
409
- supportedMethods,
410
- canonicalLocation:receipt.canonicalLocation,
411
- contentSha256:receipt.contentSha256
412
- });
413
- }
414
-
415
- function providerGenerationSummary(value,{optional=false}={}){
416
- if(value===undefined&&optional)return null;
417
- if(!isObject(value)
418
- ||value.kind!=='arcane-native-provider-generation'
419
- ||!SHA256_PATTERN.test(value.generationSha256)
420
- ||!SHA256_PATTERN.test(value.contentSha256)
421
- ||!Number.isInteger(value.moduleCount)
422
- ||value.moduleCount<1
423
- ||value.moduleCount>128){
424
- fail(
425
- 'Native provider generation evidence is invalid.',
426
- ERROR_CODES.integrityFailed
427
- );
428
- }
429
- return immutable({
430
- kind:'arcane-native-provider-generation',
431
- generationSha256:value.generationSha256,
432
- contentSha256:value.contentSha256,
433
- moduleCount:value.moduleCount
434
- });
435
- }
436
-
437
- function assertProviderGenerationBinding(summary,provider,label){
438
- if(!summary)return;
439
- const actual=providerGenerationSummary(provider?.providerGeneration,{optional:true});
440
- if(!actual||JSON.stringify(actual)!==JSON.stringify(summary)){
441
- fail(
442
- `${label} belongs to a different native provider module generation.`,
443
- ERROR_CODES.integrityFailed
444
- );
445
116
  }
117
+ return provider;
446
118
  }
447
119
 
448
- function assertExpectedPlanBinding(state,{expectedNativeBuilder,expectedTarget}={}){
449
- assertProviderGenerationBinding(
450
- state.providerGeneration,
451
- state.provider,
452
- 'Native build plan'
453
- );
454
- if(expectedNativeBuilder!==undefined){
455
- assertProviderGenerationBinding(
456
- state.providerGeneration,
457
- expectedNativeBuilder,
458
- 'Expected native builder'
459
- );
120
+ async function releaseSelection(releaseRoot,release,label){
121
+ const root=await realDirectory(releaseRoot,`${label} root`);
122
+ if(!isObject(release)||!Array.isArray(release.files)){
123
+ fail(`${label} must provide its structural file inventory.`,ERROR_CODES.usage);
460
124
  }
461
- if(expectedNativeBuilder!==undefined&&state.provider!==expectedNativeBuilder){
462
- fail('Native build plan belongs to a different paired provider.',ERROR_CODES.integrityFailed);
463
- }
464
- if(expectedTarget!==undefined&&state.plan.targetRequest.target!==expectedTarget){
465
- fail(
466
- `Native build plan target ${state.plan.targetRequest.target} does not match ${expectedTarget}.`,
467
- ERROR_CODES.integrityFailed
468
- );
125
+ const files=release.files.map((entry,index)=>normalizeRelativePath(
126
+ entry,
127
+ `${label}.files[${index}]`
128
+ ));
129
+ if(new Set(files.map(file=>file.toLocaleLowerCase('en-US'))).size!==files.length){
130
+ fail(`${label} contains duplicate file paths.`,ERROR_CODES.usage);
131
+ }
132
+ async function readSelectedFile(relativePath,{signal}={}){
133
+ throwIfAborted(signal);
134
+ const normalized=normalizeRelativePath(relativePath,`${label} file`);
135
+ if(!files.some(file=>file.toLocaleLowerCase('en-US')===normalized.toLocaleLowerCase('en-US'))){
136
+ fail(`${label} does not contain ${normalized}.`,ERROR_CODES.usage);
137
+ }
138
+ const selected=path.resolve(root,...normalized.split('/'));
139
+ if(!inside(root,selected))fail(`${label} file leaves its root.`,ERROR_CODES.policyDenied);
140
+ const info=await lstat(selected);
141
+ if(info.isSymbolicLink()||!info.isFile()){
142
+ fail(`${label} file must be a real file: ${normalized}.`,ERROR_CODES.policyDenied);
143
+ }
144
+ const canonical=await realpath(selected);
145
+ if(!inside(root,canonical))fail(`${label} file resolves outside its root.`,ERROR_CODES.policyDenied);
146
+ return readFile(canonical);
469
147
  }
148
+ return {root,release:{...release,files:[...files]},readFile:readSelectedFile};
470
149
  }
471
150
 
472
- async function authenticateState(state,{expectedNativeBuilder,expectedTarget,signal,onEvent}={}){
473
- throwIfAborted(signal);
474
- assertExpectedPlanBinding(state,{expectedNativeBuilder,expectedTarget});
475
- const authenticatedToolchain=await state.provider.authenticateToolchainReceipt(
476
- state.toolchainReceipt,
477
- {toolchainRoot:state.toolchainRoot,signal,onEvent}
478
- );
479
- const summary=toolchainSummary(authenticatedToolchain,state.toolchainRoot);
480
- if(JSON.stringify(summary)!==JSON.stringify(state.plan.toolchain)){
481
- fail('The native toolchain identity changed after plan creation.',ERROR_CODES.integrityFailed);
482
- }
483
- for(const item of [state.application,...state.dependencies]){
484
- assertNativeToolchainCompatibility({
485
- appDescriptor:item.descriptor,
486
- toolchainReceipt:summary,
487
- minimumCoreVersion:state.plan.minimumCoreVersion
488
- });
489
- }
490
- await authenticateReleaseAuthority(
491
- state.application.releaseReceipt,
492
- state.application.releaseRoot,
493
- signal,
494
- projectPackageManifest(state.application.descriptor),
495
- state.application.descriptor
496
- );
497
- for(const dependency of state.dependencies){
498
- await authenticateReleaseAuthority(
499
- dependency.releaseReceipt,
500
- dependency.releaseRoot,
501
- signal,
502
- projectPackageManifest(dependency.descriptor),
503
- dependency.descriptor
151
+ async function dependencySelections(value,{signal}={}){
152
+ if(value===undefined)return [];
153
+ if(!Array.isArray(value))fail('dependencyReleases must be an array.',ERROR_CODES.usage);
154
+ const dependencies=[];
155
+ for(const [index,item] of value.entries()){
156
+ throwIfAborted(signal);
157
+ if(!isObject(item)||typeof item.appId!=='string'){
158
+ fail(`dependencyReleases[${index}] is malformed.`,ERROR_CODES.usage);
159
+ }
160
+ const selected=await releaseSelection(
161
+ item.releaseRoot,
162
+ item.release,
163
+ `dependency release ${item.appId}`
504
164
  );
165
+ dependencies.push({appId:item.appId,...selected});
505
166
  }
506
- const outputRoot=await canonicalFutureDirectory(state.outputRoot,'Native output root');
507
- if(outputRoot!==state.outputRoot)fail('The native output root identity changed after plan creation.',ERROR_CODES.integrityFailed);
508
- return state.plan;
167
+ return dependencies;
509
168
  }
510
169
 
511
170
  export async function createNativeBuildPlan({
512
171
  nativeBuilder,
513
172
  toolchainRoot,
514
- toolchainReceipt,
173
+ toolchain,
515
174
  appReleaseRoot,
516
- appReleaseReceipt,
175
+ release,
517
176
  appDescriptor,
518
- dependencyReleases=[],
519
- providerGeneration,
177
+ dependencyReleases,
520
178
  minimumCoreVersion,
521
179
  protectedRoots=[],
522
180
  outputRoot,
@@ -525,174 +183,81 @@ export async function createNativeBuildPlan({
525
183
  onEvent
526
184
  }={}){
527
185
  throwIfAborted(signal);
528
- await onEvent?.(Object.freeze({type:'native.plan.started'}));
529
- const request=validateTargetRequest(targetRequest);
530
186
  const provider=validateNativeBuilder(nativeBuilder);
531
- const generation=providerGenerationSummary(
532
- providerGeneration===undefined?provider.providerGeneration:providerGeneration,
533
- {optional:true}
534
- );
535
- assertProviderGenerationBinding(generation,provider,'Native build plan');
536
- const canonicalToolchainRoot=await canonicalDirectory(toolchainRoot,'Native toolchain root');
537
- const authenticatedToolchain=await provider.authenticateToolchainReceipt(
538
- toolchainReceipt,
539
- {toolchainRoot:canonicalToolchainRoot,signal,onEvent}
540
- );
541
- const authenticatedToolchainSummary=toolchainSummary(authenticatedToolchain,canonicalToolchainRoot);
542
- const application=await authenticatedRelease({
543
- appDescriptor,
544
- appReleaseRoot,
545
- appReleaseReceipt
546
- },request,{signal});
547
- if(!Array.isArray(dependencyReleases)||dependencyReleases.length>64){
548
- fail('dependencyReleases must be an array with at most 64 entries.');
187
+ const selectedToolchainRoot=await realDirectory(toolchainRoot,'Native toolchain root');
188
+ if(!isObject(toolchain))fail('Native toolchain preparation result is required.',ERROR_CODES.usage);
189
+ const descriptor=JSON.parse(JSON.stringify(
190
+ validateAppDescriptor(appDescriptor,{appId:appDescriptor?.id})
191
+ ));
192
+ const application=await releaseSelection(appReleaseRoot,release,'application release');
193
+ const dependencies=await dependencySelections(dependencyReleases,{signal});
194
+ if(!Array.isArray(protectedRoots)){
195
+ fail('protectedRoots must be an array.',ERROR_CODES.usage);
549
196
  }
550
- const dependencies=[];
551
- for(const dependency of dependencyReleases){
552
- dependencies.push(await authenticatedRelease(dependency,request,{signal}));
197
+ if(typeof outputRoot!=='string'||!outputRoot.trim()){
198
+ fail('outputRoot is required.',ERROR_CODES.usage);
553
199
  }
554
- assertDependencyClosure(application,dependencies);
555
- dependencies.sort((left,right)=>left.descriptor.id.localeCompare(right.descriptor.id,'en'));
556
- const requiredCoreVersion=highestCoreMinimum([
557
- minimumCoreVersion,
558
- application.descriptor.requirements.minimumCoreVersion,
559
- ...dependencies.map(item=>item.descriptor.requirements.minimumCoreVersion)
560
- ]);
561
- for(const item of [application,...dependencies]){
562
- assertNativeToolchainCompatibility({
563
- appDescriptor:item.descriptor,
564
- toolchainReceipt:authenticatedToolchainSummary,
565
- minimumCoreVersion:requiredCoreVersion
566
- });
200
+ const selectedOutput=path.resolve(outputRoot);
201
+ if(protectedRoots.some(root=>overlap(path.resolve(root),selectedOutput))){
202
+ fail('Native output root overlaps a protected source directory.',ERROR_CODES.policyDenied);
567
203
  }
568
-
569
- const canonicalOutputRoot=await canonicalFutureDirectory(outputRoot,'Native output root');
570
- const protectedPaths=[
571
- canonicalToolchainRoot,
572
- application.releaseRoot,
573
- ...dependencies.map(item=>item.releaseRoot)
574
- ];
575
- if(!Array.isArray(protectedRoots))fail('protectedRoots must be an array.');
576
- for(const [index,root] of protectedRoots.entries()){
577
- protectedPaths.push(await canonicalDirectory(root,`Protected source root ${index}`));
578
- }
579
- const conflict=protectedPaths.find(root=>overlaps(root,canonicalOutputRoot));
580
- if(conflict)fail('Native output root must not overlap a toolchain, source, or verified release root.');
581
-
582
- const plan=immutable({
204
+ const request=selectedTargetRequest(targetRequest);
205
+ const plan={
583
206
  protocol:NATIVE_BUILD_PLAN_PROTOCOL,
584
- generation:randomBytes(16).toString('hex'),
585
- ...(generation?{providerGeneration:generation}:{}),
586
- toolchain:authenticatedToolchainSummary,
587
- minimumCoreVersion:requiredCoreVersion,
588
- app:application.summary,
589
- dependencies:dependencies.map(item=>item.summary),
590
- targetRequest:request,
591
- outputRoot:canonicalOutputRoot,
592
- operations:[
593
- 'authenticate-toolchain',
594
- 'authenticate-app-release',
595
- 'authenticate-dependency-releases',
596
- 'build-selected-target',
597
- 'verify-native-artifact'
598
- ]
599
- });
600
- issuedPlans.set(plan,Object.freeze({
601
- plan,
602
- provider,
603
- providerGeneration:generation,
604
- toolchainRoot:canonicalToolchainRoot,
605
- toolchainReceipt,
606
- minimumCoreVersion:requiredCoreVersion,
207
+ nativeBuilder:provider,
208
+ toolchainRoot:selectedToolchainRoot,
209
+ toolchain:{...toolchain},
210
+ appDescriptor:descriptor,
607
211
  application,
608
- dependencies:Object.freeze(dependencies),
609
- outputRoot:canonicalOutputRoot,
212
+ dependencies,
213
+ minimumCoreVersion:minimumCoreVersion??null,
214
+ protectedRoots:protectedRoots.map(root=>path.resolve(root)),
215
+ outputRoot:selectedOutput,
610
216
  targetRequest:request
611
- }));
612
- await onEvent?.(Object.freeze({type:'native.plan.completed',appId:application.descriptor.id,target:request.target}));
217
+ };
218
+ await emit(onEvent,{type:'native.plan.created',target:request.target,outputRoot:selectedOutput});
613
219
  return plan;
614
220
  }
615
221
 
616
- export async function authenticateNativeBuildPlan(plan,{
617
- expectedNativeBuilder,
618
- expectedTarget,
619
- signal,
620
- onEvent
621
- }={}){
622
- const state=issuedPlans.get(plan);
623
- if(!state)fail('Native build plan was not issued by this SDK process.',ERROR_CODES.integrityFailed);
624
- return authenticateState(state,{expectedNativeBuilder,expectedTarget,signal,onEvent});
625
- }
626
-
627
222
  export async function executeNativeBuildPlan(plan,{
628
223
  expectedNativeBuilder,
629
224
  expectedTarget,
630
225
  signal,
631
226
  onEvent
632
227
  }={}){
633
- const state=issuedPlans.get(plan);
634
- if(!state)fail('Native build plan was not issued by this SDK process.',ERROR_CODES.integrityFailed);
635
228
  throwIfAborted(signal);
636
- if(attemptedPlans.has(plan)){
637
- fail(
638
- 'Native build plan generation has already been attempted; create a new plan before retrying.',
639
- ERROR_CODES.integrityFailed
640
- );
641
- }
642
- attemptedPlans.add(plan);
643
- await onEvent?.(Object.freeze({type:'native.build.started',appId:plan.app.id,target:plan.targetRequest.target}));
644
- await authenticateState(state,{expectedNativeBuilder,expectedTarget,signal,onEvent});
645
- const result=await state.provider.build({
646
- toolchainRoot:state.toolchainRoot,
647
- toolchainReceipt:state.toolchainReceipt,
648
- appReleaseRoot:state.application.releaseRoot,
649
- appReleaseReceipt:state.application.releaseReceipt,
650
- readAppReleaseFile:(relativePath)=>state.application.readFile(relativePath,{signal}),
651
- appDescriptor:state.application.descriptor,
652
- descriptorSha256:state.application.descriptorSha256,
653
- dependencyReleases:state.dependencies.map(item=>Object.freeze({
654
- appReleaseRoot:item.releaseRoot,
655
- appReleaseReceipt:item.releaseReceipt,
656
- readFile:(relativePath)=>item.readFile(relativePath,{signal}),
657
- appDescriptor:item.descriptor,
658
- descriptorSha256:item.descriptorSha256
229
+ if(!isObject(plan)||plan.protocol!==NATIVE_BUILD_PLAN_PROTOCOL){
230
+ fail('Native build plan is malformed.',ERROR_CODES.usage);
231
+ }
232
+ const provider=validateNativeBuilder(plan.nativeBuilder);
233
+ if(expectedNativeBuilder!==undefined&&provider!==expectedNativeBuilder){
234
+ fail('Native build plan belongs to a different provider.',ERROR_CODES.usage);
235
+ }
236
+ if(expectedTarget!==undefined&&plan.targetRequest?.target!==expectedTarget){
237
+ fail('Native build plan targets a different platform.',ERROR_CODES.usage);
238
+ }
239
+ await emit(onEvent,{type:'native.build.started',target:plan.targetRequest.target});
240
+ const result=await provider.build({
241
+ toolchainRoot:plan.toolchainRoot,
242
+ toolchain:plan.toolchain,
243
+ appDescriptor:plan.appDescriptor,
244
+ appReleaseRoot:plan.application.root,
245
+ release:plan.application.release,
246
+ readReleaseFile:plan.application.readFile,
247
+ dependencies:plan.dependencies.map(dependency=>({
248
+ appId:dependency.appId,
249
+ releaseRoot:dependency.root,
250
+ release:dependency.release,
251
+ readReleaseFile:dependency.readFile
659
252
  })),
660
- outputRoot:state.outputRoot,
661
- targetRequest:state.targetRequest,
253
+ minimumCoreVersion:plan.minimumCoreVersion,
254
+ protectedRoots:[...plan.protectedRoots],
255
+ outputRoot:plan.outputRoot,
256
+ targetRequest:{...plan.targetRequest},
662
257
  signal,
663
258
  onEvent
664
259
  });
665
- if(!isObject(result)||!result.artifactReceipt){
666
- fail('Native builder did not return an artifact receipt.',ERROR_CODES.integrityFailed);
667
- }
668
- await onEvent?.(Object.freeze({
669
- type:'native.build.committed',
670
- appId:plan.app.id,
671
- target:plan.targetRequest.target
672
- }));
673
- await onEvent?.(Object.freeze({
674
- type:'native.verify.started',
675
- appId:plan.app.id,
676
- target:plan.targetRequest.target
677
- }));
678
- const artifactVerification=await state.provider.verify({
679
- toolchainRoot:state.toolchainRoot,
680
- toolchainReceipt:state.toolchainReceipt,
681
- artifactReceipt:result.artifactReceipt,
682
- outputRoot:state.outputRoot,
683
- targetRequest:state.targetRequest,
684
- signal,
685
- onEvent
686
- });
687
- if(!isObject(artifactVerification)||artifactVerification.verified!==true){
688
- fail('Native builder did not verify the built artifact.',ERROR_CODES.integrityFailed);
689
- }
690
260
  throwIfAborted(signal);
691
- await onEvent?.(Object.freeze({
692
- type:'native.verify.completed',
693
- appId:plan.app.id,
694
- target:plan.targetRequest.target
695
- }));
696
- await onEvent?.(Object.freeze({type:'native.build.completed',appId:plan.app.id,target:plan.targetRequest.target}));
697
- return {...result,artifactVerification};
261
+ await emit(onEvent,{type:'native.build.completed',target:plan.targetRequest.target});
262
+ return result;
698
263
  }