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,255 @@
1
+ export const ARCANE_NETWORK_POLICY_SCHEMA_VERSION=1;
2
+ export const ARCANE_NETWORK_POLICY_URL=new URL('../security/arcane-network-policy.json',import.meta.url);
3
+
4
+ const EMPTY_POLICY=Object.freeze({
5
+ schemaVersion:ARCANE_NETWORK_POLICY_SCHEMA_VERSION,
6
+ generation:1,
7
+ domainRules:Object.freeze([]),
8
+ networkRules:Object.freeze([]),
9
+ });
10
+ const PROTOCOLS=new Set(['any','tcp','udp','icmp','icmpv6']);
11
+ const RULE_KEYS=new Set(['id','domain','reason','source']);
12
+ const NETWORK_RULE_KEYS=new Set(['id','cidr','protocol','localPorts','remotePorts','reason','source']);
13
+ const POLICY_KEYS=new Set(['schemaVersion','generation','domainRules','networkRules']);
14
+ const REASON_KEYS=new Set(['code','title','description']);
15
+ const SOURCE_KEYS=new Set(['id','label','reference']);
16
+ const PORT_KEYS=new Set(['from','to']);
17
+ const NORMALIZED_POLICIES=new WeakSet([EMPTY_POLICY]);
18
+ const DOMAIN_RULE_INDEXES=new WeakMap();
19
+ const NETWORK_RULE_INDEXES=new WeakMap();
20
+ const NETWORK_RULE_ORDERS=new WeakMap();
21
+ const NETWORK_MATCH_CONTEXT_KEYS=new Set(['protocol','localPort','remotePort']);
22
+ const NETWORK_MATCH_PROTOCOLS=new Set(['tcp','udp','icmp','icmpv6']);
23
+ let defaultPolicyPromise=null;
24
+ const DEFAULT_POLICY_LOAD_TIMEOUT_MS=5000;
25
+
26
+ function fail(message){const error=new TypeError(`ARCANE_NETWORK_POLICY_INVALID: ${message}`);error.code='ARCANE_NETWORK_POLICY_INVALID';throw error;}
27
+ function isRecord(value){return Boolean(value)&&typeof value==='object'&&!Array.isArray(value);}
28
+ function assertOnlyKeys(value,allowed,label){for(const key of Object.keys(value)){if(!allowed.has(key))fail(`${label} contains unsupported field ${key}.`);}}
29
+ function boundedText(value,label,maximum,{nullable=false}={}){
30
+ if(nullable&&value===null)return null;
31
+ if(typeof value!=='string'||value!==value.trim()||value.length<1||value.length>maximum||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value))fail(`${label} must be bounded plain text.`);
32
+ return value;
33
+ }
34
+ function identifier(value,label){const text=boundedText(value,label,80);if(!/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(text))fail(`${label} must be a lowercase identifier.`);return text;}
35
+ function normalizeReason(value,label){
36
+ if(!isRecord(value))fail(`${label} must be an object.`);assertOnlyKeys(value,REASON_KEYS,label);
37
+ return Object.freeze({code:identifier(value.code,`${label}.code`),title:boundedText(value.title,`${label}.title`,120),description:boundedText(value.description,`${label}.description`,500)});
38
+ }
39
+ function normalizeSource(value,label){
40
+ if(!isRecord(value))fail(`${label} must be an object.`);assertOnlyKeys(value,SOURCE_KEYS,label);
41
+ return Object.freeze({id:identifier(value.id,`${label}.id`),label:boundedText(value.label,`${label}.label`,120),reference:value.reference===undefined||value.reference===null?null:boundedText(value.reference,`${label}.reference`,500)});
42
+ }
43
+ function isIpLiteral(value){return /^\d+(?:\.\d+){3}$/.test(value)||value.includes(':');}
44
+
45
+ export function canonicalNetworkHostname(value){
46
+ if(typeof value!=='string')return null;
47
+ const raw=value.trim(),candidate=raw.endsWith('.')?raw.slice(0,-1):raw;
48
+ if(!candidate||candidate.endsWith('.')||candidate.includes('/')||candidate.includes('@')||candidate.includes(':'))return null;
49
+ try{
50
+ const parsed=new URL(`http://${candidate}/`),hostname=parsed.hostname.toLowerCase();
51
+ if(parsed.username||parsed.password||parsed.port||isIpLiteral(hostname)||hostname.length>253||!/^[a-z0-9.-]+$/.test(hostname))return null;
52
+ const labels=hostname.split('.');
53
+ if(labels.length<2||labels.some(label=>!label||label.length>63||!/[a-z0-9]/.test(label[0])||!/[a-z0-9]/.test(label.at(-1))||!/^[a-z0-9-]+$/.test(label)))return null;
54
+ return hostname;
55
+ }catch{return null;}
56
+ }
57
+
58
+ function normalizeDomainRule(value,index,ids){
59
+ const label=`domainRules[${index}]`;
60
+ if(!isRecord(value))fail(`${label} must be an object.`);assertOnlyKeys(value,RULE_KEYS,label);
61
+ const id=identifier(value.id,`${label}.id`);if(ids.has(id))fail(`rule id ${id} is duplicated.`);ids.add(id);
62
+ const domain=canonicalNetworkHostname(value.domain);if(domain!==value.domain)fail(`${label}.domain must be a canonical lowercase ASCII hostname.`);
63
+ return Object.freeze({id,domain,reason:normalizeReason(value.reason,`${label}.reason`),source:normalizeSource(value.source,`${label}.source`)});
64
+ }
65
+
66
+ function parseIpv4(value){
67
+ const parts=value.split('.');if(parts.length!==4)return null;
68
+ const octets=parts.map(part=>/^(?:0|[1-9]\d{0,2})$/.test(part)?Number(part):NaN);
69
+ if(octets.some(part=>!Number.isInteger(part)||part<0||part>255))return null;
70
+ return {numeric:octets.reduce((total,part)=>((total<<8)>>>0)+part,0)>>>0,text:octets.join('.')};
71
+ }
72
+ function parseIpv6(value){
73
+ if(!/^[0-9a-f:.]+$/.test(value)||value!==value.toLowerCase()||value.split('::').length>2)return null;
74
+ const convertIpv4Tail=parts=>{
75
+ if(!parts.length||!parts.at(-1).includes('.'))return parts;
76
+ const ipv4=parseIpv4(parts.at(-1));if(!ipv4)return null;
77
+ return [...parts.slice(0,-1),((ipv4.numeric>>>16)&0xffff).toString(16),(ipv4.numeric&0xffff).toString(16)];
78
+ };
79
+ const halves=value.split('::');let left=convertIpv4Tail(halves[0]?halves[0].split(':'):[]),right=convertIpv4Tail(halves[1]?halves[1].split(':'):[]);
80
+ if(!left||!right)return null;
81
+ const valid=part=>/^[0-9a-f]{1,4}$/.test(part);
82
+ if(left.some(part=>!valid(part))||right.some(part=>!valid(part)))return null;
83
+ const missing=8-left.length-right.length;
84
+ if((halves.length===1&&missing!==0)||(halves.length===2&&missing<1))return null;
85
+ const groups=[...left,...Array(missing).fill('0'),...right].map(part=>Number.parseInt(part,16));
86
+ if(groups.length!==8)return null;
87
+ let numeric=0n;for(const group of groups)numeric=(numeric<<16n)|BigInt(group);
88
+ return {numeric,groups};
89
+ }
90
+ function formatIpv6(numeric){
91
+ const groups=[];for(let shift=112n;shift>=0n;shift-=16n)groups.push(((numeric>>shift)&0xffffn).toString(16));
92
+ return new URL(`http://[${groups.join(':')}]/`).hostname.slice(1,-1);
93
+ }
94
+ function normalizeCidr(value,label){
95
+ if(typeof value!=='string'||value!==value.trim()||value.split('/').length!==2)fail(`${label} must be a canonical IPv4 or IPv6 CIDR.`);
96
+ const [address,prefixText]=value.split('/');if(!/^(?:0|[1-9]\d{0,2})$/.test(prefixText))fail(`${label} has an invalid prefix.`);
97
+ const prefix=Number(prefixText),ipv4=parseIpv4(address);
98
+ if(ipv4){if(prefix>32)fail(`${label} has an invalid IPv4 prefix.`);const mask=prefix===0?0:(0xffffffff<<(32-prefix))>>>0;const network=(ipv4.numeric&mask)>>>0;const normalized=`${[(network>>>24)&255,(network>>>16)&255,(network>>>8)&255,network&255].join('.')}/${prefix}`;if(normalized!==value)fail(`${label} must use its canonical network address.`);return normalized;}
99
+ const ipv6=parseIpv6(address);if(!ipv6||prefix>128)fail(`${label} must be a canonical IPv4 or IPv6 CIDR.`);
100
+ const mask=prefix===0?0n:((1n<<BigInt(prefix))-1n)<<BigInt(128-prefix),network=ipv6.numeric&mask;
101
+ const normalized=`${formatIpv6(network)}/${prefix}`;if(normalized!==value)fail(`${label} must use its canonical network address.`);return normalized;
102
+ }
103
+ function normalizePortRanges(value,label){
104
+ if(value===undefined)return Object.freeze([]);
105
+ if(!Array.isArray(value)||value.length>64)fail(`${label} must be a bounded array.`);
106
+ let previous=0;
107
+ return Object.freeze(value.map((range,index)=>{
108
+ const itemLabel=`${label}[${index}]`;if(!isRecord(range))fail(`${itemLabel} must be an object.`);assertOnlyKeys(range,PORT_KEYS,itemLabel);
109
+ const from=range.from,to=range.to;if(!Number.isInteger(from)||!Number.isInteger(to)||from<1||to>65535||from>to||from<=previous)fail(`${itemLabel} must be a sorted, non-overlapping port range.`);previous=to;
110
+ return Object.freeze({from,to});
111
+ }));
112
+ }
113
+ function normalizeNetworkRule(value,index,ids){
114
+ const label=`networkRules[${index}]`;if(!isRecord(value))fail(`${label} must be an object.`);assertOnlyKeys(value,NETWORK_RULE_KEYS,label);
115
+ const id=identifier(value.id,`${label}.id`);if(ids.has(id))fail(`rule id ${id} is duplicated.`);ids.add(id);
116
+ if(!PROTOCOLS.has(value.protocol))fail(`${label}.protocol is invalid.`);
117
+ const localPorts=normalizePortRanges(value.localPorts,`${label}.localPorts`),remotePorts=normalizePortRanges(value.remotePorts,`${label}.remotePorts`);
118
+ if(value.protocol!=='tcp'&&value.protocol!=='udp'&&(localPorts.length||remotePorts.length))fail(`${label} may select ports only for TCP or UDP.`);
119
+ const cidr=normalizeCidr(value.cidr,`${label}.cidr`),isIpv6=cidr.includes(':');
120
+ if(value.protocol==='icmpv6'&&!isIpv6)fail(`${label}.protocol icmpv6 requires an IPv6 CIDR.`);
121
+ if(value.protocol==='icmp'&&isIpv6)fail(`${label}.protocol icmp requires an IPv4 CIDR.`);
122
+ return Object.freeze({id,cidr,protocol:value.protocol,localPorts,remotePorts,reason:normalizeReason(value.reason,`${label}.reason`),source:normalizeSource(value.source,`${label}.source`)});
123
+ }
124
+
125
+ function ipv4Network(numeric,prefix){return prefix===0?0:(numeric&((0xffffffff<<(32-prefix))>>>0))>>>0;}
126
+ function ipv6Network(numeric,prefix){return prefix===0?0n:numeric&(((1n<<BigInt(prefix))-1n)<<BigInt(128-prefix));}
127
+ function parsedNetworkRule(rule){
128
+ const [address,prefixText]=rule.cidr.split('/'),prefix=Number(prefixText),ipv4=parseIpv4(address);
129
+ if(ipv4)return {family:4,numeric:ipv4.numeric,prefix};
130
+ return {family:6,numeric:parseIpv6(address).numeric,prefix};
131
+ }
132
+ function buildDomainRuleIndex(domainRules){
133
+ const bySuffix=new Map();
134
+ for(const rule of domainRules)if(!bySuffix.has(rule.domain))bySuffix.set(rule.domain,rule);
135
+ return bySuffix;
136
+ }
137
+ function buildNetworkFamilyIndex(){return {byPrefix:new Map(),prefixes:new Set()};}
138
+ function finishNetworkFamilyIndex(index){
139
+ for(const networks of index.byPrefix.values())for(const [numeric,rules] of networks)networks.set(numeric,Object.freeze(rules));
140
+ return Object.freeze({byPrefix:index.byPrefix,prefixes:Object.freeze([...index.prefixes].sort((left,right)=>right-left))});
141
+ }
142
+ function buildNetworkRuleIndex(networkRules){
143
+ const ipv4=buildNetworkFamilyIndex(),ipv6=buildNetworkFamilyIndex();
144
+ for(const rule of networkRules){
145
+ const parsed=parsedNetworkRule(rule),family=parsed.family===4?ipv4:ipv6;
146
+ family.prefixes.add(parsed.prefix);
147
+ let networks=family.byPrefix.get(parsed.prefix);if(!networks){networks=new Map();family.byPrefix.set(parsed.prefix,networks);}
148
+ let rules=networks.get(parsed.numeric);if(!rules){rules=[];networks.set(parsed.numeric,rules);}rules.push(rule);
149
+ }
150
+ return Object.freeze({ipv4:finishNetworkFamilyIndex(ipv4),ipv6:finishNetworkFamilyIndex(ipv6)});
151
+ }
152
+ function indexNormalizedPolicy(policy){
153
+ DOMAIN_RULE_INDEXES.set(policy,buildDomainRuleIndex(policy.domainRules));
154
+ NETWORK_RULE_INDEXES.set(policy,buildNetworkRuleIndex(policy.networkRules));
155
+ NETWORK_RULE_ORDERS.set(policy,new Map(policy.networkRules.map((rule,index)=>[rule,index])));
156
+ }
157
+
158
+ indexNormalizedPolicy(EMPTY_POLICY);
159
+
160
+ export function validateArcaneNetworkPolicy(value){
161
+ if(!isRecord(value))fail('policy must be an object.');assertOnlyKeys(value,POLICY_KEYS,'policy');
162
+ if(value.schemaVersion!==ARCANE_NETWORK_POLICY_SCHEMA_VERSION)fail('schemaVersion is unsupported.');
163
+ if(!Number.isSafeInteger(value.generation)||value.generation<1)fail('generation must be a positive safe integer.');
164
+ if(!Array.isArray(value.domainRules)||!Array.isArray(value.networkRules)||value.domainRules.length>50_000||value.networkRules.length>50_000)fail('rule collections must be bounded arrays.');
165
+ const ids=new Set(),domainRules=value.domainRules.map((rule,index)=>normalizeDomainRule(rule,index,ids)),networkRules=value.networkRules.map((rule,index)=>normalizeNetworkRule(rule,index,ids));
166
+ const normalized=Object.freeze({schemaVersion:value.schemaVersion,generation:value.generation,domainRules:Object.freeze(domainRules),networkRules:Object.freeze(networkRules)});
167
+ NORMALIZED_POLICIES.add(normalized);
168
+ indexNormalizedPolicy(normalized);
169
+ return normalized;
170
+ }
171
+
172
+ function hostnameFromInput(value){
173
+ if(typeof value!=='string')return null;
174
+ try{if(/^[a-z][a-z0-9+.-]*:\/\//i.test(value))return canonicalNetworkHostname(new URL(value).hostname);}
175
+ catch{return null;}
176
+ return canonicalNetworkHostname(value);
177
+ }
178
+
179
+ export function findDeniedDomainRule(policy,hostnameOrUrl){
180
+ const normalizedPolicy=NORMALIZED_POLICIES.has(policy)?policy:validateArcaneNetworkPolicy(policy),hostname=hostnameFromInput(hostnameOrUrl);
181
+ if(!hostname)return null;
182
+ const bySuffix=DOMAIN_RULE_INDEXES.get(normalizedPolicy);
183
+ let suffix=hostname;
184
+ while(suffix){const rule=bySuffix.get(suffix);if(rule)return rule;const separator=suffix.indexOf('.');if(separator<0)return null;suffix=suffix.slice(separator+1);}
185
+ return null;
186
+ }
187
+
188
+ function networkQueryFail(message){const error=new TypeError(`ARCANE_NETWORK_POLICY_QUERY_INVALID: ${message}`);error.code='ARCANE_NETWORK_POLICY_QUERY_INVALID';throw error;}
189
+ function normalizeNetworkMatchContext(value){
190
+ if(value===undefined)return {};
191
+ if(!isRecord(value))networkQueryFail('context must be an object when supplied.');
192
+ for(const key of Object.keys(value))if(!NETWORK_MATCH_CONTEXT_KEYS.has(key))networkQueryFail(`context contains unsupported field ${key}.`);
193
+ const protocol=value.protocol;
194
+ if(protocol!==undefined&&!NETWORK_MATCH_PROTOCOLS.has(protocol))networkQueryFail('context.protocol must be tcp, udp, icmp, or icmpv6.');
195
+ for(const key of ['localPort','remotePort'])if(value[key]!==undefined&&(!Number.isInteger(value[key])||value[key]<1||value[key]>65535))networkQueryFail(`context.${key} must be an integer from 1 through 65535.`);
196
+ if((value.localPort!==undefined||value.remotePort!==undefined)&&protocol!==undefined&&protocol!=='tcp'&&protocol!=='udp')networkQueryFail('port match context requires protocol tcp or udp when protocol is supplied.');
197
+ return {protocol,localPort:value.localPort,remotePort:value.remotePort};
198
+ }
199
+ function parsedIpLiteral(value){
200
+ if(typeof value!=='string')return null;
201
+ const trimmed=value.trim(),candidate=trimmed.startsWith('[')&&trimmed.endsWith(']')?trimmed.slice(1,-1):trimmed,ipv4=parseIpv4(candidate);if(ipv4)return {family:4,numeric:ipv4.numeric};
202
+ const ipv6=parseIpv6(candidate.toLowerCase());return ipv6?{family:6,numeric:ipv6.numeric}:null;
203
+ }
204
+ function portRangesMatch(ranges,port){return port===undefined||ranges.length===0||ranges.some(range=>port>=range.from&&port<=range.to);}
205
+ function networkRuleMatchesContext(rule,context,family){
206
+ if(context.protocol!==undefined){
207
+ if((context.protocol==='icmp'&&family!==4)||(context.protocol==='icmpv6'&&family!==6))return false;
208
+ if(rule.protocol!=='any'&&rule.protocol!==context.protocol)return false;
209
+ }
210
+ return portRangesMatch(rule.localPorts,context.localPort)&&portRangesMatch(rule.remotePorts,context.remotePort);
211
+ }
212
+ function findIndexedNetworkMatch(family,numeric,context,addressFamily){
213
+ for(const prefix of family.prefixes){
214
+ const network=addressFamily===4?ipv4Network(numeric,prefix):ipv6Network(numeric,prefix),rules=family.byPrefix.get(prefix).get(network);
215
+ if(rules)for(const rule of rules)if(networkRuleMatchesContext(rule,context,addressFamily))return {rule,prefix};
216
+ }
217
+ return null;
218
+ }
219
+ function mappedIpv4Numeric(address){return address.family===6&&address.numeric>>32n===0xffffn?Number(address.numeric&0xffffffffn):null;}
220
+
221
+ export function findDeniedNetworkRule(policy,ipLiteral,context){
222
+ const normalizedPolicy=NORMALIZED_POLICIES.has(policy)?policy:validateArcaneNetworkPolicy(policy),normalizedContext=normalizeNetworkMatchContext(context),address=parsedIpLiteral(ipLiteral);
223
+ if(!address)return null;
224
+ const index=NETWORK_RULE_INDEXES.get(normalizedPolicy);
225
+ if(address.family===4)return findIndexedNetworkMatch(index.ipv4,address.numeric,normalizedContext,4)?.rule??null;
226
+ const ipv6Match=findIndexedNetworkMatch(index.ipv6,address.numeric,normalizedContext,6),mapped=mappedIpv4Numeric(address);
227
+ if(mapped===null)return ipv6Match?.rule??null;
228
+ const ipv4Match=findIndexedNetworkMatch(index.ipv4,mapped,normalizedContext,4);
229
+ if(!ipv4Match)return ipv6Match?.rule??null;
230
+ if(!ipv6Match)return ipv4Match.rule;
231
+ const ipv4Specificity=96+ipv4Match.prefix;
232
+ if(ipv4Specificity!==ipv6Match.prefix)return ipv4Specificity>ipv6Match.prefix?ipv4Match.rule:ipv6Match.rule;
233
+ const order=NETWORK_RULE_ORDERS.get(normalizedPolicy);
234
+ return order.get(ipv4Match.rule)<order.get(ipv6Match.rule)?ipv4Match.rule:ipv6Match.rule;
235
+ }
236
+
237
+ export function invalidateArcaneNetworkPolicyCache(){defaultPolicyPromise=null;}
238
+
239
+ export async function loadArcaneNetworkPolicy({url=ARCANE_NETWORK_POLICY_URL,fetchImpl=globalThis.fetch,refresh=false,timeoutMs=DEFAULT_POLICY_LOAD_TIMEOUT_MS}={}){
240
+ const isDefault=String(url)===String(ARCANE_NETWORK_POLICY_URL)&&fetchImpl===globalThis.fetch;
241
+ if(typeof refresh!=='boolean')networkQueryFail('refresh must be a boolean.');
242
+ if(!Number.isSafeInteger(timeoutMs)||timeoutMs<1||timeoutMs>60000)networkQueryFail('timeoutMs must be an integer from 1 through 60000.');
243
+ if(isDefault&&!refresh&&defaultPolicyPromise)return defaultPolicyPromise;
244
+ if(typeof fetchImpl!=='function')fail('a fetch implementation is required to load policy.');
245
+ const load=(async()=>{
246
+ const controller=new AbortController();let timeoutId;
247
+ const operation=(async()=>{const response=await fetchImpl(url,{cache:'no-store',credentials:'same-origin',signal:controller.signal});if(!response?.ok)throw new Error(`ARCANE_NETWORK_POLICY_LOAD_FAILED: ${response?.status||'unavailable'}`);return validateArcaneNetworkPolicy(await response.json());})();
248
+ const timeout=new Promise((resolve,reject)=>{timeoutId=setTimeout(()=>{controller.abort();reject(new Error('ARCANE_NETWORK_POLICY_LOAD_TIMEOUT'));},timeoutMs);});
249
+ try{return await Promise.race([operation,timeout]);}finally{clearTimeout(timeoutId);}
250
+ })();
251
+ if(isDefault){defaultPolicyPromise=load;void load.catch(()=>{if(defaultPolicyPromise===load)defaultPolicyPromise=null;});}
252
+ return load;
253
+ }
254
+
255
+ export function emptyArcaneNetworkPolicy(){return EMPTY_POLICY;}
@@ -0,0 +1,161 @@
1
+ const DEFAULT_TIMEOUT_MS=10000;
2
+ const MAX_TIMEOUT_MS=300000;
3
+
4
+ function coded(error,code){
5
+ if(!error.code) error.code=code;
6
+ return error;
7
+ }
8
+
9
+ function invalid(message,ErrorType=TypeError){
10
+ return coded(new ErrorType(message),'ASYNC_BOUNDARY_INVALID_OPTIONS');
11
+ }
12
+
13
+ function isPlainRecord(value){
14
+ if(!value||typeof value!=='object'||Array.isArray(value)) return false;
15
+ const prototype=Object.getPrototypeOf(value);
16
+ return prototype===Object.prototype||prototype===null;
17
+ }
18
+
19
+ function isAbortSignal(value){
20
+ return Boolean(value)
21
+ &&typeof value==='object'
22
+ &&typeof value.aborted==='boolean'
23
+ &&typeof value.addEventListener==='function'
24
+ &&typeof value.removeEventListener==='function';
25
+ }
26
+
27
+ function normalizeOptions(value){
28
+ if(!isPlainRecord(value)) throw invalid('Async boundary options must be a plain object.');
29
+ const unknown=Object.keys(value).find(key=>!new Set(['signal','timeoutMs']).has(key));
30
+ if(unknown) throw invalid(`Async boundary options contain an unsupported field: ${unknown}.`);
31
+
32
+ const timeoutMs=value.timeoutMs??DEFAULT_TIMEOUT_MS;
33
+ if(!Number.isSafeInteger(timeoutMs)||timeoutMs<1||timeoutMs>MAX_TIMEOUT_MS){
34
+ throw invalid(`timeoutMs must be an integer from 1 through ${MAX_TIMEOUT_MS}.`,RangeError);
35
+ }
36
+
37
+ const signal=value.signal??null;
38
+ if(signal!==null&&!isAbortSignal(signal)){
39
+ throw invalid('signal must be an AbortSignal.');
40
+ }
41
+
42
+ return Object.freeze({signal,timeoutMs});
43
+ }
44
+
45
+ function normalizeOperation(value){
46
+ if(typeof value==='function'){
47
+ return Object.freeze({kind:'function',value});
48
+ }
49
+ if(!value||(typeof value!=='object'&&typeof value!=='function')){
50
+ throw coded(new TypeError('The asynchronous operation must be a promise or function.'),'ASYNC_BOUNDARY_INVALID_OPERATION');
51
+ }
52
+ if(typeof value.then!=='function'){
53
+ throw coded(new TypeError('The asynchronous operation must be a promise or function.'),'ASYNC_BOUNDARY_INVALID_OPERATION');
54
+ }
55
+ return Object.freeze({kind:'promise',value});
56
+ }
57
+
58
+ /**
59
+ * Identifies expiration of an AsyncBoundary timeout.
60
+ */
61
+ export class AsyncBoundaryTimeoutError extends Error{
62
+ constructor(timeoutMs){
63
+ super(`The asynchronous operation exceeded its ${timeoutMs} ms timeout.`);
64
+ this.name='AsyncBoundaryTimeoutError';
65
+ this.code='ASYNC_BOUNDARY_TIMEOUT';
66
+ this.timeoutMs=timeoutMs;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Identifies cancellation received from an external AbortSignal.
72
+ */
73
+ export class AsyncBoundaryAbortError extends Error{
74
+ constructor(reason){
75
+ super('The asynchronous operation was aborted.');
76
+ this.name='AbortError';
77
+ this.code='ASYNC_BOUNDARY_ABORTED';
78
+ if(reason!==undefined) this.cause=reason;
79
+ }
80
+ }
81
+
82
+ export const asyncBoundaryDefaults=Object.freeze({
83
+ maxTimeoutMs:MAX_TIMEOUT_MS,
84
+ timeoutMs:DEFAULT_TIMEOUT_MS,
85
+ });
86
+
87
+ /**
88
+ * Runs one promise or function behind a finite timeout and optional external
89
+ * AbortSignal. Function operations receive a child AbortSignal and must stop
90
+ * work cooperatively when it is aborted. The boundary cannot preempt
91
+ * synchronous work or stop an already-created promise by itself.
92
+ *
93
+ * @template T
94
+ * @param {PromiseLike<T>|((signal:AbortSignal)=>T|PromiseLike<T>)} operation
95
+ * @param {{timeoutMs?:number,signal?:AbortSignal|null}} [options]
96
+ * @returns {Promise<T>}
97
+ */
98
+ export function runAsyncBoundary(operation,options={}){
99
+ let settings;
100
+ let descriptor;
101
+ try{
102
+ settings=normalizeOptions(options);
103
+ descriptor=normalizeOperation(operation);
104
+ if(typeof globalThis.AbortController!=='function'){
105
+ throw coded(new Error('AbortController is unavailable in this environment.'),'ASYNC_BOUNDARY_UNAVAILABLE');
106
+ }
107
+ }catch(error){
108
+ return Promise.reject(error);
109
+ }
110
+
111
+ const controller=new AbortController();
112
+ const suppliedPromise=descriptor.kind==='promise'
113
+ ?Promise.resolve(descriptor.value)
114
+ :null;
115
+
116
+ return new Promise((resolve,reject)=>{
117
+ let settled=false;
118
+ let timer=null;
119
+
120
+ const cleanup=()=>{
121
+ if(timer!==null){
122
+ clearTimeout(timer);
123
+ timer=null;
124
+ }
125
+ settings.signal?.removeEventListener('abort',onExternalAbort);
126
+ };
127
+ const finish=(handler,value)=>{
128
+ if(settled) return;
129
+ settled=true;
130
+ cleanup();
131
+ handler(value);
132
+ };
133
+ const abortBoundary=(error)=>{
134
+ if(settled) return;
135
+ controller.abort(error);
136
+ finish(reject,error);
137
+ };
138
+ const onExternalAbort=()=>{
139
+ abortBoundary(new AsyncBoundaryAbortError(settings.signal?.reason));
140
+ };
141
+
142
+ settings.signal?.addEventListener('abort',onExternalAbort,{once:true});
143
+ if(settings.signal?.aborted){
144
+ suppliedPromise?.catch(()=>{});
145
+ onExternalAbort();
146
+ return;
147
+ }
148
+
149
+ timer=setTimeout(()=>{
150
+ abortBoundary(new AsyncBoundaryTimeoutError(settings.timeoutMs));
151
+ },settings.timeoutMs);
152
+
153
+ const outcome=suppliedPromise??Promise.resolve().then(()=>descriptor.value(controller.signal));
154
+ outcome.then(
155
+ value=>finish(resolve,value),
156
+ error=>finish(reject,error)
157
+ );
158
+ });
159
+ }
160
+
161
+ export default runAsyncBoundary;