arcane-os 0.3.0 → 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 +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 +887 -909
  5. package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
  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 -146
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
  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 +1050 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
  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 +40 -62
  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 +112 -779
  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 -187
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1252
  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 -677
  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 -2960
  150. package/docs/reference/sdk-api.md +0 -6694
  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,1126 +1,167 @@
1
- import crypto from 'node:crypto';
2
- import {lstat,readFile,realpath} from 'node:fs/promises';
1
+ import {lstat,realpath} from 'node:fs/promises';
3
2
  import path from 'node:path';
4
- import {fileURLToPath,pathToFileURL} from 'node:url';
3
+ import {pathToFileURL} from 'node:url';
5
4
  import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
6
5
  import {validateNativeBuilder} from './native-plan.mjs';
7
6
 
8
- const PROVIDER_ROOT=Object.freeze([
9
- 'machine_bundles',
10
- 'arcane-os-machine-bundle',
11
- 'tools'
12
- ]);
7
+ const PROVIDER_ROOT=['machine_bundles','arcane-os-machine-bundle','tools'];
13
8
 
14
9
  function providerPath(fileName){
15
- return Object.freeze([...PROVIDER_ROOT,fileName]);
10
+ return [...PROVIDER_ROOT,fileName];
16
11
  }
17
12
 
18
13
  const linuxProviderPath=providerPath('linux-native-provider.mjs');
19
-
20
- export const ARCANE_NATIVE_PROVIDER_PATHS=Object.freeze({
14
+ const FIXED_NATIVE_PROVIDER_PATHS={
21
15
  portable:providerPath('portable-native-provider.mjs'),
22
16
  'windows-x64':providerPath('windows-native-provider.mjs'),
23
17
  'linux-x64':linuxProviderPath,
24
18
  'linux-arm64':linuxProviderPath,
25
19
  'android-arm64':providerPath('android-native-provider.mjs')
26
- });
20
+ };
27
21
 
28
- export const ARCANE_PORTABLE_PROVIDER_PATH=ARCANE_NATIVE_PROVIDER_PATHS.portable;
22
+ export const ARCANE_NATIVE_PROVIDER_PATHS={
23
+ portable:[...FIXED_NATIVE_PROVIDER_PATHS.portable],
24
+ 'windows-x64':[...FIXED_NATIVE_PROVIDER_PATHS['windows-x64']],
25
+ 'linux-x64':[...FIXED_NATIVE_PROVIDER_PATHS['linux-x64']],
26
+ 'linux-arm64':[...FIXED_NATIVE_PROVIDER_PATHS['linux-arm64']],
27
+ 'android-arm64':[...FIXED_NATIVE_PROVIDER_PATHS['android-arm64']]
28
+ };
29
29
 
30
- const PROVIDER_GENERATION_CODE='ARCANE_NATIVE_PROVIDER_RESTART_REQUIRED';
31
- const PROVIDER_CLOSURE_CODE='ARCANE_NATIVE_PROVIDER_CLOSURE_INVALID';
32
- const MAX_PROVIDER_MODULES=128;
33
- const MAX_PROVIDER_DEPTH=32;
34
- const MAX_PROVIDER_IMPORTS=1024;
35
- const MAX_PROVIDER_MODULE_BYTES=4*1024*1024;
36
- const MAX_PROVIDER_CLOSURE_BYTES=32*1024*1024;
37
- const PROCESS_PROVIDER_STATE=Symbol.for('arcane-os-sdk.native-provider-state.v1');
38
- if(!globalThis[PROCESS_PROVIDER_STATE]){
39
- Object.defineProperty(globalThis,PROCESS_PROVIDER_STATE,{
40
- value:Object.freeze({
41
- providerGenerations:new Map(),
42
- moduleGenerations:new Map()
43
- }),
44
- configurable:false,
45
- enumerable:false,
46
- writable:false
47
- });
48
- }
49
- const providerGenerationCache=globalThis[PROCESS_PROVIDER_STATE].providerGenerations;
50
- const processModuleGenerations=globalThis[PROCESS_PROVIDER_STATE].moduleGenerations;
51
- const utf8Decoder=new TextDecoder('utf-8',{fatal:true});
52
- const REGEX_PREFIX_IDENTIFIERS=new Set([
53
- 'await','case','delete','do','else','in','instanceof','new','of','return',
54
- 'throw','typeof','void','yield'
55
- ]);
56
- const REGEX_PREFIX_PUNCTUATORS=new Set([
57
- '(','[','{',',',';',':','=','==','===','!=','!==','!','?','&&','||','??',
58
- '+','-','*','%','&','|','^','~','<','>','<=','>=','=>'
59
- ]);
60
- const MULTI_PUNCTUATORS=new Set([
61
- '>>>=','===','!==','>>>','**=','&&=','||=','??=','=>','==','!=','<=','>=',
62
- '++','--','&&','||','??','**','<<','>>','+=','-=','*=','/=','%=','&=','|=','^=',
63
- '?.','...'
64
- ]);
30
+ export const ARCANE_PORTABLE_PROVIDER_PATH=[...ARCANE_NATIVE_PROVIDER_PATHS.portable];
65
31
 
66
32
  function fail(message,details){
67
33
  throw new ArcaneError(ERROR_CODES.targetUnavailable,message,{details});
68
34
  }
69
35
 
70
- function samePath(left,right){
71
- const normalize=value=>process.platform==='win32'
72
- ?path.normalize(value).toLowerCase()
73
- :path.normalize(value);
74
- return normalize(left)===normalize(right);
75
- }
76
-
77
- function pathKey(value){
78
- const normalized=path.normalize(value);
79
- return process.platform==='win32'?normalized.toLowerCase():normalized;
36
+ function importProviderModule(specifier){
37
+ return import(specifier);
80
38
  }
81
39
 
82
40
  function insideRoot(root,candidate){
83
41
  const relative=path.relative(root,candidate);
84
- return relative===''||(!relative.startsWith(`..${path.sep}`)&&relative!=='..'&&!path.isAbsolute(relative));
85
- }
86
-
87
- async function assertUnlinkedDirectoryAncestors(location,inspect){
88
- const resolved=path.resolve(location);
89
- const parsed=path.parse(resolved);
90
- const relative=resolved.slice(parsed.root.length);
91
- let current=parsed.root;
92
- const components=relative.split(path.sep).filter(Boolean);
93
- for(let index=0;index<components.length-1;index+=1){
94
- current=path.join(current,components[index]);
95
- const info=await inspect(current);
96
- if(info.isSymbolicLink()||!info.isDirectory()){
97
- fail('The Arcane OS checkout root must not use a linked or non-directory ancestor.',{
98
- arcaneRoot:resolved,
99
- ancestor:current
100
- });
101
- }
102
- }
103
- }
104
-
105
- function closureFailure(message,details){
106
- throw new ArcaneError(PROVIDER_CLOSURE_CODE,message,{details});
107
- }
108
-
109
- function restartRequired(target,details){
110
- throw new ArcaneError(
111
- PROVIDER_GENERATION_CODE,
112
- `The Arcane ${target} provider module generation changed in this process. Restart the Arcane SDK process before pairing this provider again.`,
113
- {details}
42
+ return relative===''||Boolean(
43
+ relative&&!relative.startsWith('..')&&!path.isAbsolute(relative)
114
44
  );
115
45
  }
116
46
 
117
- function sha256(value){
118
- return crypto.createHash('sha256').update(value).digest('hex');
119
- }
120
-
121
- function identityValue(value){
122
- if(typeof value==='bigint')return value.toString(10);
123
- if(typeof value==='number'&&Number.isFinite(value))return String(value);
124
- return null;
125
- }
126
-
127
- function filesystemIdentity(info){
128
- return Object.freeze({
129
- device:identityValue(info.dev),
130
- inode:identityValue(info.ino),
131
- mode:identityValue(info.mode),
132
- links:identityValue(info.nlink),
133
- user:identityValue(info.uid),
134
- group:identityValue(info.gid),
135
- size:identityValue(info.size),
136
- modified:identityValue(info.mtimeNs??info.mtimeMs),
137
- changed:identityValue(info.ctimeNs??info.ctimeMs),
138
- created:identityValue(info.birthtimeNs??info.birthtimeMs)
139
- });
140
- }
141
-
142
- function sameIdentity(left,right){
143
- return JSON.stringify(left)===JSON.stringify(right);
144
- }
145
-
146
- function moduleToken(type,value,start,depth){
147
- return Object.freeze({type,value,start,depth});
148
- }
149
-
150
- function identifierStart(character){
151
- return /[A-Za-z_$]/u.test(character);
152
- }
153
-
154
- function identifierPart(character){
155
- return /[A-Za-z0-9_$]/u.test(character);
156
- }
157
-
158
- function regexMayStart(previous){
159
- if(!previous)return true;
160
- if(previous.type==='identifier'){
161
- return REGEX_PREFIX_IDENTIFIERS.has(previous.value);
162
- }
163
- return previous.type==='punctuator'&&REGEX_PREFIX_PUNCTUATORS.has(previous.value);
164
- }
165
-
166
- function scanQuoted(source,index,quote){
167
- let cursor=index+1;
168
- let escaped=false;
169
- while(cursor<source.length){
170
- const character=source[cursor];
171
- if(!escaped&&character===quote){
172
- return {end:cursor+1,raw:source.slice(index+1,cursor)};
173
- }
174
- if(!escaped&&(character==='\n'||character==='\r')){
175
- closureFailure('Arcane provider source contains an unterminated string literal.',{offset:index});
176
- }
177
- if(!escaped&&character==='\\')escaped=true;
178
- else escaped=false;
179
- cursor+=1;
180
- }
181
- closureFailure('Arcane provider source contains an unterminated string literal.',{offset:index});
182
- }
183
-
184
- function skipLineComment(source,index){
185
- let cursor=index+2;
186
- while(cursor<source.length&&source[cursor]!=='\n'&&source[cursor]!=='\r')cursor+=1;
187
- return cursor;
188
- }
189
-
190
- function skipBlockComment(source,index){
191
- const end=source.indexOf('*/',index+2);
192
- if(end<0)closureFailure('Arcane provider source contains an unterminated block comment.');
193
- return end+2;
194
- }
195
-
196
- function skipRegex(source,index){
197
- let cursor=index+1;
198
- let escaped=false;
199
- let characterClass=false;
200
- while(cursor<source.length){
201
- const character=source[cursor];
202
- if(!escaped){
203
- if(character==='[')characterClass=true;
204
- else if(character===']')characterClass=false;
205
- else if(character==='/'&&!characterClass){
206
- cursor+=1;
207
- while(cursor<source.length&&/[A-Za-z]/u.test(source[cursor]))cursor+=1;
208
- return cursor;
209
- }else if(character==='\n'||character==='\r'){
210
- closureFailure('Arcane provider source contains an unterminated regular expression.');
211
- }
212
- }
213
- if(!escaped&&character==='\\')escaped=true;
214
- else escaped=false;
215
- cursor+=1;
216
- }
217
- closureFailure('Arcane provider source contains an unterminated regular expression.');
218
- }
219
-
220
- function punctuatorAt(source,index){
221
- for(const width of [4,3,2]){
222
- const candidate=source.slice(index,index+width);
223
- if(MULTI_PUNCTUATORS.has(candidate))return candidate;
224
- }
225
- return source[index];
226
- }
227
-
228
- function tokenizeProviderModule(source){
229
- const tokens=[];
230
- const depths={brace:0,bracket:0,parenthesis:0,template:0};
231
- const templateExpressionBaselines=[];
232
- let index=0;
233
- let previous=null;
234
-
235
- function depth(){
236
- return depths.brace+depths.bracket+depths.parenthesis+depths.template;
237
- }
238
-
239
- function push(type,value,start){
240
- const token=moduleToken(type,value,start,depth());
241
- tokens.push(token);
242
- previous=token;
243
- }
244
-
245
- function scanTemplate(opening){
246
- if(opening){
247
- depths.template+=1;
248
- index+=1;
249
- }
250
- let escaped=false;
251
- while(index<source.length){
252
- const character=source[index];
253
- if(!escaped&&character==='`'){
254
- index+=1;
255
- depths.template-=1;
256
- return;
257
- }
258
- if(!escaped&&character==='$'&&source[index+1]==='{'){
259
- templateExpressionBaselines.push(depths.brace);
260
- depths.brace+=1;
261
- index+=2;
262
- return;
263
- }
264
- if(!escaped&&character==='\\')escaped=true;
265
- else escaped=false;
266
- index+=1;
267
- }
268
- closureFailure('Arcane provider source contains an unterminated template literal.');
269
- }
270
-
271
- while(index<source.length){
272
- const character=source[index];
273
- if(/\s/u.test(character)){
274
- index+=1;
275
- continue;
276
- }
277
- if(character==='/'&&source[index+1]==='/'){
278
- index=skipLineComment(source,index);
279
- continue;
280
- }
281
- if(character==='/'&&source[index+1]==='*'){
282
- index=skipBlockComment(source,index);
283
- continue;
284
- }
285
- if(character==='\''||character==='"'){
286
- const scanned=scanQuoted(source,index,character);
287
- push('string',scanned.raw,index);
288
- index=scanned.end;
289
- continue;
290
- }
291
- if(character==='`'){
292
- scanTemplate(true);
293
- continue;
294
- }
295
- if(character==='/'&&regexMayStart(previous)){
296
- index=skipRegex(source,index);
297
- push('regex','/',index);
298
- continue;
299
- }
300
- if(identifierStart(character)){
301
- const start=index;
302
- index+=1;
303
- while(index<source.length&&identifierPart(source[index]))index+=1;
304
- push('identifier',source.slice(start,index),start);
305
- continue;
306
- }
307
- if(/[0-9]/u.test(character)){
308
- const start=index;
309
- index+=1;
310
- while(index<source.length&&/[A-Za-z0-9_.]/u.test(source[index]))index+=1;
311
- push('number',source.slice(start,index),start);
312
- continue;
313
- }
314
- const punctuator=punctuatorAt(source,index);
315
- const tokenDepth=depth();
316
- if(character==='}'&&depths.brace>0){
317
- const templateBaseline=templateExpressionBaselines.at(-1);
318
- const closesTemplateExpression=templateBaseline!==undefined
319
- &&depths.brace===templateBaseline+1;
320
- depths.brace-=1;
321
- if(closesTemplateExpression){
322
- templateExpressionBaselines.pop();
323
- index+=1;
324
- scanTemplate(false);
325
- continue;
326
- }
327
- }else if(character===')'&&depths.parenthesis>0)depths.parenthesis-=1;
328
- else if(character===']'&&depths.bracket>0)depths.bracket-=1;
329
- const token=moduleToken('punctuator',punctuator,index,tokenDepth);
330
- tokens.push(token);
331
- previous=token;
332
- if(character==='{')depths.brace+=1;
333
- else if(character==='(')depths.parenthesis+=1;
334
- else if(character==='[')depths.bracket+=1;
335
- index+=punctuator.length;
336
- }
337
- return tokens;
338
- }
339
-
340
- function decodeModuleSpecifier(token){
341
- if(token.type!=='string')closureFailure('Arcane provider import source must be a string literal.');
342
- if(token.value.includes('\\')){
343
- closureFailure('Arcane provider module specifiers must not use escaped characters.');
344
- }
345
- return token.value;
346
- }
347
-
348
- function staticModuleSpecifiers(source,modulePath){
349
- let tokens;
350
- try{
351
- tokens=tokenizeProviderModule(source);
352
- }catch(error){
353
- if(error instanceof ArcaneError&&error.code===PROVIDER_CLOSURE_CODE){
354
- throw new ArcaneError(error.code,error.message,{
355
- cause:error,
356
- details:{modulePath,...error.details}
357
- });
358
- }
359
- throw error;
360
- }
361
- const specifiers=[];
362
- for(let index=0;index<tokens.length;index+=1){
363
- const token=tokens[index];
364
- if(token.type!=='identifier'||token.value!=='import')continue;
365
- const next=tokens[index+1];
366
- if(next?.type==='punctuator'&&next.value==='.')continue;
367
- if(next?.type==='punctuator'&&next.value==='('){
368
- closureFailure('Arcane native providers must not use dynamic import().');
369
- }
370
- if(token.depth!==0)continue;
371
- if(next?.type==='string'){
372
- specifiers.push(decodeModuleSpecifier(next));
373
- continue;
374
- }
375
- const from=tokens.slice(index+1).find(candidate=>
376
- candidate.depth===0&&candidate.type==='identifier'&&candidate.value==='from'
377
- );
378
- if(!from)closureFailure('Arcane provider import declaration has no static source.');
379
- specifiers.push(decodeModuleSpecifier(tokens[tokens.indexOf(from)+1]));
380
- }
381
- for(let index=0;index<tokens.length;index+=1){
382
- const token=tokens[index];
383
- if(token.depth!==0||token.type!=='identifier'||token.value!=='export')continue;
384
- const next=tokens[index+1];
385
- if(next?.value!=='*'&&next?.value!=='{')continue;
386
- let cursor=index+1;
387
- while(cursor<tokens.length&&!(
388
- tokens[cursor].depth===0
389
- &&tokens[cursor].type==='identifier'
390
- &&tokens[cursor].value==='from'
391
- )){
392
- if(tokens[cursor].depth===0&&tokens[cursor].value===';')break;
393
- cursor+=1;
394
- }
395
- if(tokens[cursor]?.value==='from'){
396
- specifiers.push(decodeModuleSpecifier(tokens[cursor+1]));
397
- }
398
- }
399
- return specifiers;
400
- }
401
-
402
- async function regularFile(filePath,inspect){
403
- try{
404
- const info=await inspect(filePath);
405
- return info.isFile()&&!info.isSymbolicLink();
406
- }catch(error){
407
- if(error?.code==='ENOENT')return false;
408
- throw error;
409
- }
410
- }
411
-
412
- async function stableModule({modulePath,inspect,readModule}){
413
- const before=await inspect(modulePath);
414
- if(before.isSymbolicLink()||!before.isFile()){
415
- closureFailure('Every Arcane native provider module must be a real regular file.',{
416
- modulePath
417
- });
418
- }
419
- const beforeIdentity=filesystemIdentity(before);
420
- const declaredSize=Number(before.size);
421
- if(Number.isFinite(declaredSize)&&declaredSize>MAX_PROVIDER_MODULE_BYTES){
422
- closureFailure('An Arcane native provider module exceeds the fixed size limit.',{
423
- modulePath,
424
- maximumBytes:MAX_PROVIDER_MODULE_BYTES
425
- });
426
- }
427
- const bytes=await readModule(modulePath);
428
- if(!Buffer.isBuffer(bytes)&&!(bytes instanceof Uint8Array)){
429
- closureFailure('The Arcane native provider module reader must return bytes.',{modulePath});
430
- }
431
- const normalizedBytes=Buffer.from(bytes);
432
- if(normalizedBytes.byteLength>MAX_PROVIDER_MODULE_BYTES){
433
- closureFailure('An Arcane native provider module exceeds the fixed size limit.',{
434
- modulePath,
435
- maximumBytes:MAX_PROVIDER_MODULE_BYTES
436
- });
437
- }
438
- const after=await inspect(modulePath);
439
- const afterIdentity=filesystemIdentity(after);
440
- if(after.isSymbolicLink()||!after.isFile()||!sameIdentity(beforeIdentity,afterIdentity)){
441
- closureFailure('An Arcane native provider module changed while its generation was read.',{
442
- modulePath
47
+ async function resolveProviderLocation({
48
+ arcaneRoot,
49
+ target,
50
+ inspect=lstat,
51
+ canonicalize=realpath
52
+ }={}){
53
+ const relativeProviderPath=FIXED_NATIVE_PROVIDER_PATHS[target];
54
+ if(!relativeProviderPath){
55
+ fail(`No Arcane native provider is registered for target ${String(target)}.`,{
56
+ target,
57
+ supportedTargets:Object.keys(FIXED_NATIVE_PROVIDER_PATHS)
443
58
  });
444
59
  }
445
- let source;
446
- try{
447
- source=utf8Decoder.decode(normalizedBytes);
448
- }catch(error){
449
- throw new ArcaneError(
450
- PROVIDER_CLOSURE_CODE,
451
- 'Arcane native provider modules must contain valid UTF-8 source.',
452
- {cause:error,details:{modulePath}}
453
- );
454
- }
455
- return Object.freeze({
456
- bytes:normalizedBytes.byteLength,
457
- contentSha256:sha256(normalizedBytes),
458
- filesystemIdentity:afterIdentity,
459
- source
460
- });
461
- }
462
-
463
- function resolveRelativeModule({canonicalRoot,modulePath,specifier}){
464
- if(specifier.startsWith('node:'))return null;
465
- if(!specifier.startsWith('./')&&!specifier.startsWith('../')){
466
- closureFailure(
467
- 'Arcane native providers may import only node: built-ins and static relative modules.',
468
- {modulePath,specifier}
469
- );
470
- }
471
- let resolvedUrl;
472
- let resolvedPath;
473
- try{
474
- resolvedUrl=new URL(specifier,pathToFileURL(modulePath));
475
- if(resolvedUrl.protocol!=='file:'||resolvedUrl.search||resolvedUrl.hash){
476
- closureFailure('Arcane native provider relative imports must resolve to plain files.',{
477
- modulePath,
478
- specifier
479
- });
480
- }
481
- resolvedPath=path.resolve(fileURLToPath(resolvedUrl));
482
- }catch(error){
483
- if(error instanceof ArcaneError)throw error;
484
- throw new ArcaneError(
485
- PROVIDER_CLOSURE_CODE,
486
- 'Arcane native provider module specifier is invalid.',
487
- {cause:error,details:{modulePath,specifier}}
488
- );
60
+ if(typeof arcaneRoot!=='string'||!arcaneRoot.trim()){
61
+ fail(`The ${target} native provider requires an Arcane OS checkout root.`);
489
62
  }
490
- if(!insideRoot(canonicalRoot,resolvedPath)){
491
- closureFailure('An Arcane native provider relative import escapes the Arcane checkout.',{
492
- modulePath,
493
- specifier
494
- });
63
+ if(typeof inspect!=='function'||typeof canonicalize!=='function'){
64
+ fail('The Arcane native provider loader dependencies are invalid.');
495
65
  }
496
- if(!['.js','.mjs'].includes(path.extname(resolvedPath).toLowerCase())){
497
- closureFailure('Arcane native provider relative imports must name .js or .mjs modules.',{
498
- modulePath,
499
- specifier
66
+ const requestedRoot=path.resolve(arcaneRoot);
67
+ let rootInfo;
68
+ try{rootInfo=await inspect(requestedRoot);}
69
+ catch(error){
70
+ if(error?.code==='ENOENT')fail('The selected Arcane OS checkout does not exist.',{
71
+ arcaneRoot:requestedRoot
500
72
  });
73
+ throw error;
501
74
  }
502
- return resolvedPath;
503
- }
504
-
505
- function freezeGenerationEvidence({canonicalRoot,target,providerPath,modules,totalBytes}){
506
- const sorted=[...modules.values()].sort((left,right)=>
507
- left.relativePath.localeCompare(right.relativePath,'en')
508
- );
509
- const contentRecords=sorted.map(module=>Object.freeze({
510
- path:module.relativePath,
511
- bytes:module.bytes,
512
- contentSha256:module.contentSha256
513
- }));
514
- const identityRecords=sorted.map(module=>Object.freeze({
515
- path:module.relativePath,
516
- filesystemIdentity:module.filesystemIdentity
517
- }));
518
- const contentSha256=sha256(JSON.stringify(contentRecords));
519
- const filesystemIdentitySha256=sha256(JSON.stringify(identityRecords));
520
- const generationSha256=sha256(`${contentSha256}\0${filesystemIdentitySha256}`);
521
- return Object.freeze({
522
- schemaVersion:1,
523
- kind:'arcane-native-provider-generation',
524
- target,
525
- canonicalArcaneRoot:canonicalRoot,
526
- providerPath,
527
- entryPath:path.relative(canonicalRoot,providerPath).split(path.sep).join('/'),
528
- moduleCount:sorted.length,
529
- totalBytes,
530
- contentSha256,
531
- filesystemIdentitySha256,
532
- generationSha256,
533
- modules:Object.freeze(sorted.map(module=>Object.freeze({
534
- path:module.relativePath,
535
- canonicalLocation:module.canonicalLocation,
536
- bytes:module.bytes,
537
- contentSha256:module.contentSha256,
538
- filesystemIdentity:module.filesystemIdentity
539
- })))
540
- });
541
- }
542
-
543
- async function providerGeneration({
544
- canonicalRoot,
545
- providerPath,
546
- target,
547
- inspect,
548
- canonicalize,
549
- readModule,
550
- signal
551
- }){
552
- const modules=new Map();
553
- let importCount=0;
554
- let totalBytes=0;
555
-
556
- async function visit(requestedPath,depth){
557
- throwIfAborted(signal);
558
- if(depth>MAX_PROVIDER_DEPTH){
559
- closureFailure('Arcane native provider module closure exceeds the fixed depth limit.',{
560
- maximumDepth:MAX_PROVIDER_DEPTH
561
- });
562
- }
563
- const resolvedPath=path.resolve(requestedPath);
564
- if(!insideRoot(canonicalRoot,resolvedPath)){
565
- closureFailure('An Arcane native provider module escapes the Arcane checkout.',{
566
- modulePath:resolvedPath
567
- });
568
- }
569
- if(modules.has(pathKey(resolvedPath)))return;
570
- if(modules.size>=MAX_PROVIDER_MODULES){
571
- closureFailure('Arcane native provider module closure exceeds the fixed module limit.',{
572
- maximumModules:MAX_PROVIDER_MODULES
75
+ if(rootInfo.isSymbolicLink()||!rootInfo.isDirectory()){
76
+ fail('The Arcane OS checkout root must be a real directory.',{arcaneRoot:requestedRoot});
77
+ }
78
+ const canonicalRoot=await canonicalize(requestedRoot);
79
+ let current=canonicalRoot;
80
+ for(const [index,segment] of relativeProviderPath.entries()){
81
+ current=path.join(current,segment);
82
+ let info;
83
+ try{info=await inspect(current);}
84
+ catch(error){
85
+ if(error?.code==='ENOENT')fail('The selected Arcane native provider does not exist.',{
86
+ providerPath:current
573
87
  });
574
- }
575
- let canonicalModule;
576
- try{
577
- canonicalModule=await canonicalize(resolvedPath);
578
- }catch(error){
579
- if(error?.code==='ENOENT'){
580
- closureFailure('An Arcane native provider static relative module does not exist.',{
581
- modulePath:resolvedPath
582
- });
583
- }
584
88
  throw error;
585
89
  }
586
- if(!samePath(canonicalModule,resolvedPath)||!insideRoot(canonicalRoot,canonicalModule)){
587
- closureFailure('Arcane native provider modules must not resolve through linked or escaped locations.',{
588
- modulePath:resolvedPath,
589
- canonicalModule
590
- });
591
- }
592
- const snapshot=await stableModule({modulePath:canonicalModule,inspect,readModule});
593
- totalBytes+=snapshot.bytes;
594
- if(totalBytes>MAX_PROVIDER_CLOSURE_BYTES){
595
- closureFailure('Arcane native provider module closure exceeds the fixed byte limit.',{
596
- maximumBytes:MAX_PROVIDER_CLOSURE_BYTES
90
+ if(info.isSymbolicLink()){
91
+ fail('The Arcane native provider path must not contain links or junctions.',{
92
+ providerPath:current
597
93
  });
598
94
  }
599
- const relativePath=path.relative(canonicalRoot,canonicalModule).split(path.sep).join('/');
600
- modules.set(pathKey(canonicalModule),Object.freeze({
601
- relativePath,
602
- canonicalLocation:canonicalModule,
603
- bytes:snapshot.bytes,
604
- contentSha256:snapshot.contentSha256,
605
- filesystemIdentity:snapshot.filesystemIdentity
606
- }));
607
- const specifiers=staticModuleSpecifiers(snapshot.source,canonicalModule);
608
- importCount+=specifiers.length;
609
- if(importCount>MAX_PROVIDER_IMPORTS){
610
- closureFailure('Arcane native provider module closure exceeds the fixed import limit.',{
611
- maximumImports:MAX_PROVIDER_IMPORTS
95
+ const last=index===relativeProviderPath.length-1;
96
+ if(last?!info.isFile():!info.isDirectory()){
97
+ fail('The Arcane native provider path contains a non-file entry.',{
98
+ providerPath:current
612
99
  });
613
100
  }
614
- for(const specifier of specifiers){
615
- const dependency=resolveRelativeModule({
616
- canonicalRoot,
617
- modulePath:canonicalModule,
618
- specifier
619
- });
620
- if(dependency)await visit(dependency,depth+1);
621
- }
622
- }
623
-
624
- await visit(providerPath,0);
625
- return freezeGenerationEvidence({canonicalRoot,target,providerPath,modules,totalBytes});
626
- }
627
-
628
- function sameGeneration(left,right){
629
- return left.generationSha256===right.generationSha256
630
- &&left.moduleCount===right.moduleCount
631
- &&left.totalBytes===right.totalBytes;
632
- }
633
-
634
- function generationCacheKey({arcaneRoot,target,providerPath}){
635
- return `${pathKey(arcaneRoot)}\0${target}\0${pathKey(providerPath)}`;
636
- }
637
-
638
- function generationDetails(expected,current){
639
- return Object.freeze({
640
- expectedGenerationSha256:expected.generationSha256,
641
- actualGenerationSha256:current.generationSha256,
642
- expectedContentSha256:expected.contentSha256,
643
- actualContentSha256:current.contentSha256,
644
- expectedFilesystemIdentitySha256:expected.filesystemIdentitySha256,
645
- actualFilesystemIdentitySha256:current.filesystemIdentitySha256
646
- });
647
- }
648
-
649
- function unavailableGenerationDetails(expected,error){
650
- return Object.freeze({
651
- expectedGenerationSha256:expected.generationSha256,
652
- actualGenerationSha256:null,
653
- validationCode:typeof error?.code==='string'?error.code:null,
654
- validationMessage:String(error?.message??error)
655
- });
656
- }
657
-
658
- function sameModuleGeneration(left,right){
659
- return samePath(left.canonicalLocation,right.canonicalLocation)
660
- &&left.bytes===right.bytes
661
- &&left.contentSha256===right.contentSha256
662
- &&sameIdentity(left.filesystemIdentity,right.filesystemIdentity);
663
- }
664
-
665
- function moduleGenerationDetails(expected,current){
666
- return Object.freeze({
667
- modulePath:expected.canonicalLocation,
668
- expectedContentSha256:expected.contentSha256,
669
- actualContentSha256:current?.contentSha256??null,
670
- expectedFilesystemIdentity:expected.filesystemIdentity,
671
- actualFilesystemIdentity:current?.filesystemIdentity??null
672
- });
673
- }
674
-
675
- function poisonChangedModuleBindings(record,current){
676
- const currentModules=new Map(current.modules.map(module=>[
677
- pathKey(module.canonicalLocation),
678
- module
679
- ]));
680
- for(const binding of record.moduleBindings??[]){
681
- const observed=currentModules.get(binding.key);
682
- if(!observed||!sameModuleGeneration(binding.module,observed)){
683
- binding.registryRecord.poisoned=true;
684
- }
685
- }
686
- record.state='poisoned';
687
- }
688
-
689
- function reserveProcessModuleGenerations(record,evidence){
690
- const bindings=[];
691
- const created=[];
692
- try{
693
- for(const module of evidence.modules){
694
- const key=pathKey(module.canonicalLocation);
695
- let registryRecord=processModuleGenerations.get(key);
696
- if(registryRecord){
697
- if(registryRecord.poisoned
698
- ||!sameModuleGeneration(registryRecord.module,module)){
699
- registryRecord.poisoned=true;
700
- record.state='poisoned';
701
- restartRequired(record.target,moduleGenerationDetails(
702
- registryRecord.module,
703
- module
704
- ));
705
- }
706
- }else{
707
- registryRecord={module,loaded:false,poisoned:false};
708
- processModuleGenerations.set(key,registryRecord);
709
- created.push({key,registryRecord});
710
- }
711
- bindings.push(Object.freeze({key,module,registryRecord}));
712
- }
713
- }catch(error){
714
- for(const createdBinding of created){
715
- if(!createdBinding.registryRecord.loaded
716
- &&processModuleGenerations.get(createdBinding.key)===createdBinding.registryRecord){
717
- processModuleGenerations.delete(createdBinding.key);
718
- }
719
- }
720
- throw error;
721
- }
722
- record.moduleBindings=Object.freeze(bindings);
723
- }
724
-
725
- function markProcessModulesLoaded(record){
726
- for(const binding of record.moduleBindings){
727
- if(binding.registryRecord.poisoned){
728
- record.state='poisoned';
729
- restartRequired(record.target,moduleGenerationDetails(
730
- binding.registryRecord.module,
731
- binding.module
732
- ));
733
- }
734
- binding.registryRecord.loaded=true;
735
- }
736
- }
737
-
738
- function poisonUnloadedProcessModules(record){
739
- for(const binding of record.moduleBindings??[]){
740
- if(!binding.registryRecord.loaded)binding.registryRecord.poisoned=true;
741
101
  }
742
- }
743
-
744
- function assertLoadedProcessModules(record,evidence=record.evidence){
745
- if(evidence.modules.length!==(record.moduleBindings?.length??0)){
746
- record.state='poisoned';
747
- restartRequired(record.target,unavailableGenerationDetails(
748
- record.evidence,
749
- new Error('The process-global provider module inventory changed.')
750
- ));
751
- }
752
- for(const module of evidence.modules){
753
- const key=pathKey(module.canonicalLocation);
754
- const registryRecord=processModuleGenerations.get(key);
755
- if(!registryRecord||!registryRecord.loaded||registryRecord.poisoned
756
- ||!sameModuleGeneration(registryRecord.module,module)){
757
- if(registryRecord)registryRecord.poisoned=true;
758
- record.state='poisoned';
759
- restartRequired(record.target,moduleGenerationDetails(
760
- registryRecord?.module??module,
761
- registryRecord?module:null
762
- ));
763
- }
764
- }
765
- }
766
-
767
- async function authenticateProviderGeneration({
768
- record,
769
- canonicalRoot,
770
- providerPath,
771
- target,
772
- inspect,
773
- canonicalize
774
- }){
775
- if(record.state==='poisoned'){
776
- restartRequired(target,unavailableGenerationDetails(
777
- record.evidence,
778
- new Error('The cached provider generation is no longer valid.')
779
- ));
780
- }
781
- try{
782
- const rootInfo=await inspect(canonicalRoot);
783
- const authenticatedRoot=await canonicalize(canonicalRoot);
784
- if(rootInfo.isSymbolicLink()||!rootInfo.isDirectory()
785
- ||!samePath(authenticatedRoot,canonicalRoot)
786
- ||record.evidence.moduleCount!==record.evidence.modules.length
787
- ||record.evidence.moduleCount>MAX_PROVIDER_MODULES
788
- ||!record.evidence.modules.some(module=>samePath(module.canonicalLocation,providerPath))){
789
- closureFailure('The loaded Arcane provider module inventory is no longer valid.');
790
- }
791
- assertLoadedProcessModules(record);
792
- const observedPaths=new Set();
793
- for(const module of record.evidence.modules){
794
- if(observedPaths.has(pathKey(module.canonicalLocation))
795
- ||!insideRoot(canonicalRoot,module.canonicalLocation)){
796
- closureFailure('The loaded Arcane provider module inventory is no longer canonical.');
797
- }
798
- observedPaths.add(pathKey(module.canonicalLocation));
799
- const before=await inspect(module.canonicalLocation);
800
- const canonicalModule=await canonicalize(module.canonicalLocation);
801
- const after=await inspect(module.canonicalLocation);
802
- const registryRecord=processModuleGenerations.get(pathKey(module.canonicalLocation));
803
- if(before.isSymbolicLink()||!before.isFile()
804
- ||after.isSymbolicLink()||!after.isFile()
805
- ||!samePath(canonicalModule,module.canonicalLocation)
806
- ||!sameIdentity(filesystemIdentity(before),module.filesystemIdentity)
807
- ||!sameIdentity(filesystemIdentity(after),module.filesystemIdentity)){
808
- if(registryRecord)registryRecord.poisoned=true;
809
- closureFailure('A loaded Arcane provider module identity is no longer valid.',{
810
- modulePath:module.canonicalLocation
811
- });
812
- }
813
- }
814
- }catch(error){
815
- record.state='poisoned';
816
- const details=unavailableGenerationDetails(record.evidence,error);
817
- throw new ArcaneError(
818
- PROVIDER_GENERATION_CODE,
819
- `The Arcane ${target} provider module generation can no longer be authenticated. Restart the Arcane SDK process before pairing this provider again.`,
820
- {cause:error,details}
821
- );
102
+ const canonicalProvider=await canonicalize(current);
103
+ if(!insideRoot(canonicalRoot,canonicalProvider)){
104
+ fail('The Arcane native provider resolves outside its checkout.',{
105
+ providerPath:canonicalProvider
106
+ });
822
107
  }
823
- return record.evidence;
108
+ return {canonicalRoot,providerPath:canonicalProvider};
824
109
  }
825
110
 
826
- function guardedNativeBuilder({
827
- rawBuilder,
828
- record,
829
- canonicalRoot,
830
- providerPath,
831
- target,
832
- inspect,
833
- canonicalize
834
- }){
835
- const guarded={
836
- protocol:rawBuilder.protocol,
837
- providerGeneration:record.evidence
838
- };
839
- for(const method of [
840
- 'describe','doctor','prepare','authenticateToolchainReceipt','build','verify','run'
841
- ]){
842
- guarded[method]=async function guardedProviderOperation(...args){
843
- await authenticateProviderGeneration({
844
- record,
845
- canonicalRoot,
846
- providerPath,
847
- target,
848
- inspect,
849
- canonicalize
850
- });
851
- return rawBuilder[method].apply(rawBuilder,args);
852
- };
111
+ export async function loadArcaneNativeProvider(options={}){
112
+ if(options===null||typeof options!=='object'||Array.isArray(options)){
113
+ fail('Arcane native provider options must be an object.');
853
114
  }
854
- return Object.freeze(guarded);
855
- }
856
-
857
- async function importReservedProvider(record){
858
- const requested=await providerGeneration({
859
- canonicalRoot:record.canonicalRoot,
860
- providerPath:record.providerPath,
861
- target:record.target,
862
- inspect:record.inspect,
863
- canonicalize:record.canonicalize,
864
- readModule:record.readModule
115
+ const {
116
+ arcaneRoot,
117
+ target,
118
+ inspect=lstat,
119
+ canonicalize=realpath,
120
+ importModule=importProviderModule,
121
+ signal,
122
+ onEvent
123
+ }=options;
124
+ throwIfAborted(signal);
125
+ if(typeof importModule!=='function')fail('The Arcane native provider importer is invalid.');
126
+ await onEvent?.({
127
+ type:'native.provider.load.started',
128
+ target,
129
+ message:`Loading the Arcane ${String(target)} provider.`
865
130
  });
866
- record.evidence=requested;
867
- reserveProcessModuleGenerations(record,requested);
868
- record.importAttempted=true;
869
-
131
+ const location=await resolveProviderLocation({arcaneRoot,target,inspect,canonicalize});
132
+ throwIfAborted(signal);
870
133
  let namespace;
871
- let importError;
872
- let rawBuilder;
873
- let importCompleted=false;
874
134
  try{
875
- namespace=await record.importModule(pathToFileURL(record.providerPath).href);
876
- importCompleted=true;
877
- rawBuilder=validateNativeBuilder(
878
- namespace?.arcaneNativeBuilderProvider??namespace?.default
879
- );
135
+ namespace=await importModule(pathToFileURL(location.providerPath).href);
880
136
  }catch(error){
881
- importError=error;
882
- }
883
-
884
- let afterImport;
885
- try{
886
- afterImport=await providerGeneration({
887
- canonicalRoot:record.canonicalRoot,
888
- providerPath:record.providerPath,
889
- target:record.target,
890
- inspect:record.inspect,
891
- canonicalize:record.canonicalize,
892
- readModule:record.readModule
893
- });
894
- }catch(error){
895
- record.state='poisoned';
896
- for(const binding of record.moduleBindings)binding.registryRecord.poisoned=true;
897
- throw new ArcaneError(
898
- PROVIDER_GENERATION_CODE,
899
- `The Arcane ${record.target} provider module generation changed while it was imported. Restart the Arcane SDK process before pairing this provider again.`,
900
- {cause:error,details:unavailableGenerationDetails(requested,error)}
901
- );
902
- }
903
- if(!sameGeneration(requested,afterImport)){
904
- poisonChangedModuleBindings(record,afterImport);
905
- restartRequired(record.target,generationDetails(requested,afterImport));
906
- }
907
- if(importCompleted)markProcessModulesLoaded(record);
908
- if(importError){
909
- if(!importCompleted)poisonUnloadedProcessModules(record);
910
- record.state=importCompleted?'invalid':'poisoned';
911
- if(importError instanceof ArcaneError)throw importError;
137
+ if(error instanceof ArcaneError)throw error;
912
138
  throw new ArcaneError(
913
139
  ERROR_CODES.targetUnavailable,
914
- `The Arcane ${record.target} provider could not be loaded: ${importError.message}`,
915
- {cause:importError,details:{providerPath:record.providerPath}}
916
- );
917
- }
918
-
919
- const guardedBuilder=guardedNativeBuilder({
920
- rawBuilder,
921
- record,
922
- canonicalRoot:record.canonicalRoot,
923
- providerPath:record.providerPath,
924
- target:record.target,
925
- inspect:record.inspect,
926
- canonicalize:record.canonicalize
927
- });
928
- record.namespace=namespace;
929
- record.pairing=Object.freeze({
930
- arcaneRoot:record.canonicalRoot,
931
- toolchainRoot:record.canonicalRoot,
932
- providerPath:record.providerPath,
933
- providerGeneration:requested,
934
- nativeBuilder:guardedBuilder
935
- });
936
- record.state='ready';
937
- return record.pairing;
938
- }
939
-
940
- async function initializeProviderReservation(record){
941
- record.state='loading';
942
- try{
943
- const rootInfo=await record.inspect(record.requestedRoot);
944
- if(rootInfo.isSymbolicLink()||!rootInfo.isDirectory()){
945
- fail('The Arcane OS checkout root must be a real directory.',{
946
- arcaneRoot:record.requestedRoot
947
- });
948
- }
949
- await assertUnlinkedDirectoryAncestors(record.requestedRoot,record.inspect);
950
- record.canonicalRoot=await record.canonicalize(record.requestedRoot);
951
- if(!await regularFile(record.requestedProvider,record.inspect)){
952
- fail(`The selected Arcane OS checkout does not contain the ${record.target} native provider.`,{
953
- target:record.target,
954
- arcaneRoot:record.canonicalRoot,
955
- providerPath:record.requestedProvider
956
- });
957
- }
958
- record.providerPath=await record.canonicalize(record.requestedProvider);
959
- const expectedProvider=path.join(
960
- record.canonicalRoot,
961
- ...ARCANE_NATIVE_PROVIDER_PATHS[record.target]
962
- );
963
- if(!samePath(record.providerPath,expectedProvider)){
964
- fail(`The Arcane ${record.target} provider path must not resolve through a linked location.`,{
965
- target:record.target,
966
- providerPath:record.requestedProvider
967
- });
968
- }
969
- return await importReservedProvider(record);
970
- }catch(error){
971
- if(!record.importAttempted){
972
- record.state='invalid';
973
- if(providerGenerationCache.get(record.cacheKey)===record){
974
- providerGenerationCache.delete(record.cacheKey);
975
- }
976
- }
977
- if(error?.code==='ENOENT'){
978
- fail('The selected Arcane OS checkout root or provider module does not exist.',{
979
- arcaneRoot:record.requestedRoot
980
- });
981
- }
982
- throw error;
983
- }
984
- }
985
-
986
- function createProviderReservation(options){
987
- let releaseStart;
988
- const startGate=new Promise(resolve=>{releaseStart=resolve;});
989
- const record={
990
- ...options,
991
- state:'reserved',
992
- started:false,
993
- importAttempted:false,
994
- evidence:null,
995
- moduleBindings:null,
996
- namespace:null,
997
- pairing:null,
998
- promise:null,
999
- start:null
1000
- };
1001
- record.start=()=>{
1002
- if(record.started)return;
1003
- record.started=true;
1004
- releaseStart();
1005
- };
1006
- record.promise=startGate.then(()=>initializeProviderReservation(record));
1007
- record.promise.catch(()=>{});
1008
- return record;
1009
- }
1010
-
1011
- async function authenticateReadyPairing(record){
1012
- if(record.state==='poisoned'){
1013
- restartRequired(record.target,unavailableGenerationDetails(
1014
- record.evidence,
1015
- new Error('The cached provider generation is no longer valid.')
1016
- ));
1017
- }
1018
- let current;
1019
- try{
1020
- current=await providerGeneration({
1021
- canonicalRoot:record.canonicalRoot,
1022
- providerPath:record.providerPath,
1023
- target:record.target,
1024
- inspect:record.inspect,
1025
- canonicalize:record.canonicalize,
1026
- readModule:record.readModule
1027
- });
1028
- }catch(error){
1029
- record.state='poisoned';
1030
- throw new ArcaneError(
1031
- PROVIDER_GENERATION_CODE,
1032
- `The Arcane ${record.target} provider module generation can no longer be authenticated. Restart the Arcane SDK process before pairing this provider again.`,
1033
- {cause:error,details:unavailableGenerationDetails(record.evidence,error)}
140
+ `The Arcane ${target} provider could not be loaded: ${error.message}`,
141
+ {cause:error,details:{providerPath:location.providerPath}}
1034
142
  );
1035
143
  }
1036
- if(!sameGeneration(record.evidence,current)){
1037
- poisonChangedModuleBindings(record,current);
1038
- restartRequired(record.target,generationDetails(record.evidence,current));
1039
- }
1040
- assertLoadedProcessModules(record,current);
1041
- return record.pairing;
1042
- }
1043
-
1044
- async function observeProviderReservation({record,stateAtInvocation,onEvent,signal,target}){
1045
- throwIfAborted(signal);
1046
- await onEvent?.(Object.freeze({
1047
- type:'native.provider.load.started',
1048
- target,
1049
- message:`Loading the explicitly selected Arcane ${String(target)} provider.`
1050
- }));
1051
- record.start();
1052
- throwIfAborted(signal);
1053
- const pairing=await record.promise;
1054
- if(stateAtInvocation==='ready'||stateAtInvocation==='poisoned'){
1055
- await authenticateReadyPairing(record);
1056
- }
144
+ const nativeBuilder=validateNativeBuilder(
145
+ namespace?.arcaneNativeBuilderProvider??namespace?.default
146
+ );
1057
147
  throwIfAborted(signal);
1058
- await onEvent?.(Object.freeze({
148
+ const pairing={
149
+ arcaneRoot:location.canonicalRoot,
150
+ toolchainRoot:location.canonicalRoot,
151
+ providerPath:location.providerPath,
152
+ nativeBuilder
153
+ };
154
+ await onEvent?.({
1059
155
  type:'native.provider.load.completed',
1060
156
  target,
1061
- message:`The Arcane ${target} provider is paired for this process.`
1062
- }));
1063
- return pairing;
1064
- }
1065
-
1066
- export function loadArcaneNativeProvider({
1067
- arcaneRoot,
1068
- target,
1069
- inspect=lstat,
1070
- canonicalize=realpath,
1071
- readModule=readFile,
1072
- importModule=specifier=>import(specifier),
1073
- generationCache=providerGenerationCache,
1074
- signal,
1075
- onEvent
1076
- }={}){
1077
- throwIfAborted(signal);
1078
- const relativeProviderPath=ARCANE_NATIVE_PROVIDER_PATHS[target];
1079
- if(!relativeProviderPath){
1080
- fail(`No fixed Arcane native provider is registered for target ${String(target)}.`,{
1081
- target,
1082
- supportedTargets:Object.keys(ARCANE_NATIVE_PROVIDER_PATHS)
1083
- });
1084
- }
1085
- if(typeof arcaneRoot!=='string'||!arcaneRoot.trim()){
1086
- fail(`The ${target} native provider requires an explicit Arcane OS checkout root.`);
1087
- }
1088
- if(typeof readModule!=='function'||typeof importModule!=='function'
1089
- ||typeof generationCache?.get!=='function'||typeof generationCache?.set!=='function'){
1090
- fail('The Arcane native provider loader dependencies are invalid.');
1091
- }
1092
- const requestedRoot=path.resolve(arcaneRoot);
1093
- const requestedProvider=path.join(requestedRoot,...relativeProviderPath);
1094
- const cacheKey=generationCacheKey({
1095
- arcaneRoot:requestedRoot,
1096
- target,
1097
- providerPath:requestedProvider
1098
- });
1099
- let record=providerGenerationCache.get(cacheKey);
1100
- const stateAtInvocation=record?.state??'created';
1101
- if(!record){
1102
- record=createProviderReservation({
1103
- cacheKey,
1104
- requestedRoot,
1105
- requestedProvider,
1106
- target,
1107
- inspect,
1108
- canonicalize,
1109
- readModule,
1110
- importModule
1111
- });
1112
- providerGenerationCache.set(cacheKey,record);
1113
- }
1114
- generationCache.set(cacheKey,record);
1115
- return observeProviderReservation({
1116
- record,
1117
- stateAtInvocation,
1118
- onEvent,
1119
- signal,
1120
- target,
157
+ message:`The Arcane ${String(target)} provider is ready.`
1121
158
  });
159
+ return pairing;
1122
160
  }
1123
161
 
1124
162
  export function loadArcanePortableProvider(options={}){
163
+ if(options===null||typeof options!=='object'||Array.isArray(options)){
164
+ fail('Arcane portable provider options must be an object.');
165
+ }
1125
166
  return loadArcaneNativeProvider({...options,target:'portable'});
1126
167
  }