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,117 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/TheWizardNexus/arcane-os-sdk/schemas/target-adapter.schema.json",
4
+ "title": "Arcane target adapter descriptor",
5
+ "description": "Stable, serializable description of a build and run target adapter.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "protocol",
10
+ "id",
11
+ "displayName",
12
+ "status",
13
+ "platforms",
14
+ "architectures",
15
+ "formats",
16
+ "signingModes",
17
+ "reason",
18
+ "methods"
19
+ ],
20
+ "properties": {
21
+ "protocol": {
22
+ "const": "arcane-target-adapter/1"
23
+ },
24
+ "id": {
25
+ "type": "string",
26
+ "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
27
+ },
28
+ "displayName": {
29
+ "type": "string",
30
+ "minLength": 1,
31
+ "maxLength": 128
32
+ },
33
+ "status": {
34
+ "enum": [
35
+ "available",
36
+ "pairing-required",
37
+ "host-unsupported",
38
+ "deferred",
39
+ "incompatible"
40
+ ]
41
+ },
42
+ "platforms": {
43
+ "type": "array",
44
+ "minItems": 1,
45
+ "uniqueItems": true,
46
+ "items": {
47
+ "enum": [
48
+ "any",
49
+ "windows",
50
+ "linux",
51
+ "android"
52
+ ]
53
+ }
54
+ },
55
+ "architectures": {
56
+ "type": "array",
57
+ "minItems": 1,
58
+ "uniqueItems": true,
59
+ "items": {
60
+ "enum": [
61
+ "any",
62
+ "x64",
63
+ "arm64"
64
+ ]
65
+ }
66
+ },
67
+ "formats": {
68
+ "type": "array",
69
+ "minItems": 1,
70
+ "uniqueItems": true,
71
+ "items": {
72
+ "enum": [
73
+ "directory",
74
+ "portable",
75
+ "exe",
76
+ "appimage",
77
+ "deb",
78
+ "rpm",
79
+ "apk",
80
+ "aab"
81
+ ]
82
+ }
83
+ },
84
+ "signingModes": {
85
+ "type": "array",
86
+ "minItems": 1,
87
+ "uniqueItems": true,
88
+ "items": {
89
+ "enum": [
90
+ "none",
91
+ "unsigned-local-test",
92
+ "development",
93
+ "production"
94
+ ]
95
+ }
96
+ },
97
+ "reason": {
98
+ "type": [
99
+ "string",
100
+ "null"
101
+ ],
102
+ "minLength": 1,
103
+ "maxLength": 2048
104
+ },
105
+ "methods": {
106
+ "const": [
107
+ "describe",
108
+ "doctor",
109
+ "prepare",
110
+ "plan",
111
+ "build",
112
+ "verify",
113
+ "run"
114
+ ]
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,500 @@
1
+ import {isDeepStrictEqual} from 'node:util';
2
+ import {createHash} from 'node:crypto';
3
+ import {lstat,readFile} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import {
6
+ ARCANE_MACHINE_BUNDLE_VERSION,
7
+ ARCANE_PROTOCOL,
8
+ TARGET_IDS
9
+ } from './constants.mjs';
10
+ import {
11
+ normalizeRelativePath,
12
+ parseSemver,
13
+ validateAppConfig as validateAppPackageConfig
14
+ } from './packager/core.mjs';
15
+
16
+ export const APP_DESCRIPTOR_NAME='arcane-app.json';
17
+ export const APP_DESCRIPTOR_SCHEMA_VERSION=2;
18
+
19
+ const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
20
+ const CAPABILITY_PATTERN=/^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+$/u;
21
+ const METHOD_PATTERN=/^[a-z][a-z0-9]*(?:\.[a-z][a-zA-Z0-9]*)+$/u;
22
+ const FEATURE_PATTERN=/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/u;
23
+ const PUBLISHER_PATTERN=/^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
24
+ const NATIVE_TYPES=new Set(['app']);
25
+ const SAFE_ICON_EXTENSIONS=new Set(['.ico','.jpeg','.jpg','.png','.webp']);
26
+ const REGISTRY_PATH=path.join(
27
+ 'machine_bundles',
28
+ 'arcane-os-machine-bundle',
29
+ 'arcane-apps.json'
30
+ );
31
+
32
+ function fail(message){
33
+ const error=new Error(message);
34
+ error.code='ARCANE_APP_DESCRIPTOR_INVALID';
35
+ throw error;
36
+ }
37
+
38
+ function isObject(value){
39
+ return value!==null&&typeof value==='object'&&!Array.isArray(value);
40
+ }
41
+
42
+ function assertOnlyKeys(value,allowed,label){
43
+ if(!isObject(value))fail(`${label} must be an object.`);
44
+ for(const key of Object.keys(value)){
45
+ if(!allowed.has(key))fail(`${label} contains an unsupported key: ${key}.`);
46
+ }
47
+ }
48
+
49
+ function safeText(value,label,{maximum=500}={}){
50
+ if(typeof value!=='string'||value!==value.trim()||!value||value.length>maximum
51
+ ||/[<>\u0000-\u001f\u007f]/u.test(value)){
52
+ fail(`${label} must be nonempty, trimmed, bounded presentation text.`);
53
+ }
54
+ return value;
55
+ }
56
+
57
+ function uniqueSortedStrings(value,label,{pattern,maximum=256,required=false}={}){
58
+ if(!Array.isArray(value)||(required&&value.length===0)||value.length>maximum){
59
+ fail(`${label} must be ${required?'a nonempty':'an'} array with at most ${maximum} entries.`);
60
+ }
61
+ const normalized=value.map((entry,index)=>{
62
+ if(typeof entry!=='string'||entry!==entry.trim()||!entry
63
+ ||(pattern&&!pattern.test(entry))){
64
+ fail(`${label}[${index}] is invalid.`);
65
+ }
66
+ return entry;
67
+ });
68
+ if(new Set(normalized).size!==normalized.length)fail(`${label} must not contain duplicates.`);
69
+ if(JSON.stringify(normalized)!==JSON.stringify([...normalized].sort())){
70
+ fail(`${label} must be sorted for deterministic projection.`);
71
+ }
72
+ return Object.freeze(normalized);
73
+ }
74
+
75
+ function relativePaths(value,label,{required=false}={}){
76
+ if(!Array.isArray(value)||(required&&value.length===0)||value.length>512){
77
+ fail(`${label} must be ${required?'a nonempty':'an'} array with at most 512 entries.`);
78
+ }
79
+ const normalized=value.map((entry,index)=>normalizeRelativePath(entry,`${label}[${index}]`));
80
+ if(new Set(normalized.map(entry=>entry.toLocaleLowerCase('en-US'))).size!==normalized.length){
81
+ fail(`${label} must not contain duplicate paths.`);
82
+ }
83
+ return Object.freeze(normalized);
84
+ }
85
+
86
+ function validateOrigins(value,label,{allowLoopbackHttp=false,allowHttpsScheme=false}={}){
87
+ if(!Array.isArray(value)||value.length>16)fail(`${label} must be an array with at most 16 origins.`);
88
+ const origins=value.map((origin,index)=>{
89
+ if(typeof origin!=='string'||origin!==origin.trim())fail(`${label}[${index}] is invalid.`);
90
+ if(origin==='https:'&&allowHttpsScheme)return origin;
91
+ let parsed;
92
+ try{parsed=new URL(origin);}
93
+ catch{fail(`${label}[${index}] is not a valid URL origin.`);}
94
+ if(parsed.origin!==origin||parsed.username||parsed.password
95
+ ||parsed.pathname!=='/'||parsed.search||parsed.hash){
96
+ fail(`${label}[${index}] must be a canonical allowed origin.`);
97
+ }
98
+ if(parsed.protocol==='http:'){
99
+ if(!allowLoopbackHttp||!['127.0.0.1','[::1]'].includes(parsed.hostname)){
100
+ fail(`${label}[${index}] may use HTTP only for a numeric loopback host.`);
101
+ }
102
+ }else if(parsed.protocol!=='https:'){
103
+ fail(`${label}[${index}] must use HTTPS or an approved loopback HTTP origin.`);
104
+ }
105
+ return parsed.origin;
106
+ });
107
+ if(new Set(origins).size!==origins.length)fail(`${label} must not contain duplicates.`);
108
+ if(JSON.stringify(origins)!==JSON.stringify([...origins].sort())){
109
+ fail(`${label} must be sorted for deterministic projection.`);
110
+ }
111
+ return Object.freeze(origins);
112
+ }
113
+
114
+ function validateLocalAIModelPolicy(value,label){
115
+ if(value===undefined)return undefined;
116
+ assertOnlyKeys(value,new Set(['verified_only','models']),label);
117
+ if(typeof value.verified_only!=='boolean'||!Array.isArray(value.models)||value.models.length>64){
118
+ fail(`${label} is invalid.`);
119
+ }
120
+ const models=value.models.map((model,index)=>{
121
+ const itemLabel=`${label}.models[${index}]`;
122
+ assertOnlyKeys(model,new Set(['name','definition']),itemLabel);
123
+ if(typeof model.name!=='string'||!model.name||model.name.length>256
124
+ ||typeof model.definition!=='string'||!model.definition.endsWith('Modelfile')){
125
+ fail(`${itemLabel} is invalid.`);
126
+ }
127
+ return Object.freeze({name:model.name,definition:normalizeRelativePath(model.definition,`${itemLabel}.definition`)});
128
+ });
129
+ return Object.freeze({verified_only:value.verified_only,models:Object.freeze(models)});
130
+ }
131
+
132
+ function validatePackage(value,appId){
133
+ assertOnlyKeys(value,new Set([
134
+ 'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy'
135
+ ]),'descriptor.package');
136
+ const entry=normalizeRelativePath(value.entry,'descriptor.package.entry');
137
+ if(!['static','adapter'].includes(value.strategy))fail('descriptor.package.strategy must be static or adapter.');
138
+ const include=relativePaths(value.include,'descriptor.package.include',{required:true});
139
+ const exclude=relativePaths(value.exclude??[],'descriptor.package.exclude');
140
+ if(!include.some(candidate=>entry===candidate||entry.startsWith(`${candidate}/`))){
141
+ fail('descriptor.package.entry must be covered by descriptor.package.include.');
142
+ }
143
+ const shared=uniqueSortedStrings(value.shared,'descriptor.package.shared',{
144
+ pattern:/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u,
145
+ maximum:64,
146
+ required:true
147
+ });
148
+ let adapter;
149
+ if(value.strategy==='adapter'){
150
+ adapter=normalizeRelativePath(value.adapter,'descriptor.package.adapter');
151
+ if(!/^scripts\/[A-Za-z0-9._/-]+\.mjs$/u.test(adapter)){
152
+ fail('descriptor.package.adapter must be an app-local scripts/*.mjs path.');
153
+ }
154
+ }else if(value.adapter!==undefined){
155
+ fail('descriptor.package.adapter is only valid for adapter packages.');
156
+ }
157
+ return Object.freeze({
158
+ entry,
159
+ strategy:value.strategy,
160
+ include,
161
+ exclude,
162
+ shared,
163
+ ...(adapter?{adapter}:{}),
164
+ ...(value.localAIModelPolicy===undefined?{}:{
165
+ localAIModelPolicy:validateLocalAIModelPolicy(
166
+ value.localAIModelPolicy,
167
+ `descriptor ${appId} localAIModelPolicy`
168
+ )
169
+ })
170
+ });
171
+ }
172
+
173
+ function validatePublisher(value){
174
+ assertOnlyKeys(value,new Set(['id','name']),'descriptor.publisher');
175
+ if(typeof value.id!=='string'||!PUBLISHER_PATTERN.test(value.id)){
176
+ fail('descriptor.publisher.id must be a lowercase publisher identifier.');
177
+ }
178
+ return Object.freeze({id:value.id,name:safeText(value.name,'descriptor.publisher.name',{maximum:160})});
179
+ }
180
+
181
+ function validatePermissions(value){
182
+ assertOnlyKeys(value,new Set(['capabilities','methods']),'descriptor.permissions');
183
+ const capabilities=uniqueSortedStrings(value.capabilities,'descriptor.permissions.capabilities',{
184
+ pattern:CAPABILITY_PATTERN,
185
+ maximum:256
186
+ });
187
+ if(capabilities.includes('external.open')&&capabilities.includes('web.embed')){
188
+ fail('descriptor.permissions.capabilities must not combine external.open with web.embed.');
189
+ }
190
+ return Object.freeze({
191
+ capabilities,
192
+ methods:uniqueSortedStrings(value.methods,'descriptor.permissions.methods',{
193
+ pattern:METHOD_PATTERN,
194
+ maximum:512
195
+ })
196
+ });
197
+ }
198
+
199
+ function validateSecurity(value,{appId,capabilities}){
200
+ assertOnlyKeys(value,new Set(['connectOrigins','frameOrigins','mediaOrigins']),'descriptor.security');
201
+ const frameOrigins=validateOrigins(
202
+ value.frameOrigins,
203
+ 'descriptor.security.frameOrigins',
204
+ {allowHttpsScheme:appId==='browser'}
205
+ );
206
+ if(frameOrigins.length&&!capabilities.includes('web.embed')){
207
+ fail('descriptor.security.frameOrigins requires the web.embed capability.');
208
+ }
209
+ return Object.freeze({
210
+ connectOrigins:validateOrigins(
211
+ value.connectOrigins,
212
+ 'descriptor.security.connectOrigins',
213
+ {allowLoopbackHttp:true}
214
+ ),
215
+ frameOrigins,
216
+ mediaOrigins:validateOrigins(value.mediaOrigins,'descriptor.security.mediaOrigins')
217
+ });
218
+ }
219
+
220
+ function validateDocumentCatalog(value){
221
+ if(value===undefined)return undefined;
222
+ const label='descriptor.native.documentCatalog';
223
+ if(value.policy==='empty-unpublished'){
224
+ assertOnlyKeys(value,new Set(['policy','destination','manifest']),label);
225
+ const manifest=normalizeRelativePath(value.manifest,`${label}.manifest`);
226
+ if(manifest.includes('/')||path.posix.extname(manifest).toLowerCase()!=='.json'){
227
+ fail(`${label}.manifest must be a JSON filename.`);
228
+ }
229
+ return Object.freeze({
230
+ policy:value.policy,
231
+ destination:normalizeRelativePath(value.destination,`${label}.destination`),
232
+ manifest
233
+ });
234
+ }
235
+ assertOnlyKeys(value,new Set([
236
+ 'policy','release','destination','originals','manifest','expectedCount'
237
+ ]),label);
238
+ if(value.policy!=='public-only'||!Number.isInteger(value.expectedCount)
239
+ ||value.expectedCount<1||value.expectedCount>512){
240
+ fail(`${label} has unsupported policy or expectedCount.`);
241
+ }
242
+ const destination=normalizeRelativePath(value.destination,`${label}.destination`);
243
+ const originals=normalizeRelativePath(value.originals,`${label}.originals`);
244
+ const manifest=normalizeRelativePath(value.manifest,`${label}.manifest`);
245
+ if(manifest.includes('/')||path.posix.extname(manifest).toLowerCase()!=='.json'){
246
+ fail(`${label}.manifest must be a JSON filename.`);
247
+ }
248
+ if(destination===originals||destination.startsWith(`${originals}/`)
249
+ ||originals.startsWith(`${destination}/`)){
250
+ fail(`${label}.destination and originals must not overlap.`);
251
+ }
252
+ return Object.freeze({
253
+ policy:value.policy,
254
+ release:normalizeRelativePath(value.release,`${label}.release`),
255
+ destination,
256
+ originals,
257
+ manifest,
258
+ expectedCount:value.expectedCount
259
+ });
260
+ }
261
+
262
+ function validateNative(value,appId){
263
+ assertOnlyKeys(value,new Set(['type','icon','order','bundledApps','documentCatalog']),'descriptor.native');
264
+ if(!NATIVE_TYPES.has(value.type))fail('descriptor.native.type is unsupported.');
265
+ const icon=value.icon===null?null:normalizeRelativePath(value.icon,'descriptor.native.icon');
266
+ if(icon&&(!SAFE_ICON_EXTENSIONS.has(path.posix.extname(icon).toLowerCase())||icon.length>160)){
267
+ fail('descriptor.native.icon must identify a bounded safe raster image or icon file.');
268
+ }
269
+ if(!Number.isInteger(value.order)||value.order<0||value.order>10000){
270
+ fail('descriptor.native.order must be an integer from 0 through 10000.');
271
+ }
272
+ const bundledApps=uniqueSortedStrings(value.bundledApps,'descriptor.native.bundledApps',{
273
+ pattern:APP_ID_PATTERN,
274
+ maximum:16
275
+ });
276
+ if(bundledApps.includes(appId))fail('descriptor.native.bundledApps must not include the app itself.');
277
+ const documentCatalog=validateDocumentCatalog(value.documentCatalog);
278
+ return Object.freeze({
279
+ type:value.type,
280
+ icon,
281
+ order:value.order,
282
+ bundledApps,
283
+ ...(documentCatalog?{documentCatalog}:{})
284
+ });
285
+ }
286
+
287
+ function validateRequirements(value){
288
+ assertOnlyKeys(value,new Set(['arcaneProtocol','minimumCoreVersion','features']),'descriptor.requirements');
289
+ if(value.arcaneProtocol!==ARCANE_PROTOCOL)fail(`descriptor.requirements.arcaneProtocol must be ${ARCANE_PROTOCOL}.`);
290
+ parseSemver(value.minimumCoreVersion);
291
+ return Object.freeze({
292
+ arcaneProtocol:value.arcaneProtocol,
293
+ minimumCoreVersion:value.minimumCoreVersion,
294
+ features:uniqueSortedStrings(value.features,'descriptor.requirements.features',{
295
+ pattern:FEATURE_PATTERN,
296
+ maximum:128
297
+ })
298
+ });
299
+ }
300
+
301
+ export function validateAppDescriptor(value,{appId}={}){
302
+ assertOnlyKeys(value,new Set([
303
+ 'schemaVersion','id','displayName','description','version','publisher','package',
304
+ 'permissions','security','native','requirements','targets'
305
+ ]),'Arcane app descriptor');
306
+ if(value.schemaVersion!==APP_DESCRIPTOR_SCHEMA_VERSION){
307
+ fail(`Arcane app descriptor schemaVersion must be ${APP_DESCRIPTOR_SCHEMA_VERSION}.`);
308
+ }
309
+ if(!APP_ID_PATTERN.test(value.id)||(appId&&value.id!==appId)){
310
+ fail('Arcane app descriptor id must match its apps directory.');
311
+ }
312
+ parseSemver(value.version);
313
+ const targets=uniqueSortedStrings(value.targets,'descriptor.targets',{
314
+ pattern:APP_ID_PATTERN,
315
+ maximum:TARGET_IDS.length,
316
+ required:true
317
+ });
318
+ if(targets.some(target=>!TARGET_IDS.includes(target)))fail('descriptor.targets contains an unknown SDK target.');
319
+ const packageDescriptor=validatePackage(value.package,value.id);
320
+ const native=validateNative(value.native,value.id);
321
+ const displayName=safeText(value.displayName,'descriptor.displayName',{maximum:160});
322
+ const description=safeText(value.description,'descriptor.description');
323
+ if(targets.some(target=>target!=='browser')){
324
+ if(displayName.length>80||description.length>240){
325
+ fail('Native app displayName and description must fit the Arcane presentation limits.');
326
+ }
327
+ if(!native.icon){
328
+ fail('descriptor.native.icon is required when a native target is declared.');
329
+ }
330
+ if(!packageDescriptor.include.some(candidate=>
331
+ native.icon===candidate||native.icon.startsWith(`${candidate}/`)
332
+ )){
333
+ fail('descriptor.native.icon must be covered by descriptor.package.include.');
334
+ }
335
+ }
336
+ const permissions=validatePermissions(value.permissions);
337
+ return Object.freeze({
338
+ schemaVersion:APP_DESCRIPTOR_SCHEMA_VERSION,
339
+ id:value.id,
340
+ displayName,
341
+ description,
342
+ version:value.version,
343
+ publisher:validatePublisher(value.publisher),
344
+ package:packageDescriptor,
345
+ permissions,
346
+ security:validateSecurity(value.security,{appId:value.id,capabilities:permissions.capabilities}),
347
+ native,
348
+ requirements:validateRequirements(value.requirements),
349
+ targets
350
+ });
351
+ }
352
+
353
+ export function projectPackageManifest(descriptor){
354
+ const value=validateAppDescriptor(descriptor,{appId:descriptor?.id});
355
+ const projection={
356
+ schemaVersion:1,
357
+ id:value.id,
358
+ displayName:value.displayName,
359
+ version:value.version,
360
+ entry:value.package.entry,
361
+ strategy:value.package.strategy,
362
+ security:{
363
+ connectOrigins:[...value.security.connectOrigins],
364
+ frameOrigins:[...value.security.frameOrigins],
365
+ mediaOrigins:[...value.security.mediaOrigins]
366
+ },
367
+ ...(value.package.localAIModelPolicy?{localAIModelPolicy:value.package.localAIModelPolicy}:{}),
368
+ include:[...value.package.include],
369
+ exclude:[...value.package.exclude],
370
+ shared:[...value.package.shared],
371
+ ...(value.package.adapter?{adapter:value.package.adapter}:{})
372
+ };
373
+ const sharedPayloads=Object.fromEntries(
374
+ value.package.shared.map(id=>[id,Object.freeze([])])
375
+ );
376
+ validateAppPackageConfig(
377
+ projection,
378
+ value.id,
379
+ {sharedPayloads:Object.freeze(sharedPayloads)},
380
+ `${APP_DESCRIPTOR_NAME} projection`
381
+ );
382
+ return projection;
383
+ }
384
+
385
+ export function appDescriptorSha256(descriptor){
386
+ const value=validateAppDescriptor(descriptor,{appId:descriptor?.id});
387
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
388
+ }
389
+
390
+ export function projectNativeDescriptor(descriptor,{source}={}){
391
+ const value=validateAppDescriptor(descriptor,{appId:descriptor?.id});
392
+ if(!value.targets.some(target=>target!=='browser')){
393
+ fail('A browser-only descriptor cannot be projected as a native Arcane app.');
394
+ }
395
+ return {
396
+ displayName:value.displayName,
397
+ description:value.description,
398
+ icon:value.native.icon,
399
+ order:value.native.order,
400
+ type:value.native.type,
401
+ source:source??`apps/${value.id}`,
402
+ entry:value.package.entry,
403
+ capabilities:[...value.permissions.capabilities],
404
+ security:{
405
+ connectOrigins:[...value.security.connectOrigins],
406
+ frameOrigins:[...value.security.frameOrigins],
407
+ mediaOrigins:[...value.security.mediaOrigins]
408
+ },
409
+ ...(value.native.bundledApps.length?{bundledApps:[...value.native.bundledApps]}:{}),
410
+ ...(value.native.documentCatalog?{documentCatalog:value.native.documentCatalog}:{}),
411
+ include:[...value.package.include]
412
+ };
413
+ }
414
+
415
+ function synthesizedDescriptor(packageManifest,nativeDescriptor){
416
+ const native=nativeDescriptor??{};
417
+ if(packageManifest.security!==undefined&&native.security!==undefined
418
+ &&!isDeepStrictEqual(packageManifest.security,native.security)){
419
+ fail('Legacy package and native registry security policies do not match.');
420
+ }
421
+ const security=packageManifest.security??native.security??{};
422
+ return validateAppDescriptor({
423
+ schemaVersion:APP_DESCRIPTOR_SCHEMA_VERSION,
424
+ id:packageManifest.id,
425
+ displayName:packageManifest.displayName,
426
+ description:native.description??`${packageManifest.displayName} Arcane application.`,
427
+ version:packageManifest.version,
428
+ publisher:{id:'publisher-undeclared',name:'Publisher not declared'},
429
+ package:{
430
+ entry:packageManifest.entry,
431
+ strategy:packageManifest.strategy,
432
+ ...(packageManifest.localAIModelPolicy?{localAIModelPolicy:packageManifest.localAIModelPolicy}:{}),
433
+ include:[...packageManifest.include],
434
+ exclude:[...(packageManifest.exclude??[])],
435
+ shared:[...packageManifest.shared].sort(),
436
+ ...(packageManifest.adapter?{adapter:packageManifest.adapter}:{})
437
+ },
438
+ permissions:{
439
+ capabilities:[...(native.capabilities??[])].sort(),
440
+ methods:[]
441
+ },
442
+ security:{
443
+ connectOrigins:[...(security.connectOrigins??[])].sort(),
444
+ frameOrigins:[...(security.frameOrigins??[])].sort(),
445
+ mediaOrigins:[...(security.mediaOrigins??[])].sort()
446
+ },
447
+ native:{
448
+ type:native.type??'app',
449
+ icon:native.icon??null,
450
+ order:Number.isInteger(native.order)?native.order:100,
451
+ bundledApps:[...(native.bundledApps??[])].sort(),
452
+ ...(native.documentCatalog?{documentCatalog:native.documentCatalog}:{})
453
+ },
454
+ requirements:{
455
+ arcaneProtocol:ARCANE_PROTOCOL,
456
+ minimumCoreVersion:ARCANE_MACHINE_BUNDLE_VERSION,
457
+ features:[]
458
+ },
459
+ targets:nativeDescriptor!==undefined
460
+ ?['android-arm64','browser','linux-arm64','linux-x64','portable','windows-x64']
461
+ :['browser']
462
+ },{appId:packageManifest.id});
463
+ }
464
+
465
+ async function readJsonFile(filePath,label,{optional=false}={}){
466
+ try{
467
+ const info=await lstat(filePath);
468
+ if(info.isSymbolicLink()||!info.isFile())fail(`${label} must be a real file.`);
469
+ return JSON.parse(await readFile(filePath,'utf8'));
470
+ }catch(error){
471
+ if(optional&&error?.code==='ENOENT')return null;
472
+ if(error instanceof SyntaxError)fail(`${label} is not valid JSON: ${error.message}`);
473
+ throw error;
474
+ }
475
+ }
476
+
477
+ export async function loadAppDescriptor({workspaceRoot,appRoot,appId,packageManifest}){
478
+ const descriptorPath=path.join(appRoot,APP_DESCRIPTOR_NAME);
479
+ const authored=await readJsonFile(descriptorPath,`apps/${appId}/${APP_DESCRIPTOR_NAME}`,{optional:true});
480
+ if(authored){
481
+ const descriptor=validateAppDescriptor(authored,{appId});
482
+ const projection=projectPackageManifest(descriptor);
483
+ if(!isDeepStrictEqual(projection,packageManifest)){
484
+ fail(`${APP_DESCRIPTOR_NAME} does not project exactly to arcane-package.json.`);
485
+ }
486
+ return Object.freeze({descriptor,source:'authored',descriptorPath});
487
+ }
488
+
489
+ const registry=await readJsonFile(
490
+ path.join(workspaceRoot,REGISTRY_PATH),
491
+ 'Arcane native app registry',
492
+ {optional:true}
493
+ );
494
+ const nativeDescriptor=isObject(registry?.apps?.[appId])?registry.apps[appId]:undefined;
495
+ return Object.freeze({
496
+ descriptor:synthesizedDescriptor(packageManifest,nativeDescriptor),
497
+ source:nativeDescriptor?'legacy-registry':'legacy-package',
498
+ descriptorPath:null
499
+ });
500
+ }