@graphorin/server 0.6.1 → 0.7.0

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 (181) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +3 -3
  3. package/dist/app-audit-binding.d.ts +15 -0
  4. package/dist/app-audit-binding.d.ts.map +1 -0
  5. package/dist/app-audit-binding.js +136 -0
  6. package/dist/app-audit-binding.js.map +1 -0
  7. package/dist/app-daemons.d.ts +29 -0
  8. package/dist/app-daemons.d.ts.map +1 -0
  9. package/dist/app-daemons.js +27 -0
  10. package/dist/app-daemons.js.map +1 -0
  11. package/dist/app-lifecycle.js +272 -0
  12. package/dist/app-lifecycle.js.map +1 -0
  13. package/dist/app-metrics.js +79 -0
  14. package/dist/app-metrics.js.map +1 -0
  15. package/dist/app-middleware.js +92 -0
  16. package/dist/app-middleware.js.map +1 -0
  17. package/dist/app-routes.js +131 -0
  18. package/dist/app-routes.js.map +1 -0
  19. package/dist/app-ws.js +65 -0
  20. package/dist/app-ws.js.map +1 -0
  21. package/dist/app.d.ts +16 -16
  22. package/dist/app.d.ts.map +1 -1
  23. package/dist/app.js +35 -607
  24. package/dist/app.js.map +1 -1
  25. package/dist/commentary/built-in-patterns.d.ts +1 -1
  26. package/dist/commentary/built-in-patterns.js +1 -1
  27. package/dist/commentary/built-in-patterns.js.map +1 -1
  28. package/dist/config.d.ts +100 -1
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +30 -2
  31. package/dist/config.js.map +1 -1
  32. package/dist/health/checks.d.ts +15 -1
  33. package/dist/health/checks.d.ts.map +1 -1
  34. package/dist/health/checks.js +21 -0
  35. package/dist/health/checks.js.map +1 -1
  36. package/dist/health/routes.js +1 -1
  37. package/dist/index.d.ts +8 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +19 -24
  40. package/dist/index.js.map +1 -1
  41. package/dist/internal/context.d.ts +2 -2
  42. package/dist/internal/wire-error.js +20 -0
  43. package/dist/internal/wire-error.js.map +1 -0
  44. package/dist/lifecycle/pre-bind.d.ts +1 -1
  45. package/dist/metrics/catalog.d.ts.map +1 -1
  46. package/dist/metrics/catalog.js.map +1 -1
  47. package/dist/metrics/tools-bridge.d.ts +15 -0
  48. package/dist/metrics/tools-bridge.d.ts.map +1 -0
  49. package/dist/metrics/tools-bridge.js +103 -0
  50. package/dist/metrics/tools-bridge.js.map +1 -0
  51. package/dist/middleware/audit.d.ts +1 -1
  52. package/dist/middleware/auth.js +1 -1
  53. package/dist/middleware/csrf.js +1 -1
  54. package/dist/middleware/index.js +1 -1
  55. package/dist/middleware/scope.d.ts.map +1 -1
  56. package/dist/middleware/scope.js +44 -4
  57. package/dist/middleware/scope.js.map +1 -1
  58. package/dist/package.js +1 -1
  59. package/dist/package.js.map +1 -1
  60. package/dist/registry/index.d.ts +33 -0
  61. package/dist/registry/index.d.ts.map +1 -1
  62. package/dist/registry/index.js.map +1 -1
  63. package/dist/replay/routes.d.ts +1 -1
  64. package/dist/replay/routes.js +2 -2
  65. package/dist/routes/agents.d.ts.map +1 -1
  66. package/dist/routes/agents.js +64 -18
  67. package/dist/routes/agents.js.map +1 -1
  68. package/dist/routes/auth.js +2 -2
  69. package/dist/routes/auth.js.map +1 -1
  70. package/dist/routes/sessions.js +5 -5
  71. package/dist/routes/sessions.js.map +1 -1
  72. package/dist/routes/tokens.d.ts +1 -1
  73. package/dist/routes/tokens.d.ts.map +1 -1
  74. package/dist/routes/tokens.js +21 -1
  75. package/dist/routes/tokens.js.map +1 -1
  76. package/dist/routes/workflows.d.ts.map +1 -1
  77. package/dist/routes/workflows.js +165 -19
  78. package/dist/routes/workflows.js.map +1 -1
  79. package/dist/runtime/retention.d.ts +105 -0
  80. package/dist/runtime/retention.d.ts.map +1 -0
  81. package/dist/runtime/retention.js +154 -0
  82. package/dist/runtime/retention.js.map +1 -0
  83. package/dist/runtime/run-state.d.ts +2 -0
  84. package/dist/runtime/run-state.d.ts.map +1 -1
  85. package/dist/runtime/run-state.js +34 -2
  86. package/dist/runtime/run-state.js.map +1 -1
  87. package/dist/sse/events.js +3 -4
  88. package/dist/sse/events.js.map +1 -1
  89. package/dist/tools-audit-bridge.d.ts +29 -0
  90. package/dist/tools-audit-bridge.d.ts.map +1 -0
  91. package/dist/tools-audit-bridge.js +103 -0
  92. package/dist/tools-audit-bridge.js.map +1 -0
  93. package/dist/workflows/timer-daemon.d.ts +69 -0
  94. package/dist/workflows/timer-daemon.d.ts.map +1 -0
  95. package/dist/workflows/timer-daemon.js +37 -0
  96. package/dist/workflows/timer-daemon.js.map +1 -0
  97. package/dist/ws/dispatcher.d.ts +1 -1
  98. package/dist/ws/dispatcher.js +19 -12
  99. package/dist/ws/dispatcher.js.map +1 -1
  100. package/dist/ws/index.d.ts +2 -2
  101. package/dist/ws/index.js +3 -3
  102. package/dist/ws/replay-buffer.d.ts +35 -1
  103. package/dist/ws/replay-buffer.d.ts.map +1 -1
  104. package/dist/ws/replay-buffer.js +35 -3
  105. package/dist/ws/replay-buffer.js.map +1 -1
  106. package/dist/ws/upgrade.d.ts.map +1 -1
  107. package/dist/ws/upgrade.js +24 -18
  108. package/dist/ws/upgrade.js.map +1 -1
  109. package/package.json +30 -29
  110. package/src/app-audit-binding.ts +227 -0
  111. package/src/app-daemons.ts +73 -0
  112. package/src/app-lifecycle.ts +476 -0
  113. package/src/app-metrics.ts +144 -0
  114. package/src/app-middleware.ts +149 -0
  115. package/src/app-routes.ts +305 -0
  116. package/src/app-ws.ts +111 -0
  117. package/src/app.ts +317 -0
  118. package/src/commentary/audit-bridge.ts +135 -0
  119. package/src/commentary/built-in-patterns.ts +79 -0
  120. package/src/commentary/index.ts +32 -0
  121. package/src/commentary/sanitizer.ts +238 -0
  122. package/src/commentary/types.ts +130 -0
  123. package/src/config.ts +472 -0
  124. package/src/consolidator/daemon.ts +141 -0
  125. package/src/consolidator/index.ts +14 -0
  126. package/src/errors/index.ts +247 -0
  127. package/src/health/checks.ts +322 -0
  128. package/src/health/index.ts +34 -0
  129. package/src/health/routes.ts +154 -0
  130. package/src/index.ts +243 -0
  131. package/src/internal/context.ts +77 -0
  132. package/src/internal/ids.ts +17 -0
  133. package/src/internal/json.ts +47 -0
  134. package/src/internal/wire-error.ts +44 -0
  135. package/src/lifecycle/hooks.ts +66 -0
  136. package/src/lifecycle/index.ts +16 -0
  137. package/src/lifecycle/pre-bind.ts +138 -0
  138. package/src/metrics/catalog.ts +112 -0
  139. package/src/metrics/index.ts +12 -0
  140. package/src/metrics/registry.ts +337 -0
  141. package/src/metrics/tools-bridge.ts +124 -0
  142. package/src/middleware/audit.ts +114 -0
  143. package/src/middleware/auth.ts +195 -0
  144. package/src/middleware/cors.ts +72 -0
  145. package/src/middleware/csrf.ts +98 -0
  146. package/src/middleware/idempotency.ts +336 -0
  147. package/src/middleware/index.ts +38 -0
  148. package/src/middleware/rate-limit.ts +71 -0
  149. package/src/middleware/request-state.ts +79 -0
  150. package/src/middleware/scope.ts +161 -0
  151. package/src/registry/index.ts +278 -0
  152. package/src/replay/index.ts +14 -0
  153. package/src/replay/routes.ts +255 -0
  154. package/src/routes/agents.ts +363 -0
  155. package/src/routes/audit.ts +207 -0
  156. package/src/routes/auth.ts +80 -0
  157. package/src/routes/health.ts +42 -0
  158. package/src/routes/index.ts +16 -0
  159. package/src/routes/mcp.ts +97 -0
  160. package/src/routes/memory.ts +152 -0
  161. package/src/routes/sessions.ts +250 -0
  162. package/src/routes/skills.ts +91 -0
  163. package/src/routes/tokens.ts +166 -0
  164. package/src/routes/workflows.ts +616 -0
  165. package/src/runtime/retention.ts +284 -0
  166. package/src/runtime/run-state.ts +422 -0
  167. package/src/sse/events.ts +303 -0
  168. package/src/sse/index.ts +7 -0
  169. package/src/tools-audit-bridge.ts +120 -0
  170. package/src/triggers/daemon.ts +198 -0
  171. package/src/triggers/index.ts +16 -0
  172. package/src/triggers/routes.ts +139 -0
  173. package/src/workflows/timer-daemon.ts +102 -0
  174. package/src/ws/dispatcher.ts +537 -0
  175. package/src/ws/index.ts +45 -0
  176. package/src/ws/replay-buffer.ts +209 -0
  177. package/src/ws/subjects.ts +162 -0
  178. package/src/ws/ticket.ts +174 -0
  179. package/src/ws/upgrade.ts +507 -0
  180. package/dist/lifecycle/index.js +0 -3
  181. package/dist/routes/index.js +0 -12
package/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # @graphorin/server
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-110: both audit.db open paths now apply the CS-7 cipher-selection pragmas before `PRAGMA key`, so `config.audit.cipher` actually selects the cipher (it was silently ignored - every audit.db came out as the sqlite3mc default, and tools opening it with the declared `sqlcipher` failed with SQLITE_NOTADB). The audit default is pinned to `chacha20` (NOT the main store's `sqlcipher`): pre-fix audit files were created without cipher pragmas, i.e. in chacha20 format, so the pin stays byte-compatible with every existing file. Unknown cipher values fail fast. UPGRADE NOTE: a deployment whose config has long carried `audit.cipher: 'sqlcipher'` (previously ignored) will now fail to open its existing chacha20 file - remove the setting or re-encrypt the audit database. `cipherSelectionPragmas` is now exported from `@graphorin/store-sqlite`.
8
+
9
+ - [#153](https://github.com/o-stepper/graphorin/pull/153) [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534) Thanks [@o-stepper](https://github.com/o-stepper)! - Reachable per-thread checkpoint erasure (W-005) - `CheckpointStore.deleteThread` finally has supported callers:
10
+
11
+ - `@graphorin/workflow`: the workflow handle gains `deleteThread(threadId)` next to `listCheckpoints` (idempotent; deletes every checkpoint + pending write of the thread).
12
+ - `@graphorin/server`: new route `DELETE /v1/workflows/:id/threads/:threadId` under scope `workflows:delete:<id>` (204 on success, 404 for an unknown workflow, 400 when the registered entry does not expose `deleteThread`); `ServerWorkflowLike` gains the optional `deleteThread?` member.
13
+ - `@graphorin/agent`: opt-in `AgentConfig.checkpointPolicy: 'keep' | 'delete-on-terminal'` (default `'keep'` - byte-identical to today). With `'delete-on-terminal'` the run's checkpoint thread is best-effort deleted after `completed`/`failed` runs, mirroring the TL-10 spill lifecycle; `awaiting_approval` and `aborted` runs always keep theirs (the thread is the resume state).
14
+
15
+ Full erasure cascades stay the job of the session purge path; these are the operator levers for hygiene and targeted per-thread GDPR requests.
16
+
17
+ - [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-072: every export map's `import` condition becomes `default`, and the Node floor rises to `>=22.12.0`.
18
+
19
+ CJS consumers previously hit a bewildering `ERR_PACKAGE_PATH_NOT_EXPORTED` instead of a clear ESM-only signal. With the `default` condition, plain `require('@graphorin/core')` works via Node's stable `require(esm)` - which shipped in 22.12, hence the engines bump across every workspace manifest (packages, examples, benchmarks, docs; enforced by the widened mvp-readiness sweep). No dual-instance hazard: there is no CJS build, `require()` returns the same ESM module instance. ESM consumers are unaffected (`default` serves both paths; `types` stays first). The pack gate now runs attw under the full `node16` profile (was `esm-only`) and adds a runtime `require(esm)` smoke against the packed tarballs. Installs on Node 22.0-22.11 with `engine-strict` will refuse - upgrade Node (see the migration guide).
20
+
21
+ - [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Unified retention scheduler (W-010 / W-008). The server now runs a periodic sweep (`config.retention`, default every 6 hours, first sweep immediately at startup) over every SQLite growth surface. Default-on for derived/recoverable data with conservative windows: spans older than 30 days, consolidator run counters older than 90 days, exhausted DLQ batches older than 30 days, and expired idempotency records (subsuming the W-061 hourly sweep). Primary user content is strictly opt-in via explicit windows: `sessionsDays` (+ `sessionsClosedOnly`, default true), `auditDays`, `memoryHistoryDays`, and `workflowThreadsDays` (always terminal-only threads). `retention.enabled: false` disables the mechanism entirely. Each surface prunes in its own try/catch: a failure logs WARN and never blocks the others; a sweep that deleted rows logs INFO with per-surface counts through the new `createConsoleRetentionLog` (the first consumer of `observability.logger`). New exports: `scheduleRetentionSweeps`, `RetentionConfig`, `RetentionStoreLike`, `RetentionLog`, `createConsoleRetentionLog`. BEHAVIOUR CHANGE on upgrade: old span telemetry and expired idempotency bodies start being deleted by default - set `retention: { enabled: false }` or widen the windows to keep them. The deployment guide gains a growth-surface table (including the file-based replay-JSONL directory, which stays cron-territory) and lib-mode scheduling guidance.
22
+
23
+ - [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Durable timers now fire without user polling code (W-032). The engine stamps `CheckpointMetadata.wakeAt` (earliest due frontier timer) on suspended checkpoints; `CheckpointStore` gains the optional `listSuspended(namespace, { dueBefore, limit })` enumeration (implemented by the SQLite adapter - migration 032 adds `workflow_checkpoints.wake_at` with a partial index - and by `InMemoryCheckpointStore`); the new `createTimerDriver({ workflows: [{ workflow, checkpointStore }] })` polls due threads and calls `workflow.tick`, re-arming at `min(pollIntervalMs, earliest nextWakeAt)`, with per-thread error isolation and benign handling of cross-process `checkpoint-version-conflict` races. On the server, `createServer({ workflowTimers: { driver } })` binds a lifecycle daemon and reports `checks.workflowTimers` on `/v1/health`. A custom store without `listSuspended` fails fast with `TimerDriverStoreUnsupportedError`. Threads suspended before migration 032 carry no `wake_at` and stay invisible to the driver until one manual `tick` or resume re-persists them.
24
+
25
+ - [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-051: tools/MCP telemetry finally reaches the server's operator surfaces - shadow-mode dataflow findings are observable without hand-wiring.
26
+
27
+ - `@graphorin/tools`: `CounterSnapshot` gains a `kinds` record (`'counter'` vs `'gauge'` per key, tracked at write time) so exporters can bridge monotonic counters with delta-inc and gauges with absolute set.
28
+ - `/v1/metrics`: every scrape folds the live `tool.*` / `mcp.*` counters into the Prometheus registry as lazily-registered `graphorin_tool_*` / `graphorin_mcp_*` series (delta-synced counters - re-scrapes never double-count; absolute gauges; label values sanitized). Histograms are deliberately not bridged yet (documented; raw observations stay available via `snapshotCounters()`).
29
+ - Audit chain: a new bridge subscribes `onToolAudit` to the tamper-evident audit log, governed by `audit.toolEvents`: `'security'` (default - dataflow flagged/blocked/declassified, sanitization, approvals, collisions, cap-disabled), `'all'` (adds per-call `tool:execute:*`), `'off'`. Unsubscribed and drained on shutdown before the audit DB closes. Exported for embedders as `bridgeToolAuditToAudit` / `syncToolCounters`.
30
+ - `@graphorin/server` now depends on `@graphorin/tools` directly (it previously reached tools types only transitively).
31
+
32
+ - [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - `POST /v1/tokens` enforces attenuation-only minting (W-106): a token principal can only mint scopes its own grant covers (`scopeMatches` semantics - `admin:*` covers all, two-segment grants cover three-segment requests, never the reverse), answering `403 scope-escalation-denied` with the uncovered list otherwise; delegation chains narrow monotonically since the child's `tokens:create` must itself be covered. Syntactically invalid requested scopes now answer `400` instead of minting a token that grants nothing. Integrations that minted wider tokens through a bare `tokens:create` service token must grant the minter the full target set (or `admin:*`). The anonymous trusted-loopback operator (auth disabled) is exempt; the CLI path is unaffected.
33
+
34
+ - [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Scope granularity is symmetric across every surface (W-107). Session REST reads (`GET /:id`, `/messages`, `/handoffs`, `/export`, `DELETE`) and the SSE fallback (`GET /v1/sessions/:id/events`) now require per-resource `sessions:<verb>:<sessionId>` - a token scoped to one session gets the same access on REST/SSE it already had on WS, while bare two-segment grants keep covering everything (the global list stays a two-segment administrative read). `POST /v1/session/ws-ticket` requires only authentication (the ticket adds no rights; every subscribe is per-subject gated) - read-only tokens get their browser WS path. Run control binds to the run's OWNING resource on BOTH surfaces: REST `/runs/:runId/{state,abort,resume}` and WS `run.cancel` / `notifications/cancelled` resolve the run first (unknown id answers 404/RUN_NOT_FOUND before scope; runIds are unguessable and ephemeral) and then require `agents:{read,invoke}:<agentId>` or `workflows:{read,execute}:<workflowId>`; a three-segment grant for another agent's run is now denied. Dynamic scope requirements parse with the non-throwing parser, so junk URL segments answer 403 instead of 500. New helpers `checkScope` and `createAuthenticatedMiddleware` are exported from the middleware module.
35
+
36
+ - [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - The server workflow surface exposes every D1 primitive over HTTP (W-119). `POST /:id/resume` accepts an optional `name` that routes through `resolveAwakeable` (targeting one of several parallel awakeables/approvals); new `POST /:id/retry` (background replay of a failed/aborted thread) and `POST /:id/tick` (synchronous due-timer firing with `{fired, nextWakeAt}`) under `workflows:resume:<id>`; `POST /:id/fork` performs a REAL fork (the periphery-01 honest 501 retires) returning `201 { newThreadId }`, defaulting to the thread's latest checkpoint. `ServerWorkflowLike` gains the optional `retry/tick/resolveAwakeable/fork` methods and an `opts.signal` third parameter on `resume`; background resume/retry now receive the run tracker's AbortSignal so `runs.abort(runId)` actually cancels them.
37
+
38
+ - [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - `POST /agents/:id/stream` rejects a malformed body with the same `400 config-invalid` envelope as its sibling `/run` route (W-151), validated through one shared helper BEFORE any run is registered - previously a failed parse silently launched the agent on an empty prompt behind a successful-looking 202, burning provider tokens. Empty and absent bodies stay valid via the schema default, so minimal legitimate calls are unaffected; clients that relied on the silent 202 for invalid bodies now get the honest 400.
39
+
40
+ ### Patch Changes
41
+
42
+ - [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-011: cross-process fencing for the audit hash chain. `AuditDb` gains an optional `transact` member (BEGIN-IMMEDIATE semantics, additive - custom bindings keep compiling); the default binding in `@graphorin/server` implements it with rollback strictly in `finally`. `appendAudit` wraps its `latest()`+`insert()` read-modify-write in the fence, so two processes sharing one audit.db can no longer hash against the same tip; on bindings WITHOUT the fence a seq primary-key collision is now retried (bounded, re-reading the tip) instead of silently dropping the losing security entry. `pruneAudit` runs its delete + suffix re-hash inside ONE transaction - a concurrent append waits for the lock and chains to the post-prune tip instead of leaving a permanent verify break - and FAILS CLOSED on bindings without `transact` (an unsafe prune is worse than a refused one). Verified with worker-thread contention tests against a real encrypted audit.db.
43
+
44
+ - [#153](https://github.com/o-stepper/graphorin/pull/153) [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534) Thanks [@o-stepper](https://github.com/o-stepper)! - Disk-hygiene fix (W-061): expired `idempotency_records` (each row stores the full `response_json` of a keyed POST) are now swept from the database on a periodic timer (`scheduleIdempotencyPruning`, hourly, unref-ed, stopped on `server.stop()`). Previously `SqliteIdempotencyStore.prune` existed but had zero callers - expiry was only checked on the read path, so response bodies accumulated on disk indefinitely. The sweep deletes exactly the records the read path already refuses to replay; IETF-draft replay semantics are unchanged.
45
+
46
+ - [#162](https://github.com/o-stepper/graphorin/pull/162) [`73b19ca`](https://github.com/o-stepper/graphorin/commit/73b19caeda388bda628a48138cb7d70b1db839a3) Thanks [@o-stepper](https://github.com/o-stepper)! - Remove phantom workspace dependencies that no source file imports: agent no longer depends on provider and observability, mcp/workflow/server no longer depend on observability, sessions no longer depends on security (and its memory edge moves to devDependencies where the single test import lives), skills no longer depends on tools. Dead tsdown `external` entries for the removed edges are gone too, so a future import can no longer build as external without a declared dependency. Consumer install graphs shrink accordingly; a new repo-wide `check-phantom-deps` CI gate keeps the manifest graph honest from here on.
47
+
48
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - TSDoc `{@link}` hygiene sweep (W-130): all 55 broken links found by TypeDoc's now-enabled `validation.invalidLink` are fixed - two resolved to their real targets (`GraphorinMCPError` was misnamed `MCPError`), the rest (cross-package, `import()`-form, unexported-constant, and DOM-type references that have never rendered as hrefs) converted to plain inline code. The docs build now fails on any new broken `{@link}` via a scoped gate.
49
+
50
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - The server's sibling peer floors (`@graphorin/agent`/`memory`/`sessions`/`workflow`) now track the current minor (W-135): the static `workspace:>=0.5.0 <1.0.0` range let npm silently assemble a mixed install (agent@0.5.0 under server@0.6.x). The floor is rewritten to the just-computed minor by `bump-version --sync` AFTER `changeset version` (a statically narrow range would re-trigger the changesets fixed-group escalation to a phantom 1.0.0), and `check-version-consistency` fails any release pass that skips the rewrite.
51
+
52
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Tarballs now ship `src/` so the published `dist/**/*.d.ts.map` files actually work (W-136): the maps referenced `../src/*.ts` that the `files` whitelist excluded, so go-to-definition fell back into `.d.ts` and the shipped maps were dead weight. The pack gate gains a `map-integrity` leg: every source referenced by a shipped map must resolve inside the tarball (or be embedded via `sourcesContent`), with an anti-vacuous guard - a package whose tsdown config emits declaration maps must contain a non-zero number of `.d.ts.map` files, so a cache-restored dist that silently dropped maps fails the gate instead of passing vacuously. `mvp-readiness` now requires `src` in every publishable `files` array.
53
+
54
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Every published package now declares its tree-shaking contract via `sideEffects` (W-137): 18 packages audited to a pure module scope get `false`, the CLI declares its bin entry (`["./dist/bin/*"]`), and `@graphorin/security` gets an explicit `true` - its secrets subsystem registers built-in resolvers and the SecretValue caller-context provider at import time, so marking it pure would let bundlers drop those registrations. `mvp-readiness` now fails any publishable manifest without a declared `sideEffects`, closing the drift for future packages.
55
+
56
+ - [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-046: JSON-safe `WireAgentEvent` projection for all binary-bearing event variants, applied on the server WS path.
57
+
58
+ `@graphorin/core` gains `WireFileGeneratedEvent`, `WireToolExecutePartialEvent`, `WireAgentEndEvent` (whose `result.state` is the `WireRunState` projection) plus the `WireAgentEvent` union and pure `toWireAgentEvent`/`fromWireAgentEvent` codecs. The `AgentEvent` TSDoc now documents the real two-layer wire contract (envelope `{eventId, subject, type, payload}` with `payload = WireAgentEvent`) instead of the false claim that `@graphorin/protocol` re-exports the union; protocol stays zod-only with a doc pointer. The server's `backgroundStreamAgent` projects every streamed event before emitting, so `file.generated`, binary `tool.execute.partial` chunks and a multimodal `agent.end` state arrive at WS clients decodable instead of as numeric-key mush. An exhaustive `Record<AgentEvent['type'], ...>` fixture gate in core forces a wire decision for every future event variant.
59
+
60
+ - [#153](https://github.com/o-stepper/graphorin/pull/153) [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534) Thanks [@o-stepper](https://github.com/o-stepper)! - Machine-readable workflow failure codes on the wire (W-052): the `workflow.error` WS frame payload and the run-status `error` object now carry `code` (and `hint` when the source error has one) next to the unchanged `message`. Normalization happens at the server boundary via the new internal `toWireError` helper - `err.code` (agent/workflow error family) wins, `err.kind` (tools/memory/provider/server family) is the fallback, `'unknown'` otherwise - so clients can retry `checkpoint-version-conflict` and abandon `node-execution-failed` without parsing English text. No package-internal discriminators were renamed; the field is additive.
61
+
62
+ - [#153](https://github.com/o-stepper/graphorin/pull/153) [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534) Thanks [@o-stepper](https://github.com/o-stepper)! - WS replay-buffer memory-leak fix (W-028): a periodic prune sweep (`scheduleReplayBufferPruning`, default every 60 s, `stream.replayBuffer.pruneIntervalSeconds` config) now applies the already-documented TTL to every subject, so finished-run subjects (a fresh runId per run, never touched again by push/replay) no longer retain up to 1000 full payloads forever on a long-living server. Emptied subjects release their `dropped`-map entry too; a resume whose cursor misses the buffer still reports `droppedCount >= 1` (the miss itself proves the gap), so the replay-marker keeps firing. The `@stable` `ReplayBuffer` interface gains an OPTIONAL `stats?(): { subjects, events }` (external implementations keep compiling; `createReplayBuffer` always provides it), and the `graphorin_replay_buffer_events` gauge now reports buffered EVENTS as its name says - previously it was filled with the subscription count (dashboards reading it will see the corrected semantics). Replay semantics inside the TTL window are unchanged; servers without WS are unaffected.
63
+
64
+ - [#153](https://github.com/o-stepper/graphorin/pull/153) [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534) Thanks [@o-stepper](https://github.com/o-stepper)! - Security fix (W-027): the WebSocket replay path now sanitizes every buffered frame with the same delivery-commentary sanitizer as the live path and the SSE replay. Previously a fresh WS subscription (or a reconnect-resume) received up to 1000 raw buffered frames, bypassing the `wrap`/`strip` commentary policy and its audit decisions exactly on the reconnect scenario the buffer exists for. Delivery now goes through a single `dispatchSanitized` choke point shared by the live and replay paths, so future delivery paths cannot silently bypass the sanitizer; replayed bytes are identical to live bytes for the same frame (single sanitize per delivery, no double-wrap), and the `lastEventId` ordering invariant is unchanged.
65
+
66
+ - Updated dependencies [[`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`73b19ca`](https://github.com/o-stepper/graphorin/commit/73b19caeda388bda628a48138cb7d70b1db839a3), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04)]:
67
+ - @graphorin/core@0.7.0
68
+ - @graphorin/security@0.7.0
69
+ - @graphorin/store-sqlite@0.7.0
70
+ - @graphorin/tools@0.7.0
71
+ - @graphorin/protocol@0.7.0
72
+ - @graphorin/triggers@0.7.0
73
+
3
74
  ## 0.6.1
4
75
 
5
76
  ### Patch Changes
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
6
  [![Node.js: 22+](https://img.shields.io/badge/Node.js-22%2B-43853d.svg)](https://nodejs.org)
7
7
 
8
- - **Version:** v0.6.1
8
+ - **Version:** v0.7.0
9
9
  - **License:** [MIT](./LICENSE) (© 2026 Oleksiy Stepurenko)
10
10
  - **Repository:** <https://github.com/o-stepper/graphorin/tree/main/packages/server>
11
11
  - **Issues:** <https://github.com/o-stepper/graphorin/issues>
@@ -109,8 +109,8 @@ The most-frequently-touched options:
109
109
 
110
110
  ## Status
111
111
 
112
- `@graphorin/server` is part of the Graphorin framework's `v0.6.1` pre-release. Once published, the package follows the lockstep release cadence shared by every `@graphorin/*` package on the `0.x` line.
112
+ `@graphorin/server` is part of the Graphorin framework's `v0.7.0` pre-release. Once published, the package follows the lockstep release cadence shared by every `@graphorin/*` package on the `0.x` line.
113
113
 
114
114
  ---
115
115
 
116
- **Project Graphorin** · v0.6.1 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
116
+ **Project Graphorin** · v0.7.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
@@ -0,0 +1,15 @@
1
+ //#region src/app-audit-binding.d.ts
2
+ /**
3
+ * Default audit-db binding registration for `createServer({...})`.
4
+ *
5
+ * Extracted from `app.ts` so the binding (and its SQLite row mapping)
6
+ * lives next to nothing but itself; the server lifecycle and the CLI
7
+ * both call {@link ensureStoreAuditBinding} before opening the audit
8
+ * chain.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ declare function ensureStoreAuditBinding(): void;
13
+ //#endregion
14
+ export { ensureStoreAuditBinding };
15
+ //# sourceMappingURL=app-audit-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-audit-binding.d.ts","names":[],"sources":["../src/app-audit-binding.ts"],"sourcesContent":[],"mappings":";;AAuCA;;;;;;;;;iBAAgB,uBAAA,CAAA"}
@@ -0,0 +1,136 @@
1
+ import { cipherSelectionPragmas, loadCipherDriver } from "@graphorin/store-sqlite";
2
+ import { registerAuditDbBinding } from "@graphorin/security/audit";
3
+
4
+ //#region src/app-audit-binding.ts
5
+ /**
6
+ * Default audit-db binding registration for `createServer({...})`.
7
+ *
8
+ * Extracted from `app.ts` so the binding (and its SQLite row mapping)
9
+ * lives next to nothing but itself; the server lifecycle and the CLI
10
+ * both call {@link ensureStoreAuditBinding} before opening the audit
11
+ * chain.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ /** The ciphers sqlite3mc supports - mirrors `EncryptionCipher`. */
16
+ const KNOWN_CIPHERS = new Set([
17
+ "sqlcipher",
18
+ "chacha20",
19
+ "aes256cbc",
20
+ "aes128cbc",
21
+ "rc4"
22
+ ]);
23
+ /**
24
+ * Pre-built audit-db binding shipped from `@graphorin/store-sqlite`.
25
+ * Registered exactly once per process so {@link openAuditDb} can find
26
+ * a default binding without forcing operators to wire it manually.
27
+ *
28
+ * Exported as {@link ensureStoreAuditBinding} so the CLI (Phase 15
29
+ * `graphorin audit verify | prune | export`) can reach into the same
30
+ * binding without booting the HTTP listener.
31
+ *
32
+ * @stable
33
+ */
34
+ let storeAuditBindingRegistered = false;
35
+ function ensureStoreAuditBinding() {
36
+ if (storeAuditBindingRegistered) return;
37
+ registerAuditDbBinding({
38
+ id: "better-sqlite3-multiple-ciphers",
39
+ description: "Default audit-db binding shipped by @graphorin/store-sqlite.",
40
+ open: async (opts) => {
41
+ const driver = await loadCipherDriver();
42
+ const passphrase = await opts.passphrase.use((value) => value);
43
+ const Db = driver;
44
+ const cipher = opts.cipher ?? "chacha20";
45
+ if (!KNOWN_CIPHERS.has(cipher)) throw new Error(`[graphorin/server] unknown audit.cipher '${cipher}'. Supported: ${[...KNOWN_CIPHERS].join(", ")}.`);
46
+ const db = new Db(opts.path);
47
+ for (const pragma of cipherSelectionPragmas(cipher)) db.pragma(pragma);
48
+ db.pragma(`key = '${passphrase.replace(/'/g, "''")}'`);
49
+ db.pragma("journal_mode = WAL");
50
+ db.pragma("synchronous = NORMAL");
51
+ db.pragma("busy_timeout = 5000");
52
+ db.pragma("foreign_keys = ON");
53
+ db.exec(`CREATE TABLE IF NOT EXISTS audit_log (
54
+ seq INTEGER PRIMARY KEY,
55
+ ts INTEGER NOT NULL,
56
+ actor_json TEXT NOT NULL,
57
+ action TEXT NOT NULL,
58
+ target TEXT NOT NULL,
59
+ decision TEXT NOT NULL,
60
+ context_json TEXT,
61
+ metadata_json TEXT,
62
+ prev_hash TEXT NOT NULL,
63
+ hash TEXT NOT NULL UNIQUE
64
+ ) WITHOUT ROWID;`);
65
+ return {
66
+ binding: "better-sqlite3-multiple-ciphers",
67
+ path: opts.path,
68
+ async insert(entry) {
69
+ db.prepare(`INSERT INTO audit_log (
70
+ seq, ts, actor_json, action, target, decision,
71
+ context_json, metadata_json, prev_hash, hash
72
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(entry.seq, entry.ts, JSON.stringify(entry.actor), entry.action, entry.target, entry.decision, entry.context !== void 0 ? JSON.stringify(entry.context) : null, entry.metadata !== void 0 ? JSON.stringify(entry.metadata) : null, entry.prevHash, entry.hash);
73
+ return entry;
74
+ },
75
+ async latest() {
76
+ const row = db.prepare("SELECT * FROM audit_log ORDER BY seq DESC LIMIT 1").get();
77
+ if (row === void 0) return void 0;
78
+ return rowToEntry(row);
79
+ },
80
+ async *iterate(bounds) {
81
+ const lo = bounds?.fromSeq ?? 1;
82
+ const hi = bounds?.toSeq ?? Number.MAX_SAFE_INTEGER;
83
+ const iter = db.prepare("SELECT * FROM audit_log WHERE seq BETWEEN ? AND ? ORDER BY seq ASC").iterate(lo, hi);
84
+ for (const row of iter) yield rowToEntry(row);
85
+ },
86
+ async count() {
87
+ return db.prepare("SELECT COUNT(*) AS n FROM audit_log").get().n;
88
+ },
89
+ async deleteUpTo(threshold) {
90
+ const before = db.prepare("SELECT COUNT(*) AS n FROM audit_log WHERE seq <= ?").get(threshold);
91
+ db.prepare("DELETE FROM audit_log WHERE seq <= ?").run(threshold);
92
+ return before.n;
93
+ },
94
+ async replaceEntry(entry) {
95
+ db.prepare(`UPDATE audit_log SET prev_hash = ?, hash = ? WHERE seq = ?`).run(entry.prevHash, entry.hash, entry.seq);
96
+ },
97
+ async transact(fn) {
98
+ db.exec("BEGIN IMMEDIATE");
99
+ let committed = false;
100
+ try {
101
+ const result = await fn();
102
+ db.exec("COMMIT");
103
+ committed = true;
104
+ return result;
105
+ } finally {
106
+ if (!committed) try {
107
+ db.exec("ROLLBACK");
108
+ } catch {}
109
+ }
110
+ },
111
+ async close() {
112
+ if (db.open) db.close();
113
+ }
114
+ };
115
+ }
116
+ }, { setAsDefault: true });
117
+ storeAuditBindingRegistered = true;
118
+ }
119
+ function rowToEntry(row) {
120
+ return {
121
+ seq: row.seq,
122
+ ts: row.ts,
123
+ actor: JSON.parse(row.actor_json),
124
+ action: row.action,
125
+ target: row.target,
126
+ decision: row.decision,
127
+ ...row.context_json !== null ? { context: JSON.parse(row.context_json) } : {},
128
+ ...row.metadata_json !== null ? { metadata: JSON.parse(row.metadata_json) } : {},
129
+ prevHash: row.prev_hash,
130
+ hash: row.hash
131
+ };
132
+ }
133
+
134
+ //#endregion
135
+ export { ensureStoreAuditBinding };
136
+ //# sourceMappingURL=app-audit-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-audit-binding.js","names":["KNOWN_CIPHERS: ReadonlySet<string>"],"sources":["../src/app-audit-binding.ts"],"sourcesContent":["/**\n * Default audit-db binding registration for `createServer({...})`.\n *\n * Extracted from `app.ts` so the binding (and its SQLite row mapping)\n * lives next to nothing but itself; the server lifecycle and the CLI\n * both call {@link ensureStoreAuditBinding} before opening the audit\n * chain.\n *\n * @packageDocumentation\n */\n\nimport { registerAuditDbBinding } from '@graphorin/security/audit';\nimport {\n cipherSelectionPragmas,\n type EncryptionCipher,\n loadCipherDriver,\n} from '@graphorin/store-sqlite';\n\n/** The ciphers sqlite3mc supports - mirrors `EncryptionCipher`. */\nconst KNOWN_CIPHERS: ReadonlySet<string> = new Set([\n 'sqlcipher',\n 'chacha20',\n 'aes256cbc',\n 'aes128cbc',\n 'rc4',\n]);\n\n/**\n * Pre-built audit-db binding shipped from `@graphorin/store-sqlite`.\n * Registered exactly once per process so {@link openAuditDb} can find\n * a default binding without forcing operators to wire it manually.\n *\n * Exported as {@link ensureStoreAuditBinding} so the CLI (Phase 15\n * `graphorin audit verify | prune | export`) can reach into the same\n * binding without booting the HTTP listener.\n *\n * @stable\n */\nlet storeAuditBindingRegistered = false;\nexport function ensureStoreAuditBinding(): void {\n if (storeAuditBindingRegistered) return;\n registerAuditDbBinding(\n {\n id: 'better-sqlite3-multiple-ciphers',\n description: 'Default audit-db binding shipped by @graphorin/store-sqlite.',\n open: async (opts) => {\n const driver = await loadCipherDriver();\n const passphrase = await opts.passphrase.use((value) => value);\n const Db = driver as unknown as new (\n path: string,\n ) => {\n pragma(s: string): unknown;\n prepare(sql: string): {\n run(...args: unknown[]): unknown;\n get(...args: unknown[]): unknown;\n all(...args: unknown[]): unknown;\n iterate(...args: unknown[]): IterableIterator<unknown>;\n };\n exec(sql: string): unknown;\n close(): void;\n open: boolean;\n };\n // W-110 / CS-7: pin the cipher BEFORE PRAGMA key - `opts.cipher`\n // (config.audit.cipher) was previously ignored, so every audit.db\n // silently came out as sqlite3mc's default. Default 'chacha20':\n // pre-fix files were created without cipher pragmas (= chacha20\n // format), so this pin is byte-compatible with every existing\n // audit.db, while 'sqlcipher' (the MAIN store's ADR-030 default)\n // would brick them. Unknown values fail fast.\n const cipher = opts.cipher ?? 'chacha20';\n if (!KNOWN_CIPHERS.has(cipher)) {\n throw new Error(\n `[graphorin/server] unknown audit.cipher '${cipher}'. Supported: ${[...KNOWN_CIPHERS].join(', ')}.`,\n );\n }\n const db = new Db(opts.path);\n for (const pragma of cipherSelectionPragmas(cipher as EncryptionCipher)) {\n db.pragma(pragma);\n }\n db.pragma(`key = '${passphrase.replace(/'/g, \"''\")}'`);\n db.pragma('journal_mode = WAL');\n db.pragma('synchronous = NORMAL');\n db.pragma('busy_timeout = 5000');\n db.pragma('foreign_keys = ON');\n db.exec(\n `CREATE TABLE IF NOT EXISTS audit_log (\n seq INTEGER PRIMARY KEY,\n ts INTEGER NOT NULL,\n actor_json TEXT NOT NULL,\n action TEXT NOT NULL,\n target TEXT NOT NULL,\n decision TEXT NOT NULL,\n context_json TEXT,\n metadata_json TEXT,\n prev_hash TEXT NOT NULL,\n hash TEXT NOT NULL UNIQUE\n ) WITHOUT ROWID;`,\n );\n return {\n binding: 'better-sqlite3-multiple-ciphers',\n path: opts.path,\n async insert(entry) {\n db.prepare(\n `INSERT INTO audit_log (\n seq, ts, actor_json, action, target, decision,\n context_json, metadata_json, prev_hash, hash\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n entry.seq,\n entry.ts,\n JSON.stringify(entry.actor),\n entry.action,\n entry.target,\n entry.decision,\n entry.context !== undefined ? JSON.stringify(entry.context) : null,\n entry.metadata !== undefined ? JSON.stringify(entry.metadata) : null,\n entry.prevHash,\n entry.hash,\n );\n return entry;\n },\n async latest() {\n const row = db.prepare('SELECT * FROM audit_log ORDER BY seq DESC LIMIT 1').get() as\n | undefined\n | {\n seq: number;\n ts: number;\n actor_json: string;\n action: string;\n target: string;\n decision: string;\n context_json: string | null;\n metadata_json: string | null;\n prev_hash: string;\n hash: string;\n };\n if (row === undefined) return undefined;\n return rowToEntry(row);\n },\n async *iterate(bounds) {\n const lo = bounds?.fromSeq ?? 1;\n const hi = bounds?.toSeq ?? Number.MAX_SAFE_INTEGER;\n const iter = db\n .prepare('SELECT * FROM audit_log WHERE seq BETWEEN ? AND ? ORDER BY seq ASC')\n .iterate(lo, hi);\n for (const row of iter) yield rowToEntry(row as Parameters<typeof rowToEntry>[0]);\n },\n async count() {\n const row = db.prepare('SELECT COUNT(*) AS n FROM audit_log').get() as { n: number };\n return row.n;\n },\n async deleteUpTo(threshold) {\n const before = db\n .prepare('SELECT COUNT(*) AS n FROM audit_log WHERE seq <= ?')\n .get(threshold) as { n: number };\n db.prepare('DELETE FROM audit_log WHERE seq <= ?').run(threshold);\n return before.n;\n },\n async replaceEntry(entry) {\n db.prepare(`UPDATE audit_log SET prev_hash = ?, hash = ? WHERE seq = ?`).run(\n entry.prevHash,\n entry.hash,\n entry.seq,\n );\n },\n // W-011: cross-process fence. Raw BEGIN IMMEDIATE (not\n // better-sqlite3's .transaction() - that helper rejects async\n // functions, while this binding's methods are async-shaped\n // with synchronous bodies; the in-process WRITE_CHAINS\n // serialisation guarantees no foreign statement interleaves\n // inside the transaction). ROLLBACK strictly in finally so an\n // exception can never leave the handle wedged inside an open\n // transaction.\n async transact(fn) {\n db.exec('BEGIN IMMEDIATE');\n let committed = false;\n try {\n const result = await fn();\n db.exec('COMMIT');\n committed = true;\n return result;\n } finally {\n if (!committed) {\n try {\n db.exec('ROLLBACK');\n } catch {\n // Already rolled back / connection unusable - nothing to release.\n }\n }\n }\n },\n async close() {\n if (db.open) db.close();\n },\n };\n },\n },\n { setAsDefault: true },\n );\n storeAuditBindingRegistered = true;\n}\n\nfunction rowToEntry(row: {\n seq: number;\n ts: number;\n actor_json: string;\n action: string;\n target: string;\n decision: string;\n context_json: string | null;\n metadata_json: string | null;\n prev_hash: string;\n hash: string;\n}) {\n return {\n seq: row.seq,\n ts: row.ts,\n actor: JSON.parse(row.actor_json),\n action: row.action,\n target: row.target,\n decision: row.decision as 'success' | 'denied' | 'error' | 'not-found',\n ...(row.context_json !== null ? { context: JSON.parse(row.context_json) } : {}),\n ...(row.metadata_json !== null ? { metadata: JSON.parse(row.metadata_json) } : {}),\n prevHash: row.prev_hash,\n hash: row.hash,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,MAAMA,gBAAqC,IAAI,IAAI;CACjD;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;;AAaF,IAAI,8BAA8B;AAClC,SAAgB,0BAAgC;AAC9C,KAAI,4BAA6B;AACjC,wBACE;EACE,IAAI;EACJ,aAAa;EACb,MAAM,OAAO,SAAS;GACpB,MAAM,SAAS,MAAM,kBAAkB;GACvC,MAAM,aAAa,MAAM,KAAK,WAAW,KAAK,UAAU,MAAM;GAC9D,MAAM,KAAK;GAqBX,MAAM,SAAS,KAAK,UAAU;AAC9B,OAAI,CAAC,cAAc,IAAI,OAAO,CAC5B,OAAM,IAAI,MACR,4CAA4C,OAAO,gBAAgB,CAAC,GAAG,cAAc,CAAC,KAAK,KAAK,CAAC,GAClG;GAEH,MAAM,KAAK,IAAI,GAAG,KAAK,KAAK;AAC5B,QAAK,MAAM,UAAU,uBAAuB,OAA2B,CACrE,IAAG,OAAO,OAAO;AAEnB,MAAG,OAAO,UAAU,WAAW,QAAQ,MAAM,KAAK,CAAC,GAAG;AACtD,MAAG,OAAO,qBAAqB;AAC/B,MAAG,OAAO,uBAAuB;AACjC,MAAG,OAAO,sBAAsB;AAChC,MAAG,OAAO,oBAAoB;AAC9B,MAAG,KACD;;;;;;;;;;;4BAYD;AACD,UAAO;IACL,SAAS;IACT,MAAM,KAAK;IACX,MAAM,OAAO,OAAO;AAClB,QAAG,QACD;;;wDAID,CAAC,IACA,MAAM,KACN,MAAM,IACN,KAAK,UAAU,MAAM,MAAM,EAC3B,MAAM,QACN,MAAM,QACN,MAAM,UACN,MAAM,YAAY,SAAY,KAAK,UAAU,MAAM,QAAQ,GAAG,MAC9D,MAAM,aAAa,SAAY,KAAK,UAAU,MAAM,SAAS,GAAG,MAChE,MAAM,UACN,MAAM,KACP;AACD,YAAO;;IAET,MAAM,SAAS;KACb,MAAM,MAAM,GAAG,QAAQ,oDAAoD,CAAC,KAAK;AAcjF,SAAI,QAAQ,OAAW,QAAO;AAC9B,YAAO,WAAW,IAAI;;IAExB,OAAO,QAAQ,QAAQ;KACrB,MAAM,KAAK,QAAQ,WAAW;KAC9B,MAAM,KAAK,QAAQ,SAAS,OAAO;KACnC,MAAM,OAAO,GACV,QAAQ,qEAAqE,CAC7E,QAAQ,IAAI,GAAG;AAClB,UAAK,MAAM,OAAO,KAAM,OAAM,WAAW,IAAwC;;IAEnF,MAAM,QAAQ;AAEZ,YADY,GAAG,QAAQ,sCAAsC,CAAC,KAAK,CACxD;;IAEb,MAAM,WAAW,WAAW;KAC1B,MAAM,SAAS,GACZ,QAAQ,qDAAqD,CAC7D,IAAI,UAAU;AACjB,QAAG,QAAQ,uCAAuC,CAAC,IAAI,UAAU;AACjE,YAAO,OAAO;;IAEhB,MAAM,aAAa,OAAO;AACxB,QAAG,QAAQ,6DAA6D,CAAC,IACvE,MAAM,UACN,MAAM,MACN,MAAM,IACP;;IAUH,MAAM,SAAS,IAAI;AACjB,QAAG,KAAK,kBAAkB;KAC1B,IAAI,YAAY;AAChB,SAAI;MACF,MAAM,SAAS,MAAM,IAAI;AACzB,SAAG,KAAK,SAAS;AACjB,kBAAY;AACZ,aAAO;eACC;AACR,UAAI,CAAC,UACH,KAAI;AACF,UAAG,KAAK,WAAW;cACb;;;IAMd,MAAM,QAAQ;AACZ,SAAI,GAAG,KAAM,IAAG,OAAO;;IAE1B;;EAEJ,EACD,EAAE,cAAc,MAAM,CACvB;AACD,+BAA8B;;AAGhC,SAAS,WAAW,KAWjB;AACD,QAAO;EACL,KAAK,IAAI;EACT,IAAI,IAAI;EACR,OAAO,KAAK,MAAM,IAAI,WAAW;EACjC,QAAQ,IAAI;EACZ,QAAQ,IAAI;EACZ,UAAU,IAAI;EACd,GAAI,IAAI,iBAAiB,OAAO,EAAE,SAAS,KAAK,MAAM,IAAI,aAAa,EAAE,GAAG,EAAE;EAC9E,GAAI,IAAI,kBAAkB,OAAO,EAAE,UAAU,KAAK,MAAM,IAAI,cAAc,EAAE,GAAG,EAAE;EACjF,UAAU,IAAI;EACd,MAAM,IAAI;EACX"}
@@ -0,0 +1,29 @@
1
+ import "./consolidator/daemon.js";
2
+ import { TriggersDaemon } from "./triggers/daemon.js";
3
+ import { WorkflowTimerDaemon, WorkflowTimerDriverLike } from "./workflows/timer-daemon.js";
4
+ import * as _graphorin_triggers0 from "@graphorin/triggers";
5
+
6
+ //#region src/app-daemons.d.ts
7
+
8
+ /**
9
+ * Discriminated union accepted by `CreateServerOptions.triggers`. A
10
+ * caller may either supply a fully-built daemon (e.g. constructed
11
+ * around a custom `Scheduler`) or just the underlying scheduler - the
12
+ * server wraps it with {@link createTriggersDaemon} automatically.
13
+ *
14
+ * @stable
15
+ */
16
+ type TriggersDaemonInput = {
17
+ readonly daemon: TriggersDaemon;
18
+ } | {
19
+ readonly scheduler: _graphorin_triggers0.Scheduler;
20
+ };
21
+ /** W-032: accepted forms for `createServer({ workflowTimers })`. */
22
+ type WorkflowTimersInput = {
23
+ readonly daemon: WorkflowTimerDaemon;
24
+ } | {
25
+ readonly driver: WorkflowTimerDriverLike;
26
+ };
27
+ //#endregion
28
+ export { TriggersDaemonInput, WorkflowTimersInput };
29
+ //# sourceMappingURL=app-daemons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-daemons.d.ts","names":[],"sources":["../src/app-daemons.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;KAwBY,mBAAA;mBACW;;sBAAc,oBAAA,CACmB;;;KAG5C,mBAAA;mBACW;;mBACA"}
@@ -0,0 +1,27 @@
1
+ import { createConsolidatorDaemon } from "./consolidator/daemon.js";
2
+ import { createTriggersDaemon } from "./triggers/daemon.js";
3
+ import { createWorkflowTimerDaemon } from "./workflows/timer-daemon.js";
4
+
5
+ //#region src/app-daemons.ts
6
+ /**
7
+ * Wrap the operator-supplied triggers / consolidator surfaces in their
8
+ * daemon adapters. Pure construction - nothing is started here.
9
+ */
10
+ function buildDaemons(options) {
11
+ let triggersDaemon;
12
+ if (options.triggers !== void 0) if ("daemon" in options.triggers) triggersDaemon = options.triggers.daemon;
13
+ else triggersDaemon = createTriggersDaemon({ scheduler: options.triggers.scheduler });
14
+ let consolidatorDaemon;
15
+ if (options.consolidator !== void 0) consolidatorDaemon = createConsolidatorDaemon({ consolidator: options.consolidator });
16
+ let workflowTimerDaemon;
17
+ if (options.workflowTimers !== void 0) workflowTimerDaemon = "daemon" in options.workflowTimers ? options.workflowTimers.daemon : createWorkflowTimerDaemon({ driver: options.workflowTimers.driver });
18
+ return {
19
+ triggersDaemon,
20
+ consolidatorDaemon,
21
+ workflowTimerDaemon
22
+ };
23
+ }
24
+
25
+ //#endregion
26
+ export { buildDaemons };
27
+ //# sourceMappingURL=app-daemons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-daemons.js","names":["triggersDaemon: TriggersDaemon | undefined","consolidatorDaemon: ConsolidatorDaemon | undefined","workflowTimerDaemon: WorkflowTimerDaemon | undefined"],"sources":["../src/app-daemons.ts"],"sourcesContent":["/**\n * Daemon construction for `createServer({...})` - wraps the caller's\n * triggers scheduler / consolidator surfaces in their daemon adapters.\n * Start/stop sequencing stays in `app-lifecycle.ts` because the order\n * relative to the listener is part of the lifecycle contract.\n *\n * @packageDocumentation\n */\n\nimport type { ConsolidatorDaemon, ConsolidatorLike } from './consolidator/daemon.js';\nimport { createConsolidatorDaemon } from './consolidator/daemon.js';\nimport type { TriggersDaemon } from './triggers/daemon.js';\nimport { createTriggersDaemon } from './triggers/daemon.js';\nimport type { WorkflowTimerDaemon, WorkflowTimerDriverLike } from './workflows/timer-daemon.js';\nimport { createWorkflowTimerDaemon } from './workflows/timer-daemon.js';\n\n/**\n * Discriminated union accepted by `CreateServerOptions.triggers`. A\n * caller may either supply a fully-built daemon (e.g. constructed\n * around a custom `Scheduler`) or just the underlying scheduler - the\n * server wraps it with {@link createTriggersDaemon} automatically.\n *\n * @stable\n */\nexport type TriggersDaemonInput =\n | { readonly daemon: TriggersDaemon }\n | { readonly scheduler: import('@graphorin/triggers').Scheduler };\n\n/** W-032: accepted forms for `createServer({ workflowTimers })`. */\nexport type WorkflowTimersInput =\n | { readonly daemon: WorkflowTimerDaemon }\n | { readonly driver: WorkflowTimerDriverLike };\n\n/** Subset of `CreateServerOptions` consumed by {@link buildDaemons}. */\nexport interface BuildDaemonsOptions {\n readonly triggers?: TriggersDaemonInput | undefined;\n readonly consolidator?: ConsolidatorLike | undefined;\n readonly workflowTimers?: WorkflowTimersInput | undefined;\n}\n\n/** Daemon handles constructed (or adopted) by {@link buildDaemons}. */\nexport interface BuiltDaemons {\n readonly triggersDaemon: TriggersDaemon | undefined;\n readonly consolidatorDaemon: ConsolidatorDaemon | undefined;\n readonly workflowTimerDaemon: WorkflowTimerDaemon | undefined;\n}\n\n/**\n * Wrap the operator-supplied triggers / consolidator surfaces in their\n * daemon adapters. Pure construction - nothing is started here.\n */\nexport function buildDaemons(options: BuildDaemonsOptions): BuiltDaemons {\n let triggersDaemon: TriggersDaemon | undefined;\n if (options.triggers !== undefined) {\n if ('daemon' in options.triggers) {\n triggersDaemon = options.triggers.daemon;\n } else {\n triggersDaemon = createTriggersDaemon({ scheduler: options.triggers.scheduler });\n }\n }\n let consolidatorDaemon: ConsolidatorDaemon | undefined;\n if (options.consolidator !== undefined) {\n consolidatorDaemon = createConsolidatorDaemon({ consolidator: options.consolidator });\n }\n let workflowTimerDaemon: WorkflowTimerDaemon | undefined;\n if (options.workflowTimers !== undefined) {\n workflowTimerDaemon =\n 'daemon' in options.workflowTimers\n ? options.workflowTimers.daemon\n : createWorkflowTimerDaemon({ driver: options.workflowTimers.driver });\n }\n return { triggersDaemon, consolidatorDaemon, workflowTimerDaemon };\n}\n"],"mappings":";;;;;;;;;AAmDA,SAAgB,aAAa,SAA4C;CACvE,IAAIA;AACJ,KAAI,QAAQ,aAAa,OACvB,KAAI,YAAY,QAAQ,SACtB,kBAAiB,QAAQ,SAAS;KAElC,kBAAiB,qBAAqB,EAAE,WAAW,QAAQ,SAAS,WAAW,CAAC;CAGpF,IAAIC;AACJ,KAAI,QAAQ,iBAAiB,OAC3B,sBAAqB,yBAAyB,EAAE,cAAc,QAAQ,cAAc,CAAC;CAEvF,IAAIC;AACJ,KAAI,QAAQ,mBAAmB,OAC7B,uBACE,YAAY,QAAQ,iBAChB,QAAQ,eAAe,SACvB,0BAA0B,EAAE,QAAQ,QAAQ,eAAe,QAAQ,CAAC;AAE5E,QAAO;EAAE;EAAgB;EAAoB;EAAqB"}