arcane-os 0.2.3 → 0.3.1

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 (118) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +8 -8
  3. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
  4. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
  5. package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
  6. package/browser-runtime/ai/browser-speech-artifacts.mjs +3242 -391
  7. package/browser-runtime/ai/browser-speech-providers.mjs +1209 -157
  8. package/browser-runtime/ai/browser-speech.mjs +2 -0
  9. package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
  10. package/browser-runtime/ai/model-controller.mjs +285 -95
  11. package/browser-runtime/ai/speech-worker-client.mjs +249 -32
  12. package/browser-runtime/ai/speech-worker-runtime.mjs +2322 -167
  13. package/browser-runtime/event-manager.mjs +1097 -1
  14. package/docs/architecture.md +2 -2
  15. package/docs/event-manager.md +155 -27
  16. package/docs/reference/README.md +18 -20
  17. package/docs/reference/ai/browser-speech-package-authority.json +835 -0
  18. package/docs/reference/ai/browser-speech.md +1205 -246
  19. package/docs/reference/ai/browser-wasm.md +18 -7
  20. package/docs/reference/availability-and-normalization.md +2 -2
  21. package/docs/reference/behavioral-testing.md +28 -5
  22. package/docs/reference/cli.md +115 -7
  23. package/docs/reference/core/arcane-ai-contracts.md +1 -1
  24. package/docs/reference/event-manager.md +577 -32
  25. package/docs/reference/inventory/package-api.json +478 -2
  26. package/docs/reference/inventory/runtime-components.json +86 -37
  27. package/docs/reference/inventory/runtime-modules.json +126 -49
  28. package/docs/reference/mail.md +316 -0
  29. package/docs/reference/protocols.md +185 -33
  30. package/docs/reference/runtime-components.md +192 -80
  31. package/docs/reference/runtime-modules.md +589 -70
  32. package/docs/reference/sdk-api.md +1017 -24
  33. package/package.json +5 -4
  34. package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
  35. package/runtime/arcane/components/app-bar.html +34 -13
  36. package/runtime/arcane/components/assistant-panel.html +110 -57
  37. package/runtime/arcane/components/calculator.html +7 -4
  38. package/runtime/arcane/components/chart.html +58 -17
  39. package/runtime/arcane/components/chat.html +606 -136
  40. package/runtime/arcane/components/conversation-view.html +13 -6
  41. package/runtime/arcane/components/dashboard-config.html +96 -59
  42. package/runtime/arcane/components/data-maintenance.html +69 -14
  43. package/runtime/arcane/components/data-view.html +53 -7
  44. package/runtime/arcane/components/directory-picker.html +118 -32
  45. package/runtime/arcane/components/document-inspector.html +47 -10
  46. package/runtime/arcane/components/file-drop.html +81 -35
  47. package/runtime/arcane/components/file-inspector.html +72 -22
  48. package/runtime/arcane/components/file-manager.html +374 -79
  49. package/runtime/arcane/components/integration-settings.html +12 -5
  50. package/runtime/arcane/components/local-ai-status.html +48 -19
  51. package/runtime/arcane/components/markdown-document.html +161 -68
  52. package/runtime/arcane/components/markdown-editor.html +110 -33
  53. package/runtime/arcane/components/media-embed.html +8 -5
  54. package/runtime/arcane/components/modal.html +15 -5
  55. package/runtime/arcane/components/output-panel.html +28 -23
  56. package/runtime/arcane/components/preferences-form.html +22 -4
  57. package/runtime/arcane/components/record-timeline.html +18 -2
  58. package/runtime/arcane/components/relationship-board.html +23 -3
  59. package/runtime/arcane/components/screen-capture.html +10 -4
  60. package/runtime/arcane/components/source-code-viewer.html +76 -9
  61. package/runtime/arcane/components/source-explanation.html +23 -3
  62. package/runtime/arcane/components/speech.html +453 -131
  63. package/runtime/arcane/components/summary-strip.html +22 -11
  64. package/runtime/arcane/components/table.html +39 -21
  65. package/runtime/arcane/components/task-progress.html +79 -21
  66. package/runtime/arcane/components/terminal-workspace.html +7 -4
  67. package/runtime/arcane/components/theme-editor.html +7 -3
  68. package/runtime/arcane/components/unified-inbox.html +9 -4
  69. package/runtime/arcane/components/voice-transcription.html +244 -72
  70. package/runtime/arcane/components/weather-widget.html +5 -3
  71. package/runtime/arcane/components/web-navigator.html +48 -8
  72. package/runtime/arcane/entities/Chat.js +1 -1
  73. package/runtime/arcane/entities/User.js +110 -23
  74. package/runtime/arcane/modules/AI.js +2117 -130
  75. package/runtime/arcane/modules/AIProviderRuntime.js +756 -13
  76. package/runtime/arcane/modules/AIRuntimeState.js +109 -52
  77. package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
  78. package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
  79. package/runtime/arcane/modules/CalculatorEngine.js +63 -3
  80. package/runtime/arcane/modules/CommunicationAppController.js +588 -28
  81. package/runtime/arcane/modules/CommunicationHub.js +590 -10
  82. package/runtime/arcane/modules/ComponentContracts.js +235 -37
  83. package/runtime/arcane/modules/ConversationTimebox.js +152 -33
  84. package/runtime/arcane/modules/DBLS.js +40 -7
  85. package/runtime/arcane/modules/DBOPFS.js +35 -11
  86. package/runtime/arcane/modules/DataMaintenance.js +12 -2
  87. package/runtime/arcane/modules/Errors.js +65 -7
  88. package/runtime/arcane/modules/HTMLImport.js +198 -14
  89. package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
  90. package/runtime/arcane/modules/Mail.js +738 -115
  91. package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
  92. package/runtime/arcane/modules/MailTransport.mjs +197 -39
  93. package/runtime/arcane/modules/Ollama.js +36 -1
  94. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
  95. package/runtime/arcane/modules/PreferenceStore.js +367 -33
  96. package/runtime/arcane/modules/RecordReviewStore.js +322 -23
  97. package/runtime/arcane/modules/ScreenCapture.js +1397 -15
  98. package/runtime/arcane/modules/SpeechPlayback.js +438 -41
  99. package/runtime/arcane/modules/TerminalClient.js +277 -12
  100. package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
  101. package/runtime/arcane/modules/ThemeManager.js +39 -7
  102. package/runtime/arcane/modules/TimeGuard.js +131 -20
  103. package/runtime/arcane/modules/WaitForComponent.js +386 -33
  104. package/schemas/arcane-lock.schema.json +2 -2
  105. package/src/cli/main.mjs +435 -9
  106. package/src/event-manager.mjs +1097 -1
  107. package/src/import-map.mjs +21 -3
  108. package/src/index.mjs +13 -0
  109. package/src/installed-sdk-runtime.mjs +130 -0
  110. package/src/mail-api.mjs +22 -0
  111. package/src/mail-credentials.mjs +667 -0
  112. package/src/mail-server.mjs +1769 -0
  113. package/src/mail.mjs +261 -0
  114. package/src/sdk-browser-runtime.mjs +85 -41
  115. package/src/testing-loader.mjs +7 -0
  116. package/src/toolchain.mjs +3 -0
  117. package/src/workspace-runtime.mjs +804 -22
  118. package/src/workspace.mjs +1 -1
@@ -1,97 +1,228 @@
1
1
  # Browser speech providers
2
2
 
3
- `arcane-os/ai/browser-speech` is the shipped browser-only provider package for
4
- caller-supplied Whisper speech-to-text and Kokoro text-to-speech runtimes. It
5
- implements the same provider-neutral role contract used by
6
- `AIProviderRuntime.js`; it does not ship model weights, runtime adapter bytes,
7
- voices, download URLs, a catalog, or a cloud fallback.
3
+ `arcane-os/ai/browser-speech` is the browser-only, event-neutral SDK boundary
4
+ for caller-supplied Whisper speech-to-text and Kokoro text-to-speech runtimes.
5
+ It supplies immutable artifact-graph validation, DBOPFS admission, role Workers,
6
+ provider/2 adapters, audio normalization, cancellation, and cleanup. It does
7
+ not ship or select runtime modules, model weights, voice bytes, a default
8
+ catalog, credentials, a native provider, or a cloud fallback.
9
+ The package contains no speech runtime, model, or voice payload and
10
+ never downloads one before explicit `load()`.
8
11
 
9
- Use this page when an application deliberately owns browser-local speech model
10
- and runtime selection. For ordinary application AI lifecycle and status, start
11
- with the [normalized AI guide](../README.md#normalized-ai) and
12
- [`AIProviderRuntime.js`](../runtime-modules.md#aiproviderruntimejs).
12
+ Use this entrypoint when an application deliberately owns every browser-local
13
+ speech choice. Higher-level selection, startup policy, shared state, and event
14
+ projection belong to the application runtime; this package exposes only
15
+ promises, `AbortSignal`, and the caller's progress callback.
13
16
 
14
17
  ## Availability
15
18
 
16
19
  | Host | Availability | Notes |
17
20
  | --- | --- | --- |
18
- | Browser | Shipped | Requires DBOPFS/OPFS, Web Locks, Workers, Fetch or an injected fetch function, Blob/File, and object URLs. |
19
- | Native WebView | Conditional | Works only when the WebView exposes the same browser APIs and the application admits every artifact. This does not invoke Core speech. |
20
- | Node | Importable, execution unavailable | The ESM subpath is exported, but `0.2.2` publishes no Node speech storage, Worker, audio-decoder, or execution adapter. |
21
- | Cloud | Not provided | A cloud speech provider can implement `arcane-ai-provider/2`, but this package never selects one. |
21
+ | Browser | Shipped | Requires DBOPFS/OPFS, Web Locks, Workers, Fetch, Blob/File, and object URLs. Authenticated graphs additionally require `MessageChannel`; shared Blob/File STT requests require the browser audio decoder. |
22
+ | Native WebView | Conditional | Available only when the WebView exposes the same browser APIs and the application admits every artifact. It does not invoke Core speech. |
23
+ | Node | Importable, execution unavailable | The ESM subpath can be imported, but the SDK supplies no Node speech storage, Worker, or audio-decoder host. |
24
+ | Cloud | Not provided | A cloud speech adapter may separately implement `arcane-ai-provider/2`; this package never selects it. |
22
25
 
23
- Browser speech is independent from `arcane-os/ai/browser-wasm`: the Wllama
24
- entrypoint owns the LLM role, while this entrypoint owns optional STT and TTS
25
- providers. Each role loads, unloads, reports status, cancels, and recovers
26
- independently.
26
+ Whisper `stt` and Kokoro `tts` each own an independent provider lifecycle. A
27
+ failure or cancellation in either role does not disable LLM use and never
28
+ authorizes a different local, native, Core, or cloud provider.
27
29
 
28
- ## Import
30
+ ## Public exports
29
31
 
30
32
  ```javascript
31
33
  import {
34
+ BROWSER_SPEECH_ARTIFACT_GRAPH_PROTOCOL,
32
35
  BROWSER_SPEECH_ARTIFACT_PROTOCOL,
33
36
  createBrowserKokoroProvider,
37
+ createBrowserSpeechArtifactGraph,
34
38
  createBrowserSpeechAuthority,
35
39
  createBrowserWhisperProvider,
36
40
  createDbopfsSpeechArtifactStore
37
41
  } from 'arcane-os/ai/browser-speech';
38
42
  ```
39
43
 
40
- The entrypoint exports exactly those five names. Importing it downloads no
41
- model and starts no Worker.
44
+ The entrypoint exports exactly those seven names. Importing it downloads no
45
+ artifact, opens no cache, creates no Worker, and publishes no event.
42
46
 
43
- ## `BROWSER_SPEECH_ARTIFACT_PROTOCOL`
47
+ ## Protocol and enum registry
44
48
 
45
- The constant is exactly `"arcane-ai-browser-speech-artifacts/1"`. It identifies
46
- an SDK-created artifact store; it is not model authority, a capability grant,
47
- or evidence that a cache is complete.
49
+ | Subject | Exact value or closed set |
50
+ | --- | --- |
51
+ | Artifact-store protocol | `arcane-ai-browser-speech-artifacts/1` |
52
+ | Authenticated artifact-graph protocol | `arcane-ai-browser-speech-artifact-graph/1` |
53
+ | Graph `kind` and prepared `runtime.moduleGraph` | `browser-speech-authenticated-artifact-graph` |
54
+ | Legacy prepared `runtime.moduleGraph` | `self-contained` |
55
+ | Legacy authority protocol | `arcane-ai-model-authority/1` |
56
+ | Provider protocol | `arcane-ai-provider/2` |
57
+ | Worker protocol | `arcane-ai-speech-worker/1` |
58
+ | Worker error-envelope protocol | `arcane-ai-speech-worker-error/1` |
59
+ | Nested artifact module-Worker protocol | `arcane-ai-browser-speech-artifact-module-worker/1` |
60
+ | Nested module-Worker rejection event | `artifact-module-worker-bootstrap-rejected` |
61
+ | Runtime graph-guard protocol | `arcane-ai-browser-speech-artifact-graph-runtime/1` |
62
+ | Roles | `stt`, `tts` |
63
+ | Role operations | `transcribe` for `stt`; `synthesize` for `tts` |
64
+ | Public Worker operations | `load`, `use`, `status`, `unload`, `dispose` |
65
+ | Worker transport-only control operation | `cancel` |
66
+ | TTS response format | `wav` |
67
+ | Runtime adapters | `transformers-whisper` for `stt`; `kokoro-js` for `tts` |
68
+ | ONNX namespace identifiers | `transformers-env-backends-onnx-wasm` for `stt`; `kokoro-env-wasm-paths` for `tts` |
69
+ | Edge policies | `artifact-targets-admitted`, `inactive-runtime-branch-rejected` |
70
+ | Import target matches | `exact-runtime-specifier`, `materialized-module-url`; module-Worker targets additionally admit `self-module-url` |
71
+ | Transform kinds | `function-return-this-to-global-this`, `typed-array-constructor` |
72
+ | Graph descriptor status | `artifact-graph-descriptor-verified` |
73
+ | Graph admission | `artifact-graph-network-dbopfs-verified`, `artifact-graph-dbopfs-cache-verified`, `artifact-graph-offline-dbopfs-cache-verified` |
74
+
75
+ These values are exact, case-sensitive public contract values. Unknown values
76
+ fail closed; a newer-looking value is not treated as compatible.
77
+
78
+ ## `createBrowserSpeechArtifactGraph()`
48
79
 
49
- ## `createBrowserSpeechAuthority()`
80
+ This is the operational authority for a real auxiliary ESM/WASM/model/voice
81
+ closure. It creates one frozen, caller-selected graph and computes its SHA-256
82
+ identity from a canonical JSON projection. The inventory is dynamic and has no
83
+ fixed file or edge count; every declared member must still belong to the one
84
+ complete reachable closure.
85
+
86
+ The top-level shape is:
50
87
 
51
88
  ```text
52
- createBrowserSpeechAuthority({
53
- providerId,
89
+ createBrowserSpeechArtifactGraph({
90
+ kind?,
91
+ identitySha256?,
92
+ providerId?,
54
93
  role,
55
- model,
56
- runtime,
57
- security
58
- } = {})
94
+ model: { ... },
95
+ runtime: { ... },
96
+ files: [fileDescriptor, ...],
97
+ edges: {
98
+ staticImports: [...],
99
+ dynamicImports: [...],
100
+ moduleWorkers: [...],
101
+ fetches: [...],
102
+ cacheOpens: [...]
103
+ },
104
+ transforms: [...]
105
+ })
59
106
  ```
60
107
 
61
- Validates and freezes one caller-owned model/runtime declaration. Construction
62
- binds the declared identities and descriptor rules; actual downloaded or cached
63
- runtime-graph closure is validated by `store.prepare()` before a Worker starts.
108
+ ### Top-level and model fields
109
+
110
+ | Field | Contract |
111
+ | --- | --- |
112
+ | `kind` | Omit it or use exactly `browser-speech-authenticated-artifact-graph`. |
113
+ | `identitySha256` | Optional 64-character lowercase SHA-256 assertion. If supplied, it must equal the SDK's canonical descriptor digest. The returned graph always contains the computed value. |
114
+ | `providerId` | `null`/omitted, or one trimmed 1–128 character identity. A non-null value must equal the provider constructor's `id`. |
115
+ | `role` | Exactly `stt` or `tts`. |
116
+ | `model.id` | Trimmed 1–128 character caller-owned model identity. |
117
+ | `model.repository` | Trimmed 1–128 character logical repository identity. The SDK passes it to the admitted adapter but does not resolve it independently. |
118
+ | `model.revision` | Trimmed 1–128 character immutable revision. Every model and voice file must use this same revision. |
119
+ | `model.dtype` | Trimmed 1–128 character caller-owned dtype. There is no SDK-selected graph default. |
120
+ | `model.inputSampleRate` | Required positive safe integer for `stt`; caller-owned. STT must not declare voices. |
121
+ | `model.outputSampleRate` | Required positive safe integer for `tts`; caller-owned. |
122
+ | `model.defaultVoice` | Required for `tts`; must name one declared `voices[].id`. |
123
+ | `model.voices` | Required nonempty TTS array of unique `{id,path}` records. Every path must name one unique `voice-style-binary`, and every such file must be in this inventory. |
124
+
125
+ ### Runtime fields
126
+
127
+ | Field | Contract |
128
+ | --- | --- |
129
+ | `runtime.adapter` | `transformers-whisper` for `stt`; `kokoro-js` for `tts`. |
130
+ | `runtime.version` / `runtime.revision` | Trimmed 1–128 character caller-pinned identities. The sole entrypoint file revision must equal `runtime.revision`. |
131
+ | `runtime.entrypoint` | Canonical path of the sole `runtime-entrypoint-javascript`. `runtime.entry` is accepted as an input alias; the normalized graph exposes `runtime.entry`. |
132
+ | `runtime.onnxWasm.namespace` | `transformers-env-backends-onnx-wasm` for STT or `kokoro-env-wasm-paths` for TTS. |
133
+ | `runtime.onnxWasm.mjsPath` | Path of a declared `runtime-auxiliary-javascript`. |
134
+ | `runtime.onnxWasm.wasmPath` | Path of a declared `runtime-wasm-binary`. |
135
+ | `runtime.onnxWasm.numThreads` | Optional positive safe integer for Transformers STT only. Kokoro does not expose a verified thread setting and rejects this field with reason `kokoro-env-num-threads-field-not-exposed`. No hardware-derived default is chosen. |
136
+ | `runtime.negativeRuntimeRequestUrls` | Optional unique absolute HTTPS routes that a declared fetch edge may intentionally resolve as a local `404`. They may not overlap a positive source or runtime route and must be referenced by an edge. |
64
137
 
65
- ### Authority input
138
+ ### File descriptor
139
+
140
+ Every `files[]` entry contains all of these fields:
141
+
142
+ ```text
143
+ {
144
+ kind,
145
+ path,
146
+ sourceUrl,
147
+ revision,
148
+ license,
149
+ mediaType,
150
+ sourceMediaType?,
151
+ bytes,
152
+ sha256,
153
+ runtimeRequestUrls?,
154
+ redirectFinalOrigins?
155
+ }
156
+ ```
66
157
 
67
158
  | Field | Contract |
68
159
  | --- | --- |
69
- | `providerId` | Trimmed 1–128 character provider identity. |
70
- | `role` | Exactly `"stt"` or `"tts"`. |
71
- | `model.id` | Caller-owned model identity. |
72
- | `model.repository` | Immutable logical repository identity; the SDK does not resolve it. |
73
- | `model.revision` | Caller-pinned revision. Every model URL must contain this revision or its supplied SHA-256. |
74
- | `model.files` | Nonempty unique `{path,url,bytes?,sha256?,mediaType?}[]`. Paths are normalized relative paths; URLs are immutable HTTPS or same-origin URLs without credentials/fragments. |
75
- | `model.defaultVoice` | Required only for TTS. |
76
- | `runtime.adapter` | Exactly `"transformers-whisper"` for STT or `"kokoro-js"` for TTS. |
77
- | `runtime.version` / `runtime.revision` | Caller-pinned runtime identity. |
78
- | `runtime.entry` | Path of one JavaScript module in `runtime.files`. |
79
- | `runtime.files` | Nonempty immutable runtime closure. The entry module must be self-contained: undeclared imports, re-exports, executable strings, alternate transports, and child workers are rejected. |
80
- | `security` | Closed `{secure?,checks?:{byteLength?,sha256?}}` artifact policy. Disabled checks are disclosed rather than silently claimed. |
81
-
82
- The result is a frozen `arcane-ai-model-authority/1` record. The SDK owns the
83
- validation rules; the application owns every model/runtime choice, license,
84
- revision, URL, hash, voice, and business policy.
85
-
86
- Provider security resolves each field independently in this order: SDK default,
87
- `appSecurity`, provider `security`, then `load({security})`. The SDK default is
88
- `secure:false`. After resolution, each omitted check defaults to the effective
89
- `secure` value; an explicit `checks.byteLength` or `checks.sha256` boolean
90
- overrides that default. An enabled check requires the corresponding `bytes` or
91
- `sha256` field on every admitted artifact and fails closed when evidence is
92
- missing or mismatched. A disabled check never becomes a claim that bytes were
93
- verified. Direct `store.prepare()` has only its supplied security scope above
94
- the SDK default because it is not bound to a provider.
160
+ | `kind` | One exact kind from the table below. |
161
+ | `path` | Nonempty NFC-normalized relative path. Leading/trailing slash, backslash, empty/`.`/`..` segment, percent escape, query/fragment delimiter, control character, duplicate spelling, or case-folded collision is rejected. |
162
+ | `sourceUrl` | Immutable starting HTTPS or same-origin authority without credentials or fragment. The pathname must not name `main`, `master`, `latest`, `refs/heads/main`, `refs/heads/master`, `resolve/main`, or `resolve/master`, or an `@latest`/`@next` channel. The URL must contain the declared revision or SHA-256 and remains authoritative even when redirect following is opted in. `url` is accepted as an input alias. |
163
+ | `revision` | Nonempty 1–128 character immutable file revision. Runtime entry revision equals the runtime revision; all model/voice revisions equal the model revision. |
164
+ | `license` | Nonempty declaration up to 256 characters. It is part of graph identity and the DBOPFS manifest authority, but it is only a declaration: it is not provenance, a composite notice, corresponding-source evidence, or a license inferred from bytes. The caller remains responsible for complete immutable license/notice evidence. |
165
+ | `mediaType` | Exact lowercase `type/subtype` without parameters. JavaScript is `application/javascript` or `text/javascript`; WASM is `application/wasm`; `*-json` kinds are `application/json`. |
166
+ | `sourceMediaType` | Optional exact lowercase `type/subtype` without parameters for the cold HTTP response. It defaults to `mediaType`; when different, it is exposed in the normalized descriptor and graph identity while `mediaType` remains the authenticated materialized Blob type. |
167
+ | `bytes` | Positive safe integer; always required and always verified for graphs. |
168
+ | `sha256` | Exactly 64 lowercase hexadecimal characters; always required and always verified for graphs. |
169
+ | `runtimeRequestUrls` | Optional unique absolute HTTPS aliases used only inside the authenticated Worker. An alias may describe a third-party runtime's hard-coded mutable request, but it is never a download authority: the Worker maps it to this already verified local file. |
170
+ | `redirectFinalOrigins` | Optional nonempty array that opts this file into Fetch redirect following. Every member must canonicalize to a unique HTTPS origin with no credentials, path, query, or fragment. The normalized array is lexically sorted, frozen, and graph-identity-bound. Omit the field for redirect rejection; an empty array is rejected. |
171
+
172
+ The closed file kinds are:
173
+
174
+ | Runtime kinds | Model/data kinds |
175
+ | --- | --- |
176
+ | `runtime-entrypoint-javascript` | `model-configuration-json` |
177
+ | `runtime-auxiliary-javascript` | `model-generation-configuration-json` |
178
+ | `runtime-wasm-binary` | `model-onnx-binary` |
179
+ | `runtime-opaque-data` | `model-onnx-external-data` |
180
+ | | `model-preprocessor-json` |
181
+ | | `model-tokenizer-json` |
182
+ | | `model-opaque-data` |
183
+ | | `voice-style-binary` |
184
+
185
+ Paths and immutable source URLs are globally unique within a graph. Positive
186
+ runtime request aliases are also unique and cannot overlap a source URL or a
187
+ negative route. Every non-entry file must be reachable from the declared ONNX
188
+ pair, TTS voice inventory, or one exact edge; unreachable files and routes are
189
+ rejected rather than silently retained.
190
+
191
+ ### Edge and transform descriptors
192
+
193
+ Edge `occurrence` values are positive, one-based occurrences in the named
194
+ `modulePath`. A declaration must match the scanner's exact source occurrence;
195
+ missing, duplicate, extra, occurrence-mismatched, or undeclared runtime edges
196
+ fail closed. Descriptor collections are canonically sorted for graph identity,
197
+ so caller array order is not semantic.
198
+
199
+ | Collection | Exact record |
200
+ | --- | --- |
201
+ | `edges.staticImports` | `{modulePath,occurrence,specifier,targetPath}`. The literal specifier and declared JavaScript target must match. |
202
+ | `edges.dynamicImports` | `{modulePath,occurrence,edgePolicy,targets}`. Each target is `{match,targetPath,exactSpecifier?}`. `exactSpecifier` is required only for `exact-runtime-specifier`. |
203
+ | `edges.moduleWorkers` | Same shape as dynamic imports; `self-module-url` is additionally available and must target the declaring module. Only module Workers are admitted. |
204
+ | `edges.fetches` | `{modulePath,occurrence,edgePolicy,methods:["GET"],targetPaths,negativeRuntimeRequestUrls,allowMaterializedUrls}`. `methods` defaults to the same one-element `GET` array; the two target arrays default empty; `allowMaterializedUrls` is `true` only when explicitly set and otherwise normalizes to `false`. No other method is admitted, and a fetch target cannot be JavaScript. |
205
+ | `edges.cacheOpens` | `{modulePath,occurrence,edgePolicy,cacheName,targetPaths}`. `artifact-targets-admitted` requires at least one exact non-JavaScript file path; `inactive-runtime-branch-rejected` requires none. The returned cache can read only those files. |
206
+ | `transforms` | `{kind,modulePath,occurrence}`. `function-return-this-to-global-this` replaces the one audited `Function("return this")()` pattern. `typed-array-constructor` rewrites only a scanner-recognized typed-array `.constructor(...)` call and later proves the receiver uses an intrinsic typed-array prototype. |
207
+
208
+ An omitted `edgePolicy` normalizes to `artifact-targets-admitted`, which
209
+ requires a declared target. An
210
+ `inactive-runtime-branch-rejected` edge has no target and proves only that the
211
+ branch must remain inactive; execution of that occurrence rejects. The source
212
+ scanner rejects undeclared imports, fetches, Workers, executable-string
213
+ construction or constructor access, Cache Storage access outside a direct
214
+ declared `caches.open(...)`, computed capability access, ambiguous tokens, and
215
+ unmatched transforms. It also reserves the SDK guard name so artifact code
216
+ cannot address the guard directly. Static import cycles are rejected so
217
+ materialization order remains deterministic; runtime isolation separately
218
+ denies every raw transport listed below.
219
+
220
+ The returned graph is deeply frozen and has exactly
221
+ `{protocol,kind,providerId,role,model,runtime,files,edges,transforms,identitySha256,artifactGraphStatus}`.
222
+ The status is `artifact-graph-descriptor-verified`. Its computed identity binds
223
+ every preceding descriptor field, including paths, revisions, licenses,
224
+ runtime aliases, edge policy, and transform occurrences; the status itself is
225
+ the result of that computation rather than identity input.
95
226
 
96
227
  ## `createDbopfsSpeechArtifactStore()`
97
228
 
@@ -104,226 +235,1055 @@ createDbopfsSpeechArtifactStore({
104
235
  } = {})
105
236
  ```
106
237
 
107
- Adapts an existing DBOPFS instance into an authority-scoped store. The returned
108
- frozen object exposes `{protocol,tableName,prepare,remove}`.
238
+ The frozen store exposes `{protocol,tableName,prepare,remove}`. It accepts both
239
+ the authenticated graph above and an SDK-created legacy authority.
240
+ `objectUrlFactory` remains a legacy compatibility seam. Graph preparation
241
+ ignores it and uses module-captured native Blob URL creation, revocation, and
242
+ fetch so a caller cannot substitute the executable materialization boundary.
109
243
 
110
- `prepare(authority,{signal,onProgress,offline=false,security})`:
244
+ For a graph, `prepare(graph,{signal,onProgress,offline=false,security})` follows
245
+ the shared model-security flag. Its default `secure:false` path retains the
246
+ selected graph, scans and rewrites its declared module edges, and reports
247
+ unchecked or partially checked admission without enabling optional strict
248
+ capability isolation. Explicit `secure:true` defaults byte-length and SHA-256
249
+ checks on and adds strict Worker isolation. Either mode rejects malformed graph
250
+ records, unreadable Fetch responses, media-type mismatches, structurally
251
+ unmaterializable runtime modules, failed object-URL readback, and every enabled
252
+ byte-length or SHA-256 mismatch.
111
253
 
112
- 1. obtains an exclusive Web Lock for the exact authority;
113
- 2. admits a complete cached manifest when every record still matches;
114
- 3. otherwise, unless `offline:true`, downloads every declared file without
115
- credentials;
116
- 4. enforces enabled byte-length and SHA-256 checks;
117
- 5. validates the closed runtime module graph;
118
- 6. commits `arcane.ai.browser-speech.assets.v1` only after every file succeeds;
119
- 7. returns materialized object URLs plus a `release()` callback.
254
+ ### Cold admission
120
255
 
121
- An installation or cache-validation failure removes incomplete stored state for
122
- that authority. A request or provider-use failure does not imply cache removal.
123
- `offline:true` never falls back to network and rejects with
124
- `ARCANE_AI_ARTIFACT_OFFLINE_MISS` when no admitted cache exists.
256
+ 1. Acquire the exact authority's exclusive Web Lock.
257
+ 2. Delete incomplete prior state.
258
+ 3. Fetch only each declared immutable `sourceUrl` using `credentials:"omit"`,
259
+ `cache:"no-store"`, `mode:"cors"`, and `referrerPolicy:"no-referrer"`.
260
+ The default is `redirect:"error"`; only a file with declared
261
+ `redirectFinalOrigins` uses `redirect:"follow"`.
262
+ 4. Without a followed redirect, require the response URL to remain exactly the
263
+ immutable `sourceUrl`. After a followed redirect, require a readable final
264
+ HTTPS URL without credentials or a fragment and require its canonical origin
265
+ to occur in that file's declared final-origin inventory.
266
+ 5. Require the response Content-Type to equal `sourceMediaType` (which defaults
267
+ to `mediaType`), then stream into DBOPFS while applying each enabled
268
+ byte-length or SHA-256 check. `secure:true` enables both checks by default;
269
+ `secure:false` leaves both disabled unless the caller selects them.
270
+ 6. Reopen every persisted file and repeat only the enabled byte-length or
271
+ SHA-256 checks.
272
+ 7. Decode and scan every runtime JavaScript file and prove exact edge/transform
273
+ closure.
274
+ 8. Persist `arcane.ai.browser-speech.authenticated-artifact-graph.v1` only
275
+ after every file and the complete runtime graph pass.
125
276
 
126
- ## `createBrowserWhisperProvider()`
277
+ The returned admission is `artifact-graph-network-dbopfs-verified`,
278
+ `artifact-graph-network-dbopfs-partially-checked`, or
279
+ `artifact-graph-network-dbopfs-unchecked` according to the effective checks. A
280
+ failure before manifest completion removes the graph's incomplete records.
127
281
 
128
- ```text
129
- createBrowserWhisperProvider({
130
- id = 'arcane-browser-whisper',
131
- localOnly = true,
132
- model,
133
- runtime,
134
- appSecurity,
135
- security,
136
- store,
137
- offline = false
138
- } = {})
139
- ```
282
+ `redirectFinalOrigins` admits only a final origin, not a final path, query, or
283
+ signed/expiring URL. The browser may follow such an implementation-specific
284
+ final URL, but that URL is neither persisted nor accepted as source, revision,
285
+ signature, graph identity, or future download authority. The graph remains
286
+ bound to its immutable starting `sourceUrl`; selected length and SHA-256 checks
287
+ apply only when enabled and are reported honestly by the admission status.
140
288
 
141
- Returns a frozen `arcane-ai-provider/2` object for role `stt`. The provider
142
- surface is `{protocol,role,id,localOnly,catalog,inspect,status,load,request,
143
- unload,dispose}`.
289
+ Browser Fetch exposes the final CORS response, not an inspectable list of every
290
+ intermediate redirect hop. The SDK therefore cannot authenticate intermediate
291
+ hop origins or headers. CORS must succeed for the browser-managed chain, and
292
+ the SDK can enforce only the immutable start, the declared final HTTPS origin,
293
+ the final response metadata, and the end-to-end bytes. A caller that requires
294
+ every hop to be independently pinned must use a direct immutable source rather
295
+ than this redirect opt-in.
144
296
 
145
- The only request operation is `transcribe`:
297
+ ### Warm and offline admission
146
298
 
147
- ```javascript
148
- async function transcribeAfterUserChoice(provider, pcmFloat32, signal) {
149
- // The admitted Whisper provider must already be ready.
150
- const result = await provider.request({
151
- role: 'stt',
152
- operation: 'transcribe',
153
- signal,
154
- payload: {
155
- audio: pcmFloat32,
156
- sampleRate: 16_000
157
- }
158
- });
159
-
160
- console.log(result.text);
299
+ A warm prepare requires the exact manifest authority and file count, reopens
300
+ every DBOPFS record, checks the manifest's observed byte count and declared
301
+ descriptor identity, repeats the enabled byte-length or SHA-256 checks, and
302
+ rescans the runtime graph. It returns the corresponding
303
+ `artifact-graph-dbopfs-cache-verified`,
304
+ `artifact-graph-dbopfs-cache-partially-checked`, or
305
+ `artifact-graph-dbopfs-cache-unchecked` status.
306
+
307
+ `offline:true` runs the same cache verification and never calls the source
308
+ fetch function. It returns
309
+ `artifact-graph-offline-dbopfs-cache-{verified|partially-checked|unchecked}`,
310
+ or rejects with code
311
+ `ARCANE_AI_ARTIFACT_GRAPH_OFFLINE_CACHE_MISS` and reason
312
+ `artifact-graph-offline-cache-miss`. There is no network repair, runtime CDN,
313
+ private Cache Storage fallback, or partial-cache admission.
314
+
315
+ The manifest authority binds each file's `sourceMediaType` and
316
+ `redirectFinalOrigins` through the graph identity. A valid warm or offline
317
+ admission does not resolve the starting URL again, follow a redirect, or reuse a
318
+ prior final URL; it admits only the complete cached records and graph under the
319
+ selected checks.
320
+
321
+ After every cold, warm, or offline admission, the store materializes a fresh
322
+ set of unique native `blob:` URLs. Each URL is fetched back with omitted
323
+ credentials and redirect rejection, and its exact URL identity, media type,
324
+ byte length, and SHA-256 are compared with the Blob that was created. Only then
325
+ does the SDK apply the already-scanned deterministic module rewrites. A fresh
326
+ cryptographically random 32-byte lowercase-hex guard capability binds every
327
+ rewritten dynamic import, fetch, cache-open, child-Worker, and typed-array call
328
+ for that materialization. The capability is ephemeral: it is neither supplied
329
+ by the caller nor persisted in the manifest or graph identity.
330
+
331
+ Successful graph preparation returns:
332
+
333
+ ```text
334
+ {
335
+ cache, // the exact graph admission value
336
+ artifactGraphId,
337
+ artifactGraphAdmission,
338
+ warnings, // frozen runtime-inspection warnings
339
+ runtime: {
340
+ ...normalizedRuntime,
341
+ files, edges, transforms, warnings,
342
+ guardCapability,
343
+ artifactGraphId,
344
+ artifactGraphAdmission
345
+ },
346
+ model: {...normalizedModel, files},
347
+ release
161
348
  }
162
349
  ```
163
350
 
164
- `audio` must be a `Float32Array` sampled at exactly 16,000 Hz. The provider-native
165
- provider result is a structured `{text}` record transferred by Worker
166
- `postMessage`; the client does not re-freeze that cloned record. The same
167
- provider also accepts the shared AI.js STT
168
- payload `{audio:Blob|File,mimeType,model}`. It verifies `model`, decodes the
169
- browser audio to authoritative 16 kHz mono PCM, and then enters the same provider-native
170
- provider operation. Decode support and failure remain explicit.
351
+ `release()` revokes the materialized object URLs after Worker termination. It
352
+ does not delete the caller-owned DBOPFS cache. `remove(graph)` is the explicit
353
+ cache-deletion operation.
354
+
355
+ ## Graph Worker host
356
+
357
+ Graph loading creates one dedicated Worker for the selected role. With explicit
358
+ `secure:true`, its first load envelope transfers a new private `MessagePort`;
359
+ all graph responses, progress, cancellation settlement, and later operations
360
+ stay on that port. Strict loading rejects if `MessageChannel`/`MessagePort` is
361
+ unavailable, if a Worker-global transport was already selected, or if the
362
+ Worker receives a strict graph load without the private port. Default
363
+ `secure:false` graph loading uses the ordinary Worker-global message transport,
364
+ as does the published single-module compatibility path.
365
+
366
+ Before importing the entrypoint in either mode, the graph Worker installs the
367
+ functional graph boundary:
368
+
369
+ - exact graph routes map declared source URLs, runtime aliases, and materialized
370
+ URLs to the selected materialized object URLs;
371
+ - the ephemeral guard capability must match at every rewritten call;
372
+ - each rewritten fetch, cache-open, import, and child-Worker edge resolves
373
+ through its declared graph route and local materialized bytes;
374
+ - the returned graph cache facade serves only declared `targetPaths`, while
375
+ `put`, `add`, and `addAll` reject, so DBOPFS remains the graph's sole durable
376
+ store;
377
+ - a `typed-array-constructor` rewrite returns only the intrinsic constructor of
378
+ a typed-array receiver; an own or non-intrinsic `constructor`
379
+ rejects;
380
+ - an admitted child module Worker is created through the SDK's role Worker,
381
+ receives the graph configuration, installs the same guard, and imports only
382
+ its declared materialized target.
383
+
384
+ Explicit `secure:true` additionally installs these strict isolation controls:
385
+
386
+ - raw global `fetch` rejects unless scanned source was rewritten to one
387
+ declared fetch edge, and raw Cache Storage access is replaced by the graph
388
+ facade;
389
+ - the `Function`, `AsyncFunction`, `GeneratorFunction`, and
390
+ `AsyncGeneratorFunction` prototype constructor escape is replaced before
391
+ import, and string callbacks to `setTimeout` or `setInterval` reject;
392
+ - `indexedDB` and `navigator.storage`/OPFS are made unavailable;
393
+ - raw `BroadcastChannel`, `EventSource`, `Function`, `RTCPeerConnection`,
394
+ `ShadowRealm`, `SharedWorker`, `WebSocket`, `WebSocketStream`, `WebTransport`,
395
+ `Worker`, `XMLHttpRequest`, `eval`, and `importScripts` capabilities are
396
+ denied.
397
+
398
+ An immutable `sourceUrl` may be an explicitly caller-selected HTTPS origin.
399
+ Every rewritten declared request is answered from local object bytes and an
400
+ undeclared transformed edge rejects. Explicit `secure:true` additionally
401
+ prevents raw runtime access from escalating to that or another network origin;
402
+ default warn-first mode does not make that isolation claim.
403
+
404
+ ### ONNX Runtime Web configuration
405
+
406
+ The Worker accepts only the two mechanically verified namespace shapes:
171
407
 
172
- ## `createBrowserKokoroProvider()`
408
+ - Kokoro: `namespace.env.wasmPaths = {mjs,wasm}`. No Kokoro cache setting or
409
+ thread setting is invented. A missing property rejects as
410
+ `kokoro-env-wasm-paths-unavailable`; a property that rejects or does not
411
+ retain the exact assignment rejects as
412
+ `kokoro-env-wasm-paths-assignment-rejected`.
413
+ - Transformers: `namespace.env.backends.onnx.wasm.wasmPaths = {mjs,wasm}`.
414
+ The Worker also requires the verified outer `env` fields, sets
415
+ `allowLocalModels:false` and `allowRemoteModels:true`. In explicit
416
+ `secure:true`, it additionally sets `useBrowserCache:false` and
417
+ `useFSCache:false`; with no admitted `cacheOpens` edge named exactly
418
+ `transformers-cache`, it assigns `useCustomCache:false` and `customCache:null`,
419
+ while exactly one such edge selects the target-limited read-only graph
420
+ facade. Default `secure:false` leaves those optional upstream cache settings
421
+ unchanged. More than one declared Transformers cache edge rejects as
422
+ ambiguous in either mode. Only a caller-declared STT `numThreads` is assigned. The
423
+ `allowRemoteModels` value permits the audited library code path to issue its
424
+ declared request; the rewritten graph edge still serves its selected local
425
+ route. Strict mode separately prevents unrewritten network access.
426
+
427
+ For Transformers, the exact missing-field reasons are
428
+ `transformers-env-backends-onnx-wasm-unavailable`,
429
+ `transformers-env-allow-local-models-unavailable`,
430
+ `transformers-env-allow-remote-models-unavailable`,
431
+ `transformers-env-browser-cache-unavailable`,
432
+ `transformers-env-fs-cache-unavailable`,
433
+ `transformers-env-custom-cache-toggle-unavailable`, and
434
+ `transformers-env-custom-cache-unavailable`. The corresponding exact
435
+ assignment-rejection reasons are
436
+ `transformers-env-allow-local-models-assignment-rejected`,
437
+ `transformers-env-allow-remote-models-assignment-rejected`,
438
+ `transformers-env-browser-cache-assignment-rejected`,
439
+ `transformers-env-fs-cache-assignment-rejected`,
440
+ `transformers-env-custom-cache-toggle-assignment-rejected`, and
441
+ `transformers-env-custom-cache-assignment-rejected`. The optional, creatable
442
+ ONNX fields fail assignment as
443
+ `transformers-env-wasm-paths-assignment-rejected` or
444
+ `transformers-env-num-threads-assignment-rejected`; their defensive
445
+ unavailable reasons are `transformers-env-wasm-paths-unavailable` and
446
+ `transformers-env-num-threads-unavailable`. Every case rejects with
447
+ `ARCANE_AI_PROVIDER_UNAVAILABLE`; the Worker never guesses a different
448
+ namespace shape.
449
+
450
+ Kokoro's audited bundle hard-codes a mutable
451
+ `.../resolve/main/voices/${voice}.bin` request and opens `kokoro-voices` Cache
452
+ Storage. That URL is never accepted as `sourceUrl`. An operational graph must
453
+ declare the exact resolved voice file as an immutable authenticated source,
454
+ bind the hard-coded URL only as that file's `runtimeRequestUrls` alias, and
455
+ declare the exact `cacheOpens` occurrence with `cacheName:"kokoro-voices"` and
456
+ the caller-owned voice paths. Every scanned fetch occurrence still requires its
457
+ own explicit admitted or inactive policy. The Worker satisfies the cache read
458
+ from verified local voice bytes; it never opens Kokoro's private durable cache.
459
+ If the graph does not prove those exact edges, Kokoro fails closed before the
460
+ mutable request can reach the network.
461
+
462
+ That graph closure is an optional caller-owned secure/offline configuration,
463
+ not the public runtime's default and not a publication gate. In warn-first mode
464
+ the SDK loads the caller-selected version-pinned upstream package and provider
465
+ assets from their publishers. The SDK neither republishes those bytes nor
466
+ requires their optional provenance or legal metadata before the provider can
467
+ operate. When `secure:true` is selected, incomplete graph metadata rejects that
468
+ specific secure load without disabling ordinary warn-first operation.
469
+
470
+ ## Providers
173
471
 
174
472
  ```text
473
+ createBrowserWhisperProvider({
474
+ id = 'arcane-browser-whisper',
475
+ localOnly = true,
476
+ graph?,
477
+ model?, runtime?,
478
+ store,
479
+ offline = false,
480
+ appSecurity,
481
+ security
482
+ } = {})
483
+
175
484
  createBrowserKokoroProvider({
176
485
  id = 'arcane-browser-kokoro',
177
486
  localOnly = true,
178
- model,
179
- runtime,
180
- appSecurity,
181
- security,
487
+ graph?,
488
+ model?, runtime?,
182
489
  store,
183
- offline = false
490
+ offline = false,
491
+ appSecurity,
492
+ security
184
493
  } = {})
185
494
  ```
186
495
 
187
- Returns the same provider/2 lifecycle for role `tts`. The only request
188
- operation is `synthesize`:
496
+ `graph` is mutually exclusive with the legacy `model`/`runtime` options. It may
497
+ load under default `secure:false` warn-first operation or explicit
498
+ `secure:true` strict operation. Both constructors require an SDK-created
499
+ DBOPFS speech store and return one frozen `arcane-ai-provider/2` object:
189
500
 
190
- ```javascript
191
- async function synthesizeAfterUserChoice(provider, signal) {
192
- // The admitted Kokoro provider must already be ready.
193
- const result = await provider.request({
194
- role: 'tts',
195
- operation: 'synthesize',
196
- signal,
197
- payload: {
198
- text: 'Hello from Arcane.',
199
- voice: 'caller-owned-voice-id',
200
- speed: 1
201
- }
202
- });
203
-
204
- console.log(result.audio, result.sampleRate, result.voice);
501
+ ```text
502
+ {
503
+ protocol, role, id, localOnly,
504
+ catalog, inspect, status, load, request, unload, dispose
205
505
  }
206
506
  ```
207
507
 
208
- `speed` must be greater than zero and at most four. Omitted `voice` selects the
209
- caller-supplied `model.defaultVoice`. The provider-native result is the structured
210
- `{audio:Float32Array,sampleRate:24000,voice}` record transferred by Worker
211
- `postMessage`; the client does not re-freeze that cloned record. The shared
212
- AI.js TTS payload is
213
- `{model,input,responseFormat,voice?,speed?}`. Only `responseFormat:"wav"` is
214
- admitted; the provider maps it to `{text,voice,speed}` and returns frozen
215
- `{audio:Uint8Array,contentType:"audio/wav"}` containing 24 kHz mono PCM.
216
- Shared AI.js synthesis reads the selected model's catalog default and never
217
- replaces it with a voice saved for a different provider route.
508
+ `localOnly` must remain `true`. `offline:true` makes every later explicit load
509
+ use the selected offline graph/cache admission policy; it does not trigger a
510
+ load itself.
218
511
 
219
- ## Lifecycle and status
512
+ `catalog()` exposes only the caller's selected model/runtime/files. A graph
513
+ catalog also reports `artifactGraphId`, caller dtype, exact sample rate, and the
514
+ caller voice inventory. Its one exact catalog record is
515
+ `{id,providerId,role,localOnly,defaultVoice?,repository,revision,dtype?,runtime,files,artifactGraphId?,voices?,speech}`;
516
+ the `speech` record is `{inputSampleRate}` for STT or
517
+ `{outputSampleRate,responseFormats:["wav"],defaultResponseFormat:"wav"}` for
518
+ TTS.
220
519
 
221
- Whisper and Kokoro use the same independent state machine:
520
+ `inspect()` admits only the exact provider/model/local selection and, when
521
+ supplied, the matching role. A successful graph inspection returns
522
+ `{available:true,authority}`. Its frozen authority has exactly
523
+ `{protocol,admitted,graph,artifactGraphProtocol,providerId,role,modelId,repository,revision,dtype,defaultVoice,voices,inputSampleRate,outputSampleRate,runtime,files,security,artifactGraphId}`;
524
+ `protocol` is `arcane-ai-model-authority/1` and `artifactGraphId` is the graph
525
+ identity. An unavailable record uses
526
+ `ARCANE_AI_MODEL_AUTHORITY_REQUIRED` and the exact role-specific reason
527
+ `stt-provider-inspection-selection-authority-mismatch` or
528
+ `tts-provider-inspection-selection-authority-mismatch`. `load()` is explicit;
529
+ construction, catalog, status, and inspection never start a download or Worker.
222
530
 
223
- | Operation | Behavior |
224
- | --- | --- |
225
- | `catalog()` | Returns the single caller-admitted model descriptor. TTS exposes its admitted `defaultVoice`; STT has no voice field. |
226
- | `inspect(selection,{signal})` | Returns `{available:true,authority}` only for the exact provider/model/local selection; mismatch returns an unavailable record. |
227
- | `status()` | Returns `{role,providerId,modelId,state,loaded,busy,generation,errorCode,cache}`. |
228
- | `load(context)` | Requires `{role,selection,progress,signal?,security?}`, admits/cache-materializes artifacts, creates one role Worker, and loads the adapter. Compatible repeated loads coalesce. |
229
- | `request(context)` | Runs exactly one STT or TTS operation. A provider rejects concurrent use with `ARCANE_AI_PROVIDER_BUSY`. |
230
- | `unload()` | Aborts active load/use, terminates the Worker, releases object URLs, and returns `unloaded`. |
231
- | `dispose()` | Completes unload and permanently returns `disposed`. |
232
-
233
- Cancellation after a Worker request begins terminates that Worker slot and
234
- returns the provider to `unloaded`; a later request requires a fresh load. A
235
- shared STT request can instead be aborted while the browser is still decoding
236
- its `Blob` or `File`, before Worker use begins. That request rejects with
237
- `ARCANE_AI_REQUEST_ABORTED`, but the loaded Worker remains intact and the
238
- provider stays `ready`. Worker crash/message failures become explicit error
239
- state. No result is retried through another provider.
240
-
241
- Register the providers with the projected
242
- [`AIProviderRuntime`](../runtime-modules.md#aiproviderruntimejs) to normalize
243
- selection, startup, application calls, and
244
- [`AIRuntimeState`](../runtime-modules.md#airuntimestatejs) observation. The
245
- provider objects themselves do not expose an event target.
531
+ ### Whisper STT
532
+
533
+ The provider-native operation is:
246
534
 
247
535
  ```javascript
248
- async function loadSpeechProviderAfterUserChoice(provider) {
249
- const selection = {
250
- providerId: provider.id,
251
- modelId: provider.catalog()[0].id,
252
- localOnly: true
253
- };
254
- return provider.load({
255
- role: provider.role,
256
- selection,
257
- progress(update) {
258
- console.info(update.phase, update.completed, update.total);
259
- }
260
- });
536
+ const result = await whisper.request({
537
+ role: 'stt',
538
+ operation: 'transcribe',
539
+ signal,
540
+ payload: {
541
+ audio: pcmFloat32,
542
+ sampleRate: graph.model.inputSampleRate
543
+ }
544
+ });
545
+ ```
546
+
547
+ The native payload is exact `Float32Array` mono PCM at the graph's
548
+ caller-selected input sample rate. The result is `{text}`. The shared AI
549
+ payload `{audio:Blob|File,mimeType,model}` verifies the exact model, uses the
550
+ browser decoder to normalize to that same rate, and then enters the same Worker
551
+ operation. Multi-channel decoded audio is averaged to one mono `Float32Array`.
552
+ Cancellation during Blob/audio decoding rejects with
553
+ `ARCANE_AI_REQUEST_ABORTED` / `stt-transcription-cancelled` while the already
554
+ loaded Worker remains ready.
555
+
556
+ ### Kokoro TTS
557
+
558
+ ```javascript
559
+ const result = await kokoro.request({
560
+ role: 'tts',
561
+ operation: 'synthesize',
562
+ signal,
563
+ payload: {
564
+ text: 'Hello from Arcane.',
565
+ voice: 'caller-voice-id',
566
+ speed: 1
567
+ }
568
+ });
569
+ ```
570
+
571
+ `voice` must belong to the graph's caller-declared inventory; omission uses
572
+ only that graph's `model.defaultVoice`. `speed` must be greater than zero and at
573
+ most four. The provider-native result is
574
+ `{audio:Float32Array,sampleRate,voice}` at the caller-selected output sample
575
+ rate. The shared AI payload `{model,input,responseFormat,voice?,speed?}` accepts
576
+ only `responseFormat:"wav"` and returns frozen
577
+ `{audio:Uint8Array,contentType:"audio/wav"}` containing mono signed 16-bit PCM
578
+ at the caller-selected output rate. No saved voice from another route is
579
+ substituted.
580
+
581
+ ## Lifecycle, progress, cancellation, and cleanup
582
+
583
+ Provider `status()` returns:
584
+
585
+ ```text
586
+ {
587
+ role,
588
+ providerId,
589
+ modelId,
590
+ state,
591
+ lifecycleStatus,
592
+ lifecycleReason,
593
+ activeOperation,
594
+ loaded,
595
+ busy,
596
+ generation,
597
+ errorCode,
598
+ cache,
599
+ security,
600
+ integrity,
601
+ warnings,
602
+ artifactGraphId,
603
+ artifactGraphAdmission
261
604
  }
262
605
  ```
263
606
 
264
- The example only defines the operation; invoke it from an explicit user action
265
- after showing the admitted runtime/model and download policy. Requests are
266
- valid only after that load resolves successfully. Applications
267
- register and configure browser-speech providers on the shared runtime. `AI.js`
268
- obtains that singleton and translates its existing browser speech request shapes
269
- at this one provider boundary; it does not register providers, choose
270
- authorities, or trigger a hidden model download.
607
+ The provider states are exactly `unloaded`, `loading`, `ready`, `unloading`,
608
+ `error`, and `disposed`. `lifecycleStatus` is therefore exactly one of
609
+ `stt-provider-unloaded`, `stt-provider-loading`, `stt-provider-ready`,
610
+ `stt-provider-unloading`, `stt-provider-error`, `stt-provider-disposed`,
611
+ `tts-provider-unloaded`, `tts-provider-loading`, `tts-provider-ready`,
612
+ `tts-provider-unloading`, `tts-provider-error`, or `tts-provider-disposed`.
613
+
614
+ `activeOperation` is `null` or one of `stt-provider-load`,
615
+ `tts-provider-load`, `stt-provider-transcription`, `tts-provider-synthesis`,
616
+ `stt-provider-unload`, `tts-provider-unload`, `stt-provider-dispose`, and
617
+ `tts-provider-dispose`.
618
+
619
+ `loaded` is true only for `ready`. `busy` reports an active transcription or
620
+ synthesis request; load, unload, and dispose remain visible through `state` and
621
+ `activeOperation`. `cache` is `null`, a graph admission value, or the legacy
622
+ `installed`/`cached` value. Graph identity and admission are otherwise `null`.
623
+ `security` reports the effective `secure`, `byteLength`, and `sha256` choices.
624
+ `integrity.state` is `unchecked` when no integrity check is enabled, `pending`
625
+ while enabled checks have not completed, `verified` only after every enabled
626
+ check passes, and `failed` when an enabled verification attempt rejects. This
627
+ verification outcome is independent of later Worker initialization and remains
628
+ visible after a failed or unloaded provider. Default `secure:false` status
629
+ includes the frozen warning code
630
+ `browser-speech-warn-first-secure-mode-disabled` without blocking upstream
631
+ package, provider, model, voice, fetch, cache, or Worker behavior. Graph runtime
632
+ inspection warnings are deduplicated into the same frozen status array. The
633
+ high-level AI runtime owns the single configuration warning event; providers
634
+ and Workers do not create a competing event source. Strict byte admission,
635
+ private transport, and capability isolation remain exclusive to explicit
636
+ `secure:true`.
637
+
638
+ Provider-owned lifecycle reasons are:
639
+
640
+ - creation and load: `stt-provider-created`, `tts-provider-created`,
641
+ `stt-load-started`, `tts-load-started`, `stt-load-completed`,
642
+ `tts-load-completed`, `stt-load-cancelled`, `tts-load-cancelled`,
643
+ `stt-provider-load-rejected`, `tts-provider-load-rejected`,
644
+ `stt-load-rejected-during-unload`,
645
+ `tts-load-rejected-during-unload`,
646
+ `stt-load-superseded-by-security-change`,
647
+ `tts-load-superseded-by-security-change`,
648
+ `stt-load-superseded-by-unload`, `tts-load-superseded-by-unload`,
649
+ `stt-load-progress-callback-threw`, and
650
+ `tts-load-progress-callback-threw`;
651
+ - use: `stt-transcription-started`, `stt-transcription-completed`,
652
+ `stt-transcription-cancelled`,
653
+ `stt-transcription-engine-operation-rejected`,
654
+ `tts-synthesis-started`, `tts-synthesis-completed`,
655
+ `tts-synthesis-cancelled`, `tts-synthesis-engine-operation-rejected`,
656
+ `stt-transcription-superseded-by-unload`, and
657
+ `tts-synthesis-superseded-by-unload`;
658
+ - teardown: `stt-unload-started`, `tts-unload-started`,
659
+ `stt-unload-completed`, `tts-unload-completed`, `stt-dispose-started`,
660
+ `tts-dispose-started`, `stt-dispose-completed`, `tts-dispose-completed`,
661
+ `stt-worker-terminated-by-unload`, `tts-worker-terminated-by-unload`,
662
+ `stt-worker-terminated`, and `tts-worker-terminated`; and
663
+ - Worker failure: `stt-worker-crashed`, `tts-worker-crashed`,
664
+ `stt-worker-message-rejected`, `tts-worker-message-rejected`,
665
+ `stt-worker-private-message-rejected`,
666
+ `tts-worker-private-message-rejected`,
667
+ `stt-worker-progress-envelope-rejected`,
668
+ `tts-worker-progress-envelope-rejected`,
669
+ `stt-worker-response-envelope-shape-rejected`,
670
+ `tts-worker-response-envelope-shape-rejected`,
671
+ `stt-worker-error-envelope-rejected`, `tts-worker-error-envelope-rejected`,
672
+ `stt-worker-protocol-mismatch`, and
673
+ `tts-worker-protocol-mismatch`.
674
+
675
+ When an active load or use fails with one of the stable reasons below, the
676
+ provider preserves that exact reason as `lifecycleReason`; it does not collapse
677
+ the boundary to a generic failure label.
678
+
679
+ Worker statuses use exactly `stt-worker-unloaded`, `stt-worker-ready`,
680
+ `stt-worker-disposed`, `tts-worker-unloaded`, `tts-worker-ready`, and
681
+ `tts-worker-disposed`. The nested bootstrap status is exactly
682
+ `stt-artifact-module-worker-awaiting-initialization` or
683
+ `tts-artifact-module-worker-awaiting-initialization`.
684
+
685
+ Worker `activeOperation` is `null`, `stt-load`, `tts-load`,
686
+ `stt-transcription`, `tts-synthesis`, `stt-status`, `tts-status`, `stt-unload`,
687
+ `tts-unload`, `stt-dispose`, or `tts-dispose`. Its own lifecycle starts with
688
+ `stt-worker-created` or `tts-worker-created`. Load uses exactly
689
+ `stt-load-started`, `stt-load-completed`, `stt-load-cancelled`,
690
+ `stt-worker-runtime-configuration-rejected`,
691
+ `stt-worker-runtime-import-rejected`, `stt-worker-model-load-rejected`,
692
+ `tts-load-started`, `tts-load-completed`, `tts-load-cancelled`,
693
+ `tts-worker-runtime-configuration-rejected`,
694
+ `tts-worker-runtime-import-rejected`, and
695
+ `tts-worker-model-load-rejected`. Use uses exactly
696
+ `stt-transcription-started`, `stt-transcription-completed`,
697
+ `stt-transcription-cancelled`, `stt-transcription-engine-operation-rejected`,
698
+ `tts-synthesis-started`, `tts-synthesis-completed`,
699
+ `tts-synthesis-cancelled`, and `tts-synthesis-engine-operation-rejected`; a stable underlying
700
+ reason replaces only the matching failure fallback. Orderly Worker teardown
701
+ additionally uses `stt-unload-completed`,
702
+ `tts-unload-completed`, `stt-worker-engine-dispose-rejected`,
703
+ `tts-worker-engine-dispose-rejected`, `stt-dispose-unloaded-worker`,
704
+ `tts-dispose-unloaded-worker`, `stt-dispose-completed`, and
705
+ `tts-dispose-completed`. A Worker protocol cancel result is
706
+ `stt-cancel-target-not-active` or `tts-cancel-target-not-active` when its target
707
+ is already settled; otherwise it names the exact cancelled load, transcription,
708
+ synthesis, status, unload, or dispose operation.
709
+
710
+ The Worker client admits only `load`, `use`, `status`, `unload`, and `dispose`.
711
+ The transport host additionally admits only its internal `cancel` control
712
+ message. Any other `op` rejects as `ARCANE_AI_INVALID_REQUEST` with
713
+ message `The speech worker operation is not part of its protocol.` and reason
714
+ `stt-worker-operation-unknown` or `tts-worker-operation-unknown` before it can
715
+ enter active-operation, cancellation, or serialized-error naming.
716
+
717
+ Progress records have exactly `{phase,completed,total,unit,heartbeat}`. They do
718
+ not add a `role` field; Worker phase names carry the role and the provider
719
+ forwards the same provider-neutral record. Graph store phases are exactly
720
+ `artifact-graph-network-download`,
721
+ `artifact-graph-dbopfs-persisted-rehash`, and
722
+ `artifact-graph-dbopfs-cache-rehash`. Worker phases are
723
+ `stt-runtime-import-started`, `tts-runtime-import-started`,
724
+ `stt-model-load-started`, `tts-model-load-started`,
725
+ `stt-model-load-progress`, `tts-model-load-progress`,
726
+ `stt-provider-ready`, and `tts-provider-ready`.
727
+
728
+ `load(context)` requires the exact role/selection and a `progress` function.
729
+ Compatible concurrent loads coalesce behind one underlying preparation/Worker
730
+ operation while each caller keeps its own progress callback and cancellation
731
+ observation. Cancelling or throwing from the last observer aborts that shared
732
+ load; one observer cannot cancel another observer that remains attached. A
733
+ security change unloads and reloads; graph verification itself can never be
734
+ weakened. `request(context)` admits one role operation at a time and otherwise
735
+ rejects with `ARCANE_AI_PROVIDER_BUSY`.
736
+
737
+ An `AbortSignal` before or during graph preparation/load rejects that exact
738
+ operation. Once a Worker request has started, cancellation terminates the whole
739
+ role Worker, rejects every pending operation, closes private ports, terminates
740
+ nested Workers, discards that isolated Worker's guards and runtime settings,
741
+ revokes object URLs, and returns the provider to `unloaded`; a later request
742
+ requires explicit load. `unload()` uses the same destructive boundary for
743
+ active load/use before
744
+ releasing state. `dispose()` completes unload and permanently enters
745
+ `disposed`. Cancelling an `unload()` or `dispose()` caller rejects only that
746
+ caller's wait with the corresponding stable reason; the already-started shared
747
+ teardown continues to its terminal state. Late results are generation-checked
748
+ and cannot settle a superseded operation.
749
+
750
+ The provider owns no listener registry or event source. Applications may
751
+ project its promises, status, and progress records into the one SDK event/state
752
+ authority, and DOM components may project presentation/intent events, but this
753
+ package creates no `EventPubSub`, exposes no public `EventTarget` lifecycle
754
+ source, and owns no competing listener registry or event bus.
755
+
756
+ The provider also has no implicit mute flag. A higher-level shared state owner
757
+ can implement mute by awaiting the role's explicit `unload()` and unmute by
758
+ performing an explicit `load()`; it projects the returned status and progress
759
+ without inventing a second provider, listener registry, or background load.
760
+
761
+ ## Stable errors and reasons
762
+
763
+ Operational graph, provider, client, and Worker rejections expose a stable
764
+ `code` and a concise `reason` naming the failed operation or boundary.
765
+ `ARCANE_AI_REQUEST_ABORTED` errors use `name:"AbortError"`; an arbitrary
766
+ caller `AbortSignal.reason` is retained only as `cause` and cannot replace the
767
+ stable public reason. Immediate JavaScript API-shape misuse can instead throw a
768
+ plain `TypeError` before an operation exists.
769
+
770
+ ### Compatibility and provider codes
771
+
772
+ Worker failures cross the private port only in the exact envelope
773
+ `{protocol:"arcane-ai-speech-worker-error/1",code,message,reason}`. Its own-key
774
+ set must be exactly `code,message,protocol,reason`, every field must be a data
775
+ property rather than an accessor, `protocol` and `message` must match the
776
+ registered code, and the finite registered code/reason pair must match the
777
+ pending role and operation. A missing field, extra string or symbol key,
778
+ foreign value, accessor, or cross-role/cross-operation pairing is rejected and
779
+ the role Worker is terminated; arbitrary third-party error fields never become
780
+ public authority.
781
+
782
+ | Code | Exact caller-visible reasons |
783
+ | --- | --- |
784
+ | `ARCANE_AI_MODEL_AUTHORITY_REQUIRED` | `stt-provider-inspection-selection-authority-mismatch`, `tts-provider-inspection-selection-authority-mismatch`, `stt-load-selection-authority-mismatch`, `tts-load-selection-authority-mismatch`, `stt-provider-request-selection-authority-mismatch`, `tts-provider-request-selection-authority-mismatch`, `stt-provider-unload-selection-authority-mismatch`, `tts-provider-unload-selection-authority-mismatch`, `stt-provider-dispose-selection-authority-mismatch`, `tts-provider-dispose-selection-authority-mismatch`, `stt-transcription-model-authority-missing`, `stt-transcription-model-authority-mismatch`, `tts-synthesis-model-authority-missing`, `tts-synthesis-model-authority-mismatch`, `tts-synthesis-voice-not-declared` |
785
+ | `ARCANE_AI_INVALID_REQUEST` | The exact context/payload and Worker reasons enumerated immediately below this table. |
786
+ | `ARCANE_AI_NOT_READY` | `stt-provider-request-not-ready`, `tts-provider-request-not-ready`, `stt-transcription-rejected-before-load`, `tts-synthesis-rejected-before-load` |
787
+ | `ARCANE_AI_PROVIDER_BUSY` | `stt-provider-request-already-active`, `tts-provider-request-already-active` |
788
+ | `ARCANE_AI_PROVIDER_DISPOSED` | `stt-provider-load-rejected-after-dispose`, `tts-provider-load-rejected-after-dispose`, `stt-load-rejected-after-dispose`, `tts-load-rejected-after-dispose` |
789
+ | `ARCANE_AI_REQUEST_ABORTED` | `stt-provider-inspection-cancelled`, `tts-provider-inspection-cancelled`, `stt-load-cancelled`, `tts-load-cancelled`, `stt-transcription-cancelled`, `tts-synthesis-cancelled`, `stt-unload-cancelled`, `tts-unload-cancelled`, `stt-dispose-cancelled`, `tts-dispose-cancelled`, `stt-status-cancelled`, `tts-status-cancelled`, `artifact-graph-preparation-cancelled` |
790
+ | `ARCANE_AI_OPERATION_SUPERSEDED` | `stt-load-rejected-during-unload`, `tts-load-rejected-during-unload`, `stt-load-superseded-by-security-change`, `tts-load-superseded-by-security-change`, `stt-load-superseded-by-unload`, `tts-load-superseded-by-unload`, `stt-transcription-superseded-by-unload`, `tts-synthesis-superseded-by-unload`, `stt-worker-terminated-by-unload`, `tts-worker-terminated-by-unload`, `stt-worker-terminated`, `tts-worker-terminated`, `stt-worker-already-terminated`, `tts-worker-already-terminated` |
791
+ | `ARCANE_AI_AUDIO_DECODE_UNAVAILABLE` | `stt-browser-offline-audio-context-unavailable`, `stt-browser-offline-audio-context-construction-rejected`, `stt-browser-audio-decode-method-unavailable` |
792
+ | `ARCANE_AI_AUDIO_DECODE_FAILED` | `stt-browser-audio-decode-operation-rejected`, `stt-browser-decoded-audio-not-object`, `stt-browser-decoded-audio-sample-rate-mismatch`, `stt-browser-decoded-audio-frame-length-not-safe-integer`, `stt-browser-decoded-audio-empty`, `stt-browser-decoded-audio-channel-count-not-safe-integer`, `stt-browser-decoded-audio-channel-count-zero`, `stt-browser-decoded-audio-get-channel-data-not-function`, `stt-browser-decoded-audio-channel-read-rejected`, `stt-browser-decoded-audio-channel-not-float32-array`, `stt-browser-decoded-audio-channel-length-mismatch`, `stt-browser-decoded-audio-sample-non-finite` |
793
+ | `ARCANE_AI_UNSUPPORTED_RESPONSE_FORMAT` | `tts-synthesis-response-format-not-wav` |
794
+ | `ARCANE_AI_INVALID_PROVIDER_RESULT` | `stt-transcription-result-text-not-string`, `tts-synthesis-result-not-object`, `tts-synthesis-result-audio-not-float32-array`, `tts-synthesis-result-sample-rate-mismatch`, `tts-synthesis-result-audio-empty`, `tts-synthesis-result-wav-byte-length-overflow`, `tts-synthesis-pcm-result-not-float32array`, `tts-synthesis-pcm-sample-non-finite` |
795
+ | `ARCANE_AI_PROVIDER_LOAD_FAILED` | `stt-provider-load-rejected`, `tts-provider-load-rejected` |
796
+ | `ARCANE_AI_PROVIDER_REQUEST_FAILED` | `stt-worker-runtime-configuration-rejected`, `tts-worker-runtime-configuration-rejected`, `stt-worker-runtime-import-rejected`, `tts-worker-runtime-import-rejected`, `stt-worker-model-load-rejected`, `tts-worker-model-load-rejected`, `stt-transcription-engine-operation-rejected`, `tts-synthesis-engine-operation-rejected`, `stt-worker-engine-dispose-rejected`, `tts-worker-engine-dispose-rejected`, `stt-worker-dispose-rejected`, `tts-worker-dispose-rejected`, `stt-worker-status-rejected`, `tts-worker-status-rejected` |
797
+ | `ARCANE_AI_PROVIDER_UNAVAILABLE` | `speech-worker-fetch-unavailable`, `artifact-graph-fetch-constructor-unavailable`, `artifact-graph-negative-response-constructor-unavailable`, `artifact-graph-module-worker-constructor-unavailable`, `artifact-graph-onnx-wasm-pair-not-materialized`, `transformers-env-backends-onnx-wasm-unavailable`, `transformers-env-allow-local-models-unavailable`, `transformers-env-allow-local-models-assignment-rejected`, `transformers-env-allow-remote-models-unavailable`, `transformers-env-allow-remote-models-assignment-rejected`, `transformers-env-browser-cache-unavailable`, `transformers-env-browser-cache-assignment-rejected`, `transformers-env-fs-cache-unavailable`, `transformers-env-fs-cache-assignment-rejected`, `transformers-env-custom-cache-toggle-unavailable`, `transformers-env-custom-cache-toggle-assignment-rejected`, `transformers-env-custom-cache-unavailable`, `transformers-env-custom-cache-assignment-rejected`, `transformers-env-wasm-paths-unavailable`, `transformers-env-wasm-paths-assignment-rejected`, `transformers-env-num-threads-unavailable`, `transformers-env-num-threads-assignment-rejected`, `transformers-whisper-pipeline-export-missing`, `stt-transcription-method-unavailable`, `kokoro-env-wasm-paths-unavailable`, `kokoro-env-wasm-paths-assignment-rejected`, `kokoro-tts-constructor-export-missing`, `tts-synthesis-method-unavailable` |
798
+ | `ARCANE_AI_LOAD_PROGRESS_CALLBACK_THREW` | `stt-load-progress-callback-threw`, `tts-load-progress-callback-threw` at the public provider observer boundary |
799
+ | `ARCANE_AI_PROGRESS_CALLBACK_THREW` | `stt-load-progress-callback-threw`, `tts-load-progress-callback-threw` at the internal Worker-client callback boundary |
800
+ | `ARCANE_AI_ADAPTER_PROTOCOL_MISMATCH` | `stt-worker-client-authority-mismatch`, `tts-worker-client-authority-mismatch`, `stt-worker-protocol-mismatch`, `tts-worker-protocol-mismatch`, `artifact-graph-private-message-port-established-too-late`, `stt-worker-runtime-transport-mode-mismatch`, `tts-worker-runtime-transport-mode-mismatch` |
801
+ | `ARCANE_AI_WORKER_CRASHED` | `stt-worker-crashed`, `tts-worker-crashed` |
802
+ | `ARCANE_AI_WORKER_MESSAGE_ERROR` | `stt-worker-message-rejected`, `tts-worker-message-rejected`, `stt-worker-private-message-rejected`, `tts-worker-private-message-rejected`, `stt-worker-progress-envelope-rejected`, `tts-worker-progress-envelope-rejected`, `stt-worker-response-envelope-shape-rejected`, `tts-worker-response-envelope-shape-rejected`, `stt-worker-error-envelope-rejected`, `tts-worker-error-envelope-rejected`, `stt-load-message-rejected`, `tts-load-message-rejected`, `stt-transcription-message-rejected`, `tts-synthesis-message-rejected`, `stt-status-message-rejected`, `tts-status-message-rejected`, `stt-unload-message-rejected`, `tts-unload-message-rejected`, `stt-dispose-message-rejected`, `tts-dispose-message-rejected`, `artifact-graph-private-message-channel-unavailable`, `artifact-graph-private-message-port-unavailable`, `artifact-graph-module-worker-error-envelope-rejected`, `artifact-graph-module-worker-initialization-message-rejected` |
803
+ | `ARCANE_AI_WORKER_MESSAGE_REJECTED` | `stt-worker-error-envelope-rejected`, `tts-worker-error-envelope-rejected` when the provider rejects a foreign or missing SDK error brand |
804
+ | `ARCANE_AI_UNDECLARED_ARTIFACT` | `speech-worker-artifact-request-method-rejected`, `speech-worker-artifact-request-undeclared`, `speech-worker-cache-open-rejected`, `speech-worker-cache-match-rejected`, `artifact-graph-cache-write-rejected`, `artifact-graph-runtime-request-url-malformed` |
805
+ | `ARCANE_AI_ARTIFACT_GRAPH_CONFIGURATION_INVALID` | The exact Worker graph-configuration reasons enumerated below. |
806
+ | `ARCANE_AI_ARTIFACT_GRAPH_FETCH_EDGE_UNDECLARED` | `artifact-graph-fetch-edge-undeclared`, `artifact-graph-fetch-edge-undeclared-inactive-runtime-branch-entered`, `artifact-graph-fetch-method-undeclared`, `artifact-graph-fetch-target-undeclared`, `artifact-graph-fetch-guard-bypassed` |
807
+ | `ARCANE_AI_ARTIFACT_GRAPH_IMPORT_EDGE_UNDECLARED` | `artifact-graph-dynamic-import-edge-undeclared`, `artifact-graph-dynamic-import-edge-undeclared-inactive-runtime-branch-entered`, `artifact-graph-dynamic-import-target-undeclared` |
808
+ | `ARCANE_AI_ARTIFACT_GRAPH_CACHE_EDGE_UNDECLARED` | `artifact-graph-cache-open-edge-undeclared`, `artifact-graph-cache-open-edge-undeclared-inactive-runtime-branch-entered`, `artifact-graph-cache-name-mismatch`, `artifact-graph-cache-read-target-undeclared`, `artifact-graph-cache-open-guard-bypassed`, `artifact-graph-cache-match-guard-bypassed` |
809
+ | `ARCANE_AI_ARTIFACT_GRAPH_WORKER_EDGE_UNDECLARED` | `artifact-graph-module-worker-edge-undeclared`, `artifact-graph-module-worker-edge-undeclared-inactive-runtime-branch-entered`, `artifact-graph-module-worker-target-undeclared`, `artifact-graph-module-worker-type-mismatch` |
810
+ | `ARCANE_AI_ARTIFACT_GRAPH_ISOLATION_UNAVAILABLE` | The exact guard, typed-array, dynamic-code, timer, storage, and denied-capability reasons enumerated below. |
811
+
812
+ The exact provider context reasons under `ARCANE_AI_INVALID_REQUEST` are:
813
+
814
+ | Boundary | Exact reasons |
815
+ | --- | --- |
816
+ | Context is not an object | `stt-provider-inspection-context-not-object`, `tts-provider-inspection-context-not-object`, `stt-provider-load-context-not-object`, `tts-provider-load-context-not-object`, `stt-provider-request-context-not-object`, `tts-provider-request-context-not-object`, `stt-provider-unload-context-not-object`, `tts-provider-unload-context-not-object`, `stt-provider-dispose-context-not-object`, `tts-provider-dispose-context-not-object` |
817
+ | Role does not match | `stt-provider-inspection-role-mismatch`, `tts-provider-inspection-role-mismatch`, `stt-provider-load-role-mismatch`, `tts-provider-load-role-mismatch`, `stt-provider-request-role-mismatch`, `tts-provider-request-role-mismatch`, `stt-provider-unload-role-mismatch`, `tts-provider-unload-role-mismatch`, `stt-provider-dispose-role-mismatch`, `tts-provider-dispose-role-mismatch` |
818
+ | Signal is not an `AbortSignal` | `stt-provider-inspection-signal-not-abort-signal`, `tts-provider-inspection-signal-not-abort-signal`, `stt-provider-load-signal-not-abort-signal`, `tts-provider-load-signal-not-abort-signal`, `stt-provider-request-signal-not-abort-signal`, `tts-provider-request-signal-not-abort-signal`, `stt-provider-unload-signal-not-abort-signal`, `tts-provider-unload-signal-not-abort-signal`, `stt-provider-dispose-signal-not-abort-signal`, `tts-provider-dispose-signal-not-abort-signal` |
819
+ | Lifecycle context property read rejects | `stt-provider-unload-context-read-rejected`, `tts-provider-unload-context-read-rejected`, `stt-provider-dispose-context-read-rejected`, `tts-provider-dispose-context-read-rejected` |
820
+
821
+ The other exact provider request reasons under `ARCANE_AI_INVALID_REQUEST` are
822
+ `stt-load-progress-callback-not-function`,
823
+ `tts-load-progress-callback-not-function`, `stt-provider-operation-mismatch`,
824
+ `tts-provider-operation-mismatch`, `stt-transcription-payload-not-object`,
825
+ `tts-synthesis-payload-not-object`, and these eight structural payload reasons:
826
+ `stt-transcription-payload-not-plain-object`,
827
+ `stt-transcription-payload-field-unknown`,
828
+ `stt-transcription-payload-accessor-rejected`,
829
+ `stt-transcription-payload-required-field-missing`,
830
+ `tts-synthesis-payload-not-plain-object`,
831
+ `tts-synthesis-payload-field-unknown`,
832
+ `tts-synthesis-payload-accessor-rejected`, and
833
+ `tts-synthesis-payload-required-field-missing`.
834
+
835
+ The remaining exact provider input reasons are
836
+ `stt-transcription-blob-constructor-unavailable`,
837
+ `stt-transcription-audio-not-blob-or-file`,
838
+ `stt-transcription-audio-blob-empty`,
839
+ `stt-transcription-mime-type-not-string`,
840
+ `stt-transcription-mime-type-malformed`,
841
+ `stt-transcription-audio-blob-mime-type-not-string`,
842
+ `stt-transcription-audio-blob-mime-type-malformed`,
843
+ `stt-transcription-audio-blob-mime-type-mismatch`,
844
+ `stt-transcription-audio-not-float32-array`,
845
+ `stt-transcription-pcm-input-empty`,
846
+ `stt-transcription-sample-rate-mismatch`,
847
+ `stt-transcription-pcm-sample-non-finite`, `tts-synthesis-text-empty`,
848
+ `tts-synthesis-voice-empty`, and `tts-synthesis-speed-out-of-range`.
849
+
850
+ The exact Worker configuration/input reasons under
851
+ `ARCANE_AI_INVALID_REQUEST` are `speech-worker-configuration-missing`,
852
+ `speech-worker-runtime-selection-mismatch`, `speech-worker-model-id-empty`,
853
+ `speech-worker-model-repository-empty`, `speech-worker-model-revision-empty`,
854
+ `speech-worker-model-dtype-empty`, `speech-worker-runtime-entry-empty`,
855
+ `speech-worker-materialized-files-missing`,
856
+ `speech-runtime-entrypoint-not-materialized`,
857
+ `speech-model-sample-rate-not-positive-safe-integer`,
858
+ `tts-default-voice-empty`, `artifact-graph-materialized-file-not-object`,
859
+ `artifact-graph-materialized-path-empty`,
860
+ `artifact-graph-materialized-source-url-empty`,
861
+ `artifact-graph-materialized-module-url-empty`,
862
+ `artifact-graph-materialized-media-type-empty`,
863
+ `artifact-graph-runtime-request-routes-not-array`,
864
+ `artifact-graph-materialized-path-ambiguous`,
865
+ `artifact-graph-entrypoint-not-materialized`,
866
+ `stt-worker-message-envelope-shape-rejected`,
867
+ `tts-worker-message-envelope-shape-rejected`,
868
+ `stt-worker-operation-unknown`, `tts-worker-operation-unknown`,
869
+ `stt-transcription-audio-not-float32array`,
870
+ `stt-transcription-sample-rate-mismatch`, `tts-synthesis-text-empty`,
871
+ `tts-synthesis-voice-empty`, `tts-synthesis-voice-not-declared`, and
872
+ `tts-synthesis-speed-out-of-range`.
873
+
874
+ The exact `ARCANE_AI_ARTIFACT_GRAPH_CONFIGURATION_INVALID` reasons are
875
+ `artifact-graph-worker-configuration-incomplete`,
876
+ `artifact-graph-onnx-wasm-configuration-mismatch`,
877
+ `artifact-graph-kokoro-voice-inventory-missing`,
878
+ `artifact-graph-negative-request-routes-not-array`,
879
+ `artifact-graph-runtime-request-route-ambiguous`,
880
+ `artifact-graph-module-worker-target-not-materialized`,
881
+ `artifact-graph-transform-kind-not-admitted`,
882
+ `artifact-graph-transform-module-path-empty`,
883
+ `artifact-graph-transform-occurrence-not-positive-safe-integer`,
884
+ `artifact-graph-transform-identity-ambiguous`,
885
+ `artifact-graph-typed-array-constructor-transform-undeclared`,
886
+ `artifact-graph-transformers-cache-edge-ambiguous`,
887
+ `kokoro-env-num-threads-field-not-exposed`, and
888
+ `transformers-env-num-threads-not-positive-safe-integer`. For each exact edge
889
+ subject `cache-open`, `dynamic-import`, `fetch`, and `module-worker`, that code
890
+ also admits the six exact endings `edges-not-array`, `edge-not-object`,
891
+ `edge-module-path-empty`, `edge-occurrence-not-positive-safe-integer`,
892
+ `edge-policy-not-admitted`, and `edge-identity-ambiguous`, joined as
893
+ `artifact-graph-{subject}-{ending}`.
894
+
895
+ `browser-speech-provider-error-reason-unmapped` is a fail-closed internal
896
+ sentinel for a provider error constructed without a mapped reason. No current
897
+ public operation deliberately selects it.
271
898
 
272
- ## Errors
899
+ The published legacy authority/store code-to-reason pairs remain:
273
900
 
274
- | Code | Meaning |
901
+ | Code | Exact compatibility reason |
275
902
  | --- | --- |
276
- | `ARCANE_AI_MODEL_AUTHORITY_REQUIRED` | Selection or authority does not match the provider. |
277
- | `ARCANE_AI_INVALID_REQUEST` | Role, operation, audio, text, voice, or speed is invalid. |
278
- | `ARCANE_AI_NOT_READY` | Use was requested before a successful load. |
279
- | `ARCANE_AI_PROVIDER_BUSY` | The role already has an active request. |
280
- | `ARCANE_AI_PROVIDER_DISPOSED` | Load/use was requested after disposal. |
281
- | `ARCANE_AI_REQUEST_ABORTED` | The caller's AbortSignal cancelled work. |
282
- | `ARCANE_AI_OPERATION_SUPERSEDED` | A newer unload/load/dispose generation replaced the operation. |
283
- | `ARCANE_AI_ARTIFACT_SOURCE_INVALID` | Artifact bytes or body shape are not readable. |
284
- | `ARCANE_AI_ARTIFACT_SOURCE_UNAVAILABLE` | Fetch is unavailable. |
285
- | `ARCANE_AI_ARTIFACT_DOWNLOAD_FAILED` | Network or HTTP download failed. |
286
- | `ARCANE_AI_ARTIFACT_SOURCE_CHANGED` | Redirect/final URL differs from the admitted immutable URL. |
287
- | `ARCANE_AI_ARTIFACT_SIZE_MISMATCH` | Enabled byte-length evidence differs. |
288
- | `ARCANE_AI_ARTIFACT_DIGEST_MISMATCH` | Enabled SHA-256 evidence differs. |
289
- | `ARCANE_AI_ARTIFACT_CACHE_REJECTED` | DBOPFS did not preserve the completed file. |
290
- | `ARCANE_AI_ARTIFACT_OFFLINE_MISS` | Strict offline use has no admitted complete cache. |
291
- | `ARCANE_AI_STORAGE_BUSY` | Another context owns the same authority lock. |
292
- | `ARCANE_AI_STORAGE_UNAVAILABLE` / `ARCANE_AI_STORAGE_READ_FAILED` / `ARCANE_AI_STORAGE_DELETE_FAILED` | The selected DBOPFS store is unavailable or could not read/delete the authority. |
293
- | `ARCANE_AI_RUNTIME_MODULE_GRAPH_UNDECLARED` | Runtime code leaves the closed self-contained module grammar. |
294
- | `ARCANE_AI_WORKER_CRASHED` / `ARCANE_AI_WORKER_MESSAGE_ERROR` | The isolated adapter Worker failed. |
295
- | `ARCANE_AI_ADAPTER_PROTOCOL_MISMATCH` | The Worker/client/provider protocol is not the SDK-owned contract. |
296
- | `ARCANE_AI_PROVIDER_LOAD_FAILED` / `ARCANE_AI_PROVIDER_REQUEST_FAILED` / `ARCANE_AI_PROVIDER_UNAVAILABLE` | Adapter load/request failed or the admitted runtime did not expose the requested provider. |
297
- | `ARCANE_AI_INVALID_PROVIDER_RESULT` | Whisper/Kokoro returned a result outside the normalized contract. |
298
- | `ARCANE_AI_UNDECLARED_ARTIFACT` | The runtime requested a file outside its admitted closure. |
299
- | `ARCANE_AI_AUDIO_DECODE_UNAVAILABLE` / `ARCANE_AI_AUDIO_DECODE_FAILED` | Shared Blob/File STT cannot obtain a browser decoder or cannot produce nonempty 16 kHz mono PCM. |
300
- | `ARCANE_AI_UNSUPPORTED_RESPONSE_FORMAT` | Shared TTS requested a format other than `wav`. |
301
-
302
- ## Security and ownership
303
-
304
- - The SDK contains no speech model or adapter runtime bytes in this entrypoint.
305
- - The application must verify the license and authority for every supplied byte.
306
- - URLs never carry credentials; fetch uses `credentials: "omit"`.
307
- - Mutable `main`, `master`, and `latest` paths are rejected.
308
- - The completion manifest is consistency evidence for the admitted DBOPFS
309
- bytes. It is not publisher authenticity by itself.
310
- - The runtime entry is isolated in a Worker and limited to its declared
311
- artifact closure.
312
- - Tool calls are an LLM concern; speech providers execute no application tool.
313
- - A native `Arcane.speech` service is a different privileged host path. This
314
- browser package neither calls it nor inherits its capability admission.
315
-
316
- <details>
317
- <summary>Protocol details</summary>
318
-
319
- The artifact store identifies itself as
320
- `arcane-ai-browser-speech-artifacts/1`. Authorities use
321
- `arcane-ai-model-authority/1`. Providers implement `arcane-ai-provider/2` with
322
- roles `stt` and `tts`; their only operations are `transcribe` and `synthesize`.
323
- The Worker client additionally binds exact role, operation, request IDs, and
324
- generation so stale or cross-role messages cannot settle current work.
325
-
326
- </details>
903
+ | `ARCANE_AI_REQUEST_ABORTED` | `browser-speech-artifact-preparation-cancelled` |
904
+ | `ARCANE_AI_STORAGE_BUSY` | `browser-speech-artifact-dbopfs-write-lock-unavailable` |
905
+ | `ARCANE_AI_STORAGE_UNAVAILABLE` | `browser-speech-artifact-dbopfs-table-unavailable` |
906
+ | `ARCANE_AI_STORAGE_DELETE_FAILED` | `browser-speech-artifact-dbopfs-delete-rejected` |
907
+ | `ARCANE_AI_STORAGE_READ_FAILED` | `browser-speech-artifact-dbopfs-read-rejected` |
908
+ | `ARCANE_AI_ARTIFACT_SOURCE_INVALID` | `browser-speech-artifact-source-body-unreadable` |
909
+ | `ARCANE_AI_RUNTIME_MODULE_GRAPH_UNDECLARED` | `browser-speech-runtime-module-graph-undeclared` |
910
+ | `ARCANE_AI_ARTIFACT_SOURCE_UNAVAILABLE` | `browser-speech-artifact-fetch-unavailable` |
911
+ | `ARCANE_AI_ARTIFACT_DOWNLOAD_FAILED` | `browser-speech-artifact-fetch-rejected` |
912
+ | `ARCANE_AI_ARTIFACT_SOURCE_CHANGED` | `browser-speech-artifact-source-redirected` |
913
+ | `ARCANE_AI_ARTIFACT_SIZE_MISMATCH` | `browser-speech-artifact-byte-length-mismatch` |
914
+ | `ARCANE_AI_ARTIFACT_DIGEST_MISMATCH` | `browser-speech-artifact-sha256-mismatch` |
915
+ | `ARCANE_AI_ARTIFACT_CACHE_REJECTED` | `browser-speech-artifact-dbopfs-cache-rejected` |
916
+ | `ARCANE_AI_ARTIFACT_OFFLINE_MISS` | `browser-speech-artifact-offline-cache-miss` |
917
+
918
+ ### Graph reason/code rule
919
+
920
+ Graph construction and store errors use
921
+ `code = "ARCANE_AI_" + reason.toUpperCase().replaceAll("-", "_")`. The
922
+ following closed reason groups therefore define their exact matching codes.
923
+
924
+ The redirect and source-response descriptor/runtime pairs are exactly:
925
+
926
+ | Reason | Code |
927
+ | --- | --- |
928
+ | `artifact-graph-file-source-media-type-missing` | `ARCANE_AI_ARTIFACT_GRAPH_FILE_SOURCE_MEDIA_TYPE_MISSING` |
929
+ | `artifact-graph-file-source-media-type-format-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_FILE_SOURCE_MEDIA_TYPE_FORMAT_MISMATCH` |
930
+ | `artifact-graph-source-redirect-final-origins-not-array` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGINS_NOT_ARRAY` |
931
+ | `artifact-graph-source-redirect-final-origin-inventory-empty` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_INVENTORY_EMPTY` |
932
+ | `artifact-graph-source-redirect-final-origin-text-required` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_TEXT_REQUIRED` |
933
+ | `artifact-graph-source-redirect-final-origin-whitespace-rejected` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_WHITESPACE_REJECTED` |
934
+ | `artifact-graph-source-redirect-final-origin-not-absolute` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_NOT_ABSOLUTE` |
935
+ | `artifact-graph-source-redirect-final-origin-protocol-not-https` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_PROTOCOL_NOT_HTTPS` |
936
+ | `artifact-graph-source-redirect-final-origin-credentials-rejected` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_CREDENTIALS_REJECTED` |
937
+ | `artifact-graph-source-redirect-final-origin-path-rejected` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_PATH_REJECTED` |
938
+ | `artifact-graph-source-redirect-final-origin-query-rejected` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_QUERY_REJECTED` |
939
+ | `artifact-graph-source-redirect-final-origin-fragment-rejected` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_FRAGMENT_REJECTED` |
940
+ | `artifact-graph-source-redirect-final-origin-duplicate` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_DUPLICATE` |
941
+ | `artifact-graph-source-response-url-unreadable` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_RESPONSE_URL_UNREADABLE` |
942
+ | `artifact-graph-source-redirected` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECTED` |
943
+ | `artifact-graph-source-response-url-protocol-not-https` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_RESPONSE_URL_PROTOCOL_NOT_HTTPS` |
944
+ | `artifact-graph-source-response-url-credentials-rejected` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_RESPONSE_URL_CREDENTIALS_REJECTED` |
945
+ | `artifact-graph-source-response-url-fragment-rejected` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_RESPONSE_URL_FRAGMENT_REJECTED` |
946
+ | `artifact-graph-source-redirect-final-origin-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_REDIRECT_FINAL_ORIGIN_MISMATCH` |
947
+ | `artifact-graph-source-response-url-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_SOURCE_RESPONSE_URL_MISMATCH` |
948
+
949
+ When an explicit `sourceMediaType` differs from `mediaType`, a cold response
950
+ mismatch uses the exact file-kind-specific pair below. When the two fields are
951
+ equal, the existing `*-media-type-mismatch` pair applies instead.
952
+
953
+ | Reason | Code |
954
+ | --- | --- |
955
+ | `artifact-graph-entrypoint-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_ENTRYPOINT_SOURCE_MEDIA_TYPE_MISMATCH` |
956
+ | `artifact-graph-runtime-auxiliary-javascript-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_RUNTIME_AUXILIARY_JAVASCRIPT_SOURCE_MEDIA_TYPE_MISMATCH` |
957
+ | `artifact-graph-runtime-wasm-binary-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_RUNTIME_WASM_BINARY_SOURCE_MEDIA_TYPE_MISMATCH` |
958
+ | `artifact-graph-runtime-opaque-data-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_RUNTIME_OPAQUE_DATA_SOURCE_MEDIA_TYPE_MISMATCH` |
959
+ | `artifact-graph-model-configuration-json-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_MODEL_CONFIGURATION_JSON_SOURCE_MEDIA_TYPE_MISMATCH` |
960
+ | `artifact-graph-model-generation-configuration-json-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_MODEL_GENERATION_CONFIGURATION_JSON_SOURCE_MEDIA_TYPE_MISMATCH` |
961
+ | `artifact-graph-model-onnx-binary-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_MODEL_ONNX_BINARY_SOURCE_MEDIA_TYPE_MISMATCH` |
962
+ | `artifact-graph-model-onnx-external-data-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_MODEL_ONNX_EXTERNAL_DATA_SOURCE_MEDIA_TYPE_MISMATCH` |
963
+ | `artifact-graph-model-opaque-data-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_MODEL_OPAQUE_DATA_SOURCE_MEDIA_TYPE_MISMATCH` |
964
+ | `artifact-graph-model-preprocessor-json-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_MODEL_PREPROCESSOR_JSON_SOURCE_MEDIA_TYPE_MISMATCH` |
965
+ | `artifact-graph-model-tokenizer-json-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_MODEL_TOKENIZER_JSON_SOURCE_MEDIA_TYPE_MISMATCH` |
966
+ | `artifact-graph-voice-style-binary-source-media-type-mismatch` | `ARCANE_AI_ARTIFACT_GRAPH_VOICE_STYLE_BINARY_SOURCE_MEDIA_TYPE_MISMATCH` |
967
+
968
+ Descriptor and identity reasons:
969
+
970
+ - `artifact-graph-kind-mismatch`, `artifact-graph-role-not-stt-or-tts`,
971
+ `artifact-graph-provider-id-missing`,
972
+ `artifact-graph-provider-id-length-exceeded`,
973
+ `artifact-graph-model-descriptor-missing`,
974
+ `artifact-graph-runtime-descriptor-missing`,
975
+ `artifact-graph-file-inventory-missing`,
976
+ `artifact-graph-file-descriptor-not-object`,
977
+ `artifact-graph-file-kind-missing`,
978
+ `artifact-graph-file-kind-not-admitted`,
979
+ `artifact-graph-file-path-missing`,
980
+ `artifact-graph-file-path-noncanonical`,
981
+ `artifact-graph-file-revision-missing`,
982
+ `artifact-graph-file-revision-length-exceeded`,
983
+ `artifact-graph-file-byte-length-positive-safe-integer-required`,
984
+ `artifact-graph-file-sha256-missing`,
985
+ `artifact-graph-file-sha256-format-mismatch`,
986
+ `artifact-graph-file-license-missing`,
987
+ `artifact-graph-file-license-whitespace-rejected`,
988
+ `artifact-graph-file-license-not-nfc`,
989
+ `artifact-graph-file-license-control-character-rejected`,
990
+ `artifact-graph-file-license-length-exceeded`,
991
+ `artifact-graph-file-media-type-missing`,
992
+ `artifact-graph-file-media-type-format-mismatch`,
993
+ `artifact-graph-file-source-media-type-missing`,
994
+ `artifact-graph-file-source-media-type-format-mismatch`,
995
+ `artifact-graph-file-identity-ambiguous`,
996
+ `artifact-graph-source-url-missing`,
997
+ `artifact-graph-source-url-mutable`,
998
+ `artifact-graph-source-revision-unbound`,
999
+ `artifact-graph-source-redirect-final-origins-not-array`,
1000
+ `artifact-graph-source-redirect-final-origin-inventory-empty`,
1001
+ `artifact-graph-source-redirect-final-origin-text-required`,
1002
+ `artifact-graph-source-redirect-final-origin-whitespace-rejected`,
1003
+ `artifact-graph-source-redirect-final-origin-not-absolute`,
1004
+ `artifact-graph-source-redirect-final-origin-protocol-not-https`,
1005
+ `artifact-graph-source-redirect-final-origin-credentials-rejected`,
1006
+ `artifact-graph-source-redirect-final-origin-path-rejected`,
1007
+ `artifact-graph-source-redirect-final-origin-query-rejected`,
1008
+ `artifact-graph-source-redirect-final-origin-fragment-rejected`,
1009
+ `artifact-graph-source-redirect-final-origin-duplicate`,
1010
+ `artifact-graph-javascript-media-type-mismatch`,
1011
+ `artifact-graph-wasm-media-type-mismatch`,
1012
+ `artifact-graph-json-media-type-mismatch`,
1013
+ `artifact-graph-entrypoint-path-missing`,
1014
+ `artifact-graph-entrypoint-path-noncanonical`,
1015
+ `artifact-graph-entrypoint-file-kind-mismatch`,
1016
+ `artifact-graph-entrypoint-count-mismatch`,
1017
+ `artifact-graph-entrypoint-revision-mismatch`,
1018
+ `artifact-graph-runtime-adapter-missing`,
1019
+ `artifact-graph-runtime-adapter-role-mismatch`,
1020
+ `artifact-graph-runtime-version-missing`,
1021
+ `artifact-graph-runtime-version-length-exceeded`,
1022
+ `artifact-graph-runtime-revision-missing`,
1023
+ `artifact-graph-runtime-revision-length-exceeded`,
1024
+ `artifact-graph-onnx-wasm-descriptor-missing`,
1025
+ `artifact-graph-onnx-wasm-namespace-missing`,
1026
+ `artifact-graph-onnx-wasm-namespace-role-mismatch`,
1027
+ `artifact-graph-onnx-wasm-file-kind-mismatch`,
1028
+ `artifact-graph-onnx-wasm-num-threads-positive-safe-integer-required`,
1029
+ `artifact-graph-negative-runtime-routes-not-array`,
1030
+ `artifact-graph-negative-runtime-route-duplicate`,
1031
+ `artifact-graph-negative-runtime-route-ambiguous`,
1032
+ `artifact-graph-model-id-missing`,
1033
+ `artifact-graph-model-id-length-exceeded`,
1034
+ `artifact-graph-model-repository-missing`,
1035
+ `artifact-graph-model-repository-length-exceeded`,
1036
+ `artifact-graph-model-revision-missing`,
1037
+ `artifact-graph-model-revision-length-exceeded`,
1038
+ `artifact-graph-model-dtype-missing`,
1039
+ `artifact-graph-model-dtype-length-exceeded`,
1040
+ `artifact-graph-sample-rate-missing`,
1041
+ `artifact-graph-sample-rate-positive-safe-integer-required`,
1042
+ `artifact-graph-default-voice-missing`,
1043
+ `artifact-graph-default-voice-length-exceeded`,
1044
+ `artifact-graph-default-voice-undeclared`,
1045
+ `artifact-graph-stt-voice-authority-declared`,
1046
+ `artifact-graph-stt-voice-file-declared`,
1047
+ `artifact-graph-voice-inventory-missing`,
1048
+ `artifact-graph-voice-descriptor-not-object`,
1049
+ `artifact-graph-voice-id-missing`,
1050
+ `artifact-graph-voice-id-length-exceeded`,
1051
+ `artifact-graph-voice-file-kind-mismatch`,
1052
+ `artifact-graph-voice-inventory-ambiguous`,
1053
+ `artifact-graph-voice-file-undeclared`,
1054
+ `artifact-graph-model-file-inventory-missing`,
1055
+ `artifact-graph-model-file-revision-mismatch`,
1056
+ `artifact-graph-file-unreachable`,
1057
+ `artifact-graph-runtime-request-routes-not-array`,
1058
+ `artifact-graph-runtime-request-url-text-required`,
1059
+ `artifact-graph-runtime-request-url-not-absolute`,
1060
+ `artifact-graph-runtime-request-url-protocol-not-https`,
1061
+ `artifact-graph-runtime-request-url-credentials-rejected`,
1062
+ `artifact-graph-runtime-request-url-fragment-rejected`,
1063
+ `artifact-graph-runtime-request-route-duplicate`,
1064
+ `artifact-graph-runtime-request-route-ambiguous`,
1065
+ `artifact-graph-runtime-request-route-unreachable`,
1066
+ `artifact-graph-negative-runtime-route-unreachable`,
1067
+ `artifact-graph-identity-sha256-text-required`,
1068
+ `artifact-graph-identity-sha256-format-mismatch`, and
1069
+ `artifact-graph-identity-sha256-mismatch`.
1070
+
1071
+ The lower-level field helpers retain the exact reasons
1072
+ `artifact-graph-field-text-required`, `artifact-graph-identifier-missing`,
1073
+ `artifact-graph-identifier-length-exceeded`, and
1074
+ `artifact-graph-positive-safe-integer-required`. A Kokoro thread declaration
1075
+ raises `kokoro-env-num-threads-field-not-exposed` at graph construction; the
1076
+ Worker retains that same reason under its defensive graph-configuration
1077
+ boundary if construction is bypassed.
1078
+
1079
+ Edge and transform reasons:
1080
+
1081
+ - `artifact-graph-edges-not-object`, `artifact-graph-edge-kind-not-admitted`,
1082
+ `artifact-graph-edge-list-not-array`,
1083
+ `artifact-graph-edge-not-object`, `artifact-graph-edge-module-path-missing`,
1084
+ `artifact-graph-edge-module-path-noncanonical`,
1085
+ `artifact-graph-edge-module-path-not-runtime-javascript`,
1086
+ `artifact-graph-edge-occurrence-positive-safe-integer-required`,
1087
+ `artifact-graph-edge-occurrence-duplicate`,
1088
+ `artifact-graph-edge-policy-not-admitted`,
1089
+ `artifact-graph-edge-targets-not-array`,
1090
+ `artifact-graph-edge-target-not-object`,
1091
+ `artifact-graph-edge-target-match-missing`,
1092
+ `artifact-graph-edge-target-match-not-admitted`,
1093
+ `artifact-graph-edge-target-path-missing`,
1094
+ `artifact-graph-edge-target-path-noncanonical`,
1095
+ `artifact-graph-edge-target-path-undeclared`,
1096
+ `artifact-graph-edge-target-specifier-missing`,
1097
+ `artifact-graph-edge-target-duplicate`,
1098
+ `artifact-graph-static-import-specifier-missing`,
1099
+ `artifact-graph-dynamic-import-policy-target-mismatch`,
1100
+ `artifact-graph-module-worker-policy-target-mismatch`,
1101
+ `artifact-graph-module-worker-self-target-mismatch`,
1102
+ `artifact-graph-fetch-method-not-get`,
1103
+ `artifact-graph-fetch-targets-not-array`,
1104
+ `artifact-graph-fetch-target-duplicate`,
1105
+ `artifact-graph-fetch-javascript-target-rejected`,
1106
+ `artifact-graph-fetch-negative-routes-not-array`,
1107
+ `artifact-graph-fetch-negative-route-undeclared`,
1108
+ `artifact-graph-fetch-targets-incomplete`,
1109
+ `artifact-graph-fetch-policy-target-mismatch`,
1110
+ `artifact-graph-cache-name-missing`,
1111
+ `artifact-graph-cache-targets-not-array`,
1112
+ `artifact-graph-cache-target-duplicate`,
1113
+ `artifact-graph-cache-javascript-target-rejected`,
1114
+ `artifact-graph-cache-policy-target-mismatch`,
1115
+ `artifact-graph-transforms-not-array`,
1116
+ `artifact-graph-transform-not-object`,
1117
+ `artifact-graph-transform-kind-not-admitted`, and
1118
+ `artifact-graph-transform-occurrence-duplicate`.
1119
+
1120
+ Runtime scan and materialization reasons:
1121
+
1122
+ - `artifact-graph-runtime-javascript-file-missing`,
1123
+ `artifact-graph-runtime-javascript-utf8-decode-rejected`,
1124
+ `artifact-graph-javascript-block-comment-unterminated`,
1125
+ `artifact-graph-javascript-escape-unterminated`,
1126
+ `artifact-graph-javascript-hexadecimal-escape-malformed`,
1127
+ `artifact-graph-javascript-unicode-code-point-escape-malformed`,
1128
+ `artifact-graph-javascript-unicode-code-point-out-of-range`,
1129
+ `artifact-graph-javascript-unicode-escape-malformed`,
1130
+ `artifact-graph-javascript-quoted-string-line-break-rejected`,
1131
+ `artifact-graph-javascript-quoted-string-unterminated`,
1132
+ `artifact-graph-javascript-regexp-line-break-rejected`,
1133
+ `artifact-graph-javascript-regexp-unterminated`,
1134
+ `artifact-graph-javascript-template-expression-unterminated`,
1135
+ `artifact-graph-javascript-template-literal-unterminated`,
1136
+ `artifact-graph-javascript-escaped-identifier-rejected`,
1137
+ `artifact-graph-runtime-fetch-direct-call-required`,
1138
+ `artifact-graph-runtime-fetch-receiver-not-global`,
1139
+ `artifact-graph-runtime-cache-open-direct-call-required`,
1140
+ `artifact-graph-runtime-cache-open-receiver-not-global`,
1141
+ `artifact-graph-runtime-worker-constructor-call-required`,
1142
+ `artifact-graph-runtime-worker-receiver-not-global`,
1143
+ `artifact-graph-runtime-guard-reference-reserved`,
1144
+ `artifact-graph-runtime-dynamic-code-undeclared`,
1145
+ `artifact-graph-runtime-computed-dynamic-code-undeclared`,
1146
+ `artifact-graph-runtime-constructor-dynamic-code-undeclared`,
1147
+ `artifact-graph-runtime-edge-undeclared`,
1148
+ `artifact-graph-runtime-edge-declaration-unmatched`,
1149
+ `artifact-graph-runtime-edge-occurrence-noncanonical`,
1150
+ `artifact-graph-static-import-specifier-mismatch`,
1151
+ `artifact-graph-static-import-specifier-unresolved`,
1152
+ `artifact-graph-static-import-target-unmaterialized`,
1153
+ `artifact-graph-runtime-static-import-cycle`,
1154
+ `artifact-graph-module-transform-overlap`,
1155
+ `artifact-graph-guard-capability-unavailable`,
1156
+ `artifact-graph-object-url-platform-unavailable`,
1157
+ `artifact-graph-object-url-scheme-not-blob`,
1158
+ `artifact-graph-object-url-identity-ambiguous`,
1159
+ `artifact-graph-object-url-readback-unavailable`,
1160
+ `artifact-graph-object-url-readback-http-status-rejected`,
1161
+ `artifact-graph-object-url-readback-identity-mismatch`,
1162
+ `artifact-graph-object-url-media-type-mismatch`,
1163
+ `artifact-graph-object-url-byte-length-mismatch`, and
1164
+ `artifact-graph-object-url-sha256-mismatch`.
1165
+
1166
+ Source, cache, and security reasons:
1167
+
1168
+ - `artifact-graph-load-security-contract-rejected`,
1169
+ `artifact-graph-source-fetch-rejected`,
1170
+ `artifact-graph-source-http-response-rejected`,
1171
+ `artifact-graph-source-redirected`,
1172
+ `artifact-graph-source-response-url-unreadable`,
1173
+ `artifact-graph-source-response-url-protocol-not-https`,
1174
+ `artifact-graph-source-response-url-credentials-rejected`,
1175
+ `artifact-graph-source-response-url-fragment-rejected`,
1176
+ `artifact-graph-source-redirect-final-origin-mismatch`,
1177
+ `artifact-graph-source-response-url-mismatch`,
1178
+ `artifact-graph-offline-cache-miss`, and
1179
+ `artifact-graph-preparation-cancelled`.
1180
+
1181
+ For an exact file verification failure, `reason` is
1182
+ `artifact-graph-{subject}-{boundary}`. `{subject}` is `entrypoint` for
1183
+ `runtime-entrypoint-javascript`; otherwise it is the exact file kind from the
1184
+ closed kind table. `{boundary}` is one of `media-type-mismatch`,
1185
+ `source-media-type-mismatch`, `byte-length-mismatch`, `sha256-mismatch`,
1186
+ `dbopfs-persisted-byte-length-mismatch`, or
1187
+ `dbopfs-persisted-sha256-mismatch`. `source-media-type-mismatch` occurs only
1188
+ when an explicit `sourceMediaType` differs from `mediaType`; otherwise a source
1189
+ Content-Type mismatch uses `media-type-mismatch`. This expansion produces the
1190
+ exact public code by the rule above; for example,
1191
+ `artifact-graph-entrypoint-sha256-mismatch` becomes
1192
+ `ARCANE_AI_ARTIFACT_GRAPH_ENTRYPOINT_SHA256_MISMATCH`.
1193
+
1194
+ The other exact isolation reasons are
1195
+ `artifact-graph-private-message-port-missing`,
1196
+ `artifact-graph-guard-global-collision`,
1197
+ `artifact-graph-guard-global-definition-rejected`,
1198
+ `artifact-graph-guard-capability-mismatch`,
1199
+ `artifact-graph-fetch-isolation-unavailable`,
1200
+ `artifact-graph-cache-isolation-unavailable`,
1201
+ `speech-worker-fetch-isolation-unavailable`,
1202
+ `speech-worker-cache-isolation-unavailable`,
1203
+ `artifact-graph-typed-array-validation-unavailable`,
1204
+ `artifact-graph-typed-array-constructor-receiver-not-typed-array`,
1205
+ `artifact-graph-typed-array-constructor-intrinsic-mismatch`,
1206
+ `artifact-graph-dynamic-code-constructor-rejected`,
1207
+ `artifact-graph-dynamic-code-constructor-isolation-unavailable`,
1208
+ `artifact-graph-setinterval-isolation-unavailable`,
1209
+ `artifact-graph-settimeout-isolation-unavailable`,
1210
+ `artifact-graph-setinterval-string-callback-rejected`,
1211
+ `artifact-graph-settimeout-string-callback-rejected`,
1212
+ `artifact-graph-indexeddb-isolation-unavailable`, and
1213
+ `artifact-graph-opfs-isolation-unavailable`.
1214
+
1215
+ The denied-capability Worker reasons are
1216
+ `artifact-graph-{capability}-capability-undeclared` and
1217
+ `artifact-graph-{capability}-isolation-unavailable`, where `{capability}` is
1218
+ exactly `broadcastchannel`, `eventsource`, `function`, `rtcpeerconnection`,
1219
+ `shadowrealm`, `sharedworker`, `websocket`, `websocketstream`, `webtransport`,
1220
+ `worker`, `xmlhttprequest`, `eval`, or `importscripts`.
1221
+
1222
+ ## `createBrowserSpeechAuthority()` upstream package mode
1223
+
1224
+ `BROWSER_SPEECH_ARTIFACT_PROTOCOL` remains exactly
1225
+ `arcane-ai-browser-speech-artifacts/1`, and
1226
+ `createBrowserSpeechAuthority({providerId,role,model,runtime,security})`
1227
+ loads a caller-selected browser bundle from npm or another upstream package
1228
+ authority. The SDK stores only the downloaded application-selected entrypoint;
1229
+ the runtime uses its normal provider fetch and browser cache behavior. Its
1230
+ optional byte/SHA checks still resolve independently from SDK default to app,
1231
+ provider, and load scopes. Its cache values remain `installed` and `cached`,
1232
+ and its offline miss remains `ARCANE_AI_ARTIFACT_OFFLINE_MISS`.
1233
+
1234
+ The model descriptor is
1235
+ `{id,repository,revision,defaultVoice?,files?}`; `defaultVoice` is required only
1236
+ for `tts`. In default warn-first mode `files` may be omitted so the selected
1237
+ provider downloads its own model and voice assets. The runtime descriptor is
1238
+ `{adapter,version,revision,entry,wasmPaths?,files}` and its adapter is exactly
1239
+ `transformers-whisper` for `stt` or `kokoro-js` for `tts`. Every legacy file is
1240
+ `{path,url,bytes?,sha256?,mediaType?}` with a normalized relative path, a
1241
+ unique immutable URL containing the descriptor revision or SHA-256 identity,
1242
+ and optional positive byte length and SHA-256. Legacy SHA input is normalized
1243
+ to lowercase before validation and projection. `runtime.entry` must
1244
+ name the one `text/javascript` `.js` or `.mjs` runtime file. The frozen result
1245
+ is
1246
+ `{protocol,providerId,modelId,admitted,role,repository,revision,defaultVoice,runtime,files,security}`.
1247
+
1248
+ `runtime.wasmPaths` may name a version-pinned upstream npm CDN directory in
1249
+ warn-first mode. `secure:true` rejects remote `wasmPaths`, requires explicit
1250
+ model files, applies the closed-module scan, and replaces ordinary fetch/cache
1251
+ access with the admitted file map. The authenticated artifact graph remains an
1252
+ advanced strict-control option; it is not required for normal speech use.
1253
+
1254
+ ## Security and ownership summary
1255
+
1256
+ - The SDK redistributes no speech runtime, model, voice, third-party license,
1257
+ or corresponding-source payload. Explicit `load()` resolves them from the
1258
+ caller-selected npm/package/provider authorities.
1259
+ - Default direct-authority and graph operation is warn-first and preserves
1260
+ ordinary upstream Worker capabilities. `secure:true` explicitly opts into
1261
+ strict graph/file verification, private transport, and capability isolation.
1262
+ - Every graph descriptor is caller-selected and binds immutable source,
1263
+ declared length, SHA-256, revision, media type, license declaration, and
1264
+ closed graph identity. Runtime status calls the bytes `verified` only when
1265
+ both byte checks actually complete; warn-first can honestly report
1266
+ `partially-checked` or `unchecked`.
1267
+ - Redirects are rejected by default. An undeclared redirect, an undeclared or
1268
+ non-HTTPS final origin, a mutable starting source authority, an ambiguous
1269
+ path/route, an undeclared transformed code/data edge, a graph-cache write, or
1270
+ an incomplete offline closure fails closed. Explicit `secure:true` also
1271
+ rejects raw network and other undeclared Worker capabilities.
1272
+ - DBOPFS is the graph's sole SDK-owned durable artifact store. Declared graph
1273
+ routes resolve to local object bytes and an exact read-only cache facade;
1274
+ default warn-first mode does not claim to deny every ordinary upstream
1275
+ browser capability.
1276
+ - Runtime/model/sample-rate/default-voice/voice inventory and optional
1277
+ Transformers thread count remain caller authority. No hardware heuristic,
1278
+ hidden fallback, startup download, native/Core call, or cloud retry is added.
1279
+ - A completion manifest and SHA-256 prove consistency with the caller's graph;
1280
+ they are not independent publisher authenticity or complete license evidence.
1281
+ - The component resolution record is
1282
+ `browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json`; the detailed
1283
+ package/model audit is `docs/reference/ai/browser-speech-package-authority.json`.
1284
+ - Upstream publishers remain responsible for their distributed package and
1285
+ provider assets. Applications own selected model/voice policy and integration
1286
+ with the one shared SDK state/event owner.
327
1287
 
328
1288
  ## Related
329
1289
 
@@ -331,6 +1291,5 @@ generation so stale or cross-role messages cannot settle current work.
331
1291
  - [Browser-WASM LLM](browser-wasm.md)
332
1292
  - [AIProviderRuntime.js](../runtime-modules.md#aiproviderruntimejs)
333
1293
  - [AIRuntimeState.js](../runtime-modules.md#airuntimestatejs)
334
- - [PersistentAIChatSession.js](../runtime-modules.md#persistentaichatsessionjs)
335
1294
  - [Availability and normalization](../availability-and-normalization.md)
336
1295
  - [Protocol architecture](../protocols.md#portable-ai-provider-runtime)