arcane-os 0.3.0 → 0.3.2

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 (153) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +86 -117
  3. package/bin/arcane-test.mjs +170 -46
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +887 -909
  5. package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
  7. package/browser-runtime/ai/browser-wasm.mjs +24 -35
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
  9. package/browser-runtime/ai/model-controller.mjs +584 -181
  10. package/browser-runtime/ai/speech-worker-client.mjs +8 -146
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
  12. package/browser-runtime/dom-event-instrumentation.mjs +55 -147
  13. package/browser-runtime/event-manager.mjs +239 -624
  14. package/package.json +5 -6
  15. package/runtime/arcane/components/app-bar.html +3 -15
  16. package/runtime/arcane/components/assistant-panel.html +10 -10
  17. package/runtime/arcane/components/calculator.html +1 -1
  18. package/runtime/arcane/components/chat.html +1359 -135
  19. package/runtime/arcane/components/conversation-view.html +2 -2
  20. package/runtime/arcane/components/document-inspector.html +11 -17
  21. package/runtime/arcane/components/file-manager.html +13 -56
  22. package/runtime/arcane/components/markdown-document.html +82 -281
  23. package/runtime/arcane/components/markdown-editor.html +7 -10
  24. package/runtime/arcane/components/media-embed.html +6 -6
  25. package/runtime/arcane/components/screen-capture.html +4 -4
  26. package/runtime/arcane/components/source-explanation.html +2 -2
  27. package/runtime/arcane/components/speech.html +112 -68
  28. package/runtime/arcane/components/terminal-workspace.html +4 -4
  29. package/runtime/arcane/components/theme-editor.html +1 -1
  30. package/runtime/arcane/components/unified-inbox.html +2 -2
  31. package/runtime/arcane/components/voice-transcription.html +31 -21
  32. package/runtime/arcane/entities/Calculation.js +2 -3
  33. package/runtime/arcane/entities/Chat.js +228 -43
  34. package/runtime/arcane/entities/Preference.js +3 -5
  35. package/runtime/arcane/entities/Weather.js +5 -5
  36. package/runtime/arcane/modules/AI.js +1050 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
  38. package/runtime/arcane/modules/AIResponseLength.js +9 -19
  39. package/runtime/arcane/modules/AIRuntimeState.js +109 -72
  40. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
  41. package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
  42. package/runtime/arcane/modules/CalculatorEngine.js +9 -9
  43. package/runtime/arcane/modules/CommunicationAppController.js +3 -7
  44. package/runtime/arcane/modules/ComponentContracts.js +30 -32
  45. package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
  46. package/runtime/arcane/modules/ConversationActionItems.js +26 -59
  47. package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
  48. package/runtime/arcane/modules/ConversationTimebox.js +27 -15
  49. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
  50. package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
  51. package/runtime/arcane/modules/HTMLImport.js +54 -1
  52. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
  53. package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
  54. package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
  55. package/runtime/arcane/modules/MD.js +1 -45
  56. package/runtime/arcane/modules/Mail.js +51 -103
  57. package/runtime/arcane/modules/MailOutbox.mjs +95 -193
  58. package/runtime/arcane/modules/MailTransport.mjs +36 -57
  59. package/runtime/arcane/modules/ModelDefinition.js +22 -106
  60. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
  61. package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
  62. package/runtime/arcane/modules/PreferenceStore.js +102 -30
  63. package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
  64. package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
  65. package/runtime/arcane/modules/ScreenCapture.js +175 -128
  66. package/runtime/arcane/modules/SpeechPlayback.js +46 -149
  67. package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
  68. package/runtime/arcane/modules/ToolCallRouter.js +25 -12
  69. package/runtime/arcane/modules/YouTubeMedia.js +6 -5
  70. package/schemas/arcane-app-bundle.schema.json +13 -78
  71. package/schemas/arcane-app.schema.json +9 -25
  72. package/schemas/arcane-lock.schema.json +18 -151
  73. package/schemas/arcane-package.schema.json +2 -16
  74. package/schemas/native-build-plan.schema.json +119 -122
  75. package/src/app-descriptor.mjs +75 -132
  76. package/src/application-tests.mjs +200 -0
  77. package/src/cli/main.mjs +27 -46
  78. package/src/constants.mjs +3 -4
  79. package/src/dev-server.mjs +30 -324
  80. package/src/doctor.mjs +92 -154
  81. package/src/dom-event-instrumentation.mjs +55 -147
  82. package/src/errors.mjs +2 -3
  83. package/src/event-manager.mjs +239 -624
  84. package/src/event-queue.mjs +3 -3
  85. package/src/import-map.mjs +273 -1028
  86. package/src/index.mjs +14 -16
  87. package/src/installed-sdk-runtime.mjs +40 -62
  88. package/src/integrated-provider-loader.mjs +53 -382
  89. package/src/mail-api.mjs +0 -2
  90. package/src/mail-server.mjs +224 -580
  91. package/src/mail.mjs +4 -10
  92. package/src/native-plan.mjs +163 -598
  93. package/src/native-provider-loader.mjs +104 -1063
  94. package/src/packager/core.mjs +485 -3229
  95. package/src/process.mjs +5 -10
  96. package/src/release-bundle.mjs +292 -2405
  97. package/src/runtime.mjs +76 -396
  98. package/src/scaffold.mjs +30 -80
  99. package/src/sdk-browser-runtime.mjs +70 -626
  100. package/src/source-server.mjs +588 -0
  101. package/src/targets/index.mjs +78 -188
  102. package/src/templates/workspace-template.mjs +19 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +131 -544
  106. package/src/update-check.mjs +26 -64
  107. package/src/workspace-operation-lock.mjs +139 -430
  108. package/src/workspace-runtime.mjs +112 -779
  109. package/src/workspace.mjs +40 -302
  110. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
  111. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
  112. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
  113. package/browser-runtime/ai/internal/sha256.mjs +0 -166
  114. package/docs/architecture.md +0 -344
  115. package/docs/compatibility.md +0 -36
  116. package/docs/event-manager.md +0 -294
  117. package/docs/platform-targets.md +0 -108
  118. package/docs/publishing.md +0 -201
  119. package/docs/reference/README.md +0 -187
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1252
  122. package/docs/reference/ai/browser-wasm.md +0 -530
  123. package/docs/reference/arcane-ollama.md +0 -288
  124. package/docs/reference/availability-and-normalization.md +0 -183
  125. package/docs/reference/behavioral-testing.md +0 -133
  126. package/docs/reference/cli.md +0 -779
  127. package/docs/reference/core/README.md +0 -62
  128. package/docs/reference/core/arcane-ai-contracts.md +0 -907
  129. package/docs/reference/core/arcane-api.md +0 -601
  130. package/docs/reference/core/arcane-entities.md +0 -65
  131. package/docs/reference/core/arcane-events.md +0 -134
  132. package/docs/reference/core/ollama-module.md +0 -181
  133. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
  134. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
  135. package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
  136. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
  137. package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
  138. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
  139. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
  140. package/docs/reference/event-manager.md +0 -1511
  141. package/docs/reference/inventory/package-api.json +0 -3284
  142. package/docs/reference/inventory/runtime-components.json +0 -1011
  143. package/docs/reference/inventory/runtime-entities.json +0 -26
  144. package/docs/reference/inventory/runtime-modules.json +0 -1431
  145. package/docs/reference/mail.md +0 -316
  146. package/docs/reference/protocols.md +0 -677
  147. package/docs/reference/runtime-components.md +0 -1366
  148. package/docs/reference/runtime-entities.md +0 -303
  149. package/docs/reference/runtime-modules.md +0 -2960
  150. package/docs/reference/sdk-api.md +0 -6694
  151. package/docs/roadmap.md +0 -79
  152. package/docs/work-amplification.md +0 -129
  153. package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
@@ -1,316 +0,0 @@
1
- # Mail gateway and durable outbox
2
-
3
- Arcane Mail is a pure-JavaScript SDK path. It does not use WebAssembly: mail is
4
- bounded network and durable-state work, while the Resend credential belongs in
5
- the local Node gateway rather than in browser or WASM bytes.
6
-
7
- ## Ownership and availability
8
-
9
- | Surface | Runtime | Responsibility |
10
- | --- | --- | --- |
11
- | `Mail.js` | Browser or native WebView | Validates and formats reports, persists each exact outbound request in DBOPFS, and owns retry/drain lifecycle. |
12
- | `MailOutbox.mjs` | Browser or compatible injected storage | Stores immutable requests in the `mail_outbox` DBOPFS table before delivery and normalizes terminal, retry, and reconciliation states. |
13
- | `MailTransport.mjs` | Browser, WebView, or compatible Fetch host | Sends one already-persisted request to the configured Arcane gateway with the stable report key as its idempotency key. |
14
- | `arcane mail send` | Node on the local machine | Reads one bounded provider-neutral report from redirected stdin and performs one explicit Resend attempt with a caller-owned idempotency key. |
15
- | `arcane mail serve` | Node on the local machine | Authenticates the local caller, enforces the app/Origin/recipient policy, and makes the single server-side Resend request. |
16
- | `arcane mail key ...` | Node on Windows | Stores, inspects, or deletes a Resend API key in Windows Credential Manager. |
17
-
18
- The browser never receives the Resend API key. The gateway never writes that
19
- key to source, argv, logs, events, fixtures, browser storage, or its public
20
- lifecycle result. Non-Windows credential operations fail closed; there is no
21
- plaintext fallback.
22
-
23
- ## Public npm import
24
-
25
- The 0.3.0 portable programmatic contract is one subpath:
26
-
27
- ```javascript
28
- import Mail,{
29
- MailOutbox,
30
- createMailOutbox,
31
- sendMailReport
32
- } from 'arcane-os/mail';
33
- ```
34
-
35
- `arcane-os/mail` projects `src/mail-api.mjs` and has these exact exports:
36
-
37
- - default and named `Mail`, plus `resolveMailConfig`;
38
- - `MailOutbox`, `createMailOutbox`, `MAIL_OUTBOX_PROTOCOL`,
39
- `MAIL_OUTBOX_TABLE`, `MAIL_OUTBOX_IDEMPOTENCY_WINDOW_MS`,
40
- `MAIL_OUTBOX_STATES`, and `MAIL_OUTBOX_ACCEPTANCE_AUTHORITIES`; and
41
- - `MailTransportError`, `normalizeMailEndpoint`, `serializeMailReport`,
42
- `sendMailReport`, `DEFAULT_MAIL_REQUEST_TIMEOUT_MS`, and
43
- `MAX_MAIL_RESPONSE_BYTES`.
44
-
45
- This entrypoint contains only the portable browser/WebView runtime, outbox, and
46
- transport contract. It does not import the Node HTTP gateway or Windows
47
- Credential Manager adapter. Programmatic developer tooling reaches those
48
- host-owned operations through the existing `createToolchain().mail(...)`
49
- boundary; ordinary operators use `arcane mail send`, `arcane mail serve`, and
50
- `arcane mail key ...`. This keeps Node credential and server authority out of a
51
- browser import while preserving one shared CLI/toolchain implementation.
52
-
53
- ## Two separate credential boundaries
54
-
55
- Arcane Mail deliberately separates two credentials:
56
-
57
- - The **Resend API key** is provider authority. `arcane mail key set <profile>`
58
- stores it in Windows Credential Manager. `mail send --profile <profile>` and
59
- `mail serve --profile <profile>` read it only inside the owning Node process.
60
- - The **mail app key** authenticates one browser/application caller to the
61
- loopback gateway. It is supplied to `mail serve` through hidden terminal
62
- input, or through redirected input with `--app-key-stdin`, and must match the
63
- browser's `arcane.config.mail.appKey`. It is a 16–512 character printable
64
- ASCII bearer-like local admission value, never the Resend API key.
65
-
66
- Do not put either secret on the command line. Command-line arguments may be
67
- recorded by the operating system or shell history. Structured CLI output
68
- requires the matching explicit redirected-input flag and rejects TTY input so
69
- the terminal cannot echo a secret.
70
-
71
- The mail app key is not confidential from scripts executing in the same page:
72
- same-runtime script or XSS can read browser configuration and issue the same
73
- request. Inject it at runtime, never hardcode it in shipped assets, protect the
74
- page's script boundary, restrict the exact gateway destination, and rotate it
75
- when page or process trust is lost. It protects the loopback server from
76
- unadmitted local callers; it is not provider authority or a replacement for
77
- browser application security.
78
-
79
- ## Configure the browser runtime
80
-
81
- One application declares an exact app id, gateway endpoint, and local app key:
82
-
83
- ```javascript
84
- globalThis.arcane = globalThis.arcane || {};
85
- globalThis.arcane.config = globalThis.arcane.config || {};
86
- globalThis.arcane.config.mail = {
87
- appName: 'arcane-dev',
88
- appKey: localMailAppKey,
89
- endpoint: 'http://127.0.0.1:8025/v1/mail'
90
- };
91
- ```
92
-
93
- The transport endpoint must be HTTPS or loopback HTTP at `localhost`,
94
- `127.0.0.1`, or `[::1]`. The SDK CLI gateway itself binds numeric loopback only. An
95
- explicit HTTPS endpoint receives the app key as `X-Mail-Key`, so its ownership
96
- and trust must be verified before configuration. A hosted default is derived
97
- only when the page declares an admitted Arcane mail base domain; otherwise
98
- configuration fails rather than selecting an arbitrary remote host.
99
-
100
- The browser request timeout must be 1,000–600,000 milliseconds. Its default is
101
- 590,000 milliseconds. If overridden, keep it longer than the gateway's
102
- provider timeout plus response margin; equal client/provider deadlines can
103
- discard the gateway's normalized result and force an uncertain retry path. The
104
- transport reads at most 64 KiB of gateway response data.
105
-
106
- ## Durable send semantics
107
-
108
- `Mail.send(to, subject, payload, messageStyle, messageType)` preserves the
109
- existing signature. `messageType` is `error`, `report`, or `crisis_detected`.
110
- Report and crisis mail require at least one recipient; error mail may use the
111
- gateway's configured allowlisted fallback recipients.
112
-
113
- In a browser, the module owns the one `window.mail` singleton. An explicit
114
- `new Mail(config, options)` may configure that owned singleton only before its
115
- durable lifecycle or outbox has begun; later reconfiguration fails with
116
- `MAIL_CONFIGURATION_LOCKED`. `dispose()` clears the global registration only
117
- when that exact instance owns it, so a later construction creates a fresh
118
- instance instead of returning stale disposed state. A truthy `window.mail`
119
- owned by another implementation fails closed with `MAIL_SINGLETON_CONFLICT`;
120
- the SDK never returns or silently adopts an unverified foreign singleton.
121
-
122
- Runtime context enrichment is off by default. With the explicit constructor
123
- option `{includeContext:true}`, every message also captures
124
- `location.pathname` as `source_path`; report and crisis messages load the
125
- current User entity and add its `username`, `email`, `language`, and `phone`
126
- values to the locally rendered content. Those fields are then stored and sent
127
- unencrypted as part of the message, so the application owns consent, purpose,
128
- recipient scope, retention, and disclosure. Without that option, Mail neither
129
- loads the User profile nor adds the path/profile fields. The generated
130
- `source_at` timestamp, caller-supplied payload, subject, type, and recipients
131
- remain part of the requested report in either mode.
132
-
133
- The public `Mail` integration requires its verified DBOPFS adapter. Before the
134
- first delivery attempt, it serializes the exact provider-neutral report and
135
- commits it to DBOPFS table `mail_outbox`. A generated report key
136
- contains only time/random identity and never includes the subject or an email
137
- address. Its random identity requires platform Web Crypto (`randomUUID()` or
138
- `getRandomValues()`); Mail fails with `MAIL_CRYPTO_UNAVAILABLE` instead of
139
- falling back to `Math.random()`. A compatible injected `crypto` provider is a
140
- host/test boundary and must supply cryptographically secure randomness.
141
- Delivery receives the stored serialized bytes and the same report key on every
142
- safe retry.
143
-
144
- `MailTransport.mjs` is also a lower-level public transport and does not persist
145
- raw caller requests by itself. A directly constructed `MailOutbox` can accept
146
- another injected storage adapter plus a Web Locks compatible `lockManager`;
147
- durable claims then belong to that adapter's verified `get`, `set`,
148
- `getAllKeys`, and shared-lock semantics rather than to DBOPFS. The browser
149
- default uses `navigator.locks`; absence of that cross-context authority fails
150
- closed with `MAIL_OUTBOX_LOCK_UNAVAILABLE`.
151
-
152
- Call `await mail.start()` during application startup so pre-existing records
153
- are scanned even when the application does not send a new report. The first
154
- `send()` also starts the lifecycle if needed.
155
-
156
- | Mail method/property | Contract |
157
- | --- | --- |
158
- | `start({signal})` | Idempotently scans/drains startup work and installs one owned online listener. |
159
- | `send(to, subject, payload, style, type)` | Formats, persists, then conditionally attempts one new report and returns a privacy-safe send summary. |
160
- | `drain({reason, signal})` | Runs or joins the one bounded FIFO drain. |
161
- | `listOutbox()` / `getOutboxRecord(reportKey)` | Returns valid durable records, including unredacted serialized report content. Invalid files do not hide valid records. |
162
- | `auditOutbox()` / `invalidOutboxRecords` | Returns the bounded valid inventory and filename/code/repairability metadata for invalid files; it never publishes that metadata as a semantic event. |
163
- | `repairInvalidOutbox(fileName, record)` | Replaces one invalid, correctly named file only after the replacement passes the full record contract. |
164
- | `deleteInvalidOutbox(fileName)` | Explicitly deletes one invalid file after bounded inventory confirmation; it requires a storage adapter with `delete`. |
165
- | `quarantineInvalidOutbox({limit})` | Moves at most 64 confirmed invalid files per call into `mail_outbox_quarantine`; it must retain a bounded serialized snapshot before deleting each original. |
166
- | `stop()` | Removes the online listener and aborts in-flight work owned by this Mail instance; persisted requests and uncertain attempt state remain available for a later restart. |
167
- | `dispose()` | Idempotently stops lifecycle and releases the singleton event source. |
168
- | `events` | Read-only event-source handle for `mail-outbox-state`, `mail-outbox-delivery`, and `mail-outbox-drain`. |
169
-
170
- The returned durable record has one of these states:
171
-
172
- | State | Meaning |
173
- | --- | --- |
174
- | `queued` | Persisted, but no attempt was made, normally because the device is offline. |
175
- | `sending` | An attempt was durably recorded before calling the transport. An interrupted instance recovers this state on the next drain. |
176
- | `retry_wait` | A retryable or uncertain result is retained inside Resend's 24-hour idempotency window. |
177
- | `accepted` | Direct gateway delivery returned both its request id and a Resend provider id, or the exact native `mail-send-v1` bridge returned accepted under named `arcane-core-mail-send-v1` authority without fabricating a provider id. This is API acceptance, not an inbox-delivery claim. |
178
- | `failed` | A permanent failure or expired non-ambiguous retry cannot be retried automatically. |
179
- | `reconciliation_required` | An ambiguous attempt reached the end of the idempotency window. Automatic retry stops to avoid a duplicate send. |
180
-
181
- The outbox owns one bounded FIFO drain per instance and attempts at most 16
182
- records in one default drain. A shared Web Lock extends that single-drain
183
- authority across MailOutbox instances and browser contexts for the same origin
184
- and table. Startup, the browser's `online` event, and explicit calls can trigger
185
- a drain; there are no polling/retry timers. A
186
- future-due `retry_wait` record and records beyond the per-drain bound therefore
187
- require a later startup, connectivity transition, or host-owned manual drain.
188
- Every successful durable write publishes its privacy-safe record transition,
189
- including transitions produced by startup, manual, and online drains.
190
- `dispose()` aborts owned in-flight work, removes the online listener, and
191
- releases the singleton-event registration. A provider attempt interrupted after
192
- it began is retained as an uncertain same-key retry rather than being discarded.
193
- Cancellation that arrives while the durable `sending` transition is being
194
- written restores the prior non-attempted state before returning and never calls
195
- the transport. A restart after `stop()` waits for the cancelled start generation
196
- to settle, then begins a distinct lifecycle generation.
197
-
198
- Each durable record contains the exact unredacted serialized message and the
199
- public list/get APIs return that content. Never place credentials in a report.
200
- Protect the application's OPFS origin and any code allowed to inspect it. The
201
- default outbox cap is 512 records, including terminal records, and enqueue fails
202
- closed at capacity. The outbox exposes no implicit retention/deletion policy;
203
- the owning application must explicitly remove terminal DBOPFS records under an
204
- appropriate audited policy.
205
-
206
- Malformed or unreadable files are excluded from that logical 512-record
207
- capacity, reported through `audit()` / `invalidRecords` on `MailOutbox` and the
208
- Mail proxies above, and skipped without aborting valid listing or draining.
209
- Inventory processing is bounded to `maxRecords + maxInvalidRecords` (512 + 128
210
- by default). If more physical files exist, audit marks `truncated:true` and
211
- enqueue fails with `MAIL_OUTBOX_MAINTENANCE_REQUIRED` until an operator repairs,
212
- deletes, or quarantines bounded batches. This prevents corrupted files from
213
- permanently consuming logical capacity without turning maintenance into
214
- unbounded background work. Quarantine remains local and may contain unredacted
215
- message bytes; protect and retain the quarantine table accordingly. Transient
216
- storage read failures propagate as `MAIL_OUTBOX_STORAGE_FAILED` and never
217
- authorize destructive maintenance. Each maintenance target is revalidated and
218
- serialized against record writes through one origin-wide exclusive table lock;
219
- a file that became valid in another MailOutbox instance or browser context is
220
- preserved, and quarantine refuses deletion when it cannot capture the bounded
221
- source snapshot. Same-key admission, immutable-body comparison, and capacity
222
- checks execute under that table lock before the queued record is committed.
223
- Injected adapters that can share a table must share the same Web Locks
224
- compatible manager and must not mutate MailOutbox-owned records behind that
225
- boundary.
226
-
227
- Mail publishes privacy-minimal semantic events through the SDK singleton event
228
- authority. Public event detail can contain only report identity, state,
229
- attempt/count information, and normalized code/reason fields. Addresses,
230
- subject/body content, headers, credentials, attachment bytes, provider error
231
- text or stacks, provider ids, and private paths are never public event detail.
232
- Listener exceptions are observational and cannot change a committed mail
233
- operation result.
234
-
235
- When both an explicit endpoint and native `Arcane.mail.send` exist, Mail uses
236
- the configured HTTP endpoint so the authenticated SDK gateway can return its
237
- provider acceptance id. The native bridge remains a fallback when no endpoint
238
- is configured; its exact Core result is recorded with the named acceptance
239
- authority above. A malformed or unreadable native response is retained as an
240
- uncertain same-key retry, while temporary native transport unavailability is a
241
- non-ambiguous retryable failure. Once a valid accepted result has returned, a
242
- racing lifecycle cancellation cannot erase that authoritative acceptance.
243
-
244
- ## Operate the CLI and local gateway
245
-
246
- Store one Resend key under a local profile:
247
-
248
- ```text
249
- arcane mail key set arcane-dev
250
- arcane mail key status arcane-dev
251
- arcane mail key delete arcane-dev
252
- ```
253
-
254
- `key set` prompts with hidden input. `--secret-stdin` is the explicit
255
- non-interactive alternative and rejects a TTY.
256
-
257
- Perform one provider attempt directly from the SDK CLI:
258
-
259
- ```text
260
- arcane mail send --profile arcane-dev --from "Arcane <verified@example.com>" --report-key <stable-id> --report-stdin
261
- ```
262
-
263
- The redirected UTF-8 JSON input is bounded to 52 MiB and uses the exact closed
264
- report shape admitted by the gateway: `type`, `to`, `subject`, and at least one
265
- of `text` or `html`. Direct CLI sends require one to 50 explicit recipients,
266
- including for `error` reports. Message content is not accepted in argv and is
267
- never copied into progress events, results, or normalized errors.
268
-
269
- The caller must create and retain the 8–128 character `--report-key` before the
270
- attempt. It is the Resend idempotency key and may be reused only with the same
271
- byte-equivalent logical report for an intentional retry or reconciliation. The
272
- CLI performs exactly one attempt and never retries automatically. Exit zero
273
- requires a successful Resend response containing a valid provider id; that is
274
- provider acceptance, not an inbox-delivery claim. Timeout, connection loss, or
275
- cancellation after the provider attempt begins is returned as an ambiguous
276
- nonzero outcome because the provider may already have accepted the request.
277
- Cancellation before the attempt exits 130 without sending.
278
-
279
- Start the authenticated gateway:
280
-
281
- ```text
282
- arcane mail serve --profile arcane-dev --from "Arcane <verified@example.com>" --app arcane-dev --origin http://127.0.0.1:8000 --allow-to recipient@example.com
283
- ```
284
-
285
- Human output prompts for the separate mail app key with hidden input.
286
- Non-interactive structured output requires `--app-key-stdin` and redirected
287
- stdin. The server binds numeric loopback only; the default is
288
- `127.0.0.1:8025/v1/mail`.
289
-
290
- The gateway admits only:
291
-
292
- - its exact numeric-loopback `Host` authority and `/v1/mail` route;
293
- - an exact configured `Origin`, app id, and constant-time app-key match;
294
- - JSON requests within the configured byte limit;
295
- - one to 50 allowlisted recipients, with server-owned fallback recipients for
296
- error reports (the CLI default sends a recipientless error to every address
297
- in `--allow-to`);
298
- - bounded request-body, send-count, aggregate send-byte, and provider-response
299
- work; and
300
- - one fixed Resend endpoint with the stable Arcane report key forwarded as
301
- `Idempotency-Key`.
302
-
303
- The gateway returns `202` only after Resend returns a valid provider id.
304
- Transport loss, timeout, an invalid success body, or an unreadable/oversized
305
- provider response returns an explicit uncertain result and never claims
306
- delivery. Rate limits, concurrent idempotency requests, permanent validation
307
- failures, and provider failures are mapped to structured retryable/permanent
308
- results without returning provider message text.
309
-
310
- ## Operational verification
311
-
312
- The focused SDK tests use only synthetic keys, addresses, responses, storage,
313
- and loopback requests. They do not contact Resend or send email. A live
314
- acceptance send is a separate operational boundary: use a disposable message,
315
- the real allowlist, and the selected credential profile, then verify both the
316
- gateway's provider-acceptance id and the intended inbox outcome.