arcane-os 0.1.0-dev.5 → 0.1.0

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.
@@ -0,0 +1,841 @@
1
+ import {createHash,randomUUID} from 'node:crypto';
2
+ import {constants as FS_CONSTANTS} from 'node:fs';
3
+ import {
4
+ lstat,
5
+ mkdir,
6
+ open,
7
+ readdir,
8
+ realpath,
9
+ rename,
10
+ rm
11
+ } from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ import {
14
+ authenticateRuntimeReceipt,
15
+ getSdkRoot,
16
+ readVerifiedRuntimeFile
17
+ } from './runtime.mjs';
18
+ import {
19
+ authenticateSdkBrowserRuntimeReceipt,
20
+ getSdkBrowserRuntimeRoot,
21
+ readVerifiedSdkBrowserRuntimeFile
22
+ } from './sdk-browser-runtime.mjs';
23
+
24
+ const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
25
+ const CREATE_NEW_NO_FOLLOW=FS_CONSTANTS.O_CREAT|FS_CONSTANTS.O_EXCL
26
+ |FS_CONSTANTS.O_WRONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
27
+ const MAX_VERIFIED_WORKSPACE_RUNTIME_FILE_BYTES=64*1024*1024;
28
+ const STAGING_PREFIX='.arcane-runtime-stage-';
29
+ const issuedReceipts=new WeakSet();
30
+
31
+ function fail(message,code='ARCANE_WORKSPACE_RUNTIME_INTEGRITY_FAILED'){
32
+ const error=new Error(message);
33
+ error.code=code;
34
+ throw error;
35
+ }
36
+
37
+ function throwIfAborted(signal){
38
+ if(!signal?.aborted)return;
39
+ const error=signal.reason instanceof Error?signal.reason:new Error('Operation cancelled.');
40
+ error.code=error.code||'ARCANE_CANCELLED';
41
+ throw error;
42
+ }
43
+
44
+ async function emit(onEvent,event){
45
+ if(typeof onEvent==='function')await onEvent(Object.freeze(event));
46
+ }
47
+
48
+ function compareText(left,right){
49
+ const a=String(left);
50
+ const b=String(right);
51
+ return a<b?-1:a>b?1:0;
52
+ }
53
+
54
+ function safeRelativePath(value){
55
+ if(typeof value!=='string'||!value||value.includes('\\')||value.includes('\0')
56
+ ||path.posix.isAbsolute(value)||path.posix.normalize(value)!==value
57
+ ||value==='.'||value.startsWith('../')||value.includes('/../')){
58
+ fail(`Workspace runtime contains an unsafe path: ${String(value)}.`);
59
+ }
60
+ return value;
61
+ }
62
+
63
+ function portableKey(value){
64
+ const normalized=value.normalize('NFC');
65
+ return process.platform==='win32'?normalized.toLowerCase():normalized;
66
+ }
67
+
68
+ function resolveContained(root,relativePath){
69
+ const safe=safeRelativePath(relativePath);
70
+ const absolute=path.resolve(root,...safe.split('/'));
71
+ const relative=path.relative(root,absolute);
72
+ if(relative.startsWith('..')||path.isAbsolute(relative)){
73
+ fail(`Workspace runtime path escapes its root: ${safe}.`);
74
+ }
75
+ return absolute;
76
+ }
77
+
78
+ function projectSourcePath(sourcePath){
79
+ const safe=safeRelativePath(sourcePath);
80
+ if(safe.startsWith('arcane/'))return safe.slice('arcane/'.length);
81
+ if(safe.startsWith('strong-type/')){
82
+ return `dependencies/strong-type/${safe.slice('strong-type/'.length)}`;
83
+ }
84
+ fail(`SDK runtime path cannot be projected into a workspace: ${safe}.`);
85
+ }
86
+
87
+ function projectRuntimeFiles(runtimeReceipt,sdkBrowserRuntimeReceipt){
88
+ if(!runtimeReceipt||!Array.isArray(runtimeReceipt.files)){
89
+ fail('An authenticated SDK runtime receipt is required for workspace projection.');
90
+ }
91
+ if(!sdkBrowserRuntimeReceipt||!Array.isArray(sdkBrowserRuntimeReceipt.files)){
92
+ fail('An authenticated SDK browser runtime receipt is required for workspace projection.');
93
+ }
94
+ const seen=new Map();
95
+ const files=[];
96
+ const add=(sourceFile,projectedPath,authority)=>{
97
+ const key=portableKey(projectedPath);
98
+ const collision=seen.get(key);
99
+ if(collision){
100
+ fail(
101
+ `SDK runtime paths collide in the workspace projection: ${collision} and ${projectedPath}.`
102
+ );
103
+ }
104
+ seen.set(key,projectedPath);
105
+ files.push({
106
+ path:projectedPath,
107
+ sourcePath:sourceFile.path,
108
+ authority,
109
+ bytes:sourceFile.bytes,
110
+ sha256:sourceFile.sha256
111
+ });
112
+ };
113
+ for(const sourceFile of runtimeReceipt.files){
114
+ add(sourceFile,projectSourcePath(sourceFile.path),'arcane-runtime');
115
+ }
116
+ for(const sourceFile of sdkBrowserRuntimeReceipt.files){
117
+ add(sourceFile,`sdk/${safeRelativePath(sourceFile.path)}`,'sdk-browser-runtime');
118
+ }
119
+ files.sort((left,right)=>compareText(left.path,right.path));
120
+ return files;
121
+ }
122
+
123
+ function projectedDirectories(files){
124
+ const directories=new Set();
125
+ for(const file of files){
126
+ const parts=file.path.split('/');
127
+ parts.pop();
128
+ let current='';
129
+ for(const part of parts){
130
+ current=current?`${current}/${part}`:part;
131
+ directories.add(current);
132
+ }
133
+ }
134
+ return [...directories].sort(compareText);
135
+ }
136
+
137
+ function sameIdentity(before,after){
138
+ return before.dev===after.dev&&before.ino===after.ino&&before.size===after.size
139
+ &&before.mtimeNs===after.mtimeNs&&before.ctimeNs===after.ctimeNs
140
+ &&before.nlink===after.nlink;
141
+ }
142
+
143
+ function fileIdentity(info){
144
+ return Object.freeze({
145
+ device:String(info.dev),
146
+ inode:String(info.ino),
147
+ bytes:Number(info.size),
148
+ modifiedNanoseconds:String(info.mtimeNs),
149
+ changedNanoseconds:String(info.ctimeNs),
150
+ links:String(info.nlink)
151
+ });
152
+ }
153
+
154
+ function identityMatches(info,identity){
155
+ return String(info.dev)===identity.device
156
+ &&String(info.ino)===identity.inode
157
+ &&Number(info.size)===identity.bytes
158
+ &&String(info.mtimeNs)===identity.modifiedNanoseconds
159
+ &&String(info.ctimeNs)===identity.changedNanoseconds
160
+ &&String(info.nlink)===identity.links;
161
+ }
162
+
163
+ function locationIdentityMatches(info,identity){
164
+ return String(info.dev)===identity.device&&String(info.ino)===identity.inode;
165
+ }
166
+
167
+ async function workspaceLocation(workspaceRoot){
168
+ const requestedRoot=path.resolve(workspaceRoot);
169
+ let info;
170
+ try{info=await lstat(requestedRoot,{bigint:true});}
171
+ catch(error){
172
+ if(error?.code==='ENOENT')fail(`Workspace root does not exist: ${requestedRoot}.`);
173
+ throw error;
174
+ }
175
+ if(info.isSymbolicLink()||!info.isDirectory()){
176
+ fail('Workspace root must be a real directory.');
177
+ }
178
+ const canonicalRoot=await realpath(requestedRoot);
179
+ const canonicalInfo=await lstat(canonicalRoot,{bigint:true});
180
+ if(canonicalInfo.isSymbolicLink()||!canonicalInfo.isDirectory()
181
+ ||!sameIdentity(info,canonicalInfo)){
182
+ fail('Workspace root changed while its location was being resolved.');
183
+ }
184
+ return {
185
+ canonicalRoot,
186
+ identity:fileIdentity(canonicalInfo)
187
+ };
188
+ }
189
+
190
+ async function assertRealDirectoryLocation(directory,identity,label){
191
+ let before;
192
+ try{before=await lstat(directory,{bigint:true});}
193
+ catch(error){
194
+ if(error?.code==='ENOENT')fail(`${label} is missing.`);
195
+ throw error;
196
+ }
197
+ if(before.isSymbolicLink()||!before.isDirectory()
198
+ ||!locationIdentityMatches(before,identity)){
199
+ fail(`${label} changed while workspace runtime materialization was active.`);
200
+ }
201
+ const canonical=await realpath(directory);
202
+ if(canonical!==directory){
203
+ fail(`${label} became a symbolic link or junction.`);
204
+ }
205
+ const after=await lstat(directory,{bigint:true});
206
+ if(after.isSymbolicLink()||!after.isDirectory()
207
+ ||!locationIdentityMatches(after,identity)
208
+ ||!locationIdentityMatches(before,fileIdentity(after))){
209
+ fail(`${label} changed while its location was being authenticated.`);
210
+ }
211
+ }
212
+
213
+ async function inspectTree(root,{signal}={}){
214
+ throwIfAborted(signal);
215
+ const rootInfo=await lstat(root,{bigint:true});
216
+ if(rootInfo.isSymbolicLink()||!rootInfo.isDirectory()){
217
+ fail('Workspace arcane runtime root must be a real directory.');
218
+ }
219
+ const files=[];
220
+ const directories=[];
221
+ async function visit(directory,relativeRoot=''){
222
+ throwIfAborted(signal);
223
+ const entries=await readdir(directory,{withFileTypes:true});
224
+ entries.sort((left,right)=>compareText(left.name,right.name));
225
+ for(const entry of entries){
226
+ throwIfAborted(signal);
227
+ const relative=relativeRoot?`${relativeRoot}/${entry.name}`:entry.name;
228
+ safeRelativePath(relative);
229
+ const absolute=path.join(directory,entry.name);
230
+ const details=await lstat(absolute,{bigint:true});
231
+ if(details.isSymbolicLink()){
232
+ fail(`Workspace arcane runtime contains a symbolic link or junction: ${relative}.`);
233
+ }
234
+ if(details.isDirectory()){
235
+ directories.push({path:relative,identity:fileIdentity(details)});
236
+ await visit(absolute,relative);
237
+ }else if(details.isFile()){
238
+ files.push(relative);
239
+ }else{
240
+ fail(`Workspace arcane runtime contains a non-file entry: ${relative}.`);
241
+ }
242
+ }
243
+ }
244
+ await visit(root);
245
+ files.sort(compareText);
246
+ directories.sort((left,right)=>compareText(left.path,right.path));
247
+ return {rootIdentity:fileIdentity(rootInfo),files,directories};
248
+ }
249
+
250
+ async function hashExactFile(filePath,expectedBytes,signal){
251
+ throwIfAborted(signal);
252
+ let handle;
253
+ try{
254
+ handle=await open(filePath,READ_ONLY_NO_FOLLOW);
255
+ }catch(error){
256
+ if(error?.code==='ELOOP')fail(`Workspace runtime file became a symbolic link: ${filePath}.`);
257
+ throw error;
258
+ }
259
+ const hash=createHash('sha256');
260
+ const buffer=Buffer.allocUnsafe(1024*1024);
261
+ try{
262
+ const before=await handle.stat({bigint:true});
263
+ if(!before.isFile()||before.size!==BigInt(expectedBytes)){
264
+ fail(`Workspace runtime file size is invalid: ${filePath}.`);
265
+ }
266
+ while(true){
267
+ throwIfAborted(signal);
268
+ const {bytesRead}=await handle.read(buffer,0,buffer.length,null);
269
+ if(bytesRead===0)break;
270
+ hash.update(buffer.subarray(0,bytesRead));
271
+ }
272
+ const after=await handle.stat({bigint:true});
273
+ if(!sameIdentity(before,after)){
274
+ fail(`Workspace runtime file changed while it was being verified: ${filePath}.`);
275
+ }
276
+ return {sha256:hash.digest('hex'),identity:fileIdentity(after)};
277
+ }finally{
278
+ await handle.close();
279
+ }
280
+ }
281
+
282
+ async function assertIdentityAt(root,entry,{directory}){
283
+ const absolute=resolveContained(root,entry.path);
284
+ const info=await lstat(absolute,{bigint:true});
285
+ if(info.isSymbolicLink()
286
+ ||(directory?!info.isDirectory():!info.isFile())
287
+ ||!identityMatches(info,entry)){
288
+ fail(`Workspace runtime ${directory?'directory':'file'} changed after verification: ${entry.path}.`);
289
+ }
290
+ }
291
+
292
+ async function verifyProjectedTree(root,expectedFiles,{signal,onProgress}={}){
293
+ const expectedPaths=expectedFiles.map(file=>file.path);
294
+ const expectedDirectoryPaths=projectedDirectories(expectedFiles);
295
+ const before=await inspectTree(root,{signal});
296
+ if(JSON.stringify(before.files)!==JSON.stringify(expectedPaths)){
297
+ fail('Workspace arcane runtime file inventory does not match the authenticated SDK runtime.');
298
+ }
299
+ if(JSON.stringify(before.directories.map(entry=>entry.path))
300
+ !==JSON.stringify(expectedDirectoryPaths)){
301
+ fail('Workspace arcane runtime directory inventory does not match the authenticated SDK runtime.');
302
+ }
303
+
304
+ const identities=[];
305
+ let verifiedBytes=0;
306
+ for(const [index,file] of expectedFiles.entries()){
307
+ throwIfAborted(signal);
308
+ const absolute=resolveContained(root,file.path);
309
+ const result=await hashExactFile(absolute,file.bytes,signal);
310
+ if(result.sha256!==file.sha256){
311
+ fail(`Workspace runtime integrity check failed for ${file.path}.`);
312
+ }
313
+ verifiedBytes+=file.bytes;
314
+ identities.push({path:file.path,...result.identity});
315
+ if(onProgress){
316
+ await onProgress({
317
+ current:index+1,
318
+ total:expectedFiles.length,
319
+ verifiedBytes,
320
+ totalBytes:expectedFiles.reduce((total,entry)=>total+entry.bytes,0),
321
+ path:file.path
322
+ });
323
+ }
324
+ }
325
+
326
+ const rootAfter=await lstat(root,{bigint:true});
327
+ if(rootAfter.isSymbolicLink()||!rootAfter.isDirectory()
328
+ ||!identityMatches(rootAfter,before.rootIdentity)){
329
+ fail('Workspace arcane runtime root changed while it was being verified.');
330
+ }
331
+ for(const directory of before.directories){
332
+ throwIfAborted(signal);
333
+ await assertIdentityAt(root,{path:directory.path,...directory.identity},{directory:true});
334
+ }
335
+ for(const identity of identities){
336
+ throwIfAborted(signal);
337
+ await assertIdentityAt(root,identity,{directory:false});
338
+ }
339
+ return {
340
+ rootIdentity:before.rootIdentity,
341
+ directoryIdentities:before.directories.map(entry=>Object.freeze({
342
+ path:entry.path,
343
+ ...entry.identity
344
+ })),
345
+ identities:identities.map(Object.freeze)
346
+ };
347
+ }
348
+
349
+ async function assertRequestedWorkspace(receipt,workspaceRoot){
350
+ const requested=await workspaceLocation(workspaceRoot);
351
+ if(requested.canonicalRoot!==receipt.canonicalWorkspaceLocation
352
+ ||!locationIdentityMatches(
353
+ {
354
+ dev:BigInt(requested.identity.device),
355
+ ino:BigInt(requested.identity.inode)
356
+ },
357
+ receipt.workspaceIdentity
358
+ )){
359
+ fail('Workspace runtime receipt belongs to a different workspace location.');
360
+ }
361
+ const expectedRoot=path.join(requested.canonicalRoot,'arcane');
362
+ const canonicalRoot=await realpath(expectedRoot);
363
+ if(canonicalRoot!==receipt.canonicalLocation||canonicalRoot!==expectedRoot){
364
+ fail('Workspace runtime receipt belongs to a different arcane runtime location.');
365
+ }
366
+ return {workspace:requested,root:canonicalRoot};
367
+ }
368
+
369
+ async function assertWorkspaceRuntimeState(receipt,{workspaceRoot,signal}){
370
+ throwIfAborted(signal);
371
+ const {root}=await assertRequestedWorkspace(receipt,workspaceRoot);
372
+ const actual=await inspectTree(root,{signal});
373
+ if(!identityMatches(
374
+ {
375
+ dev:BigInt(actual.rootIdentity.device),
376
+ ino:BigInt(actual.rootIdentity.inode),
377
+ size:BigInt(actual.rootIdentity.bytes),
378
+ mtimeNs:BigInt(actual.rootIdentity.modifiedNanoseconds),
379
+ ctimeNs:BigInt(actual.rootIdentity.changedNanoseconds),
380
+ nlink:BigInt(actual.rootIdentity.links)
381
+ },
382
+ receipt.rootIdentity
383
+ )){
384
+ fail('Workspace arcane runtime root changed after verification.');
385
+ }
386
+ const actualPaths=actual.files;
387
+ const expectedPaths=receipt.files.map(file=>file.path);
388
+ if(JSON.stringify(actualPaths)!==JSON.stringify(expectedPaths)){
389
+ fail('Workspace arcane runtime file inventory changed after verification.');
390
+ }
391
+ const actualDirectories=actual.directories.map(entry=>entry.path);
392
+ const expectedDirectories=receipt.directoryIdentities.map(entry=>entry.path);
393
+ if(JSON.stringify(actualDirectories)!==JSON.stringify(expectedDirectories)){
394
+ fail('Workspace arcane runtime directory inventory changed after verification.');
395
+ }
396
+ for(const directory of receipt.directoryIdentities){
397
+ throwIfAborted(signal);
398
+ await assertIdentityAt(root,directory,{directory:true});
399
+ }
400
+ for(const identity of receipt.identities){
401
+ throwIfAborted(signal);
402
+ await assertIdentityAt(root,identity,{directory:false});
403
+ }
404
+ return receipt;
405
+ }
406
+
407
+ async function writeNewFile(filePath,bytes){
408
+ let handle;
409
+ try{
410
+ handle=await open(filePath,CREATE_NEW_NO_FOLLOW,0o644);
411
+ }catch(error){
412
+ if(error?.code==='ELOOP')fail(`Workspace runtime staging path became a symbolic link: ${filePath}.`);
413
+ throw error;
414
+ }
415
+ try{
416
+ await handle.writeFile(bytes);
417
+ await handle.sync();
418
+ }finally{
419
+ await handle.close();
420
+ }
421
+ }
422
+
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)){
427
+ fail(
428
+ `Refusing to clean an unowned workspace runtime staging path: ${stagingRoot} `
429
+ +`(parent ${stagingParent}; expected ${workspace.canonicalRoot}).`
430
+ );
431
+ }
432
+ await assertRealDirectoryLocation(
433
+ workspace.canonicalRoot,
434
+ workspace.identity,
435
+ 'Workspace root'
436
+ );
437
+ let stagingInfo;
438
+ try{stagingInfo=await lstat(stagingRoot,{bigint:true});}
439
+ catch(error){
440
+ if(error?.code==='ENOENT')return;
441
+ throw error;
442
+ }
443
+ if(stagingInfo.isSymbolicLink()||!stagingInfo.isDirectory()
444
+ ||!locationIdentityMatches(stagingInfo,stagingIdentity)){
445
+ fail('Refusing to clean a workspace runtime staging path whose identity changed.');
446
+ }
447
+ await assertRealDirectoryLocation(
448
+ stagingRoot,
449
+ stagingIdentity,
450
+ 'Workspace runtime staging directory'
451
+ );
452
+ try{await inspectTree(stagingRoot);}
453
+ catch(error){
454
+ fail(`Refusing to clean an unauthenticated workspace runtime staging tree: ${error.message}`);
455
+ }
456
+ await assertRealDirectoryLocation(
457
+ workspace.canonicalRoot,
458
+ workspace.identity,
459
+ 'Workspace root'
460
+ );
461
+ await assertRealDirectoryLocation(
462
+ stagingRoot,
463
+ stagingIdentity,
464
+ 'Workspace runtime staging directory'
465
+ );
466
+ await rm(stagingRoot,{recursive:true,force:true});
467
+ try{
468
+ await lstat(stagingRoot,{bigint:true});
469
+ fail('Workspace runtime staging path remained after cleanup.');
470
+ }catch(error){
471
+ if(error?.code!=='ENOENT')throw error;
472
+ }
473
+ await assertRealDirectoryLocation(
474
+ workspace.canonicalRoot,
475
+ workspace.identity,
476
+ 'Workspace root'
477
+ );
478
+ }
479
+
480
+ export async function verifyWorkspaceRuntime({
481
+ workspaceRoot,
482
+ runtimeRoot=path.join(getSdkRoot(),'runtime'),
483
+ runtimeReceipt,
484
+ browserRuntimeRoot=getSdkBrowserRuntimeRoot(),
485
+ sdkBrowserRuntimeReceipt,
486
+ signal,
487
+ onEvent
488
+ }={}){
489
+ if(!workspaceRoot)fail('workspaceRoot is required to verify a workspace runtime.');
490
+ await authenticateRuntimeReceipt(runtimeReceipt,{runtimeRoot,signal});
491
+ await authenticateSdkBrowserRuntimeReceipt(sdkBrowserRuntimeReceipt,{
492
+ browserRuntimeRoot,
493
+ signal
494
+ });
495
+ const expectedFiles=projectRuntimeFiles(runtimeReceipt,sdkBrowserRuntimeReceipt);
496
+ const workspace=await workspaceLocation(workspaceRoot);
497
+ const projectedRoot=path.join(workspace.canonicalRoot,'arcane');
498
+ let rootInfo;
499
+ try{rootInfo=await lstat(projectedRoot,{bigint:true});}
500
+ catch(error){
501
+ if(error?.code==='ENOENT'){
502
+ fail(`Workspace arcane runtime is missing: ${projectedRoot}.`);
503
+ }
504
+ throw error;
505
+ }
506
+ if(rootInfo.isSymbolicLink()||!rootInfo.isDirectory()){
507
+ fail('Workspace arcane runtime root must be a real directory.');
508
+ }
509
+ const canonicalRoot=await realpath(projectedRoot);
510
+ if(canonicalRoot!==projectedRoot){
511
+ fail('Workspace arcane runtime root must not resolve outside its direct workspace location.');
512
+ }
513
+
514
+ const totalBytes=expectedFiles.reduce((total,file)=>total+file.bytes,0);
515
+ await emit(onEvent,{
516
+ type:'workspace.runtime.verify.started',
517
+ fileCount:expectedFiles.length,
518
+ totalBytes
519
+ });
520
+ const verified=await verifyProjectedTree(canonicalRoot,expectedFiles,{
521
+ signal,
522
+ onProgress:event=>emit(onEvent,{type:'workspace.runtime.verify.progress',...event})
523
+ });
524
+ const inventory=expectedFiles.map(file=>Object.freeze({...file}));
525
+ const contentInventory=expectedFiles.map(file=>({
526
+ path:file.path,
527
+ bytes:file.bytes,
528
+ sha256:file.sha256
529
+ }));
530
+ const receipt=Object.freeze({
531
+ schemaVersion:1,
532
+ kind:'arcane-workspace-runtime-verification',
533
+ canonicalWorkspaceLocation:workspace.canonicalRoot,
534
+ workspaceIdentity:workspace.identity,
535
+ canonicalLocation:canonicalRoot,
536
+ rootIdentity:verified.rootIdentity,
537
+ sourceRuntimeLocation:runtimeReceipt.canonicalLocation,
538
+ sourceManifestSha256:runtimeReceipt.manifestSha256,
539
+ sourceContentSha256:runtimeReceipt.contentSha256,
540
+ sourceBrowserRuntimeLocation:sdkBrowserRuntimeReceipt.canonicalLocation,
541
+ sourceBrowserManifestSha256:sdkBrowserRuntimeReceipt.manifestSha256,
542
+ sourceBrowserContentSha256:sdkBrowserRuntimeReceipt.contentSha256,
543
+ sdkVersion:runtimeReceipt.sdkVersion,
544
+ sources:Object.freeze({
545
+ arcane:Object.freeze({
546
+ authority:'arcane-os-upstream',
547
+ location:runtimeReceipt.canonicalLocation,
548
+ manifestSha256:runtimeReceipt.manifestSha256,
549
+ contentSha256:runtimeReceipt.contentSha256,
550
+ source:runtimeReceipt.source
551
+ }),
552
+ sdkBrowser:Object.freeze({
553
+ authority:'arcane-os-sdk',
554
+ location:sdkBrowserRuntimeReceipt.canonicalLocation,
555
+ manifestSha256:sdkBrowserRuntimeReceipt.manifestSha256,
556
+ contentSha256:sdkBrowserRuntimeReceipt.contentSha256,
557
+ source:sdkBrowserRuntimeReceipt.source
558
+ })
559
+ }),
560
+ files:Object.freeze(inventory),
561
+ fileCount:inventory.length,
562
+ totalBytes,
563
+ contentSha256:createHash('sha256')
564
+ .update(JSON.stringify(contentInventory))
565
+ .digest('hex'),
566
+ directoryIdentities:Object.freeze(verified.directoryIdentities),
567
+ identities:Object.freeze(verified.identities)
568
+ });
569
+ issuedReceipts.add(receipt);
570
+ await emit(onEvent,{
571
+ type:'workspace.runtime.verify.completed',
572
+ sourceContentSha256:receipt.sourceContentSha256,
573
+ sourceBrowserContentSha256:receipt.sourceBrowserContentSha256,
574
+ contentSha256:receipt.contentSha256,
575
+ fileCount:receipt.fileCount,
576
+ totalBytes:receipt.totalBytes
577
+ });
578
+ return receipt;
579
+ }
580
+
581
+ export async function materializeWorkspaceRuntime({
582
+ workspaceRoot,
583
+ runtimeRoot=path.join(getSdkRoot(),'runtime'),
584
+ runtimeReceipt,
585
+ browserRuntimeRoot=getSdkBrowserRuntimeRoot(),
586
+ sdkBrowserRuntimeReceipt,
587
+ signal,
588
+ onEvent
589
+ }={}){
590
+ if(!workspaceRoot)fail('workspaceRoot is required to materialize a workspace runtime.');
591
+ await authenticateRuntimeReceipt(runtimeReceipt,{runtimeRoot,signal});
592
+ await authenticateSdkBrowserRuntimeReceipt(sdkBrowserRuntimeReceipt,{
593
+ browserRuntimeRoot,
594
+ signal
595
+ });
596
+ const expectedFiles=projectRuntimeFiles(runtimeReceipt,sdkBrowserRuntimeReceipt);
597
+ const workspace=await workspaceLocation(workspaceRoot);
598
+ const destinationRoot=path.join(workspace.canonicalRoot,'arcane');
599
+ try{
600
+ const existing=await lstat(destinationRoot,{bigint:true});
601
+ if(existing.isSymbolicLink()||!existing.isDirectory()){
602
+ fail('Existing workspace arcane runtime path must be a real directory.');
603
+ }
604
+ await emit(onEvent,{type:'workspace.runtime.materialize.reused'});
605
+ return verifyWorkspaceRuntime({
606
+ workspaceRoot,
607
+ runtimeRoot,
608
+ runtimeReceipt,
609
+ browserRuntimeRoot,
610
+ sdkBrowserRuntimeReceipt,
611
+ signal,
612
+ onEvent
613
+ });
614
+ }catch(error){
615
+ if(error?.code!=='ENOENT')throw error;
616
+ }
617
+
618
+ const stagingRoot=path.join(
619
+ workspace.canonicalRoot,
620
+ `${STAGING_PREFIX}${String(process.pid)}-${randomUUID()}`
621
+ );
622
+ await mkdir(stagingRoot,{mode:0o700});
623
+ const stagingInfo=await lstat(stagingRoot,{bigint:true});
624
+ if(stagingInfo.isSymbolicLink()||!stagingInfo.isDirectory()){
625
+ fail('Workspace runtime staging path must be a real directory.');
626
+ }
627
+ const stagingIdentity=fileIdentity(stagingInfo);
628
+ await assertRealDirectoryLocation(
629
+ workspace.canonicalRoot,
630
+ workspace.identity,
631
+ 'Workspace root'
632
+ );
633
+ await assertRealDirectoryLocation(
634
+ stagingRoot,
635
+ stagingIdentity,
636
+ 'Workspace runtime staging directory'
637
+ );
638
+ const bufferedEvents=[];
639
+ let stagingCleaned=false;
640
+ try{
641
+ const totalBytes=expectedFiles.reduce((total,file)=>total+file.bytes,0);
642
+ bufferedEvents.push({
643
+ type:'workspace.runtime.materialize.started',
644
+ fileCount:expectedFiles.length,
645
+ totalBytes
646
+ });
647
+ let writtenBytes=0;
648
+ for(const [index,file] of expectedFiles.entries()){
649
+ throwIfAborted(signal);
650
+ const bytes=file.authority==='sdk-browser-runtime'
651
+ ?await readVerifiedSdkBrowserRuntimeFile(sdkBrowserRuntimeReceipt,{
652
+ browserRuntimeRoot,
653
+ relativePath:file.sourcePath,
654
+ signal
655
+ })
656
+ :await readVerifiedRuntimeFile(runtimeReceipt,{
657
+ runtimeRoot,
658
+ relativePath:file.sourcePath,
659
+ signal
660
+ });
661
+ const destination=resolveContained(stagingRoot,file.path);
662
+ await mkdir(path.dirname(destination),{recursive:true,mode:0o755});
663
+ await writeNewFile(destination,bytes);
664
+ writtenBytes+=bytes.length;
665
+ bufferedEvents.push({
666
+ type:'workspace.runtime.materialize.progress',
667
+ current:index+1,
668
+ total:expectedFiles.length,
669
+ writtenBytes,
670
+ totalBytes,
671
+ path:file.path
672
+ });
673
+ }
674
+ await verifyProjectedTree(stagingRoot,expectedFiles,{signal});
675
+
676
+ // Materialization callbacks are held until every staged byte authenticates.
677
+ // They can still veto the commit, but no callback runs between staged writes.
678
+ for(const event of bufferedEvents)await emit(onEvent,event);
679
+ throwIfAborted(signal);
680
+ await assertRealDirectoryLocation(
681
+ workspace.canonicalRoot,
682
+ workspace.identity,
683
+ 'Workspace root'
684
+ );
685
+ await assertRealDirectoryLocation(
686
+ stagingRoot,
687
+ stagingIdentity,
688
+ 'Workspace runtime staging directory'
689
+ );
690
+ await verifyProjectedTree(stagingRoot,expectedFiles,{signal});
691
+ await assertRealDirectoryLocation(
692
+ workspace.canonicalRoot,
693
+ workspace.identity,
694
+ 'Workspace root'
695
+ );
696
+ await assertRealDirectoryLocation(
697
+ stagingRoot,
698
+ stagingIdentity,
699
+ 'Workspace runtime staging directory'
700
+ );
701
+
702
+ throwIfAborted(signal);
703
+ try{
704
+ await rename(stagingRoot,destinationRoot);
705
+ }catch(error){
706
+ let destinationExists=false;
707
+ try{
708
+ const existing=await lstat(destinationRoot,{bigint:true});
709
+ destinationExists=existing.isDirectory()&&!existing.isSymbolicLink();
710
+ }catch(inspectError){
711
+ if(inspectError?.code!=='ENOENT')throw inspectError;
712
+ }
713
+ if(!destinationExists)throw error;
714
+ await cleanupStaging(stagingRoot,workspace,stagingIdentity);
715
+ stagingCleaned=true;
716
+ await emit(onEvent,{type:'workspace.runtime.materialize.reused'});
717
+ return await verifyWorkspaceRuntime({
718
+ workspaceRoot,
719
+ runtimeRoot,
720
+ runtimeReceipt,
721
+ browserRuntimeRoot,
722
+ sdkBrowserRuntimeReceipt,
723
+ signal,
724
+ onEvent
725
+ });
726
+ }
727
+ await cleanupStaging(stagingRoot,workspace,stagingIdentity);
728
+ stagingCleaned=true;
729
+ const receipt=await verifyWorkspaceRuntime({
730
+ workspaceRoot,
731
+ runtimeRoot,
732
+ runtimeReceipt,
733
+ browserRuntimeRoot,
734
+ sdkBrowserRuntimeReceipt,
735
+ signal,
736
+ onEvent
737
+ });
738
+ await emit(onEvent,{
739
+ type:'workspace.runtime.materialize.completed',
740
+ contentSha256:receipt.contentSha256,
741
+ sourceContentSha256:receipt.sourceContentSha256,
742
+ sourceBrowserContentSha256:receipt.sourceBrowserContentSha256,
743
+ fileCount:receipt.fileCount,
744
+ totalBytes:receipt.totalBytes
745
+ });
746
+ return receipt;
747
+ }finally{
748
+ if(!stagingCleaned)await cleanupStaging(stagingRoot,workspace,stagingIdentity);
749
+ }
750
+ }
751
+
752
+ export async function authenticateWorkspaceRuntimeReceipt(receipt,{
753
+ workspaceRoot,
754
+ signal
755
+ }={}){
756
+ if(!receipt||!issuedReceipts.has(receipt)){
757
+ fail('Workspace runtime verification receipt was not issued by this SDK process.');
758
+ }
759
+ if(!workspaceRoot)fail('workspaceRoot is required to authenticate a workspace runtime receipt.');
760
+ return assertWorkspaceRuntimeState(receipt,{workspaceRoot,signal});
761
+ }
762
+
763
+ export async function readVerifiedWorkspaceRuntimeFile(receipt,{
764
+ workspaceRoot,
765
+ relativePath,
766
+ signal
767
+ }={}){
768
+ if(!receipt||!issuedReceipts.has(receipt)){
769
+ fail('Workspace runtime verification receipt was not issued by this SDK process.');
770
+ }
771
+ if(!workspaceRoot)fail('workspaceRoot is required to read a verified workspace runtime file.');
772
+ throwIfAborted(signal);
773
+ const normalized=safeRelativePath(relativePath);
774
+ const file=receipt.files.find(candidate=>portableKey(candidate.path)===portableKey(normalized));
775
+ if(!file)fail(`Path is not in the verified workspace runtime inventory: ${normalized}.`);
776
+ if(file.bytes>MAX_VERIFIED_WORKSPACE_RUNTIME_FILE_BYTES){
777
+ fail(
778
+ `Verified workspace runtime file exceeds the ${MAX_VERIFIED_WORKSPACE_RUNTIME_FILE_BYTES}-byte serving limit: ${file.path}.`
779
+ );
780
+ }
781
+ const identity=receipt.identities.find(
782
+ candidate=>portableKey(candidate.path)===portableKey(file.path)
783
+ );
784
+ if(!identity)fail(`Verified workspace runtime identity is missing for ${file.path}.`);
785
+
786
+ const {root}=await assertRequestedWorkspace(receipt,workspaceRoot);
787
+ const rootInfo=await lstat(root,{bigint:true});
788
+ if(rootInfo.isSymbolicLink()||!rootInfo.isDirectory()
789
+ ||!identityMatches(rootInfo,receipt.rootIdentity)){
790
+ fail('Workspace arcane runtime root changed after verification.');
791
+ }
792
+ const ancestors=[];
793
+ let current='';
794
+ for(const part of file.path.split('/').slice(0,-1)){
795
+ current=current?`${current}/${part}`:part;
796
+ ancestors.push(current);
797
+ }
798
+ for(const ancestor of ancestors){
799
+ const directory=receipt.directoryIdentities.find(entry=>entry.path===ancestor);
800
+ if(!directory)fail(`Verified workspace runtime directory identity is missing for ${ancestor}.`);
801
+ await assertIdentityAt(root,directory,{directory:true});
802
+ }
803
+ await assertIdentityAt(root,identity,{directory:false});
804
+
805
+ const filePath=resolveContained(root,file.path);
806
+ let handle;
807
+ try{
808
+ handle=await open(filePath,READ_ONLY_NO_FOLLOW);
809
+ }catch(error){
810
+ if(error?.code==='ELOOP')fail(`Workspace runtime file became a symbolic link: ${file.path}.`);
811
+ throw error;
812
+ }
813
+ try{
814
+ const opened=await handle.stat({bigint:true});
815
+ if(!opened.isFile()||!identityMatches(opened,identity)){
816
+ fail(`Workspace runtime file changed while it was being opened: ${file.path}.`);
817
+ }
818
+ const bytes=await handle.readFile();
819
+ throwIfAborted(signal);
820
+ const after=await handle.stat({bigint:true});
821
+ if(!identityMatches(after,identity)||bytes.length!==file.bytes){
822
+ fail(`Workspace runtime file changed while it was being read: ${file.path}.`);
823
+ }
824
+ if(createHash('sha256').update(bytes).digest('hex')!==file.sha256){
825
+ fail(`Workspace runtime file hash changed: ${file.path}.`);
826
+ }
827
+ await assertIdentityAt(root,identity,{directory:false});
828
+ for(const ancestor of ancestors){
829
+ const directory=receipt.directoryIdentities.find(entry=>entry.path===ancestor);
830
+ await assertIdentityAt(root,directory,{directory:true});
831
+ }
832
+ const canonicalFile=await realpath(filePath);
833
+ const canonicalRelative=path.relative(root,canonicalFile);
834
+ if(canonicalRelative.startsWith('..')||path.isAbsolute(canonicalRelative)){
835
+ fail(`Workspace runtime path left its root: ${file.path}.`);
836
+ }
837
+ return bytes;
838
+ }finally{
839
+ await handle.close();
840
+ }
841
+ }