arcane-os 0.2.3 → 0.3.1

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 (118) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +8 -8
  3. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
  4. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
  5. package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
  6. package/browser-runtime/ai/browser-speech-artifacts.mjs +3242 -391
  7. package/browser-runtime/ai/browser-speech-providers.mjs +1209 -157
  8. package/browser-runtime/ai/browser-speech.mjs +2 -0
  9. package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
  10. package/browser-runtime/ai/model-controller.mjs +285 -95
  11. package/browser-runtime/ai/speech-worker-client.mjs +249 -32
  12. package/browser-runtime/ai/speech-worker-runtime.mjs +2322 -167
  13. package/browser-runtime/event-manager.mjs +1097 -1
  14. package/docs/architecture.md +2 -2
  15. package/docs/event-manager.md +155 -27
  16. package/docs/reference/README.md +18 -20
  17. package/docs/reference/ai/browser-speech-package-authority.json +835 -0
  18. package/docs/reference/ai/browser-speech.md +1205 -246
  19. package/docs/reference/ai/browser-wasm.md +18 -7
  20. package/docs/reference/availability-and-normalization.md +2 -2
  21. package/docs/reference/behavioral-testing.md +28 -5
  22. package/docs/reference/cli.md +115 -7
  23. package/docs/reference/core/arcane-ai-contracts.md +1 -1
  24. package/docs/reference/event-manager.md +577 -32
  25. package/docs/reference/inventory/package-api.json +478 -2
  26. package/docs/reference/inventory/runtime-components.json +86 -37
  27. package/docs/reference/inventory/runtime-modules.json +126 -49
  28. package/docs/reference/mail.md +316 -0
  29. package/docs/reference/protocols.md +185 -33
  30. package/docs/reference/runtime-components.md +192 -80
  31. package/docs/reference/runtime-modules.md +589 -70
  32. package/docs/reference/sdk-api.md +1017 -24
  33. package/package.json +5 -4
  34. package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
  35. package/runtime/arcane/components/app-bar.html +34 -13
  36. package/runtime/arcane/components/assistant-panel.html +110 -57
  37. package/runtime/arcane/components/calculator.html +7 -4
  38. package/runtime/arcane/components/chart.html +58 -17
  39. package/runtime/arcane/components/chat.html +606 -136
  40. package/runtime/arcane/components/conversation-view.html +13 -6
  41. package/runtime/arcane/components/dashboard-config.html +96 -59
  42. package/runtime/arcane/components/data-maintenance.html +69 -14
  43. package/runtime/arcane/components/data-view.html +53 -7
  44. package/runtime/arcane/components/directory-picker.html +118 -32
  45. package/runtime/arcane/components/document-inspector.html +47 -10
  46. package/runtime/arcane/components/file-drop.html +81 -35
  47. package/runtime/arcane/components/file-inspector.html +72 -22
  48. package/runtime/arcane/components/file-manager.html +374 -79
  49. package/runtime/arcane/components/integration-settings.html +12 -5
  50. package/runtime/arcane/components/local-ai-status.html +48 -19
  51. package/runtime/arcane/components/markdown-document.html +161 -68
  52. package/runtime/arcane/components/markdown-editor.html +110 -33
  53. package/runtime/arcane/components/media-embed.html +8 -5
  54. package/runtime/arcane/components/modal.html +15 -5
  55. package/runtime/arcane/components/output-panel.html +28 -23
  56. package/runtime/arcane/components/preferences-form.html +22 -4
  57. package/runtime/arcane/components/record-timeline.html +18 -2
  58. package/runtime/arcane/components/relationship-board.html +23 -3
  59. package/runtime/arcane/components/screen-capture.html +10 -4
  60. package/runtime/arcane/components/source-code-viewer.html +76 -9
  61. package/runtime/arcane/components/source-explanation.html +23 -3
  62. package/runtime/arcane/components/speech.html +453 -131
  63. package/runtime/arcane/components/summary-strip.html +22 -11
  64. package/runtime/arcane/components/table.html +39 -21
  65. package/runtime/arcane/components/task-progress.html +79 -21
  66. package/runtime/arcane/components/terminal-workspace.html +7 -4
  67. package/runtime/arcane/components/theme-editor.html +7 -3
  68. package/runtime/arcane/components/unified-inbox.html +9 -4
  69. package/runtime/arcane/components/voice-transcription.html +244 -72
  70. package/runtime/arcane/components/weather-widget.html +5 -3
  71. package/runtime/arcane/components/web-navigator.html +48 -8
  72. package/runtime/arcane/entities/Chat.js +1 -1
  73. package/runtime/arcane/entities/User.js +110 -23
  74. package/runtime/arcane/modules/AI.js +2117 -130
  75. package/runtime/arcane/modules/AIProviderRuntime.js +756 -13
  76. package/runtime/arcane/modules/AIRuntimeState.js +109 -52
  77. package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
  78. package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
  79. package/runtime/arcane/modules/CalculatorEngine.js +63 -3
  80. package/runtime/arcane/modules/CommunicationAppController.js +588 -28
  81. package/runtime/arcane/modules/CommunicationHub.js +590 -10
  82. package/runtime/arcane/modules/ComponentContracts.js +235 -37
  83. package/runtime/arcane/modules/ConversationTimebox.js +152 -33
  84. package/runtime/arcane/modules/DBLS.js +40 -7
  85. package/runtime/arcane/modules/DBOPFS.js +35 -11
  86. package/runtime/arcane/modules/DataMaintenance.js +12 -2
  87. package/runtime/arcane/modules/Errors.js +65 -7
  88. package/runtime/arcane/modules/HTMLImport.js +198 -14
  89. package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
  90. package/runtime/arcane/modules/Mail.js +738 -115
  91. package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
  92. package/runtime/arcane/modules/MailTransport.mjs +197 -39
  93. package/runtime/arcane/modules/Ollama.js +36 -1
  94. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
  95. package/runtime/arcane/modules/PreferenceStore.js +367 -33
  96. package/runtime/arcane/modules/RecordReviewStore.js +322 -23
  97. package/runtime/arcane/modules/ScreenCapture.js +1397 -15
  98. package/runtime/arcane/modules/SpeechPlayback.js +438 -41
  99. package/runtime/arcane/modules/TerminalClient.js +277 -12
  100. package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
  101. package/runtime/arcane/modules/ThemeManager.js +39 -7
  102. package/runtime/arcane/modules/TimeGuard.js +131 -20
  103. package/runtime/arcane/modules/WaitForComponent.js +386 -33
  104. package/schemas/arcane-lock.schema.json +2 -2
  105. package/src/cli/main.mjs +435 -9
  106. package/src/event-manager.mjs +1097 -1
  107. package/src/import-map.mjs +21 -3
  108. package/src/index.mjs +13 -0
  109. package/src/installed-sdk-runtime.mjs +130 -0
  110. package/src/mail-api.mjs +22 -0
  111. package/src/mail-credentials.mjs +667 -0
  112. package/src/mail-server.mjs +1769 -0
  113. package/src/mail.mjs +261 -0
  114. package/src/sdk-browser-runtime.mjs +85 -41
  115. package/src/testing-loader.mjs +7 -0
  116. package/src/toolchain.mjs +3 -0
  117. package/src/workspace-runtime.mjs +804 -22
  118. package/src/workspace.mjs +1 -1
@@ -25,7 +25,18 @@ const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
25
25
  const CREATE_NEW_NO_FOLLOW=FS_CONSTANTS.O_CREAT|FS_CONSTANTS.O_EXCL
26
26
  |FS_CONSTANTS.O_WRONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
27
27
  const MAX_VERIFIED_WORKSPACE_RUNTIME_FILE_BYTES=64*1024*1024;
28
+ const MAX_WORKSPACE_RUNTIME_RECEIPT_BYTES=4*1024*1024;
28
29
  const STAGING_PREFIX='.arcane-runtime-stage-';
30
+ const BACKUP_PREFIX='.arcane-runtime-backup-';
31
+ const MATERIALIZATION_RECEIPT_DIRECTORY='.arcane';
32
+ const MATERIALIZATION_RECEIPT_NAME='installed-sdk-runtime.json';
33
+ const RECEIPT_STAGING_PREFIX='.installed-sdk-runtime-stage-';
34
+ const RECEIPT_BACKUP_PREFIX='.installed-sdk-runtime-backup-';
35
+ const MATERIALIZATION_RECEIPT_KIND='arcane-installed-sdk-runtime-projection';
36
+ const MATERIALIZATION_RECEIPT_PATH=
37
+ `${MATERIALIZATION_RECEIPT_DIRECTORY}/${MATERIALIZATION_RECEIPT_NAME}`;
38
+ const SHA256_PATTERN=/^[a-f0-9]{64}$/u;
39
+ const UUID_PATTERN=/^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u;
29
40
  const issuedReceipts=new WeakSet();
30
41
 
31
42
  function fail(message,code='ARCANE_WORKSPACE_RUNTIME_INTEGRITY_FAILED'){
@@ -51,6 +62,33 @@ function compareText(left,right){
51
62
  return a<b?-1:a>b?1:0;
52
63
  }
53
64
 
65
+ function isRecord(value){
66
+ return value!==null&&typeof value==='object'&&!Array.isArray(value);
67
+ }
68
+
69
+ function exactKeys(value,keys){
70
+ return isRecord(value)
71
+ &&Object.keys(value).sort(compareText).join('\0')===[...keys].sort(compareText).join('\0');
72
+ }
73
+
74
+ function canonicalJson(value){
75
+ return `${JSON.stringify(value,null,2)}\n`;
76
+ }
77
+
78
+ function deepFreeze(value){
79
+ if(!value||typeof value!=='object'||Object.isFrozen(value))return value;
80
+ for(const child of Object.values(value))deepFreeze(child);
81
+ return Object.freeze(value);
82
+ }
83
+
84
+ function inventoryContentSha256(files){
85
+ return createHash('sha256').update(JSON.stringify(files.map(file=>({
86
+ path:file.path,
87
+ bytes:file.bytes,
88
+ sha256:file.sha256
89
+ })))).digest('hex');
90
+ }
91
+
54
92
  function safeRelativePath(value){
55
93
  if(typeof value!=='string'||!value||value.includes('\\')||value.includes('\0')
56
94
  ||path.posix.isAbsolute(value)||path.posix.normalize(value)!==value
@@ -164,6 +202,220 @@ function locationIdentityMatches(info,identity){
164
202
  return String(info.dev)===identity.device&&String(info.ino)===identity.inode;
165
203
  }
166
204
 
205
+ function locationIdentity(identity){
206
+ return Object.freeze({device:identity.device,inode:identity.inode});
207
+ }
208
+
209
+ function assertPersistentIdentity(value,label,{locationOnly=false}={}){
210
+ const keys=locationOnly
211
+ ?['device','inode']
212
+ :['device','inode','bytes','modifiedNanoseconds','changedNanoseconds','links'];
213
+ if(!exactKeys(value,keys)
214
+ ||!keys.filter(key=>key!=='bytes')
215
+ .every(key=>typeof value[key]==='string'&&/^[0-9]+$/u.test(value[key]))
216
+ ||(!locationOnly&&(!Number.isSafeInteger(value.bytes)||value.bytes<0))){
217
+ fail(`Installed SDK runtime receipt ${label} is invalid.`,
218
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
219
+ }
220
+ }
221
+
222
+ function assertPersistentSourceReceipt(value,label,{browser=false}={}){
223
+ const keys=[
224
+ 'canonicalLocation','rootIdentity','manifestPath','manifestSha256',
225
+ 'manifestIdentity','sdkVersion','contentSha256','fileCount','totalBytes'
226
+ ];
227
+ if(browser)keys.push('builder');
228
+ if(!exactKeys(value,keys)
229
+ ||typeof value.canonicalLocation!=='string'||!path.isAbsolute(value.canonicalLocation)
230
+ ||typeof value.manifestPath!=='string'||!path.isAbsolute(value.manifestPath)
231
+ ||!SHA256_PATTERN.test(value.manifestSha256)
232
+ ||typeof value.sdkVersion!=='string'||!value.sdkVersion
233
+ ||!SHA256_PATTERN.test(value.contentSha256)
234
+ ||!Number.isSafeInteger(value.fileCount)||value.fileCount<1
235
+ ||!Number.isSafeInteger(value.totalBytes)||value.totalBytes<1
236
+ ||(browser&&(typeof value.builder!=='string'||!value.builder))){
237
+ fail(`Installed SDK runtime receipt ${label} is invalid.`,
238
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
239
+ }
240
+ assertPersistentIdentity(value.rootIdentity,`${label} root identity`);
241
+ assertPersistentIdentity(value.manifestIdentity,`${label} manifest identity`);
242
+ }
243
+
244
+ function validatePersistentMaterializationReceipt(document){
245
+ if(!exactKeys(document,[
246
+ 'schemaVersion','kind','generation','workspace','receiptPath',
247
+ 'installedPackage','runtimeReceipt','sdkBrowserRuntimeReceipt','projection'
248
+ ])||document.schemaVersion!==1||document.kind!==MATERIALIZATION_RECEIPT_KIND
249
+ ||!UUID_PATTERN.test(document.generation)
250
+ ||document.receiptPath!==MATERIALIZATION_RECEIPT_PATH){
251
+ fail('Installed SDK runtime receipt envelope is invalid.',
252
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
253
+ }
254
+ if(!exactKeys(document.workspace,['canonicalLocation','identity'])
255
+ ||typeof document.workspace.canonicalLocation!=='string'
256
+ ||!path.isAbsolute(document.workspace.canonicalLocation)){
257
+ fail('Installed SDK runtime receipt workspace is invalid.',
258
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
259
+ }
260
+ assertPersistentIdentity(document.workspace.identity,'workspace identity',{locationOnly:true});
261
+ const installed=document.installedPackage;
262
+ if(!exactKeys(installed,[
263
+ 'dependencyName','dependencyGroup','specifier','packageSource','canonicalLocation',
264
+ 'rootIdentity','packageName','packageVersion'
265
+ ])||![
266
+ installed.dependencyName,installed.dependencyGroup,installed.specifier,
267
+ installed.packageSource,installed.canonicalLocation,installed.packageName,
268
+ installed.packageVersion
269
+ ].every(value=>typeof value==='string'&&value)
270
+ ||!path.isAbsolute(installed.canonicalLocation)){
271
+ fail('Installed SDK runtime receipt package identity is invalid.',
272
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
273
+ }
274
+ safeRelativePath(installed.packageSource);
275
+ assertPersistentIdentity(installed.rootIdentity,'installed package root identity');
276
+ assertPersistentSourceReceipt(document.runtimeReceipt,'runtime source receipt');
277
+ assertPersistentSourceReceipt(
278
+ document.sdkBrowserRuntimeReceipt,
279
+ 'browser runtime source receipt',
280
+ {browser:true}
281
+ );
282
+ if(document.runtimeReceipt.sdkVersion!==installed.packageVersion
283
+ ||document.sdkBrowserRuntimeReceipt.sdkVersion!==installed.packageVersion
284
+ ||path.resolve(installed.canonicalLocation,'runtime')
285
+ !==path.resolve(document.runtimeReceipt.canonicalLocation)
286
+ ||path.resolve(installed.canonicalLocation,'browser-runtime')
287
+ !==path.resolve(document.sdkBrowserRuntimeReceipt.canonicalLocation)){
288
+ fail('Installed SDK runtime receipt source binding is inconsistent.',
289
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
290
+ }
291
+ const projection=document.projection;
292
+ if(!exactKeys(projection,[
293
+ 'relativePath','fileCount','totalBytes','contentSha256','files'
294
+ ])||projection.relativePath!=='arcane'||!Array.isArray(projection.files)
295
+ ||!Number.isSafeInteger(projection.fileCount)||projection.fileCount<1
296
+ ||!Number.isSafeInteger(projection.totalBytes)||projection.totalBytes<1
297
+ ||!SHA256_PATTERN.test(projection.contentSha256)
298
+ ||projection.fileCount!==projection.files.length){
299
+ fail('Installed SDK runtime receipt projection is invalid.',
300
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
301
+ }
302
+ const seen=new Set();
303
+ let totalBytes=0;
304
+ let previous='';
305
+ for(const [index,file] of projection.files.entries()){
306
+ if(!exactKeys(file,['path','sourcePath','authority','bytes','sha256'])
307
+ ||!['arcane-runtime','sdk-browser-runtime'].includes(file.authority)
308
+ ||!Number.isSafeInteger(file.bytes)||file.bytes<0
309
+ ||!SHA256_PATTERN.test(file.sha256)){
310
+ fail(`Installed SDK runtime receipt projection file ${String(index)} is invalid.`,
311
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
312
+ }
313
+ const projectedPath=safeRelativePath(file.path);
314
+ safeRelativePath(file.sourcePath);
315
+ const key=portableKey(projectedPath);
316
+ if(seen.has(key)||(index>0&&compareText(previous,projectedPath)>=0)){
317
+ fail('Installed SDK runtime receipt projection inventory is not unique and sorted.',
318
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
319
+ }
320
+ seen.add(key);
321
+ previous=projectedPath;
322
+ totalBytes+=file.bytes;
323
+ if(!Number.isSafeInteger(totalBytes)){
324
+ fail('Installed SDK runtime receipt projection byte total is invalid.',
325
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
326
+ }
327
+ }
328
+ if(totalBytes!==projection.totalBytes
329
+ ||inventoryContentSha256(projection.files)!==projection.contentSha256){
330
+ fail('Installed SDK runtime receipt projection inventory digest is invalid.',
331
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
332
+ }
333
+ return document;
334
+ }
335
+
336
+ function persistentSourceReceipt(receipt,{browser=false}={}){
337
+ const result={
338
+ canonicalLocation:receipt.canonicalLocation,
339
+ rootIdentity:receipt.rootIdentity,
340
+ manifestPath:receipt.manifestPath,
341
+ manifestSha256:receipt.manifestSha256,
342
+ manifestIdentity:receipt.manifestIdentity,
343
+ sdkVersion:receipt.sdkVersion,
344
+ contentSha256:receipt.contentSha256,
345
+ fileCount:receipt.fileCount,
346
+ totalBytes:receipt.totalBytes
347
+ };
348
+ if(browser)result.builder=receipt.builder;
349
+ return result;
350
+ }
351
+
352
+ async function createPersistentMaterializationReceipt({
353
+ generation,
354
+ workspace,
355
+ installedSdkAuthority,
356
+ runtimeReceipt,
357
+ sdkBrowserRuntimeReceipt,
358
+ expectedFiles
359
+ }){
360
+ const {declaration,installation}=installedSdkAuthority??{};
361
+ if(!declaration||!installation){
362
+ fail('Installed SDK authority is required for a persistent workspace projection receipt.');
363
+ }
364
+ const packageInfo=await lstat(installation.canonicalPackageRoot,{bigint:true});
365
+ if(packageInfo.isSymbolicLink()||!packageInfo.isDirectory()){
366
+ fail('Installed SDK package root must remain a real directory.');
367
+ }
368
+ const canonicalPackageRoot=await realpath(installation.canonicalPackageRoot);
369
+ if(canonicalPackageRoot!==installation.canonicalPackageRoot){
370
+ fail('Installed SDK package root changed while materialization was active.');
371
+ }
372
+ if(runtimeReceipt.sdkVersion!==installation.packageVersion
373
+ ||sdkBrowserRuntimeReceipt.sdkVersion!==installation.packageVersion){
374
+ fail('Installed SDK package and source receipt versions do not match.');
375
+ }
376
+ const files=expectedFiles.map(file=>({
377
+ path:file.path,
378
+ sourcePath:file.sourcePath,
379
+ authority:file.authority,
380
+ bytes:file.bytes,
381
+ sha256:file.sha256
382
+ }));
383
+ const document={
384
+ schemaVersion:1,
385
+ kind:MATERIALIZATION_RECEIPT_KIND,
386
+ generation,
387
+ workspace:{
388
+ canonicalLocation:workspace.canonicalRoot,
389
+ identity:locationIdentity(workspace.identity)
390
+ },
391
+ receiptPath:MATERIALIZATION_RECEIPT_PATH,
392
+ installedPackage:{
393
+ dependencyName:declaration.dependencyName,
394
+ dependencyGroup:declaration.dependencyGroup,
395
+ specifier:declaration.specifier,
396
+ packageSource:declaration.packageSource,
397
+ canonicalLocation:canonicalPackageRoot,
398
+ rootIdentity:fileIdentity(packageInfo),
399
+ packageName:installation.packageName,
400
+ packageVersion:installation.packageVersion
401
+ },
402
+ runtimeReceipt:persistentSourceReceipt(runtimeReceipt),
403
+ sdkBrowserRuntimeReceipt:persistentSourceReceipt(
404
+ sdkBrowserRuntimeReceipt,
405
+ {browser:true}
406
+ ),
407
+ projection:{
408
+ relativePath:'arcane',
409
+ fileCount:files.length,
410
+ totalBytes:files.reduce((total,file)=>total+file.bytes,0),
411
+ contentSha256:inventoryContentSha256(files),
412
+ files
413
+ }
414
+ };
415
+ validatePersistentMaterializationReceipt(document);
416
+ return deepFreeze(document);
417
+ }
418
+
167
419
  async function workspaceLocation(workspaceRoot){
168
420
  const requestedRoot=path.resolve(workspaceRoot);
169
421
  let info;
@@ -420,13 +672,108 @@ async function writeNewFile(filePath,bytes){
420
672
  }
421
673
  }
422
674
 
423
- async function cleanupStaging(stagingRoot,workspace,stagingIdentity){
424
- const stagingParent=path.dirname(stagingRoot);
425
- const stagingName=path.basename(stagingRoot);
426
- if(stagingParent!==workspace.canonicalRoot||!stagingName.startsWith(STAGING_PREFIX)){
675
+ async function readPersistentMaterializationReceipt(receiptPath,{signal}={}){
676
+ throwIfAborted(signal);
677
+ let handle;
678
+ try{
679
+ handle=await open(receiptPath,READ_ONLY_NO_FOLLOW);
680
+ }catch(error){
681
+ if(error?.code==='ELOOP'){
682
+ fail('Installed SDK runtime receipt must not be a symbolic link.',
683
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
684
+ }
685
+ throw error;
686
+ }
687
+ try{
688
+ const before=await handle.stat({bigint:true});
689
+ if(!before.isFile()||before.size>BigInt(MAX_WORKSPACE_RUNTIME_RECEIPT_BYTES)){
690
+ fail('Installed SDK runtime receipt is not a bounded regular file.',
691
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
692
+ }
693
+ const bytes=await handle.readFile();
694
+ throwIfAborted(signal);
695
+ const after=await handle.stat({bigint:true});
696
+ if(!sameIdentity(before,after)){
697
+ fail('Installed SDK runtime receipt changed while it was being read.',
698
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
699
+ }
700
+ let document;
701
+ try{document=JSON.parse(bytes.toString('utf8'));}
702
+ catch(error){
703
+ fail(`Installed SDK runtime receipt is not valid JSON: ${error.message}`,
704
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
705
+ }
706
+ validatePersistentMaterializationReceipt(document);
707
+ if(!bytes.equals(Buffer.from(canonicalJson(document)))){
708
+ fail('Installed SDK runtime receipt bytes are not canonical.',
709
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
710
+ }
711
+ const named=await lstat(receiptPath,{bigint:true});
712
+ if(named.isSymbolicLink()||!named.isFile()||!sameIdentity(named,after)){
713
+ fail('Installed SDK runtime receipt path changed while it was being read.',
714
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
715
+ }
716
+ return Object.freeze({
717
+ document:deepFreeze(document),
718
+ identity:fileIdentity(after)
719
+ });
720
+ }finally{
721
+ await handle.close();
722
+ }
723
+ }
724
+
725
+ async function assertOwnedFileLocation(filePath,identity,label){
726
+ const info=await lstat(filePath,{bigint:true});
727
+ if(info.isSymbolicLink()||!info.isFile()||!locationIdentityMatches(info,identity)){
728
+ fail(`${label} changed while workspace runtime materialization was active.`);
729
+ }
730
+ }
731
+
732
+ async function removeOwnedFile(filePath,identity,label){
733
+ await assertOwnedFileLocation(filePath,identity,label);
734
+ await rm(filePath,{force:true});
735
+ try{
736
+ await lstat(filePath,{bigint:true});
737
+ fail(`${label} remained after cleanup.`);
738
+ }catch(error){
739
+ if(error?.code!=='ENOENT')throw error;
740
+ }
741
+ }
742
+
743
+ async function materializationMetadataLocation(workspace){
744
+ const metadataRoot=path.join(workspace.canonicalRoot,MATERIALIZATION_RECEIPT_DIRECTORY);
745
+ let info;
746
+ try{info=await lstat(metadataRoot,{bigint:true});}
747
+ catch(error){
748
+ if(error?.code!=='ENOENT')throw error;
749
+ await mkdir(metadataRoot,{mode:0o700});
750
+ info=await lstat(metadataRoot,{bigint:true});
751
+ }
752
+ if(info.isSymbolicLink()||!info.isDirectory()){
753
+ fail('Workspace materialization metadata root must be a real directory.');
754
+ }
755
+ const canonical=await realpath(metadataRoot);
756
+ if(canonical!==metadataRoot){
757
+ fail('Workspace materialization metadata root must not resolve through another path.');
758
+ }
759
+ return Object.freeze({root:metadataRoot,identity:fileIdentity(info)});
760
+ }
761
+
762
+ async function optionalPathIdentity(target){
763
+ try{return fileIdentity(await lstat(target,{bigint:true}));}
764
+ catch(error){
765
+ if(error?.code==='ENOENT')return null;
766
+ throw error;
767
+ }
768
+ }
769
+
770
+ async function cleanupOwnedTree(ownedRoot,workspace,ownedIdentity,{prefix,label}){
771
+ const ownedParent=path.dirname(ownedRoot);
772
+ const ownedName=path.basename(ownedRoot);
773
+ if(ownedParent!==workspace.canonicalRoot||!ownedName.startsWith(prefix)){
427
774
  fail(
428
- `Refusing to clean an unowned workspace runtime staging path: ${stagingRoot} `
429
- +`(parent ${stagingParent}; expected ${workspace.canonicalRoot}).`
775
+ `Refusing to clean an unowned workspace runtime ${label} path: ${ownedRoot} `
776
+ +`(parent ${ownedParent}; expected ${workspace.canonicalRoot}).`
430
777
  );
431
778
  }
432
779
  await assertRealDirectoryLocation(
@@ -434,24 +781,24 @@ async function cleanupStaging(stagingRoot,workspace,stagingIdentity){
434
781
  workspace.identity,
435
782
  'Workspace root'
436
783
  );
437
- let stagingInfo;
438
- try{stagingInfo=await lstat(stagingRoot,{bigint:true});}
784
+ let ownedInfo;
785
+ try{ownedInfo=await lstat(ownedRoot,{bigint:true});}
439
786
  catch(error){
440
787
  if(error?.code==='ENOENT')return;
441
788
  throw error;
442
789
  }
443
- if(stagingInfo.isSymbolicLink()||!stagingInfo.isDirectory()
444
- ||!locationIdentityMatches(stagingInfo,stagingIdentity)){
445
- fail('Refusing to clean a workspace runtime staging path whose identity changed.');
790
+ if(ownedInfo.isSymbolicLink()||!ownedInfo.isDirectory()
791
+ ||!locationIdentityMatches(ownedInfo,ownedIdentity)){
792
+ fail(`Refusing to clean a workspace runtime ${label} path whose identity changed.`);
446
793
  }
447
794
  await assertRealDirectoryLocation(
448
- stagingRoot,
449
- stagingIdentity,
450
- 'Workspace runtime staging directory'
795
+ ownedRoot,
796
+ ownedIdentity,
797
+ `Workspace runtime ${label} directory`
451
798
  );
452
- try{await inspectTree(stagingRoot);}
799
+ try{await inspectTree(ownedRoot);}
453
800
  catch(error){
454
- fail(`Refusing to clean an unauthenticated workspace runtime staging tree: ${error.message}`);
801
+ fail(`Refusing to clean an unauthenticated workspace runtime ${label} tree: ${error.message}`);
455
802
  }
456
803
  await assertRealDirectoryLocation(
457
804
  workspace.canonicalRoot,
@@ -459,14 +806,14 @@ async function cleanupStaging(stagingRoot,workspace,stagingIdentity){
459
806
  'Workspace root'
460
807
  );
461
808
  await assertRealDirectoryLocation(
462
- stagingRoot,
463
- stagingIdentity,
464
- 'Workspace runtime staging directory'
809
+ ownedRoot,
810
+ ownedIdentity,
811
+ `Workspace runtime ${label} directory`
465
812
  );
466
- await rm(stagingRoot,{recursive:true,force:true});
813
+ await rm(ownedRoot,{recursive:true,force:true});
467
814
  try{
468
- await lstat(stagingRoot,{bigint:true});
469
- fail('Workspace runtime staging path remained after cleanup.');
815
+ await lstat(ownedRoot,{bigint:true});
816
+ fail(`Workspace runtime ${label} path remained after cleanup.`);
470
817
  }catch(error){
471
818
  if(error?.code!=='ENOENT')throw error;
472
819
  }
@@ -477,6 +824,13 @@ async function cleanupStaging(stagingRoot,workspace,stagingIdentity){
477
824
  );
478
825
  }
479
826
 
827
+ async function cleanupStaging(stagingRoot,workspace,stagingIdentity){
828
+ return cleanupOwnedTree(stagingRoot,workspace,stagingIdentity,{
829
+ prefix:STAGING_PREFIX,
830
+ label:'staging'
831
+ });
832
+ }
833
+
480
834
  export async function verifyWorkspaceRuntime({
481
835
  workspaceRoot,
482
836
  runtimeRoot=path.join(getSdkRoot(),'runtime'),
@@ -578,12 +932,428 @@ export async function verifyWorkspaceRuntime({
578
932
  return receipt;
579
933
  }
580
934
 
935
+ function attachMaterialization(receipt,{
936
+ status,
937
+ persistentReceipt,
938
+ receiptPath,
939
+ cleanupWarnings=[]
940
+ }){
941
+ const result=Object.freeze({
942
+ ...receipt,
943
+ materialization:Object.freeze({
944
+ status,
945
+ generation:persistentReceipt.generation,
946
+ receiptPath,
947
+ persistentReceipt,
948
+ cleanupWarnings:Object.freeze([...cleanupWarnings])
949
+ })
950
+ });
951
+ issuedReceipts.add(result);
952
+ return result;
953
+ }
954
+
955
+ async function inspectInstalledMaterialization({
956
+ workspace,
957
+ destinationRoot,
958
+ receiptPath,
959
+ desiredReceipt,
960
+ signal
961
+ }){
962
+ const destinationIdentity=await optionalPathIdentity(destinationRoot);
963
+ const receiptIdentity=await optionalPathIdentity(receiptPath);
964
+ if(!destinationIdentity&&!receiptIdentity)return Object.freeze({kind:'absent'});
965
+ if(!destinationIdentity){
966
+ fail('Installed SDK runtime receipt exists without its workspace projection.',
967
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
968
+ }
969
+ const destinationInfo=await lstat(destinationRoot,{bigint:true});
970
+ if(destinationInfo.isSymbolicLink()||!destinationInfo.isDirectory()){
971
+ fail('Existing workspace arcane runtime path must be a real directory.');
972
+ }
973
+ const canonicalDestination=await realpath(destinationRoot);
974
+ if(canonicalDestination!==destinationRoot){
975
+ fail('Existing workspace arcane runtime path must not resolve through another path.');
976
+ }
977
+ if(!receiptIdentity){
978
+ await inspectTree(destinationRoot,{signal});
979
+ return Object.freeze({
980
+ kind:'legacy',
981
+ destinationIdentity:fileIdentity(destinationInfo)
982
+ });
983
+ }
984
+ const recorded=await readPersistentMaterializationReceipt(receiptPath,{signal});
985
+ if(recorded.document.workspace.canonicalLocation!==workspace.canonicalRoot
986
+ ||recorded.document.workspace.identity.device!==workspace.identity.device
987
+ ||recorded.document.workspace.identity.inode!==workspace.identity.inode){
988
+ fail('Installed SDK runtime receipt belongs to a different workspace.',
989
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
990
+ }
991
+ if(canonicalJson(recorded.document)===canonicalJson(desiredReceipt)){
992
+ return Object.freeze({
993
+ kind:'current',
994
+ document:recorded.document,
995
+ destinationIdentity:fileIdentity(destinationInfo),
996
+ receiptIdentity:recorded.identity
997
+ });
998
+ }
999
+ await verifyProjectedTree(
1000
+ destinationRoot,
1001
+ recorded.document.projection.files,
1002
+ {signal}
1003
+ );
1004
+ return Object.freeze({
1005
+ kind:'stale',
1006
+ document:recorded.document,
1007
+ destinationIdentity:fileIdentity(destinationInfo),
1008
+ receiptIdentity:recorded.identity
1009
+ });
1010
+ }
1011
+
1012
+ async function rollbackInstalledReplacement(state){
1013
+ const errors=[];
1014
+ const attempt=async operation=>{
1015
+ try{await operation();}
1016
+ catch(error){errors.push(error);}
1017
+ };
1018
+ if(state.newReceiptMoved){
1019
+ await attempt(()=>rename(state.receiptPath,state.receiptStagingPath));
1020
+ }
1021
+ if(state.newRootMoved){
1022
+ await attempt(()=>rename(state.destinationRoot,state.stagingRoot));
1023
+ }
1024
+ if(state.oldReceiptMoved){
1025
+ await attempt(()=>rename(state.receiptBackupPath,state.receiptPath));
1026
+ }
1027
+ if(state.oldRootMoved){
1028
+ await attempt(()=>rename(state.backupRoot,state.destinationRoot));
1029
+ }
1030
+ if(errors.length){
1031
+ throw new AggregateError(
1032
+ errors,
1033
+ 'Installed SDK runtime replacement failed and its prior projection could not be fully restored.'
1034
+ );
1035
+ }
1036
+ state.newReceiptMoved=false;
1037
+ state.newRootMoved=false;
1038
+ state.oldReceiptMoved=false;
1039
+ state.oldRootMoved=false;
1040
+ }
1041
+
1042
+ async function materializeInstalledWorkspaceRuntime({
1043
+ workspaceRoot,
1044
+ runtimeRoot,
1045
+ runtimeReceipt,
1046
+ browserRuntimeRoot,
1047
+ sdkBrowserRuntimeReceipt,
1048
+ installedSdkAuthority,
1049
+ signal,
1050
+ onEvent
1051
+ }){
1052
+ const expectedFiles=projectRuntimeFiles(runtimeReceipt,sdkBrowserRuntimeReceipt);
1053
+ const workspace=await workspaceLocation(workspaceRoot);
1054
+ const metadata=await materializationMetadataLocation(workspace);
1055
+ const destinationRoot=path.join(workspace.canonicalRoot,'arcane');
1056
+ const receiptPath=path.join(metadata.root,MATERIALIZATION_RECEIPT_NAME);
1057
+ const generation=randomUUID();
1058
+ let desiredReceipt=await createPersistentMaterializationReceipt({
1059
+ generation,
1060
+ workspace,
1061
+ installedSdkAuthority,
1062
+ runtimeReceipt,
1063
+ sdkBrowserRuntimeReceipt,
1064
+ expectedFiles
1065
+ });
1066
+ let existingReceipt=null;
1067
+ try{
1068
+ existingReceipt=(await readPersistentMaterializationReceipt(receiptPath,{signal})).document;
1069
+ desiredReceipt=await createPersistentMaterializationReceipt({
1070
+ generation:existingReceipt.generation,
1071
+ workspace,
1072
+ installedSdkAuthority,
1073
+ runtimeReceipt,
1074
+ sdkBrowserRuntimeReceipt,
1075
+ expectedFiles
1076
+ });
1077
+ }catch(error){
1078
+ if(error?.code!=='ENOENT')throw error;
1079
+ }
1080
+ const existing=await inspectInstalledMaterialization({
1081
+ workspace,
1082
+ destinationRoot,
1083
+ receiptPath,
1084
+ desiredReceipt,
1085
+ signal
1086
+ });
1087
+ if(existing.kind==='current'){
1088
+ const verified=await verifyWorkspaceRuntime({
1089
+ workspaceRoot,
1090
+ runtimeRoot,
1091
+ runtimeReceipt,
1092
+ browserRuntimeRoot,
1093
+ sdkBrowserRuntimeReceipt,
1094
+ signal,
1095
+ onEvent
1096
+ });
1097
+ const result=attachMaterialization(verified,{
1098
+ status:'reused',
1099
+ persistentReceipt:existing.document,
1100
+ receiptPath
1101
+ });
1102
+ await emit(onEvent,{
1103
+ type:'workspace.runtime.materialize.reused',
1104
+ status:'reused',
1105
+ generation:existing.document.generation,
1106
+ receiptPath
1107
+ });
1108
+ return result;
1109
+ }
1110
+
1111
+ desiredReceipt=await createPersistentMaterializationReceipt({
1112
+ generation,
1113
+ workspace,
1114
+ installedSdkAuthority,
1115
+ runtimeReceipt,
1116
+ sdkBrowserRuntimeReceipt,
1117
+ expectedFiles
1118
+ });
1119
+ const operationSuffix=`${String(process.pid)}-${generation}`;
1120
+ const stagingRoot=path.join(workspace.canonicalRoot,`${STAGING_PREFIX}${operationSuffix}`);
1121
+ const backupRoot=path.join(workspace.canonicalRoot,`${BACKUP_PREFIX}${operationSuffix}`);
1122
+ const receiptStagingPath=path.join(
1123
+ metadata.root,
1124
+ `${RECEIPT_STAGING_PREFIX}${operationSuffix}.json`
1125
+ );
1126
+ const receiptBackupPath=path.join(
1127
+ metadata.root,
1128
+ `${RECEIPT_BACKUP_PREFIX}${operationSuffix}.json`
1129
+ );
1130
+ await mkdir(stagingRoot,{mode:0o700});
1131
+ const stagingInfo=await lstat(stagingRoot,{bigint:true});
1132
+ if(stagingInfo.isSymbolicLink()||!stagingInfo.isDirectory()){
1133
+ fail('Workspace runtime staging path must be a real directory.');
1134
+ }
1135
+ const stagingIdentity=fileIdentity(stagingInfo);
1136
+ const transaction={
1137
+ destinationRoot,
1138
+ stagingRoot,
1139
+ backupRoot,
1140
+ receiptPath,
1141
+ receiptStagingPath,
1142
+ receiptBackupPath,
1143
+ newReceiptMoved:false,
1144
+ newRootMoved:false,
1145
+ oldReceiptMoved:false,
1146
+ oldRootMoved:false
1147
+ };
1148
+ let stagingCleaned=false;
1149
+ let receiptStagingIdentity;
1150
+ let uncertainCommit=false;
1151
+ try{
1152
+ const totalBytes=expectedFiles.reduce((total,file)=>total+file.bytes,0);
1153
+ const bufferedEvents=[{
1154
+ type:'workspace.runtime.materialize.started',
1155
+ status:existing.kind==='absent'?'created':'refreshed',
1156
+ generation,
1157
+ fileCount:expectedFiles.length,
1158
+ totalBytes
1159
+ }];
1160
+ let writtenBytes=0;
1161
+ for(const [index,file] of expectedFiles.entries()){
1162
+ throwIfAborted(signal);
1163
+ const bytes=file.authority==='sdk-browser-runtime'
1164
+ ?await readVerifiedSdkBrowserRuntimeFile(sdkBrowserRuntimeReceipt,{
1165
+ browserRuntimeRoot,
1166
+ relativePath:file.sourcePath,
1167
+ signal
1168
+ })
1169
+ :await readVerifiedRuntimeFile(runtimeReceipt,{
1170
+ runtimeRoot,
1171
+ relativePath:file.sourcePath,
1172
+ signal
1173
+ });
1174
+ const destination=resolveContained(stagingRoot,file.path);
1175
+ await mkdir(path.dirname(destination),{recursive:true,mode:0o755});
1176
+ await writeNewFile(destination,bytes);
1177
+ writtenBytes+=bytes.length;
1178
+ bufferedEvents.push({
1179
+ type:'workspace.runtime.materialize.progress',
1180
+ current:index+1,
1181
+ total:expectedFiles.length,
1182
+ writtenBytes,
1183
+ totalBytes,
1184
+ path:file.path
1185
+ });
1186
+ }
1187
+ await verifyProjectedTree(stagingRoot,expectedFiles,{signal});
1188
+ await writeNewFile(receiptStagingPath,Buffer.from(canonicalJson(desiredReceipt)));
1189
+ const receiptStagingInfo=await lstat(receiptStagingPath,{bigint:true});
1190
+ if(receiptStagingInfo.isSymbolicLink()||!receiptStagingInfo.isFile()){
1191
+ fail('Workspace runtime staged receipt must be a real file.');
1192
+ }
1193
+ receiptStagingIdentity=fileIdentity(receiptStagingInfo);
1194
+ await readPersistentMaterializationReceipt(receiptStagingPath,{signal});
1195
+
1196
+ for(const event of bufferedEvents)await emit(onEvent,event);
1197
+ throwIfAborted(signal);
1198
+ await assertRealDirectoryLocation(
1199
+ workspace.canonicalRoot,
1200
+ workspace.identity,
1201
+ 'Workspace root'
1202
+ );
1203
+ await assertRealDirectoryLocation(
1204
+ metadata.root,
1205
+ metadata.identity,
1206
+ 'Workspace materialization metadata root'
1207
+ );
1208
+ await assertRealDirectoryLocation(
1209
+ stagingRoot,
1210
+ stagingIdentity,
1211
+ 'Workspace runtime staging directory'
1212
+ );
1213
+ await assertOwnedFileLocation(
1214
+ receiptStagingPath,
1215
+ receiptStagingIdentity,
1216
+ 'Workspace runtime staged receipt'
1217
+ );
1218
+ await verifyProjectedTree(stagingRoot,expectedFiles,{signal});
1219
+ if(existing.destinationIdentity){
1220
+ await assertRealDirectoryLocation(
1221
+ destinationRoot,
1222
+ existing.destinationIdentity,
1223
+ 'Existing workspace runtime projection'
1224
+ );
1225
+ }
1226
+ if(existing.receiptIdentity){
1227
+ await assertOwnedFileLocation(
1228
+ receiptPath,
1229
+ existing.receiptIdentity,
1230
+ 'Existing workspace runtime receipt'
1231
+ );
1232
+ }
1233
+ throwIfAborted(signal);
1234
+
1235
+ try{
1236
+ if(existing.destinationIdentity){
1237
+ await rename(destinationRoot,backupRoot);
1238
+ transaction.oldRootMoved=true;
1239
+ }
1240
+ if(existing.receiptIdentity){
1241
+ await rename(receiptPath,receiptBackupPath);
1242
+ transaction.oldReceiptMoved=true;
1243
+ }
1244
+ await rename(stagingRoot,destinationRoot);
1245
+ transaction.newRootMoved=true;
1246
+ await rename(receiptStagingPath,receiptPath);
1247
+ transaction.newReceiptMoved=true;
1248
+ }catch(commitError){
1249
+ try{await rollbackInstalledReplacement(transaction);}
1250
+ catch(rollbackError){
1251
+ uncertainCommit=true;
1252
+ throw new AggregateError(
1253
+ [commitError,rollbackError],
1254
+ 'Installed SDK runtime replacement and rollback both failed.',
1255
+ {cause:commitError}
1256
+ );
1257
+ }
1258
+ throw commitError;
1259
+ }
1260
+
1261
+ let committedPersistent;
1262
+ let verified;
1263
+ try{
1264
+ committedPersistent=(await readPersistentMaterializationReceipt(receiptPath)).document;
1265
+ if(canonicalJson(committedPersistent)!==canonicalJson(desiredReceipt)){
1266
+ fail('Committed installed SDK runtime receipt does not match the staged generation.',
1267
+ 'ARCANE_WORKSPACE_RUNTIME_RECEIPT_INVALID');
1268
+ }
1269
+ verified=await verifyWorkspaceRuntime({
1270
+ workspaceRoot,
1271
+ runtimeRoot,
1272
+ runtimeReceipt,
1273
+ browserRuntimeRoot,
1274
+ sdkBrowserRuntimeReceipt
1275
+ });
1276
+ }catch(verificationError){
1277
+ try{await rollbackInstalledReplacement(transaction);}
1278
+ catch(rollbackError){
1279
+ uncertainCommit=true;
1280
+ throw new AggregateError(
1281
+ [verificationError,rollbackError],
1282
+ 'Installed SDK runtime verification and rollback both failed.',
1283
+ {cause:verificationError}
1284
+ );
1285
+ }
1286
+ throw verificationError;
1287
+ }
1288
+
1289
+ const cleanupWarnings=[];
1290
+ if(existing.destinationIdentity){
1291
+ try{
1292
+ await cleanupOwnedTree(backupRoot,workspace,existing.destinationIdentity,{
1293
+ prefix:BACKUP_PREFIX,
1294
+ label:'backup'
1295
+ });
1296
+ transaction.oldRootMoved=false;
1297
+ }catch(error){cleanupWarnings.push(String(error?.message??error));}
1298
+ }
1299
+ if(existing.receiptIdentity){
1300
+ try{
1301
+ await removeOwnedFile(
1302
+ receiptBackupPath,
1303
+ existing.receiptIdentity,
1304
+ 'Workspace runtime receipt backup'
1305
+ );
1306
+ transaction.oldReceiptMoved=false;
1307
+ }catch(error){cleanupWarnings.push(String(error?.message??error));}
1308
+ }
1309
+ transaction.newRootMoved=false;
1310
+ transaction.newReceiptMoved=false;
1311
+ stagingCleaned=true;
1312
+ const status=existing.kind==='absent'?'created':'refreshed';
1313
+ const result=attachMaterialization(verified,{
1314
+ status,
1315
+ persistentReceipt:committedPersistent,
1316
+ receiptPath,
1317
+ cleanupWarnings
1318
+ });
1319
+ await emit(onEvent,{
1320
+ type:'workspace.runtime.materialize.completed',
1321
+ status,
1322
+ generation,
1323
+ receiptPath,
1324
+ contentSha256:result.contentSha256,
1325
+ sourceContentSha256:result.sourceContentSha256,
1326
+ sourceBrowserContentSha256:result.sourceBrowserContentSha256,
1327
+ fileCount:result.fileCount,
1328
+ totalBytes:result.totalBytes,
1329
+ cleanupWarnings:Object.freeze([...cleanupWarnings])
1330
+ });
1331
+ return result;
1332
+ }finally{
1333
+ if(!uncertainCommit&&!stagingCleaned){
1334
+ await cleanupStaging(stagingRoot,workspace,stagingIdentity);
1335
+ if(receiptStagingIdentity){
1336
+ try{
1337
+ await removeOwnedFile(
1338
+ receiptStagingPath,
1339
+ receiptStagingIdentity,
1340
+ 'Workspace runtime staged receipt'
1341
+ );
1342
+ }catch(error){
1343
+ if(error?.code!=='ENOENT')throw error;
1344
+ }
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+
581
1350
  export async function materializeWorkspaceRuntime({
582
1351
  workspaceRoot,
583
1352
  runtimeRoot=path.join(getSdkRoot(),'runtime'),
584
1353
  runtimeReceipt,
585
1354
  browserRuntimeRoot=getSdkBrowserRuntimeRoot(),
586
1355
  sdkBrowserRuntimeReceipt,
1356
+ installedSdkAuthority,
587
1357
  signal,
588
1358
  onEvent
589
1359
  }={}){
@@ -593,6 +1363,18 @@ export async function materializeWorkspaceRuntime({
593
1363
  browserRuntimeRoot,
594
1364
  signal
595
1365
  });
1366
+ if(installedSdkAuthority){
1367
+ return materializeInstalledWorkspaceRuntime({
1368
+ workspaceRoot,
1369
+ runtimeRoot,
1370
+ runtimeReceipt,
1371
+ browserRuntimeRoot,
1372
+ sdkBrowserRuntimeReceipt,
1373
+ installedSdkAuthority,
1374
+ signal,
1375
+ onEvent
1376
+ });
1377
+ }
596
1378
  const expectedFiles=projectRuntimeFiles(runtimeReceipt,sdkBrowserRuntimeReceipt);
597
1379
  const workspace=await workspaceLocation(workspaceRoot);
598
1380
  const destinationRoot=path.join(workspace.canonicalRoot,'arcane');