arcane-os 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +86 -117
  3. package/bin/arcane-test.mjs +170 -46
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
  5. package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
  7. package/browser-runtime/ai/browser-wasm.mjs +24 -35
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
  9. package/browser-runtime/ai/model-controller.mjs +584 -181
  10. package/browser-runtime/ai/speech-worker-client.mjs +8 -148
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
  12. package/browser-runtime/dom-event-instrumentation.mjs +55 -147
  13. package/browser-runtime/event-manager.mjs +239 -624
  14. package/package.json +5 -6
  15. package/runtime/arcane/components/app-bar.html +3 -15
  16. package/runtime/arcane/components/assistant-panel.html +10 -10
  17. package/runtime/arcane/components/calculator.html +1 -1
  18. package/runtime/arcane/components/chat.html +1359 -135
  19. package/runtime/arcane/components/conversation-view.html +2 -2
  20. package/runtime/arcane/components/document-inspector.html +11 -17
  21. package/runtime/arcane/components/file-manager.html +13 -56
  22. package/runtime/arcane/components/markdown-document.html +82 -281
  23. package/runtime/arcane/components/markdown-editor.html +7 -10
  24. package/runtime/arcane/components/media-embed.html +6 -6
  25. package/runtime/arcane/components/screen-capture.html +4 -4
  26. package/runtime/arcane/components/source-explanation.html +2 -2
  27. package/runtime/arcane/components/speech.html +112 -68
  28. package/runtime/arcane/components/terminal-workspace.html +4 -4
  29. package/runtime/arcane/components/theme-editor.html +1 -1
  30. package/runtime/arcane/components/unified-inbox.html +2 -2
  31. package/runtime/arcane/components/voice-transcription.html +31 -21
  32. package/runtime/arcane/entities/Calculation.js +2 -3
  33. package/runtime/arcane/entities/Chat.js +228 -43
  34. package/runtime/arcane/entities/Preference.js +3 -5
  35. package/runtime/arcane/entities/Weather.js +5 -5
  36. package/runtime/arcane/modules/AI.js +1042 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
  38. package/runtime/arcane/modules/AIResponseLength.js +9 -19
  39. package/runtime/arcane/modules/AIRuntimeState.js +109 -72
  40. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
  41. package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
  42. package/runtime/arcane/modules/CalculatorEngine.js +9 -9
  43. package/runtime/arcane/modules/CommunicationAppController.js +3 -7
  44. package/runtime/arcane/modules/ComponentContracts.js +30 -32
  45. package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
  46. package/runtime/arcane/modules/ConversationActionItems.js +26 -59
  47. package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
  48. package/runtime/arcane/modules/ConversationTimebox.js +27 -15
  49. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
  50. package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
  51. package/runtime/arcane/modules/HTMLImport.js +54 -1
  52. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
  53. package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
  54. package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
  55. package/runtime/arcane/modules/MD.js +1 -45
  56. package/runtime/arcane/modules/Mail.js +51 -103
  57. package/runtime/arcane/modules/MailOutbox.mjs +95 -193
  58. package/runtime/arcane/modules/MailTransport.mjs +36 -57
  59. package/runtime/arcane/modules/ModelDefinition.js +22 -106
  60. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
  61. package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
  62. package/runtime/arcane/modules/PreferenceStore.js +102 -30
  63. package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
  64. package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
  65. package/runtime/arcane/modules/ScreenCapture.js +175 -128
  66. package/runtime/arcane/modules/SpeechPlayback.js +46 -149
  67. package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
  68. package/runtime/arcane/modules/ToolCallRouter.js +25 -12
  69. package/runtime/arcane/modules/YouTubeMedia.js +6 -5
  70. package/schemas/arcane-app-bundle.schema.json +13 -78
  71. package/schemas/arcane-app.schema.json +9 -25
  72. package/schemas/arcane-lock.schema.json +18 -151
  73. package/schemas/arcane-package.schema.json +2 -16
  74. package/schemas/native-build-plan.schema.json +119 -122
  75. package/src/app-descriptor.mjs +75 -132
  76. package/src/application-tests.mjs +200 -0
  77. package/src/cli/main.mjs +27 -46
  78. package/src/constants.mjs +3 -4
  79. package/src/dev-server.mjs +30 -324
  80. package/src/doctor.mjs +92 -154
  81. package/src/dom-event-instrumentation.mjs +55 -147
  82. package/src/errors.mjs +2 -3
  83. package/src/event-manager.mjs +239 -624
  84. package/src/event-queue.mjs +3 -3
  85. package/src/import-map.mjs +273 -1028
  86. package/src/index.mjs +14 -16
  87. package/src/installed-sdk-runtime.mjs +27 -67
  88. package/src/integrated-provider-loader.mjs +53 -382
  89. package/src/mail-api.mjs +0 -2
  90. package/src/mail-server.mjs +224 -580
  91. package/src/mail.mjs +4 -10
  92. package/src/native-plan.mjs +163 -598
  93. package/src/native-provider-loader.mjs +104 -1063
  94. package/src/packager/core.mjs +485 -3229
  95. package/src/process.mjs +5 -10
  96. package/src/release-bundle.mjs +292 -2405
  97. package/src/runtime.mjs +76 -396
  98. package/src/scaffold.mjs +30 -80
  99. package/src/sdk-browser-runtime.mjs +70 -626
  100. package/src/source-server.mjs +588 -0
  101. package/src/targets/index.mjs +78 -188
  102. package/src/templates/workspace-template.mjs +19 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +131 -544
  106. package/src/update-check.mjs +26 -64
  107. package/src/workspace-operation-lock.mjs +139 -430
  108. package/src/workspace-runtime.mjs +109 -1558
  109. package/src/workspace.mjs +40 -302
  110. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
  111. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
  112. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
  113. package/browser-runtime/ai/internal/sha256.mjs +0 -166
  114. package/docs/architecture.md +0 -344
  115. package/docs/compatibility.md +0 -36
  116. package/docs/event-manager.md +0 -294
  117. package/docs/platform-targets.md +0 -108
  118. package/docs/publishing.md +0 -201
  119. package/docs/reference/README.md +0 -185
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1295
  122. package/docs/reference/ai/browser-wasm.md +0 -530
  123. package/docs/reference/arcane-ollama.md +0 -288
  124. package/docs/reference/availability-and-normalization.md +0 -183
  125. package/docs/reference/behavioral-testing.md +0 -133
  126. package/docs/reference/cli.md +0 -779
  127. package/docs/reference/core/README.md +0 -62
  128. package/docs/reference/core/arcane-ai-contracts.md +0 -907
  129. package/docs/reference/core/arcane-api.md +0 -601
  130. package/docs/reference/core/arcane-entities.md +0 -65
  131. package/docs/reference/core/arcane-events.md +0 -134
  132. package/docs/reference/core/ollama-module.md +0 -181
  133. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
  134. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
  135. package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
  136. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
  137. package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
  138. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
  139. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
  140. package/docs/reference/event-manager.md +0 -1511
  141. package/docs/reference/inventory/package-api.json +0 -3284
  142. package/docs/reference/inventory/runtime-components.json +0 -1011
  143. package/docs/reference/inventory/runtime-entities.json +0 -26
  144. package/docs/reference/inventory/runtime-modules.json +0 -1431
  145. package/docs/reference/mail.md +0 -316
  146. package/docs/reference/protocols.md +0 -719
  147. package/docs/reference/runtime-components.md +0 -1366
  148. package/docs/reference/runtime-entities.md +0 -303
  149. package/docs/reference/runtime-modules.md +0 -2965
  150. package/docs/reference/sdk-api.md +0 -6698
  151. package/docs/roadmap.md +0 -79
  152. package/docs/work-amplification.md +0 -129
  153. package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
package/src/workspace.mjs CHANGED
@@ -1,29 +1,19 @@
1
- import {createHash} from 'node:crypto';
2
- import {lstat,readFile,readdir,realpath} from 'node:fs/promises';
1
+ import {readFile,readdir,realpath,stat} from 'node:fs/promises';
3
2
  import path from 'node:path';
4
3
  import {
5
4
  normalizeRelativePath,
6
5
  validateAppConfig as validatePackagerAppConfig,
7
6
  validateRootConfig as validatePackagerRootConfig
8
7
  } from './packager/core.mjs';
9
- import {appDescriptorSha256,loadAppDescriptor} from './app-descriptor.mjs';
10
- import {
11
- SDK_NAME as EXPECTED_SDK_NAME,
12
- SDK_VERSION as EXPECTED_SDK_VERSION
13
- } from './constants.mjs';
8
+ import {loadAppDescriptor} from './app-descriptor.mjs';
9
+ import {SDK_NAME as EXPECTED_SDK_NAME} from './constants.mjs';
14
10
  import {inspectImportMapHtml} from './import-map.mjs';
15
- import {
16
- SDK_BROWSER_RUNTIME_CONTENT_SHA256,
17
- SDK_BROWSER_RUNTIME_MANIFEST_SHA256
18
- } from './sdk-browser-runtime.mjs';
19
11
 
20
12
  const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
21
- const SHA256_PATTERN=/^[a-f0-9]{64}$/;
22
- const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
23
13
  const NPM_PACKAGE_NAME_PATTERN=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
24
- const EXACT_SDK_ALIAS=`npm:${EXPECTED_SDK_NAME}@${EXPECTED_SDK_VERSION}`;
25
14
  const ROOT_CONFIG_NAME='arcane-packager.json';
26
15
  const APP_CONFIG_NAME='arcane-package.json';
16
+ const completeValue=value=>value;
27
17
 
28
18
  function fail(message,code='ARCANE_WORKSPACE_INVALID'){
29
19
  const error=new Error(message);
@@ -47,14 +37,12 @@ function throwIfAborted(signal){
47
37
  }
48
38
 
49
39
  async function emit(onEvent,event){
50
- if(typeof onEvent==='function')await onEvent(Object.freeze(event));
40
+ if(typeof onEvent==='function')await onEvent(event);
51
41
  }
52
42
 
53
43
  async function readJson(filePath,label){
54
44
  let text;
55
45
  try{
56
- const info=await lstat(filePath);
57
- if(info.isSymbolicLink()||!info.isFile())fail(`${label} must be a real file.`);
58
46
  text=await readFile(filePath,'utf8');
59
47
  }catch(error){
60
48
  if(error?.code==='ENOENT')fail(`${label} does not exist.`);
@@ -66,16 +54,12 @@ async function readJson(filePath,label){
66
54
 
67
55
  async function assertRealDirectory(directory,label){
68
56
  let info;
69
- try{info=await lstat(directory);}
57
+ try{info=await stat(directory);}
70
58
  catch(error){
71
59
  if(error?.code==='ENOENT')fail(`${label} does not exist: ${directory}.`);
72
60
  throw error;
73
61
  }
74
- if(info.isSymbolicLink()||!info.isDirectory())fail(`${label} must be a real directory: ${directory}.`);
75
- }
76
-
77
- function sameDirectoryIdentity(left,right){
78
- return left.device===right.device&&left.inode===right.inode;
62
+ if(!info.isDirectory())fail(`${label} must be a directory: ${directory}.`);
79
63
  }
80
64
 
81
65
  function sameDirectoryPath(left,right){
@@ -86,68 +70,6 @@ function sameDirectoryPath(left,right){
86
70
  return normalize(left)===normalize(right);
87
71
  }
88
72
 
89
- async function captureRealDirectoryIdentity(directory,label){
90
- const requested=path.resolve(directory);
91
- let requestedInfo;
92
- try{requestedInfo=await lstat(requested,{bigint:true});}
93
- catch(error){
94
- if(error?.code==='ENOENT')fail(`${label} does not exist: ${requested}.`);
95
- throw error;
96
- }
97
- if(requestedInfo.isSymbolicLink()||!requestedInfo.isDirectory()){
98
- fail(`${label} must be a real directory: ${requested}.`);
99
- }
100
- const canonical=await realpath(requested);
101
- const canonicalInfo=await lstat(canonical,{bigint:true});
102
- const requestedIdentity=Object.freeze({
103
- device:requestedInfo.dev,
104
- inode:requestedInfo.ino
105
- });
106
- const canonicalIdentity=Object.freeze({
107
- device:canonicalInfo.dev,
108
- inode:canonicalInfo.ino
109
- });
110
- if(canonicalInfo.isSymbolicLink()||!canonicalInfo.isDirectory()
111
- ||!sameDirectoryIdentity(requestedIdentity,canonicalIdentity)){
112
- fail(`${label} must resolve to one physical directory: ${requested}.`);
113
- }
114
- return Object.freeze({
115
- requested,
116
- requestedIdentity,
117
- canonical,
118
- identity:canonicalIdentity
119
- });
120
- }
121
-
122
- async function assertRealDirectoryIdentity(captured,label){
123
- let canonical;
124
- let requestedInfo;
125
- let canonicalInfo;
126
- try{
127
- [canonical,requestedInfo,canonicalInfo]=await Promise.all([
128
- realpath(captured.requested),
129
- lstat(captured.requested,{bigint:true}),
130
- lstat(captured.canonical,{bigint:true})
131
- ]);
132
- }catch(error){
133
- if(error?.code==='ENOENT'){
134
- fail(`${label} changed while focused validation was active.`,
135
- 'ARCANE_INTEGRITY_FAILED');
136
- }
137
- throw error;
138
- }
139
- const requestedIdentity={device:requestedInfo.dev,inode:requestedInfo.ino};
140
- const canonicalIdentity={device:canonicalInfo.dev,inode:canonicalInfo.ino};
141
- if(!sameDirectoryPath(canonical,captured.canonical)
142
- ||requestedInfo.isSymbolicLink()||!requestedInfo.isDirectory()
143
- ||canonicalInfo.isSymbolicLink()||!canonicalInfo.isDirectory()
144
- ||!sameDirectoryIdentity(requestedIdentity,captured.requestedIdentity)
145
- ||!sameDirectoryIdentity(requestedIdentity,captured.identity)
146
- ||!sameDirectoryIdentity(canonicalIdentity,captured.identity)){
147
- fail(`${label} changed while focused validation was active.`,'ARCANE_INTEGRITY_FAILED');
148
- }
149
- }
150
-
151
73
  function sdkPackageSourceForDependency(dependencyName){
152
74
  if(typeof dependencyName!=='string'||dependencyName.length>214
153
75
  ||!NPM_PACKAGE_NAME_PATTERN.test(dependencyName)){
@@ -172,12 +94,6 @@ function dependencyNameForSdkPackageSource(source){
172
94
  }
173
95
  }
174
96
 
175
- function supportedCanonicalSdkDeclaration(value){
176
- return value===EXPECTED_SDK_VERSION
177
- ||(typeof value==='string'&&LOCAL_TARBALL_PATTERN.test(value)
178
- &&!/[\x00-\x1f\x7f]/.test(value));
179
- }
180
-
181
97
  export function resolveSdkPackageDeclaration(rootPackage,{
182
98
  allowMissing=false,
183
99
  packageSource
@@ -194,17 +110,10 @@ export function resolveSdkPackageDeclaration(rootPackage,{
194
110
  &&(specifier===`npm:${EXPECTED_SDK_NAME}`
195
111
  ||specifier.startsWith(`npm:${EXPECTED_SDK_NAME}@`));
196
112
  if(!canonicalName&&!aliasTarget)continue;
197
- const supported=canonicalName
198
- ?supportedCanonicalSdkDeclaration(specifier)
199
- :specifier===EXACT_SDK_ALIAS;
200
- if(!supported){
201
- fail(
202
- `package.json ${groupName}.${dependencyName} must be ${EXPECTED_SDK_VERSION}, `
203
- +`a local file: tarball under ${EXPECTED_SDK_NAME}, or the exact npm alias `
204
- +`${EXACT_SDK_ALIAS} under a distinct dependency key.`
205
- );
113
+ if(typeof specifier!=='string'||!specifier.trim()){
114
+ fail(`package.json ${groupName}.${dependencyName} must declare an SDK package version or source.`);
206
115
  }
207
- candidates.push(Object.freeze({
116
+ candidates.push(completeValue({
208
117
  dependencyName,
209
118
  dependencyGroup:groupName,
210
119
  packageSource:sdkPackageSourceForDependency(dependencyName),
@@ -218,8 +127,7 @@ export function resolveSdkPackageDeclaration(rootPackage,{
218
127
  if(candidates.length===0){
219
128
  if(allowMissing&&packageSource===undefined)return null;
220
129
  fail(
221
- `package.json must declare exactly one ${EXPECTED_SDK_NAME} installation as `
222
- +`${EXPECTED_SDK_VERSION}, a local file: tarball, or the exact npm alias ${EXACT_SDK_ALIAS}.`
130
+ `package.json must declare exactly one ${EXPECTED_SDK_NAME} installation.`
223
131
  );
224
132
  }
225
133
  const [declaration]=candidates;
@@ -232,13 +140,6 @@ export function resolveSdkPackageDeclaration(rootPackage,{
232
140
  return declaration;
233
141
  }
234
142
 
235
- function sdkManifestPaths(packageSource){
236
- return Object.freeze({
237
- runtimeManifest:`${packageSource}/runtime/ARCANE_RUNTIME_RELEASE.json`,
238
- browserRuntimeManifest:`${packageSource}/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json`
239
- });
240
- }
241
-
242
143
  function classifyRootConfig(config){
243
144
  const validated=validatePackagerRootConfig(config,ROOT_CONFIG_NAME);
244
145
  const routes=validated.sharedPayloads['browser-runtime'];
@@ -299,7 +200,7 @@ function classifyRootConfig(config){
299
200
  }else{
300
201
  fail(`${ROOT_CONFIG_NAME} browser-runtime routes must match the external SDK or integrated Arcane workspace contract.`);
301
202
  }
302
- return Object.freeze({
203
+ return completeValue({
303
204
  ...validated,
304
205
  workspaceMode,
305
206
  browserRuntimeLayout,
@@ -330,7 +231,7 @@ async function discoverAppsInRoot(root,config){
330
231
  appId:entry.name,
331
232
  packageManifest:manifest
332
233
  });
333
- apps.push(Object.freeze({
234
+ apps.push(completeValue({
334
235
  appId:entry.name,
335
236
  appRoot,
336
237
  manifest:validatedManifest,
@@ -342,7 +243,7 @@ async function discoverAppsInRoot(root,config){
342
243
  if(!String(error?.message).includes('does not exist'))throw error;
343
244
  }
344
245
  }
345
- return Object.freeze(apps);
246
+ return completeValue(apps);
346
247
  }
347
248
 
348
249
  export async function discoverApps(workspaceRoot=process.cwd()){
@@ -357,7 +258,7 @@ export async function inspectWorkspaceProfile(workspaceRoot=process.cwd()){
357
258
  const config=classifyRootConfig(
358
259
  await readJson(path.join(canonicalRoot,ROOT_CONFIG_NAME),ROOT_CONFIG_NAME)
359
260
  );
360
- return Object.freeze({
261
+ return completeValue({
361
262
  workspaceRoot:canonicalRoot,
362
263
  workspaceMode:config.workspaceMode,
363
264
  config
@@ -400,82 +301,16 @@ export async function resolveWorkspace({workspaceRoot=process.cwd(),appId}={}){
400
301
  }else{
401
302
  fail(`This workspace contains multiple apps; select one explicitly: ${apps.map(item=>item.appId).join(', ')}.`,'ARCANE_USAGE');
402
303
  }
403
- return Object.freeze({
304
+ return completeValue({
404
305
  workspaceRoot:canonicalRoot,
405
306
  config,
406
307
  app,
407
308
  appId:app.appId,
408
309
  appRoot:app.appRoot,
409
- appIds:Object.freeze(apps.map(item=>item.appId))
310
+ appIds:completeValue(apps.map(item=>item.appId))
410
311
  });
411
312
  }
412
313
 
413
- function validateLock(lock,sdkDeclaration){
414
- const browser=lock?.sdkBrowserRuntime;
415
- const browserSource=browser?.source;
416
- const dependencies=browserSource?.dependencies;
417
- const exactKeys=(value,keys)=>isObject(value)
418
- &&Object.keys(value).sort().join('\0')===[...keys].sort().join('\0');
419
- const expectedDependencies=[
420
- {
421
- name:'event-pubsub',
422
- version:'6.1.0',
423
- resolved:'https://registry.npmjs.org/event-pubsub/-/event-pubsub-6.1.0.tgz',
424
- integrity:'sha512-FEMlhTxwqGM0hztTixG6FhVFXqp7Eq1ltk5mSreK6Mhy3xWWpLAzEUR6OMvMdNqT3jgSxA8JDhnhyAG3X4Xy7Q=='
425
- },
426
- {
427
- name:'strong-type',
428
- version:'2.0.0',
429
- resolved:'https://registry.npmjs.org/strong-type/-/strong-type-2.0.0.tgz',
430
- integrity:'sha512-HHrY9qYC7yn+5mlewiI3k9RQM9gZqGQsqbomZcd10Ks0h4RlX01nnkWbCe4AsVPCI6KaFvpkWm1nHMD+Ykup6g=='
431
- },
432
- {
433
- name:'@wllama/wllama',
434
- version:'3.6.0',
435
- resolved:'https://registry.npmjs.org/@wllama/wllama/-/wllama-3.6.0.tgz',
436
- integrity:'sha512-NN3ZBXqaaUwGXTQubkNvsCaLPjN2XVa0bVS40OYCE8zquYmRc2W3oHYEgwvuSWWDB8aUqTLyMioySCXNkcnD1w=='
437
- }
438
- ];
439
- const dependenciesMatch=Array.isArray(dependencies)&&dependencies.length===expectedDependencies.length
440
- &&dependencies.every((actual,index)=>{
441
- const expected=expectedDependencies[index];
442
- return exactKeys(actual,['name','version','resolved','integrity'])
443
- &&actual.name===expected.name&&actual.version===expected.version
444
- &&actual.resolved===expected.resolved&&actual.integrity===expected.integrity;
445
- });
446
- const manifests=sdkManifestPaths(sdkDeclaration.packageSource);
447
- if(!exactKeys(lock,['schemaVersion','sdk','runtime','sdkBrowserRuntime','protocols'])
448
- ||lock.schemaVersion!==1
449
- ||!exactKeys(lock.sdk,['name','version'])
450
- ||lock.sdk.name!==EXPECTED_SDK_NAME||lock.sdk.version!==EXPECTED_SDK_VERSION
451
- ||!exactKeys(lock.runtime,['manifest','contentSha256','upstreamCommit'])
452
- ||!SHA256_PATTERN.test(lock.runtime.contentSha256)
453
- ||!/^([a-f0-9]{40})$/.test(lock.runtime.upstreamCommit)
454
- ||lock.runtime.manifest!==manifests.runtimeManifest
455
- ||!exactKeys(browser,[
456
- 'manifest','manifestSha256','contentSha256','builder','sdkVersion','source'
457
- ])
458
- ||browser.manifest!==manifests.browserRuntimeManifest
459
- ||browser.manifestSha256!==SDK_BROWSER_RUNTIME_MANIFEST_SHA256
460
- ||browser.contentSha256!==SDK_BROWSER_RUNTIME_CONTENT_SHA256
461
- ||browser.builder!=='arcane-sdk-browser-runtime-v1'
462
- ||browser.sdkVersion!==EXPECTED_SDK_VERSION
463
- ||!exactKeys(browserSource,[
464
- 'authority','repository','protocol','browserEntry','dependencies'
465
- ])||browserSource.authority!=='arcane-os-sdk'
466
- ||browserSource.repository!=='https://github.com/TheWizardNexus/arcane-os-sdk.git'
467
- ||browserSource.protocol!=='arcane-sdk-browser-runtime/1'
468
- ||browserSource.browserEntry!=='arcane-os/event-manager'
469
- ||!dependenciesMatch
470
- ||!exactKeys(lock.protocols,['arcane','cliEvents','targetAdapter'])
471
- ||lock.protocols.arcane!=='arcane/1'
472
- ||lock.protocols.cliEvents!=='arcane-cli-events/1'
473
- ||lock.protocols.targetAdapter!=='arcane-target-adapter/1'){
474
- fail('arcane.lock.json is incompatible with this SDK. Run arcane init only after reviewing missing files; existing locks are never overwritten.');
475
- }
476
- return lock;
477
- }
478
-
479
314
  export async function resolveInstalledSdkInstallation(workspaceRoot,declaration){
480
315
  let current=workspaceRoot;
481
316
  for(const segment of declaration.packageSource.split('/')){
@@ -490,8 +325,8 @@ export async function resolveInstalledSdkInstallation(workspaceRoot,declaration)
490
325
  path.join(canonicalPackageRoot,'package.json'),
491
326
  'installed SDK package manifest'
492
327
  );
493
- if(installedPackage.name!==EXPECTED_SDK_NAME||installedPackage.version!==EXPECTED_SDK_VERSION){
494
- fail(`Installed SDK package must identify exactly as ${EXPECTED_SDK_NAME}@${EXPECTED_SDK_VERSION}.`);
328
+ if(installedPackage.name!==EXPECTED_SDK_NAME||typeof installedPackage.version!=='string'){
329
+ fail(`Installed SDK package must identify as ${EXPECTED_SDK_NAME}.`);
495
330
  }
496
331
  const runtimeRoot=path.join(canonicalPackageRoot,'runtime');
497
332
  const browserRuntimeRoot=path.join(canonicalPackageRoot,'browser-runtime');
@@ -499,40 +334,14 @@ export async function resolveInstalledSdkInstallation(workspaceRoot,declaration)
499
334
  assertRealDirectory(runtimeRoot,'Installed SDK runtime root'),
500
335
  assertRealDirectory(browserRuntimeRoot,'Installed SDK browser runtime root')
501
336
  ]);
502
- const manifests=sdkManifestPaths(declaration.packageSource);
503
- return Object.freeze({
337
+ return completeValue({
504
338
  dependencyName:declaration.dependencyName,
505
339
  packageSource:declaration.packageSource,
506
340
  canonicalPackageRoot,
507
341
  packageName:installedPackage.name,
508
342
  packageVersion:installedPackage.version,
509
343
  runtimeRoot,
510
- browserRuntimeRoot,
511
- runtimeManifest:manifests.runtimeManifest,
512
- browserRuntimeManifest:manifests.browserRuntimeManifest
513
- });
514
- }
515
-
516
- function sameBrowserRuntimeSource(actual,pinned){
517
- const exactKeys=(value,keys)=>isObject(value)
518
- &&Object.keys(value).sort().join('\0')===[...keys].sort().join('\0');
519
- const keys=['authority','repository','protocol','browserEntry','dependencies'];
520
- if(!exactKeys(actual,keys)||!exactKeys(pinned,keys)
521
- ||actual.authority!==pinned.authority
522
- ||actual.repository!==pinned.repository
523
- ||actual.protocol!==pinned.protocol
524
- ||actual.browserEntry!==pinned.browserEntry
525
- ||!Array.isArray(actual.dependencies)||!Array.isArray(pinned.dependencies)
526
- ||actual.dependencies.length!==pinned.dependencies.length){
527
- return false;
528
- }
529
- const dependencyKeys=['name','version','resolved','integrity'];
530
- return actual.dependencies.every((dependency,index)=>{
531
- const expected=pinned.dependencies[index];
532
- return exactKeys(dependency,dependencyKeys)&&exactKeys(expected,dependencyKeys)
533
- &&dependency.name===expected.name&&dependency.version===expected.version
534
- &&dependency.resolved===expected.resolved
535
- &&dependency.integrity===expected.integrity;
344
+ browserRuntimeRoot
536
345
  });
537
346
  }
538
347
 
@@ -618,21 +427,13 @@ export async function validateDiscoveredApplication({
618
427
  ||!app.manifest||!app.descriptor){
619
428
  fail('A discovered Arcane application is required for focused validation.');
620
429
  }
621
- const capturedWorkspace=await captureRealDirectoryIdentity(workspaceRoot,'Workspace');
622
- const canonicalWorkspaceRoot=capturedWorkspace.canonical;
623
- const capturedAppsRoot=await captureRealDirectoryIdentity(
624
- path.join(canonicalWorkspaceRoot,'apps'),
625
- 'Workspace apps root'
626
- );
430
+ const canonicalWorkspaceRoot=await realpath(workspaceRoot);
431
+ await assertRealDirectory(path.join(canonicalWorkspaceRoot,'apps'),'Workspace apps root');
627
432
  const expectedAppRoot=path.join(canonicalWorkspaceRoot,'apps',app.appId);
628
- const [capturedExpectedApp,capturedDiscoveredApp]=await Promise.all([
629
- captureRealDirectoryIdentity(expectedAppRoot,`apps/${app.appId}`),
630
- captureRealDirectoryIdentity(app.appRoot,`Discovered app ${app.appId}`)
631
- ]);
632
- if(!sameDirectoryIdentity(capturedExpectedApp.identity,capturedDiscoveredApp.identity)){
433
+ if(!sameDirectoryPath(app.appRoot,expectedAppRoot)){
633
434
  fail(`Discovered app ${app.appId} does not belong to the selected workspace.`);
634
435
  }
635
- const canonicalAppRoot=capturedExpectedApp.canonical;
436
+ const canonicalAppRoot=await realpath(app.appRoot);
636
437
  let config=workspaceConfig;
637
438
  if(!config){
638
439
  const profile=await inspectWorkspaceProfile(canonicalWorkspaceRoot);
@@ -660,13 +461,7 @@ export async function validateDiscoveredApplication({
660
461
  packageManifest:rawManifest
661
462
  });
662
463
  const descriptor=loadedDescriptor.descriptor;
663
- if(appDescriptorSha256(descriptor)!==appDescriptorSha256(app.descriptor)){
664
- fail(
665
- `The canonical descriptor for ${app.appId} changed after application discovery.`,
666
- 'ARCANE_INTEGRITY_FAILED'
667
- );
668
- }
669
- const freshApp=Object.freeze({
464
+ const freshApp=completeValue({
670
465
  appId:app.appId,
671
466
  appRoot:canonicalAppRoot,
672
467
  manifest,
@@ -675,23 +470,16 @@ export async function validateDiscoveredApplication({
675
470
  descriptorPath:loadedDescriptor.descriptorPath
676
471
  });
677
472
  const entryPath=path.join(canonicalAppRoot,manifest.entry);
678
- const info=await lstat(entryPath);
679
- if(info.isSymbolicLink()||!info.isFile()){
680
- fail(`apps/${app.appId}/${manifest.entry} must be a real file.`);
473
+ const info=await stat(entryPath);
474
+ if(!info.isFile()){
475
+ fail(`apps/${app.appId}/${manifest.entry} must be a file.`);
681
476
  }
682
477
  assertHtmlContract(await readFile(entryPath,'utf8'),app.appId,{
683
478
  entry:manifest.entry,
684
479
  strictStyles:workspaceMode==='external',
685
480
  allowMissingManagedImportMap
686
481
  });
687
- const assertCapturedDirectories=()=>Promise.all([
688
- assertRealDirectoryIdentity(capturedWorkspace,'Workspace'),
689
- assertRealDirectoryIdentity(capturedAppsRoot,'Workspace apps root'),
690
- assertRealDirectoryIdentity(capturedExpectedApp,`apps/${app.appId}`),
691
- assertRealDirectoryIdentity(capturedDiscoveredApp,`Discovered app ${app.appId}`)
692
- ]);
693
- await assertCapturedDirectories();
694
- const receipt=Object.freeze({
482
+ const result=completeValue({
695
483
  valid:true,
696
484
  workspaceRoot:canonicalWorkspaceRoot,
697
485
  workspaceMode,
@@ -704,8 +492,7 @@ export async function validateDiscoveredApplication({
704
492
  workspaceRoot:canonicalWorkspaceRoot,
705
493
  appId:app.appId
706
494
  });
707
- await assertCapturedDirectories();
708
- return receipt;
495
+ return result;
709
496
  }
710
497
 
711
498
  export async function validateWorkspace({
@@ -722,10 +509,9 @@ export async function validateWorkspace({
722
509
  const add=async(name,operation)=>{
723
510
  throwIfAborted(signal);
724
511
  await operation();
725
- checks.push(Object.freeze({name,ok:true}));
512
+ checks.push(completeValue({name,ok:true}));
726
513
  await emit(onEvent,{type:'workspace.validate.check',name,ok:true});
727
514
  };
728
- let lock;
729
515
  let sdkDeclaration;
730
516
  let sdkInstallation;
731
517
  let validatedApplication;
@@ -740,18 +526,6 @@ export async function validateWorkspace({
740
526
  fail('The selected Arcane application descriptor does not match the app id.');
741
527
  }
742
528
  });
743
- if(workspaceMode==='external'){
744
- await add('lock',async()=>{
745
- sdkDeclaration=resolveSdkPackageDeclaration(
746
- await readJson(path.join(resolved.workspaceRoot,'package.json'),'package.json'),
747
- {packageSource:resolved.config.sdkPackageSource}
748
- );
749
- lock=validateLock(
750
- await readJson(path.join(resolved.workspaceRoot,'arcane.lock.json'),'arcane.lock.json'),
751
- sdkDeclaration
752
- );
753
- });
754
- }
755
529
  await add('package',async()=>{
756
530
  const rootPackage=await readJson(path.join(resolved.workspaceRoot,'package.json'),'package.json');
757
531
  if(workspaceMode==='integrated'){
@@ -763,19 +537,13 @@ export async function validateWorkspace({
763
537
  const declaration=resolveSdkPackageDeclaration(rootPackage,{
764
538
  packageSource:resolved.config.sdkPackageSource
765
539
  });
540
+ sdkDeclaration=declaration;
766
541
  if(rootPackage?.private!==true||rootPackage?.type!=='module'){
767
542
  fail(
768
- `package.json must be private, use modules, and declare one exact `
543
+ `package.json must be private, use modules, and declare one `
769
544
  +`${EXPECTED_SDK_NAME} installation.`
770
545
  );
771
546
  }
772
- if(declaration.dependencyName!==sdkDeclaration.dependencyName
773
- ||declaration.dependencyGroup!==sdkDeclaration.dependencyGroup
774
- ||declaration.packageSource!==sdkDeclaration.packageSource
775
- ||declaration.specifier!==sdkDeclaration.specifier){
776
- fail('The declared SDK installation changed after workspace profile inspection.',
777
- 'ARCANE_INTEGRITY_FAILED');
778
- }
779
547
  });
780
548
  if(workspaceMode==='external'){
781
549
  await add('installed-runtime',async()=>{
@@ -783,35 +551,6 @@ export async function validateWorkspace({
783
551
  resolved.workspaceRoot,
784
552
  sdkDeclaration
785
553
  );
786
- const installed=await readJson(
787
- path.join(sdkInstallation.runtimeRoot,'ARCANE_RUNTIME_RELEASE.json'),
788
- 'installed SDK runtime manifest'
789
- );
790
- if(installed.contentSha256!==lock.runtime.contentSha256
791
- ||installed.source?.legacyProjection?.commit!==lock.runtime.upstreamCommit){
792
- fail('Installed SDK runtime does not match arcane.lock.json.');
793
- }
794
- const browserManifestPath=path.join(
795
- sdkInstallation.browserRuntimeRoot,
796
- 'ARCANE_SDK_BROWSER_RELEASE.json'
797
- );
798
- const browserBytes=await readFile(browserManifestPath);
799
- let installedBrowser;
800
- try{installedBrowser=JSON.parse(browserBytes.toString('utf8'));}
801
- catch(error){
802
- fail(`Installed SDK browser runtime manifest is not valid JSON: ${error.message}`);
803
- }
804
- if(createHash('sha256').update(browserBytes).digest('hex')
805
- !==lock.sdkBrowserRuntime.manifestSha256
806
- ||installedBrowser.contentSha256!==lock.sdkBrowserRuntime.contentSha256
807
- ||installedBrowser.builder!==lock.sdkBrowserRuntime.builder
808
- ||installedBrowser.sdkVersion!==lock.sdkBrowserRuntime.sdkVersion
809
- ||!sameBrowserRuntimeSource(
810
- installedBrowser.source,
811
- lock.sdkBrowserRuntime.source
812
- )){
813
- fail('Installed SDK browser runtime does not match arcane.lock.json.');
814
- }
815
554
  });
816
555
  }else{
817
556
  await add('workspace-runtime',async()=>{
@@ -822,9 +561,9 @@ export async function validateWorkspace({
822
561
  ['arcane','Integrated Arcane runtime'],
823
562
  [strongType,'Integrated strong-type runtime']
824
563
  ]){
825
- const info=await lstat(path.join(resolved.workspaceRoot,relative));
826
- if(info.isSymbolicLink()||!info.isDirectory()){
827
- fail(`${label} must be a real directory.`);
564
+ const info=await stat(path.join(resolved.workspaceRoot,relative));
565
+ if(!info.isDirectory()){
566
+ fail(`${label} must be a directory.`);
828
567
  }
829
568
  }
830
569
  });
@@ -840,7 +579,7 @@ export async function validateWorkspace({
840
579
  onEvent
841
580
  });
842
581
  });
843
- const receipt=Object.freeze({
582
+ const result=completeValue({
844
583
  valid:true,
845
584
  workspaceMode,
846
585
  workspaceRoot:resolved.workspaceRoot,
@@ -848,10 +587,9 @@ export async function validateWorkspace({
848
587
  appRoot:resolved.appRoot,
849
588
  config:resolved.config,
850
589
  app:validatedApplication.app,
851
- lock,
852
590
  ...(sdkInstallation?{sdkInstallation}:{}),
853
- checks:Object.freeze(checks)
591
+ checks:completeValue(checks)
854
592
  });
855
593
  await emit(onEvent,{type:'workspace.validate.completed',workspaceRoot:resolved.workspaceRoot,appId:resolved.appId,checks});
856
- return receipt;
594
+ return result;
857
595
  }