arcane-os 0.3.1 → 0.3.3

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 +27 -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 +113 -69
  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 +19 -149
  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 +42 -327
  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 +48 -66
  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 +492 -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 +48 -79
  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 +55 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +133 -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,23 +1,9 @@
1
- import {randomBytes,timingSafeEqual} from 'node:crypto';
2
1
  import {constants as FS_CONSTANTS} from 'node:fs';
3
2
  import {lstat,open,realpath} from 'node:fs/promises';
4
3
  import http from 'node:http';
5
4
  import path from 'node:path';
6
- import {
7
- authenticateAppReleaseReceipt,
8
- readVerifiedAppReleaseFile,
9
- RELEASE_MANIFEST_NAME
10
- } from './packager/core.mjs';
11
- import {
12
- authenticateWorkspaceRuntimeReceipt,
13
- readVerifiedWorkspaceRuntimeFile,
14
- verifyWorkspaceRuntime
15
- } from './workspace-runtime.mjs';
16
- import {verifyRuntime} from './runtime.mjs';
17
- import {verifySdkBrowserRuntime} from './sdk-browser-runtime.mjs';
18
- import {resolveWorkspace,validateWorkspace} from './workspace.mjs';
5
+ import {resolveWorkspace} from './workspace.mjs';
19
6
  import {createEventQueue} from './event-queue.mjs';
20
- import {SDK_NAME,SDK_VERSION} from './constants.mjs';
21
7
 
22
8
  const MIME_TYPES=new Map([
23
9
  ['.css','text/css; charset=utf-8'],
@@ -38,10 +24,6 @@ const MIME_TYPES=new Map([
38
24
  ['.woff2','font/woff2']
39
25
  ]);
40
26
  const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
41
- const MAX_DEVELOPMENT_FILE_BYTES=64*1024*1024;
42
- const MAX_CONCURRENT_FILE_RESPONSES=4;
43
- const MAX_PENDING_FILE_RESPONSES=256;
44
- const SESSION_COOKIE='Arcane-Dev-Session';
45
27
  const PRIVATE_SOURCE_SEGMENTS=new Set([
46
28
  'arcane-app.json','arcane-package.json','test','tests','scripts','node_modules','dist','local'
47
29
  ]);
@@ -49,6 +31,11 @@ const SDK_RUNTIME_SOURCE_PROTOCOL='arcane-sdk-runtime-source/1';
49
31
  const SDK_RUNTIME_SOURCE_ARCANE_ROOTS=new Set([
50
32
  'components','css','entities','img','modules','security'
51
33
  ]);
34
+ const SDK_INSTALLED_ARCANE_ROOTS=new Set([
35
+ ...SDK_RUNTIME_SOURCE_ARCANE_ROOTS,
36
+ 'dependencies',
37
+ 'sdk'
38
+ ]);
52
39
  const SDK_RUNTIME_SOURCE_PRIVATE_SEGMENTS=new Set([
53
40
  'node_modules','.git','.hg','.svn','cvs'
54
41
  ]);
@@ -57,27 +44,6 @@ const SDK_RUNTIME_SOURCE_PRIVATE_MANIFESTS=new Set([
57
44
  'arcane_app_release.json','arcane_runtime_release.json','arcane_sdk_browser_release.json'
58
45
  ]);
59
46
 
60
- // This server is a loopback-only development host, not a production policy
61
- // boundary. The bundled runtime currently needs inline component execution,
62
- // workers, remote provider requests, media, and embedded web content. The
63
- // unguessable session cookie and exact numeric Host check protect this broad
64
- // development CSP from being exposed as a general network service.
65
- const DEVELOPMENT_CSP=[
66
- "default-src 'self'",
67
- "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'",
68
- "style-src 'self' 'unsafe-inline'",
69
- "img-src 'self' data: blob: http: https:",
70
- "font-src 'self' data:",
71
- "connect-src 'self' data: blob: http: https: ws: wss:",
72
- "worker-src 'self' blob:",
73
- "frame-src 'self' data: blob: http: https:",
74
- "media-src 'self' data: blob: http: https:",
75
- "object-src 'none'",
76
- "frame-ancestors 'none'",
77
- "base-uri 'self'",
78
- "form-action 'self'"
79
- ].join('; ');
80
-
81
47
  function fail(message,code='ARCANE_OPERATION_FAILED'){
82
48
  const error=new Error(message);
83
49
  error.code=code;
@@ -91,20 +57,9 @@ function throwIfAborted(signal){
91
57
  throw error;
92
58
  }
93
59
 
94
- function responseSecurityHeaders(contentSecurityPolicy=DEVELOPMENT_CSP){
95
- return {
96
- 'cache-control':'no-store',
97
- 'x-content-type-options':'nosniff',
98
- 'content-security-policy':contentSecurityPolicy,
99
- 'cross-origin-resource-policy':'same-origin',
100
- 'referrer-policy':'no-referrer'
101
- };
102
- }
103
-
104
60
  function deny(response,status,message){
105
61
  response.writeHead(status,{
106
- 'content-type':'text/plain; charset=utf-8',
107
- ...responseSecurityHeaders("default-src 'none'; frame-ancestors 'none'; base-uri 'none'")
62
+ 'content-type':'text/plain; charset=utf-8'
108
63
  });
109
64
  response.end(`${message}\n`);
110
65
  }
@@ -136,17 +91,6 @@ function resolveInside(root,segments){
136
91
  return candidate;
137
92
  }
138
93
 
139
- function identityMatches(info,identity){
140
- return !identity||(
141
- String(info.dev)===identity.device
142
- &&String(info.ino)===identity.inode
143
- &&Number(info.size)===identity.bytes
144
- &&String(info.mtimeNs)===identity.modifiedNanoseconds
145
- &&String(info.ctimeNs)===identity.changedNanoseconds
146
- &&(identity.links===undefined||String(info.nlink)===identity.links)
147
- );
148
- }
149
-
150
94
  function inventoryKey(value){
151
95
  return process.platform==='win32'?value.toLowerCase():value;
152
96
  }
@@ -187,7 +131,7 @@ async function canonicalRealDirectory(requested,label){
187
131
  return canonical;
188
132
  }
189
133
 
190
- function sdkRuntimeSourcePathAllowed(relative,{arcaneRoot=false}={}){
134
+ function sdkRuntimeSourcePathAllowed(relative,{arcaneRoots=null}={}){
191
135
  if(relative.length===0)return false;
192
136
  const normalized=relative.map(function normalizeRuntimeSourceSegment(segment){
193
137
  return segment.normalize('NFC').toLowerCase();
@@ -196,11 +140,15 @@ function sdkRuntimeSourcePathAllowed(relative,{arcaneRoot=false}={}){
196
140
  return segment.startsWith('.')||SDK_RUNTIME_SOURCE_PRIVATE_SEGMENTS.has(segment)
197
141
  ||SDK_RUNTIME_SOURCE_PRIVATE_MANIFESTS.has(segment);
198
142
  }))return false;
199
- return !arcaneRoot||SDK_RUNTIME_SOURCE_ARCANE_ROOTS.has(normalized[0]);
143
+ return arcaneRoots===null||arcaneRoots.has(normalized[0]);
200
144
  }
201
145
 
202
146
  function sdkArcaneSourcePathAllowed(relative){
203
- return sdkRuntimeSourcePathAllowed(relative,{arcaneRoot:true});
147
+ return sdkRuntimeSourcePathAllowed(relative,{arcaneRoots:SDK_RUNTIME_SOURCE_ARCANE_ROOTS});
148
+ }
149
+
150
+ function sdkInstalledArcanePathAllowed(relative){
151
+ return sdkRuntimeSourcePathAllowed(relative,{arcaneRoots:SDK_INSTALLED_ARCANE_ROOTS});
204
152
  }
205
153
 
206
154
  function sdkDependencySourcePathAllowed(relative){
@@ -235,35 +183,6 @@ async function verifySdkRuntimeSourceRoot(sourceRoot,workspaceRoot,appId,{signal
235
183
  'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
236
184
  }
237
185
 
238
- let packageFile;
239
- try{
240
- packageFile=await openSafeFile(canonicalRoot,['package.json']);
241
- }catch{
242
- fail('SDK runtime source package.json must be a readable bounded real file.',
243
- 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
244
- }
245
- if(!packageFile){
246
- fail('SDK runtime source root must contain a real package.json.',
247
- 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
248
- }
249
- let packageDocument;
250
- try{
251
- packageDocument=JSON.parse(packageFile.bytes.toString('utf8'));
252
- }catch{
253
- fail('SDK runtime source package.json must be valid JSON.',
254
- 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
255
- }
256
- if(!packageDocument||Array.isArray(packageDocument)||packageDocument.name!==SDK_NAME){
257
- fail(`SDK runtime source package name must be exactly ${SDK_NAME}.`,
258
- 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
259
- }
260
- if(packageDocument.version!==SDK_VERSION){
261
- fail(
262
- `SDK runtime source version must exactly match the executing SDK (${SDK_VERSION}).`,
263
- 'ARCANE_DEV_RUNTIME_VERSION_MISMATCH'
264
- );
265
- }
266
-
267
186
  const roots=[
268
187
  {
269
188
  path:'runtime/arcane',
@@ -299,36 +218,23 @@ async function verifySdkRuntimeSourceRoot(sourceRoot,workspaceRoot,appId,{signal
299
218
  path:root.path
300
219
  });
301
220
  }
302
- const runtime=Object.freeze({
221
+ const runtime={
303
222
  mode:'sdk-source',
304
223
  protocol:SDK_RUNTIME_SOURCE_PROTOCOL,
305
- sdkVersion:SDK_VERSION,
306
224
  mutable:true,
307
225
  distributionAuthority:false,
308
226
  sourceRoot:canonicalRoot
309
- });
227
+ };
310
228
  await emitRuntimeSourceEvent(onEvent,{
311
229
  type:'runtime.source.mount.ready',
312
230
  appId,
313
231
  canonicalRoot,
314
- sdkVersion:SDK_VERSION,
315
232
  protocol:SDK_RUNTIME_SOURCE_PROTOCOL,
316
233
  routeCount:mappings.length
317
234
  });
318
235
  return {mappings,runtime};
319
236
  }
320
237
 
321
- function identityMap(identities,prefix=''){
322
- const normalizedPrefix=prefix?`${prefix}/`:'';
323
- const result=new Map();
324
- for(const identity of identities||[]){
325
- if(!identity.path.startsWith(normalizedPrefix))continue;
326
- const relative=identity.path.slice(normalizedPrefix.length);
327
- if(relative)result.set(inventoryKey(relative),identity);
328
- }
329
- return result;
330
- }
331
-
332
238
  function routePrefixKey(prefix){
333
239
  return prefix.map(segment=>segment.normalize('NFC').toLowerCase()).join('/');
334
240
  }
@@ -355,38 +261,17 @@ function deterministicMappings(mappings){
355
261
  }
356
262
 
357
263
  function createFileWorkLimiter(){
358
- let active=0;
359
- const pending=[];
360
- const release=()=>{
361
- const next=pending.shift();
362
- if(next)next();
363
- else active-=1;
364
- };
365
- return async work=>{
366
- if(active>=MAX_CONCURRENT_FILE_RESPONSES){
367
- if(pending.length>=MAX_PENDING_FILE_RESPONSES){
368
- fail('Development server file queue is full.','ARCANE_BACKPRESSURE');
369
- }
370
- await new Promise(resolve=>pending.push(resolve));
371
- }else{
372
- active+=1;
373
- }
374
- try{
375
- return await work();
376
- }finally{
377
- release();
378
- }
379
- };
264
+ return async work=>work();
380
265
  }
381
266
 
382
- async function openSafeFile(root,segments,expectedIdentity){
267
+ async function openSafeFile(root,segments){
383
268
  const candidate=resolveInside(root,segments);
384
269
  if(!candidate)return null;
385
270
  let current=root;
386
271
  for(const segment of segments){
387
272
  current=path.join(current,segment);
388
273
  let info;
389
- try{info=await lstat(current,{bigint:true});}
274
+ try{info=await lstat(current);}
390
275
  catch(error){
391
276
  if(error?.code==='ENOENT')return null;
392
277
  throw error;
@@ -394,8 +279,10 @@ async function openSafeFile(root,segments,expectedIdentity){
394
279
  if(info.isSymbolicLink())return null;
395
280
  }
396
281
 
397
- const before=await lstat(candidate,{bigint:true});
398
- if(before.isSymbolicLink()||!before.isFile()||!identityMatches(before,expectedIdentity))return null;
282
+ const currentInfo=await lstat(candidate);
283
+ if(currentInfo.isSymbolicLink()||!currentInfo.isFile())return null;
284
+ const canonicalCandidate=await realpath(candidate);
285
+ if(!pathIsWithin(root,canonicalCandidate))return null;
399
286
  let handle;
400
287
  try{
401
288
  handle=await open(candidate,READ_ONLY_NO_FOLLOW);
@@ -404,53 +291,15 @@ async function openSafeFile(root,segments,expectedIdentity){
404
291
  throw error;
405
292
  }
406
293
  try{
407
- const opened=await handle.stat({bigint:true});
408
- if(!opened.isFile()||!identityMatches(opened,expectedIdentity)
409
- ||!identityMatches(opened,{
410
- device:String(before.dev),
411
- inode:String(before.ino),
412
- bytes:Number(before.size),
413
- modifiedNanoseconds:String(before.mtimeNs),
414
- changedNanoseconds:String(before.ctimeNs),
415
- links:String(before.nlink)
416
- })){
294
+ const opened=await handle.stat();
295
+ if(!opened.isFile())return null;
296
+ const content=await handle.readFile();
297
+ const servedCandidate=await realpath(candidate);
298
+ if(!pathIsWithin(root,servedCandidate)
299
+ ||canonicalLocationKey(servedCandidate)!==canonicalLocationKey(canonicalCandidate)){
417
300
  return null;
418
301
  }
419
- if(Number(opened.size)>MAX_DEVELOPMENT_FILE_BYTES){
420
- fail(
421
- `Development file exceeds the ${MAX_DEVELOPMENT_FILE_BYTES}-byte serving limit.`,
422
- 'ARCANE_POLICY_DENIED'
423
- );
424
- }
425
- const bytes=await handle.readFile();
426
- const after=await handle.stat({bigint:true});
427
- if(!identityMatches(after,{
428
- device:String(opened.dev),
429
- inode:String(opened.ino),
430
- bytes:Number(opened.size),
431
- modifiedNanoseconds:String(opened.mtimeNs),
432
- changedNanoseconds:String(opened.ctimeNs),
433
- links:String(opened.nlink)
434
- })||bytes.length!==Number(opened.size)){
435
- return null;
436
- }
437
- const canonicalCandidate=await realpath(candidate);
438
- const relative=path.relative(root,canonicalCandidate);
439
- if(relative.startsWith('..')||path.isAbsolute(relative)){
440
- return null;
441
- }
442
- const currentInfo=await lstat(candidate,{bigint:true});
443
- if(!identityMatches(currentInfo,{
444
- device:String(opened.dev),
445
- inode:String(opened.ino),
446
- bytes:Number(opened.size),
447
- modifiedNanoseconds:String(opened.mtimeNs),
448
- changedNanoseconds:String(opened.ctimeNs),
449
- links:String(opened.nlink)
450
- })){
451
- return null;
452
- }
453
- return {candidate:canonicalCandidate,bytes,size:bytes.length};
302
+ return {candidate:servedCandidate,content};
454
303
  }catch(error){
455
304
  if(error?.code==='ENOENT')return null;
456
305
  throw error;
@@ -463,8 +312,7 @@ async function sendFile(response,opened,{head=false}={}){
463
312
  const extension=path.extname(opened.candidate).toLowerCase();
464
313
  response.writeHead(200,{
465
314
  'content-type':MIME_TYPES.get(extension)||'application/octet-stream',
466
- 'content-length':opened.size,
467
- ...responseSecurityHeaders()
315
+ 'content-length':opened.content.byteLength
468
316
  });
469
317
  await new Promise((resolve,reject)=>{
470
318
  let settled=false;
@@ -488,7 +336,7 @@ async function sendFile(response,opened,{head=false}={}){
488
336
  response.once('error',failed);
489
337
  response.once('finish',completed);
490
338
  response.once('close',completed);
491
- response.end(head?undefined:opened.bytes);
339
+ response.end(head?undefined:opened.content);
492
340
  });
493
341
  }
494
342
 
@@ -526,7 +374,6 @@ function sharedPathAllowed(relative,route){
526
374
  }
527
375
 
528
376
  async function sourceRoutes(workspaceRoot,appId,{
529
- workspaceRuntimeReceipt,
530
377
  sdkRuntimeSourceRoot,
531
378
  signal,
532
379
  onEvent
@@ -570,58 +417,6 @@ async function sourceRoutes(workspaceRoot,appId,{
570
417
  };
571
418
  }
572
419
  const runtimeRoot=path.join(resolved.workspaceRoot,'arcane');
573
- const validation=await validateWorkspace({
574
- workspaceRoot:resolved.workspaceRoot,
575
- appId:resolved.appId,
576
- allowMissingManagedImportMap:true,
577
- signal
578
- });
579
- const sdkInstallation=validation.sdkInstallation;
580
- if(!sdkInstallation
581
- ||typeof sdkInstallation.runtimeRoot!=='string'
582
- ||typeof sdkInstallation.browserRuntimeRoot!=='string'){
583
- fail(
584
- 'Validated external workspace is missing its bound SDK installation authority.',
585
- 'ARCANE_WORKSPACE_INVALID'
586
- );
587
- }
588
- let verified=workspaceRuntimeReceipt;
589
- if(!verified){
590
- const sdkRuntimeRoot=sdkInstallation.runtimeRoot;
591
- const sdkRuntimeReceipt=await verifyRuntime({
592
- runtimeRoot:sdkRuntimeRoot,
593
- signal
594
- });
595
- const sdkBrowserRuntimeRoot=sdkInstallation.browserRuntimeRoot;
596
- const sdkBrowserRuntimeReceipt=await verifySdkBrowserRuntime({
597
- browserRuntimeRoot:sdkBrowserRuntimeRoot,
598
- signal
599
- });
600
- verified=await verifyWorkspaceRuntime({
601
- workspaceRoot:resolved.workspaceRoot,
602
- runtimeRoot:sdkRuntimeRoot,
603
- runtimeReceipt:sdkRuntimeReceipt,
604
- browserRuntimeRoot:sdkBrowserRuntimeRoot,
605
- sdkBrowserRuntimeReceipt,
606
- signal
607
- });
608
- }
609
- verified=await authenticateWorkspaceRuntimeReceipt(verified,{
610
- workspaceRoot:resolved.workspaceRoot,
611
- signal
612
- });
613
- if(typeof verified.sourceRuntimeLocation!=='string'
614
- ||typeof verified.sourceBrowserRuntimeLocation!=='string'
615
- ||canonicalLocationKey(verified.sourceRuntimeLocation)
616
- !==canonicalLocationKey(sdkInstallation.runtimeRoot)
617
- ||canonicalLocationKey(verified.sourceBrowserRuntimeLocation)
618
- !==canonicalLocationKey(sdkInstallation.browserRuntimeRoot)){
619
- fail(
620
- 'Workspace runtime receipt sources do not match the bound SDK installation authority.',
621
- 'ARCANE_INTEGRITY_FAILED'
622
- );
623
- }
624
- const arcaneIdentities=identityMap(verified.identities);
625
420
  return {
626
421
  workspaceRoot:resolved.workspaceRoot,
627
422
  workspaceMode:'external',
@@ -632,61 +427,27 @@ async function sourceRoutes(workspaceRoot,appId,{
632
427
  {
633
428
  prefix:['arcane'],
634
429
  root:runtimeRoot,
635
- identities:arcaneIdentities,
636
- read:relative=>readVerifiedWorkspaceRuntimeFile(verified,{
637
- workspaceRoot:resolved.workspaceRoot,
638
- relativePath:relative.join('/'),
639
- signal
640
- }),
641
- allow:relative=>arcaneIdentities.has(inventoryKey(relative.join('/')))
430
+ allow:sdkInstalledArcanePathAllowed
642
431
  }
643
432
  ]
644
433
  };
645
434
  }
646
435
 
647
- async function packagedRoutes(releaseRoot,releaseReceipt,{signal}={}){
436
+ async function packagedRoutes(releaseRoot){
648
437
  if(typeof releaseRoot!=='string'||!releaseRoot.trim())fail('releaseRoot is required in packaged mode.','ARCANE_USAGE');
649
- if(!releaseReceipt)fail('An authenticated release receipt is required in packaged mode.','ARCANE_POLICY_DENIED');
650
438
  const requested=path.resolve(releaseRoot);
651
- await authenticateAppReleaseReceipt(releaseReceipt,{releaseRoot:requested,signal});
652
- const info=await lstat(requested);
653
- if(info.isSymbolicLink()||!info.isDirectory())fail('Packaged release root must be a real directory.');
654
- const canonical=await realpath(requested);
655
- const identities=identityMap(releaseReceipt.identities);
439
+ const canonical=await canonicalRealDirectory(requested,'Packaged release root');
656
440
  return {
657
441
  workspaceRoot:null,
658
442
  appId:null,
659
443
  startPath:'/index.html',
660
444
  mappings:[{
661
445
  prefix:[],
662
- root:canonical,
663
- identities,
664
- read:relative=>readVerifiedAppReleaseFile(releaseReceipt,{
665
- releaseRoot:canonical,
666
- relativePath:relative.join('/'),
667
- signal
668
- }),
669
- allow:relative=>identities.has(inventoryKey(relative.join('/')))
446
+ root:canonical
670
447
  }]
671
448
  };
672
449
  }
673
450
 
674
- function tokenMatches(value,expected){
675
- if(typeof value!=='string')return false;
676
- const received=Buffer.from(value,'utf8');
677
- const wanted=Buffer.from(expected,'utf8');
678
- return received.length===wanted.length&&timingSafeEqual(received,wanted);
679
- }
680
-
681
- function hasSessionCookie(request,cookieName,sessionToken){
682
- const matches=String(request.headers.cookie||'')
683
- .split(';')
684
- .map(part=>part.trim())
685
- .filter(part=>part.startsWith(`${cookieName}=`))
686
- .map(part=>part.slice(cookieName.length+1));
687
- return matches.length===1&&tokenMatches(matches[0],sessionToken);
688
- }
689
-
690
451
  function listen(server,{host,port,signal}){
691
452
  return new Promise((resolve,reject)=>{
692
453
  const cleanup=()=>{
@@ -718,9 +479,7 @@ async function startOwnedDevServer({
718
479
  host='127.0.0.1',
719
480
  port=0,
720
481
  signal,
721
- workspaceRuntimeReceipt,
722
- sdkRuntimeSourceRoot,
723
- releaseReceipt
482
+ sdkRuntimeSourceRoot
724
483
  }={},events,releaseSignal){
725
484
  throwIfAborted(signal);
726
485
  if(mode!=='source'&&mode!=='packaged')fail(`Unsupported server mode: ${String(mode)}.`,'ARCANE_USAGE');
@@ -744,65 +503,31 @@ async function startOwnedDevServer({
744
503
  });
745
504
  const routeSet=mode==='source'
746
505
  ?await sourceRoutes(workspaceRoot,appId,{
747
- workspaceRuntimeReceipt,
748
506
  sdkRuntimeSourceRoot,
749
507
  signal,
750
508
  onEvent:event=>events.send(event)
751
509
  })
752
- :await packagedRoutes(releaseRoot,releaseReceipt,{signal});
510
+ :await packagedRoutes(releaseRoot);
753
511
  const mappings=deterministicMappings(routeSet.mappings);
754
512
  for(const mapping of mappings){
755
513
  const info=await lstat(mapping.root);
756
514
  if(info.isSymbolicLink()||!info.isDirectory())fail(`Server route root must be a real directory: ${mapping.root}.`);
757
515
  mapping.root=await realpath(mapping.root);
758
516
  }
759
- const sessionToken=randomBytes(32).toString('hex');
760
- const sessionCookieName=`${SESSION_COOKIE}-${randomBytes(8).toString('hex')}`;
761
- let expectedAuthority=null;
762
517
  const requestTasks=new Set();
763
518
  const runFileWork=createFileWorkLimiter();
764
519
  const server=http.createServer((request,response)=>{
765
520
  let task;
766
521
  task=(async()=>{
767
- if(!expectedAuthority){deny(response,503,'Server is starting.');return;}
768
- if(request.headers.host!==expectedAuthority){
769
- deny(response,421,'Misdirected request.');
770
- return;
771
- }
772
522
  if(request.method!=='GET'&&request.method!=='HEAD'){
773
523
  deny(response,405,'Method not allowed.');
774
524
  return;
775
525
  }
776
526
  const target=parseRequestTarget(request.url);
777
527
  if(!target){deny(response,400,'Invalid request path.');return;}
778
- const queryKeys=[...target.searchParams.keys()];
779
- const isBootstrap=request.method==='GET'
780
- &&target.path===routeSet.startPath
781
- &&queryKeys.length===1
782
- &&queryKeys[0]==='arcane_session'
783
- &&tokenMatches(target.searchParams.get('arcane_session'),sessionToken);
784
- if(isBootstrap){
785
- response.writeHead(302,{
786
- location:routeSet.startPath,
787
- 'set-cookie':`${sessionCookieName}=${sessionToken}; HttpOnly; SameSite=Strict; Path=/`,
788
- ...responseSecurityHeaders()
789
- });
790
- response.end();
791
- return;
792
- }
793
- if(!hasSessionCookie(request,sessionCookieName,sessionToken)){
794
- deny(response,403,'Development server session required.');
795
- return;
796
- }
797
528
  const {segments}=target;
798
- if(mode==='packaged'&&segments.some(segment=>
799
- segment.toLowerCase()===RELEASE_MANIFEST_NAME.toLowerCase()
800
- )){
801
- deny(response,404,'Not found.');
802
- return;
803
- }
804
529
  if(segments.length===0){
805
- response.writeHead(302,{location:routeSet.startPath,...responseSecurityHeaders()});
530
+ response.writeHead(302,{location:routeSet.startPath});
806
531
  response.end();
807
532
  return;
808
533
  }
@@ -814,23 +539,14 @@ async function startOwnedDevServer({
814
539
  if(relative.length===0){deny(response,404,'Not found.');return;}
815
540
  if(mapping.allow&&!mapping.allow(relative)){deny(response,404,'Not found.');return;}
816
541
  await runFileWork(async()=>{
817
- const expectedIdentity=mapping.identities?.get(inventoryKey(relative.join('/')));
818
- const opened=mapping.read
819
- ?{
820
- candidate:path.join(mapping.root,...relative),
821
- bytes:await mapping.read(relative)
822
- }
823
- :await openSafeFile(mapping.root,relative,expectedIdentity);
542
+ const opened=await openSafeFile(mapping.root,relative);
824
543
  if(!opened){deny(response,404,'Not found.');return;}
825
- opened.size=opened.bytes.length;
826
544
  await sendFile(response,opened,{head:request.method==='HEAD'});
827
545
  });
828
546
  })().catch(async error=>{
829
547
  await events.enqueue({type:'server.request.failed',message:error.message});
830
548
  if(!response.headersSent){
831
- const status=error?.code==='ARCANE_POLICY_DENIED'?413
832
- :error?.code==='ARCANE_BACKPRESSURE'?503
833
- :500;
549
+ const status=error?.code==='ARCANE_BACKPRESSURE'?503:500;
834
550
  deny(response,status,'Internal server error.');
835
551
  }
836
552
  else response.destroy(error);
@@ -847,10 +563,9 @@ async function startOwnedDevServer({
847
563
  }
848
564
  const visibleHost=address.family==='IPv6'?`[${address.address}]`:address.address;
849
565
  const endpoint=new URL(`http://${visibleHost}:${address.port}`);
850
- expectedAuthority=endpoint.host;
851
566
  const origin=endpoint.origin;
852
567
  const cleanUrl=`${origin}${routeSet.startPath}`;
853
- const url=`${cleanUrl}?arcane_session=${sessionToken}`;
568
+ const url=cleanUrl;
854
569
  let closeInitiated=false;
855
570
  let lifecycleSettlementStarted=false;
856
571
  let operationalError=null;