arcane-os 0.1.0-dev.5

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 (248) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMMERCIAL-LICENSE.md +13 -0
  3. package/LICENSE +661 -0
  4. package/NOTICE +70 -0
  5. package/README.md +448 -0
  6. package/bin/arcane-test.mjs +741 -0
  7. package/bin/arcane.mjs +5 -0
  8. package/docs/architecture.md +234 -0
  9. package/docs/compatibility.md +36 -0
  10. package/docs/event-manager.md +166 -0
  11. package/docs/platform-targets.md +108 -0
  12. package/docs/publishing.md +203 -0
  13. package/docs/reference/README.md +117 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +123 -0
  16. package/docs/reference/behavioral-testing.md +86 -0
  17. package/docs/reference/cli.md +569 -0
  18. package/docs/reference/core/README.md +62 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +873 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +65 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +957 -0
  32. package/docs/reference/inventory/package-api.json +2632 -0
  33. package/docs/reference/inventory/runtime-components.json +934 -0
  34. package/docs/reference/inventory/runtime-entities.json +26 -0
  35. package/docs/reference/inventory/runtime-modules.json +1249 -0
  36. package/docs/reference/protocols.md +242 -0
  37. package/docs/reference/runtime-components.md +1098 -0
  38. package/docs/reference/runtime-entities.md +303 -0
  39. package/docs/reference/runtime-modules.md +2010 -0
  40. package/docs/reference/sdk-api.md +4901 -0
  41. package/docs/roadmap.md +79 -0
  42. package/docs/work-amplification.md +124 -0
  43. package/node_modules/event-pubsub/CHANGELOG.md +55 -0
  44. package/node_modules/event-pubsub/MIGRATION.md +70 -0
  45. package/node_modules/event-pubsub/README.md +363 -0
  46. package/node_modules/event-pubsub/SECURITY.md +37 -0
  47. package/node_modules/event-pubsub/index.js +141 -0
  48. package/node_modules/event-pubsub/licence +21 -0
  49. package/node_modules/event-pubsub/package.json +59 -0
  50. package/node_modules/strong-type/README.md +408 -0
  51. package/node_modules/strong-type/assets/strong-type-header.png +0 -0
  52. package/node_modules/strong-type/index.js +1151 -0
  53. package/node_modules/strong-type/licence +21 -0
  54. package/node_modules/strong-type/node.js +125 -0
  55. package/node_modules/strong-type/package.json +61 -0
  56. package/package.json +95 -0
  57. package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
  58. package/runtime/arcane/components/app-bar.html +468 -0
  59. package/runtime/arcane/components/assistant-panel.html +715 -0
  60. package/runtime/arcane/components/calculator.html +8 -0
  61. package/runtime/arcane/components/chart.html +655 -0
  62. package/runtime/arcane/components/chat.html +1225 -0
  63. package/runtime/arcane/components/conversation-view.html +13 -0
  64. package/runtime/arcane/components/dashboard-config.html +341 -0
  65. package/runtime/arcane/components/data-maintenance.html +112 -0
  66. package/runtime/arcane/components/data-view.html +92 -0
  67. package/runtime/arcane/components/directory-picker.html +197 -0
  68. package/runtime/arcane/components/document-inspector.html +252 -0
  69. package/runtime/arcane/components/file-drop.html +264 -0
  70. package/runtime/arcane/components/file-inspector.html +293 -0
  71. package/runtime/arcane/components/file-manager.html +1715 -0
  72. package/runtime/arcane/components/header.html +142 -0
  73. package/runtime/arcane/components/integration-settings.html +14 -0
  74. package/runtime/arcane/components/local-ai-status.html +360 -0
  75. package/runtime/arcane/components/markdown-document.html +1048 -0
  76. package/runtime/arcane/components/markdown-editor.html +360 -0
  77. package/runtime/arcane/components/media-embed.html +8 -0
  78. package/runtime/arcane/components/modal.html +402 -0
  79. package/runtime/arcane/components/output-panel.html +259 -0
  80. package/runtime/arcane/components/preferences-form.html +135 -0
  81. package/runtime/arcane/components/record-timeline.html +105 -0
  82. package/runtime/arcane/components/relationship-board.html +116 -0
  83. package/runtime/arcane/components/screen-capture.html +8 -0
  84. package/runtime/arcane/components/source-code-viewer.html +441 -0
  85. package/runtime/arcane/components/source-explanation.html +124 -0
  86. package/runtime/arcane/components/speech.html +365 -0
  87. package/runtime/arcane/components/summary-strip.html +177 -0
  88. package/runtime/arcane/components/table.html +77 -0
  89. package/runtime/arcane/components/task-progress.html +282 -0
  90. package/runtime/arcane/components/terminal-workspace.html +65 -0
  91. package/runtime/arcane/components/theme-editor.html +41 -0
  92. package/runtime/arcane/components/theme-switcher.html +46 -0
  93. package/runtime/arcane/components/unified-inbox.html +20 -0
  94. package/runtime/arcane/components/voice-transcription.html +476 -0
  95. package/runtime/arcane/components/weather-widget.html +8 -0
  96. package/runtime/arcane/components/web-navigator.html +239 -0
  97. package/runtime/arcane/css/communications.css +1 -0
  98. package/runtime/arcane/css/dashboard-config.css +45 -0
  99. package/runtime/arcane/css/document-site.css +981 -0
  100. package/runtime/arcane/css/layout.css +438 -0
  101. package/runtime/arcane/css/primitives.css +321 -0
  102. package/runtime/arcane/css/theme.css +112 -0
  103. package/runtime/arcane/css/utility-workspace.css +1 -0
  104. package/runtime/arcane/entities/ApiModelRecord.js +20 -0
  105. package/runtime/arcane/entities/Calculation.js +13 -0
  106. package/runtime/arcane/entities/Chat.js +581 -0
  107. package/runtime/arcane/entities/CommunicationMessage.js +29 -0
  108. package/runtime/arcane/entities/CommunicationThread.js +21 -0
  109. package/runtime/arcane/entities/Document.js +10 -0
  110. package/runtime/arcane/entities/File.js +143 -0
  111. package/runtime/arcane/entities/Image.js +135 -0
  112. package/runtime/arcane/entities/IntentEnvelope.js +834 -0
  113. package/runtime/arcane/entities/Preference.js +83 -0
  114. package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
  115. package/runtime/arcane/entities/TerminalSession.js +46 -0
  116. package/runtime/arcane/entities/Theme.js +107 -0
  117. package/runtime/arcane/entities/User.js +1046 -0
  118. package/runtime/arcane/entities/Weather.js +23 -0
  119. package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
  120. package/runtime/arcane/img/arrow-left.png +0 -0
  121. package/runtime/arcane/img/arrow-right.png +0 -0
  122. package/runtime/arcane/img/doc.svg +5 -0
  123. package/runtime/arcane/img/folder.svg +4 -0
  124. package/runtime/arcane/img/image.svg +5 -0
  125. package/runtime/arcane/img/refresh.png +0 -0
  126. package/runtime/arcane/img/send.svg +9 -0
  127. package/runtime/arcane/img/trash.svg +5 -0
  128. package/runtime/arcane/img/upload.svg +5 -0
  129. package/runtime/arcane/modules/AI.js +2048 -0
  130. package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
  131. package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
  132. package/runtime/arcane/modules/AIResponseLength.js +42 -0
  133. package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
  134. package/runtime/arcane/modules/AnsiText.js +53 -0
  135. package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
  136. package/runtime/arcane/modules/AppDataScope.js +245 -0
  137. package/runtime/arcane/modules/AppearancePreferences.js +28 -0
  138. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
  139. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
  140. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
  141. package/runtime/arcane/modules/AsyncBoundary.js +161 -0
  142. package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
  143. package/runtime/arcane/modules/CalculatorEngine.js +20 -0
  144. package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
  145. package/runtime/arcane/modules/ChartLibrary.js +35 -0
  146. package/runtime/arcane/modules/ChatRecords.js +13 -0
  147. package/runtime/arcane/modules/CommunicationAppController.js +43 -0
  148. package/runtime/arcane/modules/CommunicationHub.js +16 -0
  149. package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
  150. package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
  151. package/runtime/arcane/modules/ComponentContracts.js +586 -0
  152. package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
  153. package/runtime/arcane/modules/ConversationActionItems.js +488 -0
  154. package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
  155. package/runtime/arcane/modules/ConversationTimebox.js +527 -0
  156. package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
  157. package/runtime/arcane/modules/DBLS.js +171 -0
  158. package/runtime/arcane/modules/DBOPFS.js +1154 -0
  159. package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
  160. package/runtime/arcane/modules/DataMaintenance.js +91 -0
  161. package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
  162. package/runtime/arcane/modules/DirectoryPicker.js +109 -0
  163. package/runtime/arcane/modules/DocumentNavigation.js +223 -0
  164. package/runtime/arcane/modules/Errors.js +1025 -0
  165. package/runtime/arcane/modules/GifEncoder.js +29 -0
  166. package/runtime/arcane/modules/HTMLImport.js +114 -0
  167. package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
  168. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
  169. package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
  170. package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
  171. package/runtime/arcane/modules/MD.js +111 -0
  172. package/runtime/arcane/modules/Mail.js +352 -0
  173. package/runtime/arcane/modules/MailTransport.mjs +180 -0
  174. package/runtime/arcane/modules/Marked.min.js +71 -0
  175. package/runtime/arcane/modules/MemoryRecords.js +44 -0
  176. package/runtime/arcane/modules/MessageAdvisory.js +38 -0
  177. package/runtime/arcane/modules/ModelDefinition.js +189 -0
  178. package/runtime/arcane/modules/Ollama.js +74 -0
  179. package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
  180. package/runtime/arcane/modules/OllamaSettings.js +24 -0
  181. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
  182. package/runtime/arcane/modules/PreferenceStore.js +109 -0
  183. package/runtime/arcane/modules/QRCode.min.js +1 -0
  184. package/runtime/arcane/modules/Questionnaire.js +61 -0
  185. package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
  186. package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
  187. package/runtime/arcane/modules/RecordReviewStore.js +94 -0
  188. package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
  189. package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
  190. package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
  191. package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
  192. package/runtime/arcane/modules/ScreenCapture.js +20 -0
  193. package/runtime/arcane/modules/SpeechPlayback.js +581 -0
  194. package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
  195. package/runtime/arcane/modules/SystemAppearance.js +20 -0
  196. package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
  197. package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
  198. package/runtime/arcane/modules/TerminalClient.js +52 -0
  199. package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
  200. package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
  201. package/runtime/arcane/modules/ThemeManager.js +131 -0
  202. package/runtime/arcane/modules/TimeGuard.js +149 -0
  203. package/runtime/arcane/modules/ToolCallRouter.js +83 -0
  204. package/runtime/arcane/modules/WaitForComponent.js +102 -0
  205. package/runtime/arcane/modules/YouTubeMedia.js +16 -0
  206. package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
  207. package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
  208. package/runtime/arcane/modules/uPlot.min.css +1 -0
  209. package/runtime/arcane/security/arcane-network-policy.json +6 -0
  210. package/runtime/strong-type/index.js +352 -0
  211. package/runtime/strong-type/licence +21 -0
  212. package/runtime/strong-type/package.json +45 -0
  213. package/schemas/arcane-app-bundle.schema.json +125 -0
  214. package/schemas/arcane-app.schema.json +329 -0
  215. package/schemas/arcane-lock.schema.json +86 -0
  216. package/schemas/arcane-package.schema.json +224 -0
  217. package/schemas/cli-event.schema.json +122 -0
  218. package/schemas/event-stack.schema.json +152 -0
  219. package/schemas/native-build-plan.schema.json +176 -0
  220. package/schemas/target-adapter.schema.json +117 -0
  221. package/src/app-descriptor.mjs +500 -0
  222. package/src/cli/main.mjs +561 -0
  223. package/src/constants.mjs +31 -0
  224. package/src/dev-server.mjs +718 -0
  225. package/src/doctor.mjs +315 -0
  226. package/src/dom-event-instrumentation.mjs +594 -0
  227. package/src/errors.mjs +75 -0
  228. package/src/event-manager.mjs +1342 -0
  229. package/src/event-queue.mjs +138 -0
  230. package/src/events.mjs +219 -0
  231. package/src/index.mjs +177 -0
  232. package/src/integrated-provider-loader.mjs +432 -0
  233. package/src/native-plan.mjs +698 -0
  234. package/src/native-provider-loader.mjs +1126 -0
  235. package/src/packager/core.mjs +2691 -0
  236. package/src/process.mjs +353 -0
  237. package/src/release-bundle.mjs +2523 -0
  238. package/src/repository.mjs +90 -0
  239. package/src/runtime.mjs +452 -0
  240. package/src/scaffold.mjs +380 -0
  241. package/src/targets/index.mjs +436 -0
  242. package/src/templates/assets/app-icon.png +0 -0
  243. package/src/templates/workspace-template.mjs +388 -0
  244. package/src/testing-loader.mjs +9 -0
  245. package/src/testing.mjs +427 -0
  246. package/src/toolchain.mjs +1335 -0
  247. package/src/update-check.mjs +307 -0
  248. package/src/workspace.mjs +449 -0
@@ -0,0 +1,718 @@
1
+ import {randomBytes,timingSafeEqual} from 'node:crypto';
2
+ import {constants as FS_CONSTANTS} from 'node:fs';
3
+ import {lstat,open,realpath} from 'node:fs/promises';
4
+ import http from 'node:http';
5
+ import path from 'node:path';
6
+ import {
7
+ authenticateAppReleaseReceipt,
8
+ readVerifiedAppReleaseFile,
9
+ RELEASE_MANIFEST_NAME
10
+ } from './packager/core.mjs';
11
+ import {
12
+ authenticateRuntimeReceipt,
13
+ readVerifiedRuntimeFile,
14
+ verifyRuntime
15
+ } from './runtime.mjs';
16
+ import {resolveWorkspace} from './workspace.mjs';
17
+ import {createEventQueue} from './event-queue.mjs';
18
+
19
+ const MIME_TYPES=new Map([
20
+ ['.css','text/css; charset=utf-8'],
21
+ ['.gif','image/gif'],
22
+ ['.html','text/html; charset=utf-8'],
23
+ ['.ico','image/x-icon'],
24
+ ['.jpeg','image/jpeg'],
25
+ ['.jpg','image/jpeg'],
26
+ ['.js','text/javascript; charset=utf-8'],
27
+ ['.json','application/json; charset=utf-8'],
28
+ ['.mjs','text/javascript; charset=utf-8'],
29
+ ['.png','image/png'],
30
+ ['.svg','image/svg+xml; charset=utf-8'],
31
+ ['.txt','text/plain; charset=utf-8'],
32
+ ['.webp','image/webp'],
33
+ ['.woff','font/woff'],
34
+ ['.woff2','font/woff2']
35
+ ]);
36
+ const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
37
+ const MAX_DEVELOPMENT_FILE_BYTES=64*1024*1024;
38
+ const MAX_CONCURRENT_FILE_RESPONSES=4;
39
+ const MAX_PENDING_FILE_RESPONSES=256;
40
+ const SESSION_COOKIE='Arcane-Dev-Session';
41
+ const PRIVATE_SOURCE_SEGMENTS=new Set([
42
+ 'arcane-app.json','arcane-package.json','test','tests','scripts','node_modules','dist','local'
43
+ ]);
44
+
45
+ // This server is a loopback-only development host, not a production policy
46
+ // boundary. The bundled runtime currently needs inline component execution,
47
+ // workers, remote provider requests, media, and embedded web content. The
48
+ // unguessable session cookie and exact numeric Host check protect this broad
49
+ // development CSP from being exposed as a general network service.
50
+ const DEVELOPMENT_CSP=[
51
+ "default-src 'self'",
52
+ "script-src 'self' 'unsafe-inline'",
53
+ "style-src 'self' 'unsafe-inline'",
54
+ "img-src 'self' data: blob: http: https:",
55
+ "font-src 'self' data:",
56
+ "connect-src 'self' data: blob: http: https: ws: wss:",
57
+ "worker-src 'self' blob:",
58
+ "frame-src 'self' data: blob: http: https:",
59
+ "media-src 'self' data: blob: http: https:",
60
+ "object-src 'none'",
61
+ "frame-ancestors 'none'",
62
+ "base-uri 'self'",
63
+ "form-action 'self'"
64
+ ].join('; ');
65
+
66
+ function fail(message,code='ARCANE_OPERATION_FAILED'){
67
+ const error=new Error(message);
68
+ error.code=code;
69
+ throw error;
70
+ }
71
+
72
+ function throwIfAborted(signal){
73
+ if(!signal?.aborted)return;
74
+ const error=signal.reason instanceof Error?signal.reason:new Error('Operation cancelled.');
75
+ error.code=error.code||'ARCANE_CANCELLED';
76
+ throw error;
77
+ }
78
+
79
+ function responseSecurityHeaders(contentSecurityPolicy=DEVELOPMENT_CSP){
80
+ return {
81
+ 'cache-control':'no-store',
82
+ 'x-content-type-options':'nosniff',
83
+ 'content-security-policy':contentSecurityPolicy,
84
+ 'cross-origin-resource-policy':'same-origin',
85
+ 'referrer-policy':'no-referrer'
86
+ };
87
+ }
88
+
89
+ function deny(response,status,message){
90
+ response.writeHead(status,{
91
+ 'content-type':'text/plain; charset=utf-8',
92
+ ...responseSecurityHeaders("default-src 'none'; frame-ancestors 'none'; base-uri 'none'")
93
+ });
94
+ response.end(`${message}\n`);
95
+ }
96
+
97
+ function parseRequestTarget(rawUrl){
98
+ const raw=String(rawUrl||'/');
99
+ if(!raw.startsWith('/'))return null;
100
+ const rawPath=raw.split(/[?#]/u,1)[0];
101
+ let decoded;
102
+ try{decoded=decodeURIComponent(rawPath);}
103
+ catch{return null;}
104
+ if(!decoded.startsWith('/')||/[\x00-\x1f\x7f]/u.test(decoded)
105
+ ||/[<>"|?*]/u.test(decoded)||decoded.includes('\\'))return null;
106
+ const segments=decoded.split('/').filter(Boolean);
107
+ if(segments.some(segment=>segment==='.'||segment==='..'||segment.includes(':')
108
+ ||segment.endsWith('.')||segment.endsWith(' ')
109
+ ||/^(?:con|prn|aux|nul|clock\$|conin\$|conout\$|com[1-9¹²³]|lpt[1-9¹²³])(?:\..*)?$/iu
110
+ .test(segment)))return null;
111
+ let parsed;
112
+ try{parsed=new URL(raw,'http://127.0.0.1');}
113
+ catch{return null;}
114
+ return {segments,path:decoded,searchParams:parsed.searchParams};
115
+ }
116
+
117
+ function resolveInside(root,segments){
118
+ const candidate=path.resolve(root,...segments);
119
+ const relative=path.relative(root,candidate);
120
+ if(relative.startsWith('..')||path.isAbsolute(relative))return null;
121
+ return candidate;
122
+ }
123
+
124
+ function identityMatches(info,identity){
125
+ return !identity||(
126
+ String(info.dev)===identity.device
127
+ &&String(info.ino)===identity.inode
128
+ &&Number(info.size)===identity.bytes
129
+ &&String(info.mtimeNs)===identity.modifiedNanoseconds
130
+ &&String(info.ctimeNs)===identity.changedNanoseconds
131
+ &&(identity.links===undefined||String(info.nlink)===identity.links)
132
+ );
133
+ }
134
+
135
+ function inventoryKey(value){
136
+ return process.platform==='win32'?value.toLowerCase():value;
137
+ }
138
+
139
+ function identityMap(identities,prefix=''){
140
+ const normalizedPrefix=prefix?`${prefix}/`:'';
141
+ const result=new Map();
142
+ for(const identity of identities||[]){
143
+ if(!identity.path.startsWith(normalizedPrefix))continue;
144
+ const relative=identity.path.slice(normalizedPrefix.length);
145
+ if(relative)result.set(inventoryKey(relative),identity);
146
+ }
147
+ return result;
148
+ }
149
+
150
+ function createFileWorkLimiter(){
151
+ let active=0;
152
+ const pending=[];
153
+ const release=()=>{
154
+ const next=pending.shift();
155
+ if(next)next();
156
+ else active-=1;
157
+ };
158
+ return async work=>{
159
+ if(active>=MAX_CONCURRENT_FILE_RESPONSES){
160
+ if(pending.length>=MAX_PENDING_FILE_RESPONSES){
161
+ fail('Development server file queue is full.','ARCANE_BACKPRESSURE');
162
+ }
163
+ await new Promise(resolve=>pending.push(resolve));
164
+ }else{
165
+ active+=1;
166
+ }
167
+ try{
168
+ return await work();
169
+ }finally{
170
+ release();
171
+ }
172
+ };
173
+ }
174
+
175
+ async function openSafeFile(root,segments,expectedIdentity){
176
+ const candidate=resolveInside(root,segments);
177
+ if(!candidate)return null;
178
+ let current=root;
179
+ for(const segment of segments){
180
+ current=path.join(current,segment);
181
+ let info;
182
+ try{info=await lstat(current,{bigint:true});}
183
+ catch(error){
184
+ if(error?.code==='ENOENT')return null;
185
+ throw error;
186
+ }
187
+ if(info.isSymbolicLink())return null;
188
+ }
189
+
190
+ const before=await lstat(candidate,{bigint:true});
191
+ if(before.isSymbolicLink()||!before.isFile()||!identityMatches(before,expectedIdentity))return null;
192
+ let handle;
193
+ try{
194
+ handle=await open(candidate,READ_ONLY_NO_FOLLOW);
195
+ }catch(error){
196
+ if(error?.code==='ENOENT'||error?.code==='ELOOP')return null;
197
+ throw error;
198
+ }
199
+ try{
200
+ const opened=await handle.stat({bigint:true});
201
+ if(!opened.isFile()||!identityMatches(opened,expectedIdentity)
202
+ ||!identityMatches(opened,{
203
+ device:String(before.dev),
204
+ inode:String(before.ino),
205
+ bytes:Number(before.size),
206
+ modifiedNanoseconds:String(before.mtimeNs),
207
+ changedNanoseconds:String(before.ctimeNs),
208
+ links:String(before.nlink)
209
+ })){
210
+ return null;
211
+ }
212
+ if(Number(opened.size)>MAX_DEVELOPMENT_FILE_BYTES){
213
+ fail(
214
+ `Development file exceeds the ${MAX_DEVELOPMENT_FILE_BYTES}-byte serving limit.`,
215
+ 'ARCANE_POLICY_DENIED'
216
+ );
217
+ }
218
+ const bytes=await handle.readFile();
219
+ const after=await handle.stat({bigint:true});
220
+ if(!identityMatches(after,{
221
+ device:String(opened.dev),
222
+ inode:String(opened.ino),
223
+ bytes:Number(opened.size),
224
+ modifiedNanoseconds:String(opened.mtimeNs),
225
+ changedNanoseconds:String(opened.ctimeNs),
226
+ links:String(opened.nlink)
227
+ })||bytes.length!==Number(opened.size)){
228
+ return null;
229
+ }
230
+ const canonicalCandidate=await realpath(candidate);
231
+ const relative=path.relative(root,canonicalCandidate);
232
+ if(relative.startsWith('..')||path.isAbsolute(relative)){
233
+ return null;
234
+ }
235
+ const currentInfo=await lstat(candidate,{bigint:true});
236
+ if(!identityMatches(currentInfo,{
237
+ device:String(opened.dev),
238
+ inode:String(opened.ino),
239
+ bytes:Number(opened.size),
240
+ modifiedNanoseconds:String(opened.mtimeNs),
241
+ changedNanoseconds:String(opened.ctimeNs),
242
+ links:String(opened.nlink)
243
+ })){
244
+ return null;
245
+ }
246
+ return {candidate:canonicalCandidate,bytes,size:bytes.length};
247
+ }catch(error){
248
+ if(error?.code==='ENOENT')return null;
249
+ throw error;
250
+ }finally{
251
+ await handle.close().catch(()=>{});
252
+ }
253
+ }
254
+
255
+ async function sendFile(response,opened,{head=false}={}){
256
+ const extension=path.extname(opened.candidate).toLowerCase();
257
+ response.writeHead(200,{
258
+ 'content-type':MIME_TYPES.get(extension)||'application/octet-stream',
259
+ 'content-length':opened.size,
260
+ ...responseSecurityHeaders()
261
+ });
262
+ await new Promise((resolve,reject)=>{
263
+ let settled=false;
264
+ const cleanup=()=>{
265
+ response.removeListener('error',failed);
266
+ response.removeListener('finish',completed);
267
+ response.removeListener('close',completed);
268
+ };
269
+ const completed=()=>{
270
+ if(settled)return;
271
+ settled=true;
272
+ cleanup();
273
+ resolve();
274
+ };
275
+ const failed=error=>{
276
+ if(settled)return;
277
+ settled=true;
278
+ cleanup();
279
+ reject(error);
280
+ };
281
+ response.once('error',failed);
282
+ response.once('finish',completed);
283
+ response.once('close',completed);
284
+ response.end(head?undefined:opened.bytes);
285
+ });
286
+ }
287
+
288
+ function sourcePathAllowed(relative,manifest){
289
+ const posix=relative.join('/');
290
+ if(!posix)return false;
291
+ const segments=posix.split('/');
292
+ if(segments.some(segment=>segment.startsWith('.')
293
+ ||PRIVATE_SOURCE_SEGMENTS.has(segment.toLowerCase())))return false;
294
+ const comparable=inventoryKey(posix);
295
+ const excluded=(manifest.exclude||[]).some(item=>{
296
+ const candidate=inventoryKey(item);
297
+ return comparable===candidate||comparable.startsWith(`${candidate}/`);
298
+ });
299
+ if(excluded)return false;
300
+ return manifest.include.some(item=>{
301
+ const candidate=inventoryKey(item);
302
+ return comparable===candidate||comparable.startsWith(`${candidate}/`);
303
+ });
304
+ }
305
+
306
+ function sharedPathAllowed(relative,route){
307
+ const posix=relative.join('/');
308
+ if(!posix||relative.some(segment=>segment.startsWith('.')))return false;
309
+ const comparable=inventoryKey(posix);
310
+ const excluded=(route.exclude??[]).some(item=>{
311
+ const candidate=inventoryKey(item);
312
+ return comparable===candidate||comparable.startsWith(`${candidate}/`);
313
+ });
314
+ if(excluded)return false;
315
+ return route.include.some(item=>{
316
+ const candidate=inventoryKey(item);
317
+ return comparable===candidate||comparable.startsWith(`${candidate}/`);
318
+ });
319
+ }
320
+
321
+ async function sourceRoutes(workspaceRoot,appId,{runtimeReceipt,signal,onEvent}){
322
+ const resolved=await resolveWorkspace({workspaceRoot,appId});
323
+ if(resolved.config.workspaceMode==='integrated'){
324
+ return {
325
+ workspaceRoot:resolved.workspaceRoot,
326
+ workspaceMode:'integrated',
327
+ appId:resolved.appId,
328
+ startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
329
+ mappings:[
330
+ {
331
+ prefix:['apps',resolved.appId],
332
+ root:resolved.appRoot,
333
+ allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
334
+ },
335
+ ...resolved.config.sharedPayloads['browser-runtime'].map(route=>({
336
+ prefix:route.destination.split('/'),
337
+ root:path.join(resolved.workspaceRoot,...route.source.split('/')),
338
+ allow:relative=>sharedPathAllowed(relative,route)
339
+ }))
340
+ ]
341
+ };
342
+ }
343
+ const runtimeRoot=path.join(
344
+ resolved.workspaceRoot,
345
+ 'node_modules',
346
+ 'arcane-os',
347
+ 'runtime'
348
+ );
349
+ const verified=runtimeReceipt??await verifyRuntime({runtimeRoot,signal,onEvent});
350
+ await authenticateRuntimeReceipt(verified,{runtimeRoot});
351
+ const arcaneIdentities=identityMap(verified.identities,'arcane');
352
+ const strongTypeIdentities=identityMap(verified.identities,'strong-type');
353
+ return {
354
+ workspaceRoot:resolved.workspaceRoot,
355
+ workspaceMode:'external',
356
+ appId:resolved.appId,
357
+ startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
358
+ mappings:[
359
+ {
360
+ prefix:['apps',resolved.appId],
361
+ root:resolved.appRoot,
362
+ allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
363
+ },
364
+ {
365
+ prefix:['arcane'],
366
+ root:path.join(runtimeRoot,'arcane'),
367
+ identities:arcaneIdentities,
368
+ read:relative=>readVerifiedRuntimeFile(verified,{
369
+ runtimeRoot,
370
+ relativePath:`arcane/${relative.join('/')}`,
371
+ signal
372
+ }),
373
+ allow:relative=>arcaneIdentities.has(inventoryKey(relative.join('/')))
374
+ },
375
+ {
376
+ prefix:['node_modules','strong-type'],
377
+ root:path.join(runtimeRoot,'strong-type'),
378
+ identities:strongTypeIdentities,
379
+ read:relative=>readVerifiedRuntimeFile(verified,{
380
+ runtimeRoot,
381
+ relativePath:`strong-type/${relative.join('/')}`,
382
+ signal
383
+ }),
384
+ allow:relative=>strongTypeIdentities.has(inventoryKey(relative.join('/')))
385
+ }
386
+ ]
387
+ };
388
+ }
389
+
390
+ async function packagedRoutes(releaseRoot,releaseReceipt,{signal}={}){
391
+ if(typeof releaseRoot!=='string'||!releaseRoot.trim())fail('releaseRoot is required in packaged mode.','ARCANE_USAGE');
392
+ if(!releaseReceipt)fail('An authenticated release receipt is required in packaged mode.','ARCANE_POLICY_DENIED');
393
+ const requested=path.resolve(releaseRoot);
394
+ await authenticateAppReleaseReceipt(releaseReceipt,{releaseRoot:requested,signal});
395
+ const info=await lstat(requested);
396
+ if(info.isSymbolicLink()||!info.isDirectory())fail('Packaged release root must be a real directory.');
397
+ const canonical=await realpath(requested);
398
+ const identities=identityMap(releaseReceipt.identities);
399
+ return {
400
+ workspaceRoot:null,
401
+ appId:null,
402
+ startPath:'/index.html',
403
+ mappings:[{
404
+ prefix:[],
405
+ root:canonical,
406
+ identities,
407
+ read:relative=>readVerifiedAppReleaseFile(releaseReceipt,{
408
+ releaseRoot:canonical,
409
+ relativePath:relative.join('/'),
410
+ signal
411
+ }),
412
+ allow:relative=>identities.has(inventoryKey(relative.join('/')))
413
+ }]
414
+ };
415
+ }
416
+
417
+ function tokenMatches(value,expected){
418
+ if(typeof value!=='string')return false;
419
+ const received=Buffer.from(value,'utf8');
420
+ const wanted=Buffer.from(expected,'utf8');
421
+ return received.length===wanted.length&&timingSafeEqual(received,wanted);
422
+ }
423
+
424
+ function hasSessionCookie(request,cookieName,sessionToken){
425
+ const matches=String(request.headers.cookie||'')
426
+ .split(';')
427
+ .map(part=>part.trim())
428
+ .filter(part=>part.startsWith(`${cookieName}=`))
429
+ .map(part=>part.slice(cookieName.length+1));
430
+ return matches.length===1&&tokenMatches(matches[0],sessionToken);
431
+ }
432
+
433
+ function listen(server,{host,port,signal}){
434
+ return new Promise((resolve,reject)=>{
435
+ const cleanup=()=>{
436
+ signal?.removeEventListener('abort',abort);
437
+ server.removeListener('error',failed);
438
+ };
439
+ const abort=()=>server.close(()=>{
440
+ cleanup();
441
+ reject(signal.reason||new Error('Operation cancelled.'));
442
+ });
443
+ const failed=error=>{
444
+ cleanup();
445
+ reject(error);
446
+ };
447
+ signal?.addEventListener('abort',abort,{once:true});
448
+ server.once('error',failed);
449
+ server.listen(port,host,()=>{
450
+ cleanup();
451
+ resolve();
452
+ });
453
+ });
454
+ }
455
+
456
+ async function startOwnedDevServer({
457
+ workspaceRoot=process.cwd(),
458
+ appId,
459
+ mode='source',
460
+ releaseRoot,
461
+ host='127.0.0.1',
462
+ port=0,
463
+ signal,
464
+ runtimeReceipt,
465
+ releaseReceipt
466
+ }={},events,releaseSignal){
467
+ throwIfAborted(signal);
468
+ if(mode!=='source'&&mode!=='packaged')fail(`Unsupported server mode: ${String(mode)}.`,'ARCANE_USAGE');
469
+ if(host!=='127.0.0.1'&&host!=='::1'){
470
+ fail('Development server host must be a numeric loopback address (127.0.0.1 or ::1).','ARCANE_POLICY_DENIED');
471
+ }
472
+ if(!Number.isInteger(port)||port<0||port>65535)fail('port must be an integer from 0 through 65535.','ARCANE_USAGE');
473
+ await events.send({type:'server.starting',mode,host,port,appId});
474
+ const routeSet=mode==='source'
475
+ ?await sourceRoutes(workspaceRoot,appId,{
476
+ runtimeReceipt,
477
+ signal,
478
+ onEvent:event=>events.send(event)
479
+ })
480
+ :await packagedRoutes(releaseRoot,releaseReceipt,{signal});
481
+ for(const mapping of routeSet.mappings){
482
+ const info=await lstat(mapping.root);
483
+ if(info.isSymbolicLink()||!info.isDirectory())fail(`Server route root must be a real directory: ${mapping.root}.`);
484
+ mapping.root=await realpath(mapping.root);
485
+ }
486
+ const sessionToken=randomBytes(32).toString('hex');
487
+ const sessionCookieName=`${SESSION_COOKIE}-${randomBytes(8).toString('hex')}`;
488
+ let expectedAuthority=null;
489
+ const requestTasks=new Set();
490
+ const runFileWork=createFileWorkLimiter();
491
+ const server=http.createServer((request,response)=>{
492
+ let task;
493
+ task=(async()=>{
494
+ if(!expectedAuthority){deny(response,503,'Server is starting.');return;}
495
+ if(request.headers.host!==expectedAuthority){
496
+ deny(response,421,'Misdirected request.');
497
+ return;
498
+ }
499
+ if(request.method!=='GET'&&request.method!=='HEAD'){
500
+ deny(response,405,'Method not allowed.');
501
+ return;
502
+ }
503
+ const target=parseRequestTarget(request.url);
504
+ if(!target){deny(response,400,'Invalid request path.');return;}
505
+ const queryKeys=[...target.searchParams.keys()];
506
+ const isBootstrap=request.method==='GET'
507
+ &&target.path===routeSet.startPath
508
+ &&queryKeys.length===1
509
+ &&queryKeys[0]==='arcane_session'
510
+ &&tokenMatches(target.searchParams.get('arcane_session'),sessionToken);
511
+ if(isBootstrap){
512
+ response.writeHead(302,{
513
+ location:routeSet.startPath,
514
+ 'set-cookie':`${sessionCookieName}=${sessionToken}; HttpOnly; SameSite=Strict; Path=/`,
515
+ ...responseSecurityHeaders()
516
+ });
517
+ response.end();
518
+ return;
519
+ }
520
+ if(!hasSessionCookie(request,sessionCookieName,sessionToken)){
521
+ deny(response,403,'Development server session required.');
522
+ return;
523
+ }
524
+ const {segments}=target;
525
+ if(mode==='packaged'&&segments.some(segment=>
526
+ segment.toLowerCase()===RELEASE_MANIFEST_NAME.toLowerCase()
527
+ )){
528
+ deny(response,404,'Not found.');
529
+ return;
530
+ }
531
+ if(segments.length===0){
532
+ response.writeHead(302,{location:routeSet.startPath,...responseSecurityHeaders()});
533
+ response.end();
534
+ return;
535
+ }
536
+ const mapping=routeSet.mappings.find(route=>
537
+ route.prefix.every((segment,index)=>segments[index]===segment)
538
+ );
539
+ if(!mapping){deny(response,404,'Not found.');return;}
540
+ const relative=segments.slice(mapping.prefix.length);
541
+ if(relative.length===0){deny(response,404,'Not found.');return;}
542
+ if(mapping.allow&&!mapping.allow(relative)){deny(response,404,'Not found.');return;}
543
+ await runFileWork(async()=>{
544
+ const expectedIdentity=mapping.identities?.get(inventoryKey(relative.join('/')));
545
+ const opened=mapping.read
546
+ ?{
547
+ candidate:path.join(mapping.root,...relative),
548
+ bytes:await mapping.read(relative)
549
+ }
550
+ :await openSafeFile(mapping.root,relative,expectedIdentity);
551
+ if(!opened){deny(response,404,'Not found.');return;}
552
+ opened.size=opened.bytes.length;
553
+ await sendFile(response,opened,{head:request.method==='HEAD'});
554
+ });
555
+ })().catch(async error=>{
556
+ await events.enqueue({type:'server.request.failed',message:error.message});
557
+ if(!response.headersSent){
558
+ const status=error?.code==='ARCANE_POLICY_DENIED'?413
559
+ :error?.code==='ARCANE_BACKPRESSURE'?503
560
+ :500;
561
+ deny(response,status,'Internal server error.');
562
+ }
563
+ else response.destroy(error);
564
+ }).finally(()=>{
565
+ requestTasks.delete(task);
566
+ });
567
+ requestTasks.add(task);
568
+ });
569
+ await listen(server,{host,port,signal});
570
+ const address=server.address();
571
+ if(!address||typeof address==='string'){
572
+ server.close();
573
+ fail('Development server did not expose a TCP address.');
574
+ }
575
+ const visibleHost=address.family==='IPv6'?`[${address.address}]`:address.address;
576
+ const endpoint=new URL(`http://${visibleHost}:${address.port}`);
577
+ expectedAuthority=endpoint.host;
578
+ const origin=endpoint.origin;
579
+ const cleanUrl=`${origin}${routeSet.startPath}`;
580
+ const url=`${cleanUrl}?arcane_session=${sessionToken}`;
581
+ let closeInitiated=false;
582
+ let lifecycleSettlementStarted=false;
583
+ let operationalError=null;
584
+ let resolveLifecycle;
585
+ let rejectLifecycle;
586
+ const lifecycle=new Promise((resolve,reject)=>{
587
+ resolveLifecycle=resolve;
588
+ rejectLifecycle=reject;
589
+ });
590
+ // A library consumer may only use the raw server. Keep a later lifecycle
591
+ // rejection observable without allowing it to become unhandled.
592
+ void lifecycle.catch(()=>{});
593
+
594
+ const finishLifecycle=async()=>{
595
+ if(lifecycleSettlementStarted){
596
+ return;
597
+ }
598
+ lifecycleSettlementStarted=true;
599
+ signal?.removeEventListener('abort',abort);
600
+ server.removeListener('error',serverFailed);
601
+ try{
602
+ while(requestTasks.size>0){
603
+ await Promise.allSettled([...requestTasks]);
604
+ }
605
+ await events.send({
606
+ type:'server.stopped',
607
+ host:address.address,
608
+ port:address.port
609
+ });
610
+ await events.drain();
611
+ if(operationalError){
612
+ rejectLifecycle(operationalError);
613
+ }else{
614
+ resolveLifecycle();
615
+ }
616
+ }catch(error){
617
+ rejectLifecycle(events.error??operationalError??error);
618
+ }finally{
619
+ releaseSignal();
620
+ }
621
+ };
622
+
623
+ const close=error=>{
624
+ if(error&&!operationalError){
625
+ operationalError=error;
626
+ }
627
+ if(!closeInitiated){
628
+ closeInitiated=true;
629
+ try{
630
+ server.close(closeError=>{
631
+ if(closeError){
632
+ operationalError??=closeError;
633
+ void finishLifecycle();
634
+ }
635
+ });
636
+ }catch(closeError){
637
+ operationalError??=closeError;
638
+ void finishLifecycle();
639
+ }
640
+ }
641
+ return lifecycle;
642
+ };
643
+ const abort=()=>{void close().catch(()=>{});};
644
+ const serverFailed=error=>{void close(error).catch(()=>{});};
645
+ signal?.addEventListener('abort',abort,{once:true});
646
+ server.on('error',serverFailed);
647
+ server.once('close',()=>{void finishLifecycle();});
648
+ const result={
649
+ server,
650
+ mode,
651
+ workspaceRoot:routeSet.workspaceRoot,
652
+ appId:routeSet.appId,
653
+ host:address.address,
654
+ port:address.port,
655
+ origin,
656
+ cleanUrl,
657
+ url,
658
+ close,
659
+ closed:lifecycle,
660
+ lifecycle
661
+ };
662
+ try{
663
+ await events.send({
664
+ type:'server.started',
665
+ mode,
666
+ host:result.host,
667
+ port:result.port,
668
+ url,
669
+ appId:result.appId
670
+ });
671
+ throwIfAborted(signal);
672
+ }catch(error){
673
+ try{
674
+ await close();
675
+ }catch(lifecycleError){
676
+ throw events.error??lifecycleError;
677
+ }
678
+ throw error;
679
+ }
680
+ return result;
681
+ }
682
+
683
+ export async function startDevServer(options={}){
684
+ const inputSignal=options.signal;
685
+ throwIfAborted(inputSignal);
686
+ const controller=new AbortController();
687
+ const forwardAbort=()=>controller.abort(inputSignal?.reason);
688
+ inputSignal?.addEventListener('abort',forwardAbort,{once:true});
689
+ if(inputSignal?.aborted){
690
+ forwardAbort();
691
+ }
692
+ let released=false;
693
+ const releaseSignal=()=>{
694
+ if(released){
695
+ return;
696
+ }
697
+ released=true;
698
+ inputSignal?.removeEventListener('abort',forwardAbort);
699
+ };
700
+ const events=createEventQueue(options.onEvent,{
701
+ onFailure:error=>controller.abort(error)
702
+ });
703
+ try{
704
+ return await startOwnedDevServer(
705
+ {...options,signal:controller.signal},
706
+ events,
707
+ releaseSignal
708
+ );
709
+ }catch(error){
710
+ releaseSignal();
711
+ try{
712
+ await events.drain();
713
+ }catch(callbackFailure){
714
+ throw callbackFailure;
715
+ }
716
+ throw error;
717
+ }
718
+ }