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,449 @@
1
+ import {lstat,readFile,readdir,realpath} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {
4
+ validateAppConfig as validatePackagerAppConfig,
5
+ validateRootConfig as validatePackagerRootConfig
6
+ } from './packager/core.mjs';
7
+ import {appDescriptorSha256,loadAppDescriptor} from './app-descriptor.mjs';
8
+ import {
9
+ SDK_NAME as EXPECTED_SDK_NAME,
10
+ SDK_VERSION as EXPECTED_SDK_VERSION
11
+ } from './constants.mjs';
12
+
13
+ const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
14
+ const SHA256_PATTERN=/^[a-f0-9]{64}$/;
15
+ const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
16
+ const ROOT_CONFIG_NAME='arcane-packager.json';
17
+ const APP_CONFIG_NAME='arcane-package.json';
18
+
19
+ function fail(message,code='ARCANE_WORKSPACE_INVALID'){
20
+ const error=new Error(message);
21
+ error.code=code;
22
+ throw error;
23
+ }
24
+
25
+ function isObject(value){
26
+ return value!==null&&typeof value==='object'&&!Array.isArray(value);
27
+ }
28
+
29
+ function ordinal(left,right){
30
+ return left<right?-1:left>right?1:0;
31
+ }
32
+
33
+ function throwIfAborted(signal){
34
+ if(!signal?.aborted)return;
35
+ const error=signal.reason instanceof Error?signal.reason:new Error('Operation cancelled.');
36
+ error.code=error.code||'ARCANE_CANCELLED';
37
+ throw error;
38
+ }
39
+
40
+ async function emit(onEvent,event){
41
+ if(typeof onEvent==='function')await onEvent(Object.freeze(event));
42
+ }
43
+
44
+ async function readJson(filePath,label){
45
+ let text;
46
+ try{
47
+ const info=await lstat(filePath);
48
+ if(info.isSymbolicLink()||!info.isFile())fail(`${label} must be a real file.`);
49
+ text=await readFile(filePath,'utf8');
50
+ }catch(error){
51
+ if(error?.code==='ENOENT')fail(`${label} does not exist.`);
52
+ throw error;
53
+ }
54
+ try{return JSON.parse(text);}
55
+ catch(error){fail(`${label} is not valid JSON: ${error.message}`);}
56
+ }
57
+
58
+ async function assertRealDirectory(directory,label){
59
+ let info;
60
+ try{info=await lstat(directory);}
61
+ catch(error){
62
+ if(error?.code==='ENOENT')fail(`${label} does not exist: ${directory}.`);
63
+ throw error;
64
+ }
65
+ if(info.isSymbolicLink()||!info.isDirectory())fail(`${label} must be a real directory: ${directory}.`);
66
+ }
67
+
68
+ function classifyRootConfig(config){
69
+ const validated=validatePackagerRootConfig(config,ROOT_CONFIG_NAME);
70
+ const routes=validated.sharedPayloads['browser-runtime'];
71
+ if(!Array.isArray(routes))fail(`${ROOT_CONFIG_NAME} must define browser-runtime routes.`);
72
+ const external=[
73
+ {
74
+ source:'node_modules/arcane-os/runtime/arcane',
75
+ destination:'arcane',
76
+ include:['components','css','entities','img','modules','security']
77
+ },
78
+ {
79
+ source:'node_modules/arcane-os/runtime/strong-type',
80
+ destination:'node_modules/strong-type',
81
+ include:['index.js','licence','package.json']
82
+ },
83
+ {
84
+ source:'node_modules/arcane-os',
85
+ destination:'licenses/arcane-os',
86
+ include:['LICENSE','COMMERCIAL-LICENSE.md','NOTICE']
87
+ }
88
+ ];
89
+ const integrated=[
90
+ {
91
+ source:'arcane',
92
+ destination:'arcane',
93
+ include:['components','css','entities','img','modules','security']
94
+ },
95
+ {
96
+ source:'node_modules/strong-type',
97
+ destination:'node_modules/strong-type',
98
+ include:['index.js','licence','package.json']
99
+ }
100
+ ];
101
+ const matches=expected=>routes.length===expected.length&&routes.every((route,index)=>{
102
+ const wanted=expected[index];
103
+ return isObject(route)&&Object.keys(route).every(key=>['source','destination','include','exclude'].includes(key))
104
+ &&route.source===wanted.source&&route.destination===wanted.destination
105
+ &&JSON.stringify(route.include)===JSON.stringify(wanted.include)
106
+ &&Array.isArray(route.exclude)&&route.exclude.length===0;
107
+ });
108
+ let workspaceMode;
109
+ if(matches(external)){
110
+ workspaceMode='external';
111
+ }else if(matches(integrated)){
112
+ workspaceMode='integrated';
113
+ }else{
114
+ fail(`${ROOT_CONFIG_NAME} browser-runtime routes must match the external SDK or integrated Arcane workspace contract.`);
115
+ }
116
+ return Object.freeze({...validated,workspaceMode});
117
+ }
118
+
119
+ async function discoverAppsInRoot(root,config){
120
+ const appsRoot=path.join(root,'apps');
121
+ await assertRealDirectory(appsRoot,'Workspace apps root');
122
+ const entries=await readdir(appsRoot,{withFileTypes:true});
123
+ const apps=[];
124
+ for(const entry of entries.sort((left,right)=>ordinal(left.name,right.name))){
125
+ if(!APP_ID_PATTERN.test(entry.name))continue;
126
+ if(entry.isSymbolicLink())fail(`apps/${entry.name} must not be a symbolic link or junction.`);
127
+ if(!entry.isDirectory())continue;
128
+ const appRoot=path.join(appsRoot,entry.name);
129
+ try{
130
+ const configPath=path.join(appRoot,APP_CONFIG_NAME);
131
+ const manifest=await readJson(configPath,`apps/${entry.name}/${APP_CONFIG_NAME}`);
132
+ const validatedManifest=validatePackagerAppConfig(manifest,entry.name,config,configPath);
133
+ if(!validatedManifest.shared.includes('browser-runtime')){
134
+ fail(`apps/${entry.name}/${APP_CONFIG_NAME} must include the browser-runtime shared payload.`);
135
+ }
136
+ const descriptor=await loadAppDescriptor({
137
+ workspaceRoot:root,
138
+ appRoot,
139
+ appId:entry.name,
140
+ packageManifest:manifest
141
+ });
142
+ apps.push(Object.freeze({
143
+ appId:entry.name,
144
+ appRoot,
145
+ manifest:validatedManifest,
146
+ descriptor:descriptor.descriptor,
147
+ descriptorSource:descriptor.source,
148
+ descriptorPath:descriptor.descriptorPath
149
+ }));
150
+ }catch(error){
151
+ if(!String(error?.message).includes('does not exist'))throw error;
152
+ }
153
+ }
154
+ return Object.freeze(apps);
155
+ }
156
+
157
+ export async function discoverApps(workspaceRoot=process.cwd()){
158
+ const profile=await inspectWorkspaceProfile(workspaceRoot);
159
+ return discoverAppsInRoot(profile.workspaceRoot,profile.config);
160
+ }
161
+
162
+ export async function inspectWorkspaceProfile(workspaceRoot=process.cwd()){
163
+ const requested=path.resolve(workspaceRoot);
164
+ await assertRealDirectory(requested,'Workspace');
165
+ const canonicalRoot=await realpath(requested);
166
+ const config=classifyRootConfig(
167
+ await readJson(path.join(canonicalRoot,ROOT_CONFIG_NAME),ROOT_CONFIG_NAME)
168
+ );
169
+ return Object.freeze({
170
+ workspaceRoot:canonicalRoot,
171
+ workspaceMode:config.workspaceMode,
172
+ config
173
+ });
174
+ }
175
+
176
+ export async function selectApp(workspaceRoot=process.cwd(),appId){
177
+ const apps=await discoverApps(workspaceRoot);
178
+ if(appId!==undefined&&(typeof appId!=='string'||!APP_ID_PATTERN.test(appId))){
179
+ fail(`Invalid app id: ${String(appId)}.`,'ARCANE_USAGE');
180
+ }
181
+ if(appId){
182
+ const selected=apps.find(app=>app.appId===appId);
183
+ if(!selected)fail(`Unknown app "${appId}". Available apps: ${apps.map(app=>app.appId).join(', ')||'[none]'}.`);
184
+ return selected;
185
+ }
186
+ if(apps.length===0)fail('No Arcane applications were found under apps/.');
187
+ if(apps.length>1){
188
+ fail(`This workspace contains multiple apps; select one explicitly: ${apps.map(app=>app.appId).join(', ')}.`,'ARCANE_USAGE');
189
+ }
190
+ return apps[0];
191
+ }
192
+
193
+ export async function resolveWorkspace({workspaceRoot=process.cwd(),appId}={}){
194
+ const profile=await inspectWorkspaceProfile(workspaceRoot);
195
+ const canonicalRoot=profile.workspaceRoot;
196
+ const config=profile.config;
197
+ const apps=await discoverAppsInRoot(canonicalRoot,config);
198
+ if(appId!==undefined&&(typeof appId!=='string'||!APP_ID_PATTERN.test(appId))){
199
+ fail(`Invalid app id: ${String(appId)}.`,'ARCANE_USAGE');
200
+ }
201
+ let app;
202
+ if(appId){
203
+ app=apps.find(item=>item.appId===appId);
204
+ if(!app)fail(`Unknown app "${appId}". Available apps: ${apps.map(item=>item.appId).join(', ')||'[none]'}.`);
205
+ }else if(apps.length===1){
206
+ [app]=apps;
207
+ }else if(apps.length===0){
208
+ fail('No Arcane applications were found under apps/.');
209
+ }else{
210
+ fail(`This workspace contains multiple apps; select one explicitly: ${apps.map(item=>item.appId).join(', ')}.`,'ARCANE_USAGE');
211
+ }
212
+ return Object.freeze({
213
+ workspaceRoot:canonicalRoot,
214
+ config,
215
+ app,
216
+ appId:app.appId,
217
+ appRoot:app.appRoot,
218
+ appIds:Object.freeze(apps.map(item=>item.appId))
219
+ });
220
+ }
221
+
222
+ function validateLock(lock){
223
+ if(!isObject(lock)||lock.schemaVersion!==1||!isObject(lock.sdk)
224
+ ||lock.sdk.name!==EXPECTED_SDK_NAME||lock.sdk.version!==EXPECTED_SDK_VERSION
225
+ ||!isObject(lock.runtime)||!SHA256_PATTERN.test(lock.runtime.contentSha256)
226
+ ||!/^([a-f0-9]{40})$/.test(lock.runtime.upstreamCommit)
227
+ ||lock.runtime.manifest!=='node_modules/arcane-os/runtime/ARCANE_RUNTIME_RELEASE.json'
228
+ ||!isObject(lock.protocols)||lock.protocols.arcane!=='arcane/1'
229
+ ||lock.protocols.cliEvents!=='arcane-cli-events/1'
230
+ ||lock.protocols.targetAdapter!=='arcane-target-adapter/1'){
231
+ fail('arcane.lock.json is incompatible with this SDK. Run arcane init only after reviewing missing files; existing locks are never overwritten.');
232
+ }
233
+ return lock;
234
+ }
235
+
236
+ function assertHtmlContract(source,appId,{entry='index.html',strictStyles=true}={}){
237
+ const entryLabel=`apps/${appId}/${entry}`;
238
+ if(!new RegExp(`<meta\\s+name=["']arcane-app-id["']\\s+content=["']${appId}["']`).test(source)){
239
+ fail(`${entryLabel} must declare matching arcane-app-id metadata.`);
240
+ }
241
+ if(!/<base\s+href=["']\.\.\/\.\.\/["']/.test(source)){
242
+ fail(`${entryLabel} must declare <base href="../../">.`);
243
+ }
244
+ const theme=source.indexOf('./arcane/css/theme.css');
245
+ const primitives=source.indexOf('./arcane/css/primitives.css');
246
+ const appStyleMatches=[...source.matchAll(new RegExp(`(?:\\./|/)apps/${appId.replaceAll('-','\\-')}/[^"']+\\.css(?:\\?[^"']*)?(?=["'])`, 'g'))];
247
+ const appStyle=appStyleMatches.length?Math.min(...appStyleMatches.map(match=>match.index)):-1;
248
+ const bootstrap=source.indexOf('./arcane/modules/ThemeBootstrap.js');
249
+ const appModuleMatches=[...source.matchAll(new RegExp(`(?:\\./|/)apps/${appId.replaceAll('-','\\-')}/[^"']+\\.(?:js|mjs)(?:\\?[^"']*)?(?=["'])`, 'g'))];
250
+ const appModule=appModuleMatches.length?Math.min(...appModuleMatches.map(match=>match.index)):-1;
251
+ if(theme<0){
252
+ fail(`${entryLabel} must load the shared Arcane theme.css.`);
253
+ }
254
+ if(strictStyles&&(primitives<=theme||appStyle<=primitives)){
255
+ fail(`${entryLabel} must load theme.css, primitives.css, and app CSS in that order.`);
256
+ }
257
+ if(!strictStyles&&((primitives>=0&&primitives<=theme)||(appStyle>=0&&appStyle<=theme))){
258
+ fail(`${entryLabel} must load shared and app CSS after theme.css.`);
259
+ }
260
+ if(bootstrap<0||(appModule>=0&&appModule<=bootstrap)){
261
+ fail(`${entryLabel} must load ThemeBootstrap.js before app-local module scripts.`);
262
+ }
263
+ }
264
+
265
+ export async function validateDiscoveredApplication({
266
+ workspaceRoot,
267
+ workspaceMode,
268
+ workspaceConfig,
269
+ app,
270
+ signal,
271
+ onEvent
272
+ }={}){
273
+ throwIfAborted(signal);
274
+ if(!app||typeof app.appId!=='string'||typeof app.appRoot!=='string'
275
+ ||!app.manifest||!app.descriptor){
276
+ fail('A discovered Arcane application is required for focused validation.');
277
+ }
278
+ const canonicalWorkspaceRoot=path.resolve(workspaceRoot);
279
+ const expectedAppRoot=path.join(canonicalWorkspaceRoot,'apps',app.appId);
280
+ if(path.resolve(app.appRoot)!==expectedAppRoot){
281
+ fail(`Discovered app ${app.appId} does not belong to the selected workspace.`);
282
+ }
283
+ let config=workspaceConfig;
284
+ if(!config){
285
+ const profile=await inspectWorkspaceProfile(canonicalWorkspaceRoot);
286
+ if(profile.workspaceMode!==workspaceMode){
287
+ fail('The selected Arcane workspace profile changed before focused validation.');
288
+ }
289
+ config=profile.config;
290
+ }
291
+ if(!isObject(config?.sharedPayloads)){
292
+ fail('The selected Arcane workspace configuration is unavailable for focused validation.');
293
+ }
294
+ const configPath=path.join(app.appRoot,APP_CONFIG_NAME);
295
+ const rawManifest=await readJson(
296
+ configPath,
297
+ `apps/${app.appId}/${APP_CONFIG_NAME}`
298
+ );
299
+ const manifest=validatePackagerAppConfig(rawManifest,app.appId,config,configPath);
300
+ if(!manifest.shared.includes('browser-runtime')){
301
+ fail(`apps/${app.appId}/${APP_CONFIG_NAME} must include the browser-runtime shared payload.`);
302
+ }
303
+ const loadedDescriptor=await loadAppDescriptor({
304
+ workspaceRoot:canonicalWorkspaceRoot,
305
+ appRoot:app.appRoot,
306
+ appId:app.appId,
307
+ packageManifest:rawManifest
308
+ });
309
+ const descriptor=loadedDescriptor.descriptor;
310
+ if(appDescriptorSha256(descriptor)!==appDescriptorSha256(app.descriptor)){
311
+ fail(
312
+ `The canonical descriptor for ${app.appId} changed after application discovery.`,
313
+ 'ARCANE_INTEGRITY_FAILED'
314
+ );
315
+ }
316
+ const freshApp=Object.freeze({
317
+ appId:app.appId,
318
+ appRoot:app.appRoot,
319
+ manifest,
320
+ descriptor,
321
+ descriptorSource:loadedDescriptor.source,
322
+ descriptorPath:loadedDescriptor.descriptorPath
323
+ });
324
+ const entryPath=path.join(app.appRoot,manifest.entry);
325
+ const info=await lstat(entryPath);
326
+ if(info.isSymbolicLink()||!info.isFile()){
327
+ fail(`apps/${app.appId}/${manifest.entry} must be a real file.`);
328
+ }
329
+ assertHtmlContract(await readFile(entryPath,'utf8'),app.appId,{
330
+ entry:manifest.entry,
331
+ strictStyles:workspaceMode==='external'
332
+ });
333
+ const receipt=Object.freeze({
334
+ valid:true,
335
+ workspaceRoot:canonicalWorkspaceRoot,
336
+ workspaceMode,
337
+ appId:app.appId,
338
+ appRoot:app.appRoot,
339
+ app:freshApp
340
+ });
341
+ await emit(onEvent,{
342
+ type:'workspace.application.validated',
343
+ workspaceRoot:canonicalWorkspaceRoot,
344
+ appId:app.appId
345
+ });
346
+ return receipt;
347
+ }
348
+
349
+ export async function validateWorkspace({workspaceRoot=process.cwd(),appId,signal,onEvent}={}){
350
+ throwIfAborted(signal);
351
+ const resolved=await resolveWorkspace({workspaceRoot,appId});
352
+ await emit(onEvent,{type:'workspace.validate.started',workspaceRoot:resolved.workspaceRoot,appId:resolved.appId});
353
+ const checks=[];
354
+ const add=async(name,operation)=>{
355
+ throwIfAborted(signal);
356
+ await operation();
357
+ checks.push(Object.freeze({name,ok:true}));
358
+ await emit(onEvent,{type:'workspace.validate.check',name,ok:true});
359
+ };
360
+ let lock;
361
+ let validatedApplication;
362
+ const workspaceMode=resolved.config.workspaceMode;
363
+ await add('workspace-profile',async()=>{
364
+ if(workspaceMode!=='external'&&workspaceMode!=='integrated'){
365
+ fail(`Unsupported Arcane workspace mode: ${String(workspaceMode)}.`);
366
+ }
367
+ });
368
+ await add('descriptor',async()=>{
369
+ if(resolved.app.descriptor.id!==resolved.appId){
370
+ fail('The selected Arcane application descriptor does not match the app id.');
371
+ }
372
+ });
373
+ if(workspaceMode==='external'){
374
+ await add('lock',async()=>{
375
+ lock=validateLock(await readJson(path.join(resolved.workspaceRoot,'arcane.lock.json'),'arcane.lock.json'));
376
+ });
377
+ }
378
+ await add('package',async()=>{
379
+ const rootPackage=await readJson(path.join(resolved.workspaceRoot,'package.json'),'package.json');
380
+ if(workspaceMode==='integrated'){
381
+ if(rootPackage?.name!=='arcane-os'||rootPackage?.type!=='module'){
382
+ fail('An integrated Arcane workspace package.json must identify as arcane-os and use modules.');
383
+ }
384
+ return;
385
+ }
386
+ const configured=rootPackage?.devDependencies?.[EXPECTED_SDK_NAME]
387
+ ??rootPackage?.dependencies?.[EXPECTED_SDK_NAME];
388
+ const supported=configured===EXPECTED_SDK_VERSION
389
+ ||(typeof configured==='string'&&LOCAL_TARBALL_PATTERN.test(configured)
390
+ &&!/[\x00-\x1f\x7f]/.test(configured));
391
+ if(rootPackage?.private!==true||rootPackage?.type!=='module'||!supported){
392
+ fail(`package.json must be private, use modules, and declare ${EXPECTED_SDK_NAME} as ${EXPECTED_SDK_VERSION} or a local file: tarball.`);
393
+ }
394
+ });
395
+ if(workspaceMode==='external'){
396
+ await add('installed-runtime',async()=>{
397
+ const installedPackage=await readJson(
398
+ path.join(resolved.workspaceRoot,'node_modules','arcane-os','package.json'),
399
+ 'installed SDK package manifest'
400
+ );
401
+ if(installedPackage.name!==EXPECTED_SDK_NAME||installedPackage.version!==EXPECTED_SDK_VERSION){
402
+ fail(`Installed SDK package must identify exactly as ${EXPECTED_SDK_NAME}@${EXPECTED_SDK_VERSION}.`);
403
+ }
404
+ const installed=await readJson(
405
+ path.join(resolved.workspaceRoot,'node_modules','arcane-os','runtime','ARCANE_RUNTIME_RELEASE.json'),
406
+ 'installed SDK runtime manifest'
407
+ );
408
+ if(installed.contentSha256!==lock.runtime.contentSha256
409
+ ||installed.source?.commit!==lock.runtime.upstreamCommit){
410
+ fail('Installed SDK runtime does not match arcane.lock.json.');
411
+ }
412
+ });
413
+ }else{
414
+ await add('workspace-runtime',async()=>{
415
+ for(const [relative,label] of [
416
+ ['arcane','Integrated Arcane runtime'],
417
+ [path.join('node_modules','strong-type'),'Integrated strong-type runtime']
418
+ ]){
419
+ const info=await lstat(path.join(resolved.workspaceRoot,relative));
420
+ if(info.isSymbolicLink()||!info.isDirectory()){
421
+ fail(`${label} must be a real directory.`);
422
+ }
423
+ }
424
+ });
425
+ }
426
+ await add('app-entry',async()=>{
427
+ validatedApplication=await validateDiscoveredApplication({
428
+ workspaceRoot:resolved.workspaceRoot,
429
+ workspaceMode,
430
+ workspaceConfig:resolved.config,
431
+ app:resolved.app,
432
+ signal,
433
+ onEvent
434
+ });
435
+ });
436
+ const receipt=Object.freeze({
437
+ valid:true,
438
+ workspaceMode,
439
+ workspaceRoot:resolved.workspaceRoot,
440
+ appId:resolved.appId,
441
+ appRoot:resolved.appRoot,
442
+ config:resolved.config,
443
+ app:validatedApplication.app,
444
+ lock,
445
+ checks:Object.freeze(checks)
446
+ });
447
+ await emit(onEvent,{type:'workspace.validate.completed',workspaceRoot:resolved.workspaceRoot,appId:resolved.appId,checks});
448
+ return receipt;
449
+ }