@graphorin/server 0.5.0 → 0.6.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 (79) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +9 -9
  3. package/dist/app.d.ts +5 -5
  4. package/dist/app.d.ts.map +1 -1
  5. package/dist/app.js +11 -10
  6. package/dist/app.js.map +1 -1
  7. package/dist/commentary/audit-bridge.d.ts +2 -2
  8. package/dist/commentary/audit-bridge.js +3 -3
  9. package/dist/commentary/audit-bridge.js.map +1 -1
  10. package/dist/commentary/built-in-patterns.js +1 -1
  11. package/dist/commentary/built-in-patterns.js.map +1 -1
  12. package/dist/commentary/sanitizer.js +2 -2
  13. package/dist/commentary/sanitizer.js.map +1 -1
  14. package/dist/commentary/types.d.ts +5 -5
  15. package/dist/consolidator/daemon.d.ts +1 -1
  16. package/dist/consolidator/daemon.js.map +1 -1
  17. package/dist/health/checks.js.map +1 -1
  18. package/dist/health/routes.d.ts +2 -2
  19. package/dist/health/routes.js.map +1 -1
  20. package/dist/index.d.ts +7 -8
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +9 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/internal/ids.js +1 -1
  25. package/dist/internal/ids.js.map +1 -1
  26. package/dist/lifecycle/pre-bind.js.map +1 -1
  27. package/dist/metrics/catalog.js +1 -1
  28. package/dist/metrics/catalog.js.map +1 -1
  29. package/dist/metrics/registry.d.ts +2 -2
  30. package/dist/metrics/registry.js +1 -1
  31. package/dist/metrics/registry.js.map +1 -1
  32. package/dist/middleware/auth.d.ts +1 -1
  33. package/dist/middleware/auth.js +1 -1
  34. package/dist/middleware/auth.js.map +1 -1
  35. package/dist/middleware/cors.js.map +1 -1
  36. package/dist/middleware/csrf.js.map +1 -1
  37. package/dist/middleware/idempotency.d.ts +1 -1
  38. package/dist/middleware/idempotency.js +36 -23
  39. package/dist/middleware/idempotency.js.map +1 -1
  40. package/dist/middleware/rate-limit.js.map +1 -1
  41. package/dist/middleware/request-state.js.map +1 -1
  42. package/dist/middleware/scope.js.map +1 -1
  43. package/dist/package.js +6 -0
  44. package/dist/package.js.map +1 -0
  45. package/dist/registry/index.d.ts +2 -2
  46. package/dist/registry/index.js +1 -1
  47. package/dist/registry/index.js.map +1 -1
  48. package/dist/replay/routes.js +1 -1
  49. package/dist/replay/routes.js.map +1 -1
  50. package/dist/routes/agents.js +1 -1
  51. package/dist/routes/agents.js.map +1 -1
  52. package/dist/routes/audit.d.ts +1 -1
  53. package/dist/routes/audit.js.map +1 -1
  54. package/dist/routes/auth.js.map +1 -1
  55. package/dist/routes/health.js +1 -1
  56. package/dist/routes/health.js.map +1 -1
  57. package/dist/routes/sessions.js.map +1 -1
  58. package/dist/routes/tokens.js.map +1 -1
  59. package/dist/routes/workflows.js +42 -8
  60. package/dist/routes/workflows.js.map +1 -1
  61. package/dist/runtime/run-state.d.ts +3 -3
  62. package/dist/runtime/run-state.js +3 -3
  63. package/dist/runtime/run-state.js.map +1 -1
  64. package/dist/sse/events.js +1 -1
  65. package/dist/sse/events.js.map +1 -1
  66. package/dist/triggers/daemon.js.map +1 -1
  67. package/dist/triggers/routes.js.map +1 -1
  68. package/dist/ws/dispatcher.js.map +1 -1
  69. package/dist/ws/subjects.d.ts +1 -1
  70. package/dist/ws/subjects.d.ts.map +1 -1
  71. package/dist/ws/subjects.js +3 -3
  72. package/dist/ws/subjects.js.map +1 -1
  73. package/dist/ws/ticket.d.ts +1 -1
  74. package/dist/ws/ticket.js +1 -1
  75. package/dist/ws/ticket.js.map +1 -1
  76. package/dist/ws/upgrade.d.ts.map +1 -1
  77. package/dist/ws/upgrade.js +7 -5
  78. package/dist/ws/upgrade.js.map +1 -1
  79. package/package.json +16 -16
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # @graphorin/server
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.
8
+
9
+ - Updated dependencies [[`826ee59`](https://github.com/o-stepper/graphorin/commit/826ee5933ad38693b0dd2f20a110abfecba7d23d), [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430)]:
10
+ - @graphorin/observability@0.6.1
11
+ - @graphorin/core@0.6.1
12
+ - @graphorin/protocol@0.6.1
13
+ - @graphorin/security@0.6.1
14
+ - @graphorin/store-sqlite@0.6.1
15
+ - @graphorin/triggers@0.6.1
16
+
17
+ ## 0.6.0
18
+
19
+ ### Patch Changes
20
+
21
+ - [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Server / client / periphery correctness (audit 2026-07-04 Wave B, cluster B6).
22
+
23
+ - periphery-01: `POST /v1/workflows/:id/resume` actually resumes - it background-iterates `workflow.resume(threadId, {resume})` and emits every event on the `workflow:<id>/runs/<runId>/events` subject (mirroring execute); the fictional unmounted SSE URL is gone and the run completes. `POST /v1/workflows/:id/fork` returns an honest 501 instead of a 202 that forked nothing.
24
+ - periphery-02: `standalone-server.md` now states plainly that `graphorin start` serves health/metrics/tokens/tickets/WS-SSE only and that the domain routes mount only when adapters are composed programmatically via `createServer({...})`.
25
+ - periphery-03: the client SSE fallback survives reconnects - `#reconnect` no longer attempts the RPC resubscribe on the read-only SSE transport (which threw and permanently killed the subscription); the reconnect carries the subscription's cursor as a `Last-Event-ID` header so the server replays only missed events; a closed bound subscription is recreated on the next `subscribe()`.
26
+ - periphery-08: the idempotency middleware tracks in-flight keyed executions - a concurrent duplicate gets `409 idempotency-in-flight` + `Retry-After` instead of double-executing (per draft-ietf-httpapi-idempotency-key-header).
27
+ - periphery-09: `subscribe({ target: 'run' })` without `sessionId` throws a clear client-side error instead of building a `run:` subject the server grammar can never accept.
28
+ - periphery-10: session-stream subjects gate on `sessions:read:<sessionId>` (read-only streams, sessionId resource slot) instead of `agents:invoke:<sessionId>`, aligning with the SSE route's requirement.
29
+ - periphery-11: interval triggers under `catchupPolicy: 'none'` advance to the next FUTURE boundary after downtime instead of firing immediately on restart; `recordActivity()` no longer arms idle timers on a stopped scheduler (P-14).
30
+ - periphery-05: the TransformersJS embedder throws for an unknown model with no `dim` hint (the PS-11 fix ported from the Ollama embedder) instead of assuming 768 - a wrong assumed width baked a wrong-width id + vec0 table and the id changed after the first embed; a width drift against a bound dim now throws too.
31
+ - P-05: the WS upgrade bearer verification passes the client IP so the per-IP failure lockout engages for upgrade attempts (previously a lockout-free brute-force surface).
32
+ - Docs: the fictional "disconnect policy" section replaced with the real reconnect-and-replay behaviour; the WS ticket endpoint path corrected to `/v1/session/ws-ticket`.
33
+
34
+ - [#138](https://github.com/o-stepper/graphorin/pull/138) [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a) Thanks [@o-stepper](https://github.com/o-stepper)! - Release-pipeline and tarball-surface fixes (audit 2026-07-04 Wave E, cluster E2). `@graphorin/memory`'s `./conflict` subpath was runtime-EMPTY on npm 0.5.0 (`export { };` - preserveModules emitted the module without an explicit tsdown entry); it now ships all 8 runtime exports. `@graphorin/server`'s internal `workspace:*` peer dependencies are ranged (`workspace:>=0.5.0 <1.0.0`) so changesets stops escalating every sibling bump into a bogus MAJOR for the whole fixed group (the 1.0.0 landmine on the release bot's branch). `@graphorin/eslint-plugin` gains the `./package.json` self-export. All 27 per-package CHANGELOGs gain the 0.5.0 section (they were frozen at 0.1.0 inside every published tarball), and the `mvp-readiness` release gate now rejects a stale-CHANGELOG or unresolvable-exports release.
35
+
36
+ - Updated dependencies [[`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a), [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a), [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a)]:
37
+ - @graphorin/store-sqlite@0.6.0
38
+ - @graphorin/core@0.6.0
39
+ - @graphorin/triggers@0.6.0
40
+ - @graphorin/security@0.6.0
41
+ - @graphorin/observability@0.6.0
42
+ - @graphorin/protocol@0.6.0
43
+
44
+ ## 0.5.0
45
+
46
+ First version published to the npm registry (with Sigstore build
47
+ provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
48
+ milestones and were never published. All `@graphorin/*` packages release
49
+ lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
50
+ in the repository-level
51
+ [CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).
52
+
3
53
  ## 0.1.0
4
54
 
5
55
  Initial release. See the workspace root `CHANGELOG.md` for the full
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.5.0
8
+ - **Version:** v0.6.1
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>
@@ -21,9 +21,9 @@
21
21
  | **SSE fallback** | `GET /v1/sessions/:id/events` for non-WebSocket-friendly clients (corporate proxies, restricted networks). Same sanitization, same `Last-Event-ID` resume semantics. |
22
22
  | **Lifecycle hooks** | `beforeStart` → pre-bind validation → storage migrations → bind → triggers / consolidator daemons start → `onReady`; `SIGTERM` → `beforeShutdown` → daemons stop → drain → exit. |
23
23
  | **Pre-bind validation** | Resolves every `*Ref` field via `@graphorin/security` resolvers before binding the listener; missing pepper / unresolvable `SecretRef` / missing encryption peer fail fast with typed errors. |
24
- | **REST endpoints** | Agents (`run`, `stream`, `state`, `abort`, `resume`), workflows (`execute`, `resume`, `state`, `checkpoints`, `fork`), sessions, memory, skills, MCP, tokens, audit (`list`, `export`, `verify`), the `POST /v1/session/ws-ticket` browser ticket route, triggers (`list`, `get`, `fire`, `disable`, `prune`), replay (`runs/:runId/replay`, `sessions/:id/replay`), `/health`, `/health/secrets`, `/metrics`. Every side-effecting endpoint honours an `Idempotency-Key` per IETF draft-07. |
25
- | **Auth / scope** | `Authorization: Bearer kru_<env>_v1_<...>` (HMAC-SHA256 + pepper) wired through `TokenVerifier` from `@graphorin/security`. Per-route scope grammar `<resource>:<action>[:<id>]`. WebSocket upgrade honours both bearer and the single-use ticket flow. |
26
- | **Idempotency** | SQLite-backed `idempotency_records` + LRU read cache. Replays cached responses with `Idempotency-Replayed: true`; mismatched payloads return `409`. |
24
+ | **REST endpoints** | Agents (`run`, `stream`, `state`, `abort`, `resume`), workflows (`execute`, `resume`, `state`, `checkpoints`; `fork` answers an honest `501` until thread forking lands on the REST surface), sessions, memory, skills, MCP, tokens, audit (`list`, `export`, `verify`), the `POST /v1/session/ws-ticket` browser ticket route, triggers (`list`, `get`, `fire`, `disable`, `prune`), replay (`runs/:runId/replay`, `sessions/:id/replay`), `/health`, `/health/secrets`, `/metrics`. Every side-effecting endpoint honours an `Idempotency-Key` per IETF draft-07. |
25
+ | **Auth / scope** | `Authorization: Bearer gph_<env>_v1_<...>` (HMAC-SHA256 + pepper) wired through `TokenVerifier` from `@graphorin/security`. Per-route scope grammar `<resource>:<action>[:<id>]`. WebSocket upgrade honours both bearer and the single-use ticket flow. |
26
+ | **Idempotency** | SQLite-backed `idempotency_records` + LRU read cache. Replays cached responses with `Idempotency-Replayed: true`; mismatched payloads return `409`, and a concurrent duplicate of an in-flight key gets `409 idempotency-in-flight` with a `Retry-After` hint. |
27
27
  | **CORS / CSRF / rate limit** | Deny-by-default CORS; double-submit CSRF for browser flows; sliding-window per-IP rate limit. |
28
28
  | **Audit middleware** | Forwards every authenticated request through `appendAudit` (`@graphorin/security/audit`) with attribution, route, status, and duration. `POST /v1/audit/verify` walks the SHA-256 hash chain and surfaces the integrity status. |
29
29
  | **Triggers daemon** | Wraps `@graphorin/triggers` `Scheduler`; `beforeStart` loads every persisted `trigger_state` row + applies the per-trigger `catchupPolicy`; `beforeShutdown` stops the scheduler. Cron / interval / idle / event triggers survive process restarts. |
@@ -33,12 +33,12 @@
33
33
  | **Prometheus metrics** | `/v1/metrics` text exposition v0.0.4 with `graphorin_*` prefix discipline. The canonical inventory covers agent runs, tool calls, provider tokens / cost, WAL size, idempotency hit ratio, trigger fires, redaction drops, consolidator queue depth / DLQ / budget, OAuth token freshness, replay buffer size, in-flight runs, server uptime, and a static `graphorin_build_info` gauge. |
34
34
  | **Replay endpoints** | `POST /v1/runs/:runId/replay` and `POST /v1/sessions/:id/replay` enforce `traces:read:sanitized` (default) or `traces:read:raw` (admin); every invocation appends an audit chain entry. |
35
35
  | **Graceful shutdown** | Drains in-flight runs within the configured timeout (default 30 s), preserves run state via `RunStateTracker`, propagates `AbortSignal` cancellation to handlers, tears down WebSocket subscriptions cleanly. |
36
- | **No phone home** | Zero implicit network calls verified by the repository-wide `pnpm run check-no-network` CI check. |
36
+ | **No phone home** | Zero implicit network calls - verified by the repository-wide `pnpm run check-no-network` CI check. |
37
37
 
38
38
  ## Out of scope for Phase 14
39
39
 
40
- - Multi-tenant runtime **v0.3+**.
41
- - OpenTelemetry metrics export (in addition to Prometheus) post-MVP.
40
+ - Multi-tenant runtime - **v0.3+**.
41
+ - OpenTelemetry metrics export (in addition to Prometheus) - post-MVP.
42
42
 
43
43
  ## Install
44
44
 
@@ -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.5.0` 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.6.1` 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.5.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
116
+ **Project Graphorin** · v0.6.1 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
package/dist/app.d.ts CHANGED
@@ -59,13 +59,13 @@ interface GraphorinServer {
59
59
  */
60
60
  readonly wsTickets: WsTicketStore | undefined;
61
61
  /**
62
- * Optional triggers daemon populated when the operator wired a
62
+ * Optional triggers daemon - populated when the operator wired a
63
63
  * scheduler (or an in-process trigger surface) at construction
64
64
  * time. Phase 14c integration.
65
65
  */
66
66
  readonly triggers: TriggersDaemon | undefined;
67
67
  /**
68
- * Optional consolidator daemon populated when the operator
68
+ * Optional consolidator daemon - populated when the operator
69
69
  * supplied a `Consolidator` instance via `createServer({
70
70
  * consolidator })`. Phase 14c integration.
71
71
  */
@@ -86,7 +86,7 @@ interface GraphorinServer {
86
86
  /**
87
87
  * Discriminated union accepted by `CreateServerOptions.triggers`. A
88
88
  * caller may either supply a fully-built daemon (e.g. constructed
89
- * around a custom `Scheduler`) or just the underlying scheduler the
89
+ * around a custom `Scheduler`) or just the underlying scheduler - the
90
90
  * server wraps it with {@link createTriggersDaemon} automatically.
91
91
  *
92
92
  * @stable
@@ -100,7 +100,7 @@ type TriggersDaemonInput = {
100
100
  * @stable
101
101
  */
102
102
  interface CreateServerOptions {
103
- /** Loaded `graphorin.config.ts` payload see `defineConfig({...})`. */
103
+ /** Loaded `graphorin.config.ts` payload - see `defineConfig({...})`. */
104
104
  readonly config?: ServerConfigInput;
105
105
  /**
106
106
  * Optional pre-validated config. When supplied, `config` is ignored
@@ -128,7 +128,7 @@ interface CreateServerOptions {
128
128
  */
129
129
  readonly consolidator?: ConsolidatorLike;
130
130
  /**
131
- * Optional triggers daemon pass an existing one (e.g. built
131
+ * Optional triggers daemon - pass an existing one (e.g. built
132
132
  * from `createScheduler`) or a triggers configuration the server
133
133
  * should wrap with the daemon adapter.
134
134
  */
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","names":[],"sources":["../src/app.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmQiB,iBAxJD,uBAAA,CAAA,CAwJgB,EAAA,IAAA;;;;;;AAMf,UANA,eAAA,CAMA;EAQQ,SAAA,OAAA,EAAA,MAAA;EAMH,SAAA,MAAA,EAlBH,gBAkBG;EAMD,SAAA,GAAA,EAvBL,IAuBK,CAAA;IAMI,SAAA,EA7BS,eA6BT;EAKL,CAAA,CAAA;EACT,SAAA,MAAA,EAlCQ,aAkCR;EACqC,SAAA,SAAA,EAlC1B,gBAkC0B;EAAO,SAAA,IAAA,EAjCtC,eAiCsC;EAW3C,SAAA,WAAA,EAAmB;IAOd,SAAA,IAAA,EAAA,MAAmB;IAEhB,SAAA,IAAA,EAAA,MAAA;EAMS,CAAA,GAAA,SAAA;EAEV;;;;;;EASC,SAAA,YAAA,EA9DK,YA8DL,GAAA,SAAA;EACH;;;;;EAuBe,SAAA,SAAA,EAhFV,aAgFU,GAAA,SAAA;EAA6B;;;;;EAYnB,SAAA,QAAA,EAtFrB,cAsFqB,GAAA,SAAA;EAoBpB;;;;;yBApGG;;;;;oBAKL;WACT;;;;;;MACqC;;;;;;;;;;KAWpC,mBAAA;mBACW;;sBAAc,oBAAA,CACmB;;;;;UAKvC,mBAAA;;oBAEG;;;;;;6BAMS;;mBAEV;;kBAED;;oBAEE;uBACG;;sBAED;oBACF;oBACA;iBACH;mBACE;;;;;;0BAMO;;;;;;sBAMJ;;;;;oBAKF;;;;;gCAKY,qBAAqB,QAAQ;;;;;;4BAMjC;;mBAET;;;;mCAIgB;;;;;;;;;;;;;;iBAoBb,YAAA,WAAsB,sBAA2B,QAAQ"}
1
+ {"version":3,"file":"app.d.ts","names":[],"sources":["../src/app.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmQiB,iBAxJD,uBAAA,CAAA,CAwJgB,EAAA,IAAA;;;;;;AAMf,UANA,eAAA,CAMA;EAQQ,SAAA,OAAA,EAAA,MAAA;EAMH,SAAA,MAAA,EAlBH,gBAkBG;EAMD,SAAA,GAAA,EAvBL,IAuBK,CAAA;IAMI,SAAA,EA7BS,eA6BT;EAKL,CAAA,CAAA;EACT,SAAA,MAAA,EAlCQ,aAkCR;EACqC,SAAA,SAAA,EAlC1B,gBAkC0B;EAAO,SAAA,IAAA,EAjCtC,eAiCsC;EAW3C,SAAA,WAAA,EAAmB;IAOd,SAAA,IAAA,EAAA,MAAmB;IAEhB,SAAA,IAAA,EAAA,MAAA;EAMS,CAAA,GAAA,SAAA;EAEV;;;;;;EASC,SAAA,YAAA,EA9DK,YA8DL,GAAA,SAAA;EACH;;;;;EAuBe,SAAA,SAAA,EAhFV,aAgFU,GAAA,SAAA;EAA6B;;;;;EAYnB,SAAA,QAAA,EAtFrB,cAsFqB,GAAA,SAAA;EAsBpB;;;;;yBAtGG;;;;;oBAKL;WACT;;;;;;MACqC;;;;;;;;;;KAWpC,mBAAA;mBACW;;sBAAc,oBAAA,CACmB;;;;;UAKvC,mBAAA;;oBAEG;;;;;;6BAMS;;mBAEV;;kBAED;;oBAEE;uBACG;;sBAED;oBACF;oBACA;iBACH;mBACE;;;;;;0BAMO;;;;;;sBAMJ;;;;;oBAKF;;;;;gCAKY,qBAAqB,QAAQ;;;;;;4BAMjC;;mBAET;;;;mCAIgB;;;;;;;;;;;;;;iBAsBb,YAAA,WAAsB,sBAA2B,QAAQ"}
package/dist/app.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { version } from "./package.js";
1
2
  import { bridgeCommentaryToAudit, createLateBoundCommentarySink } from "./commentary/audit-bridge.js";
2
3
  import "./commentary/index.js";
3
4
  import { LifecycleDoubleStartError, LifecycleNotStartedError, ShutdownTimeoutError } from "./errors/index.js";
@@ -48,6 +49,9 @@ import { Hono } from "hono";
48
49
 
49
50
  //#region src/app.ts
50
51
  /**
52
+ * @stable
53
+ */
54
+ /**
51
55
  * Pre-built audit-db binding shipped from `@graphorin/store-sqlite`.
52
56
  * Registered exactly once per process so {@link openAuditDb} can find
53
57
  * a default binding without forcing operators to wire it manually.
@@ -139,10 +143,7 @@ function rowToEntry(row) {
139
143
  hash: row.hash
140
144
  };
141
145
  }
142
- /**
143
- * @stable
144
- */
145
- const VERSION = "0.5.0";
146
+ const VERSION = version;
146
147
  /**
147
148
  * Build a fully-wired Graphorin server. The returned handle is
148
149
  * inert until `start()` is awaited.
@@ -153,7 +154,7 @@ async function createServer(options = {}) {
153
154
  const config = options.validatedConfig ?? parseServerConfig(options.config ?? {});
154
155
  const now = options.now ?? Date.now;
155
156
  const startedAt = now();
156
- const version = options.version ?? VERSION;
157
+ const version$1 = options.version ?? VERSION;
157
158
  let storeEncryption;
158
159
  if (options.store === void 0 && config.storage.encryption.enabled) {
159
160
  if (config.storage.encryption.passphraseRef === void 0) throw new Error("[graphorin/server] storage.encryption.enabled is true but no passphraseRef is configured.");
@@ -174,7 +175,7 @@ async function createServer(options = {}) {
174
175
  } } : {}
175
176
  });
176
177
  const metricRegistry = options.metricRegistry ?? createServerMetricRegistry();
177
- metricRegistry.set(SERVER_METRIC_NAMES.buildInfo, 1, { version });
178
+ metricRegistry.set(SERVER_METRIC_NAMES.buildInfo, 1, { version: version$1 });
178
179
  const runs = options.runs ?? new RunStateTracker({
179
180
  now,
180
181
  onTerminal: (info) => {
@@ -239,7 +240,7 @@ async function createServer(options = {}) {
239
240
  };
240
241
  }
241
242
  return {
242
- version,
243
+ version: version$1,
243
244
  config,
244
245
  app,
245
246
  agents,
@@ -298,7 +299,7 @@ async function createServer(options = {}) {
298
299
  });
299
300
  }
300
301
  mountRoutes(app, config, {
301
- version,
302
+ version: version$1,
302
303
  startedAt,
303
304
  now,
304
305
  agents,
@@ -450,9 +451,9 @@ function mountRoutes(app, config, ctx) {
450
451
  probes
451
452
  });
452
453
  app.route(`${base}/health`, health);
453
- if (config.auth.kind === "none" && !isLoopbackHost(config.server.host)) console.warn(`[graphorin/server] WARN: auth.kind='none' disables authentication on every endpoint, but the server binds the non-loopback host '${config.server.host}'. Anyone who can reach it has full admin access use auth.kind='token' for non-loopback deployments or bind a loopback host.`);
454
+ if (config.auth.kind === "none" && !isLoopbackHost(config.server.host)) console.warn(`[graphorin/server] WARN: auth.kind='none' disables authentication on every endpoint, but the server binds the non-loopback host '${config.server.host}'. Anyone who can reach it has full admin access - use auth.kind='token' for non-loopback deployments or bind a loopback host.`);
454
455
  if (config.metrics.enabled) {
455
- if (!config.metrics.requireAuth && !isLoopbackHost(config.server.host)) console.warn(`[graphorin/server] WARN: /metrics is unauthenticated (metrics.requireAuth=false) and the server binds the non-loopback host '${config.server.host}'. The exposition leaks operational detail to anyone who can reach it set metrics.requireAuth=true or bind a loopback host.`);
456
+ if (!config.metrics.requireAuth && !isLoopbackHost(config.server.host)) console.warn(`[graphorin/server] WARN: /metrics is unauthenticated (metrics.requireAuth=false) and the server binds the non-loopback host '${config.server.host}'. The exposition leaks operational detail to anyone who can reach it - set metrics.requireAuth=true or bind a loopback host.`);
456
457
  const metricsRoute = createMetricsRoutes({
457
458
  registry: ctx.metricRegistry,
458
459
  requireAuth: config.metrics.requireAuth,
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","names":["config: ServerConfigSpec","storeEncryption:\n | { enabled: true; cipher?: never; passphraseResolver: () => Promise<string> }\n | undefined","serverInstance: ServerType | undefined","listening: { readonly host: string; readonly port: number } | undefined","stopRunPruning: (() => void) | undefined","auditDb: AuditDb | undefined","preBind: Awaited<ReturnType<typeof runPreBind>> | undefined","verifier: TokenVerifier | undefined","pepperHandle: import('@graphorin/security').SecretValue | undefined","triggersDaemon: TriggersDaemon | undefined","consolidatorDaemon: ConsolidatorDaemon | undefined","dispatcher: WsDispatcher | undefined","tickets: WsTicketStore | undefined","wsAdapter: ReturnType<typeof createNodeWebSocket> | undefined","commentaryAuditSink: LateBoundCommentarySink | undefined","out: {\n -readonly [K in keyof HealthCheckOptions]?: HealthCheckOptions[K];\n }"],"sources":["../src/app.ts"],"sourcesContent":["/**\n * `createServer({...})` — the single programmatic entry point.\n *\n * The factory returns a {@link GraphorinServer} handle the operator\n * controls via `start()` / `stop()`. The same handle is consumed by\n * the `graphorin start` CLI binary in `@graphorin/cli`.\n *\n * Phase 14a covers the full HTTP surface, the lifecycle plumbing,\n * the Hono app composition, and the auth / scope / idempotency /\n * audit middleware stack. Phase 14b/c attach the WebSocket layer +\n * triggers daemon + observability endpoints to the same handle.\n *\n * @packageDocumentation\n */\n\nimport type { AddressInfo } from 'node:net';\nimport process from 'node:process';\nimport { negotiateSubprotocol, SUBPROTOCOL_NAME } from '@graphorin/protocol';\nimport { applyProcessHardening, generatePepper, TokenVerifier } from '@graphorin/security';\nimport { type AuditDb, openAuditDb, registerAuditDbBinding } from '@graphorin/security/audit';\nimport {\n createSqliteStore,\n type GraphorinSqliteStore,\n loadCipherDriver,\n readWalSize,\n} from '@graphorin/store-sqlite';\nimport { type ServerType, serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { Hono } from 'hono';\nimport {\n bridgeCommentaryToAudit,\n createLateBoundCommentarySink,\n type LateBoundCommentarySink,\n} from './commentary/index.js';\nimport { parseServerConfig, type ServerConfigInput, type ServerConfigSpec } from './config.js';\nimport type { ConsolidatorDaemon, ConsolidatorLike } from './consolidator/daemon.js';\nimport { createConsolidatorDaemon } from './consolidator/daemon.js';\nimport {\n ConfigInvalidError,\n LifecycleDoubleStartError,\n LifecycleNotStartedError,\n ShutdownTimeoutError,\n} from './errors/index.js';\nimport {\n createExtendedHealthRoutes,\n createMetricsRoutes,\n createSecretsHealthRoutes,\n type HealthCheckOptions,\n} from './health/index.js';\nimport type { ServerVariables } from './internal/context.js';\nimport { type LifecycleHooks, type OnErrorContext, runPreBind } from './lifecycle/index.js';\nimport { createServerMetricRegistry, SERVER_METRIC_NAMES } from './metrics/catalog.js';\nimport type { MetricRegistry } from './metrics/registry.js';\nimport {\n createAnonymousAuthMiddleware,\n createAuditMiddleware,\n createAuthMiddleware,\n createCorsMiddleware,\n createCsrfMiddleware,\n createIdempotencyMiddleware,\n createRateLimitMiddleware,\n createRequestStateMiddleware,\n} from './middleware/index.js';\nimport { AgentRegistry, WorkflowRegistry } from './registry/index.js';\nimport { createReplayRoutes, type ReplayApi } from './replay/index.js';\nimport {\n type AuditApi,\n createAgentRoutes,\n createAuditRoutes,\n createAuthRoutes,\n createMcpRoutes,\n createMemoryRoutes,\n createRunRoutes,\n createSessionRoutes,\n createSkillsRoutes,\n createTokensRoutes,\n createWorkflowRoutes,\n type McpApi,\n type MemoryApi,\n type SessionApi,\n type SkillsApi,\n} from './routes/index.js';\nimport { RunStateTracker, scheduleRunPruning } from './runtime/run-state.js';\nimport { createSseRoutes } from './sse/index.js';\nimport type { TriggersDaemon } from './triggers/daemon.js';\nimport { createTriggersDaemon } from './triggers/daemon.js';\nimport { createTriggersRoutes } from './triggers/routes.js';\nimport {\n createWsDispatcher,\n createWsTicketStore,\n type WsDispatcher,\n type WsTicketStore,\n} from './ws/index.js';\nimport { createWsUpgradeEvents } from './ws/upgrade.js';\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 const db = new Db(opts.path);\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 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\n/**\n * Public surface returned by {@link createServer}.\n *\n * @stable\n */\nexport interface GraphorinServer {\n readonly version: string;\n readonly config: ServerConfigSpec;\n readonly app: Hono<{ Variables: ServerVariables }>;\n readonly agents: AgentRegistry;\n readonly workflows: WorkflowRegistry;\n readonly runs: RunStateTracker;\n readonly listeningOn: { readonly host: string; readonly port: number } | undefined;\n /**\n * Optional WebSocket dispatcher exposed when `server.ws.enabled =\n * true`. Route handlers + the agent / workflow runtimes call\n * `dispatcher.emit(subject, event)` to fan out events to subscribed\n * clients. Returns `undefined` when WS is disabled.\n */\n readonly wsDispatcher: WsDispatcher | undefined;\n /**\n * Optional WebSocket ticket store exposed when `server.ws.enabled\n * = true`. Surfaced primarily for tests; the\n * `POST /v1/session/ws-ticket` route uses it transparently.\n */\n readonly wsTickets: WsTicketStore | undefined;\n /**\n * Optional triggers daemon — populated when the operator wired a\n * scheduler (or an in-process trigger surface) at construction\n * time. Phase 14c integration.\n */\n readonly triggers: TriggersDaemon | undefined;\n /**\n * Optional consolidator daemon — populated when the operator\n * supplied a `Consolidator` instance via `createServer({\n * consolidator })`. Phase 14c integration.\n */\n readonly consolidator: ConsolidatorDaemon | undefined;\n /**\n * Phase 14c Prometheus registry. Always present; sample updates\n * are observable via `metrics.snapshot()`.\n */\n readonly metrics: MetricRegistry;\n start(): Promise<{ readonly host: string; readonly port: number }>;\n stop(options?: { readonly force?: boolean }): Promise<void>;\n}\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/**\n * @stable\n */\nexport interface CreateServerOptions {\n /** Loaded `graphorin.config.ts` payload — see `defineConfig({...})`. */\n readonly config?: ServerConfigInput;\n /**\n * Optional pre-validated config. When supplied, `config` is ignored\n * and the schema validation step is skipped. Useful for tests + the\n * `graphorin migrate` CLI command which bypasses the listener.\n */\n readonly validatedConfig?: ServerConfigSpec;\n /** Pre-built SQLite store. Tests inject an in-memory store. */\n readonly store?: GraphorinSqliteStore;\n /** Optional pre-built tracker. Tests inject deterministic timing. */\n readonly runs?: RunStateTracker;\n /** Optional pre-built registries. */\n readonly agents?: AgentRegistry;\n readonly workflows?: WorkflowRegistry;\n /** Optional in-process domain adapters wired into REST routes. */\n readonly sessions?: SessionApi;\n readonly memory?: MemoryApi;\n readonly skills?: SkillsApi;\n readonly mcp?: McpApi;\n readonly audit?: AuditApi;\n /**\n * Optional consolidator surface (`@graphorin/memory`). Phase 14c\n * starts/stops the runtime alongside the server lifecycle and\n * surfaces its status through `/v1/health`.\n */\n readonly consolidator?: ConsolidatorLike;\n /**\n * Optional triggers daemon — pass an existing one (e.g. built\n * from `createScheduler`) or a triggers configuration the server\n * should wrap with the daemon adapter.\n */\n readonly triggers?: TriggersDaemonInput;\n /**\n * Optional replay API consumed by the scope-enforced replay\n * endpoints. Phase 14c.\n */\n readonly replay?: ReplayApi;\n /**\n * Optional probes that augment `/v1/health`. Provided by consumer\n * code (e.g. `embedder` provides `embedderLoaded`).\n */\n readonly healthProbes?: () => HealthCheckOptions | Promise<HealthCheckOptions>;\n /**\n * Optional Prometheus metric registry override. When omitted, the\n * server constructs the canonical registry from\n * {@link createServerMetricRegistry}.\n */\n readonly metricRegistry?: MetricRegistry;\n /** Lifecycle hook overrides. */\n readonly hooks?: LifecycleHooks;\n /** Wall-clock provider for tests. */\n readonly now?: () => number;\n /** Override the cipher peer probe. Tests inject a stub. */\n readonly probeCipherPeer?: () => Promise<void>;\n /** Override the package version reported on `/v1/health`. */\n readonly version?: string;\n /** Skip `applyProcessHardening` (tests). */\n readonly skipHardening?: boolean;\n /** Skip starting the actual listener (tests). */\n readonly skipListen?: boolean;\n}\n\n/**\n * @stable\n */\nexport const VERSION = '0.5.0';\n\n/**\n * Build a fully-wired Graphorin server. The returned handle is\n * inert until `start()` is awaited.\n *\n * @stable\n */\nexport async function createServer(options: CreateServerOptions = {}): Promise<GraphorinServer> {\n const config: ServerConfigSpec =\n options.validatedConfig ?? parseServerConfig(options.config ?? {});\n const now = options.now ?? Date.now;\n const startedAt = now();\n const version = options.version ?? VERSION;\n\n // IP-1: when encryption is configured, resolve the passphrase ref\n // BEFORE constructing the store and thread the encryption config\n // through — `graphorin init --encrypted` produced a config nothing\n // honoured, and a database encrypted via `storage encrypt` could not\n // be opened by the server at all.\n let storeEncryption:\n | { enabled: true; cipher?: never; passphraseResolver: () => Promise<string> }\n | undefined;\n if (options.store === undefined && config.storage.encryption.enabled) {\n if (config.storage.encryption.passphraseRef === undefined) {\n throw new Error(\n '[graphorin/server] storage.encryption.enabled is true but no passphraseRef is configured.',\n );\n }\n const { resolveSecret } = await import('@graphorin/security/secrets');\n const passphrase = await resolveSecret(config.storage.encryption.passphraseRef);\n storeEncryption = {\n enabled: true,\n passphraseResolver: async () => passphrase.use((v) => v),\n } as never;\n }\n const store =\n options.store ??\n (await createSqliteStore({\n path: config.storage.path,\n mode: config.storage.mode,\n ...(storeEncryption !== undefined\n ? {\n encryption: {\n enabled: true,\n ...(config.storage.encryption.cipher !== undefined\n ? { cipher: config.storage.encryption.cipher as never }\n : {}),\n passphraseResolver: storeEncryption.passphraseResolver,\n },\n }\n : {}),\n }));\n\n const metricRegistry = options.metricRegistry ?? createServerMetricRegistry();\n metricRegistry.set(SERVER_METRIC_NAMES.buildInfo, 1, { version });\n\n // IP-15: agent / workflow run completions move graphorin_agent_runs_total +\n // graphorin_agent_run_duration_seconds. The tracker stays metric-agnostic —\n // it fires a terminal callback the server turns into samples.\n const runs =\n options.runs ??\n new RunStateTracker({\n now,\n onTerminal: (info) => {\n metricRegistry.inc(SERVER_METRIC_NAMES.agentRunsTotal, { status: info.status });\n if (info.durationMs !== undefined) {\n metricRegistry.observe(SERVER_METRIC_NAMES.agentRunDuration, info.durationMs / 1000);\n }\n },\n });\n const agents = options.agents ?? new AgentRegistry();\n const workflows = options.workflows ?? new WorkflowRegistry();\n\n const app = new Hono<{ Variables: ServerVariables }>();\n\n // Global middleware in the documented order. Per-route scope +\n // idempotency layers are mounted by the route factories so the\n // composition stays declarative + auditable.\n app.use(\n '*',\n createRequestStateMiddleware({\n now,\n ...(config.server.trustProxy ? { trustProxy: true } : {}),\n }),\n );\n app.use('*', createCorsMiddleware(config.server.cors));\n app.use('*', createCsrfMiddleware(config.server.csrf));\n app.use('*', createRateLimitMiddleware(config.server.rateLimit, { now }));\n\n let serverInstance: ServerType | undefined;\n let listening: { readonly host: string; readonly port: number } | undefined;\n let started = false;\n let stopped = false;\n // IP-16: stops the periodic terminal-run prune sweep on shutdown.\n let stopRunPruning: (() => void) | undefined;\n let auditDb: AuditDb | undefined;\n let preBind: Awaited<ReturnType<typeof runPreBind>> | undefined;\n let verifier: TokenVerifier | undefined;\n let pepperHandle: import('@graphorin/security').SecretValue | undefined;\n\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\n let dispatcher: WsDispatcher | undefined;\n let tickets: WsTicketStore | undefined;\n let wsAdapter: ReturnType<typeof createNodeWebSocket> | undefined;\n // IP-21: the dispatcher is built here, before the audit DB opens in start().\n // Hand it a late-bound commentary sink now and install the audit-writing\n // target once the DB exists — otherwise the sanitizer's documented decisions\n // (wrapped/stripped frames with before/after digests) are dropped.\n let commentaryAuditSink: LateBoundCommentarySink | undefined;\n if (config.server.ws.enabled) {\n commentaryAuditSink = createLateBoundCommentarySink();\n dispatcher = createWsDispatcher({\n commentary: {\n policy: config.server.ws.commentarySanitization.policy,\n applyToEvents: config.server.ws.commentarySanitization.applyToEvents,\n sink: commentaryAuditSink,\n },\n replayBuffer: {\n maxEvents: config.server.stream.replayBuffer.maxEvents,\n ttlMs: config.server.stream.replayBuffer.ttlSeconds * 1000,\n },\n perConnectionQueueLimit: config.server.stream.perConnectionQueueLimit,\n now,\n });\n tickets = createWsTicketStore({\n ttlMs: config.server.ws.ticketTtlMs,\n now,\n });\n wsAdapter = createNodeWebSocket({ app });\n // The WS server inside @hono/node-ws is created without any\n // subprotocol-negotiation policy; without one, the `ws` library\n // never echoes back `Sec-WebSocket-Protocol` and clients that\n // advertised a subprotocol close the connection immediately.\n // Mutate the options to install a Graphorin-aware policy that\n // selects `graphorin.protocol.v1` when the client offered it.\n const wssOptions = (\n wsAdapter.wss as unknown as {\n options: {\n handleProtocols?: (\n protocols: Set<string>,\n request: import('node:http').IncomingMessage,\n ) => string | false;\n };\n }\n ).options;\n wssOptions.handleProtocols = (protocols) => {\n const negotiated = negotiateSubprotocol(Array.from(protocols));\n if (negotiated !== null) return negotiated;\n // Browser ticket flow: the `WebSocket` constructor cannot set an\n // `Authorization` header, so the browser client offers two\n // subprotocol tokens — the canonical `graphorin.protocol.v1`\n // name plus a `ticket.<value>` token (see the wire contract in\n // `@graphorin/protocol`'s `subprotocol.ts`:\n // `SUBPROTOCOL_NAME` / `TICKET_SUBPROTOCOL_PREFIX` /\n // `parseTicketSubprotocol`). The server MUST echo back exactly\n // the canonical name (never the `ticket.*` token) so the\n // handshake's `Sec-WebSocket-Protocol` response stays valid; the\n // ticket value is consumed separately by `createWsUpgradeEvents`\n // via `parseTicketSubprotocol` and exchanged through the\n // single-use `WsTicketStore`.\n for (const candidate of protocols) {\n if (candidate === SUBPROTOCOL_NAME) return SUBPROTOCOL_NAME;\n }\n return false;\n };\n }\n\n const handle: GraphorinServer = {\n version,\n config,\n app,\n agents,\n workflows,\n runs,\n metrics: metricRegistry,\n get wsDispatcher() {\n return dispatcher;\n },\n get wsTickets() {\n return tickets;\n },\n get triggers() {\n return triggersDaemon;\n },\n get consolidator() {\n return consolidatorDaemon;\n },\n get listeningOn() {\n return listening;\n },\n async start(): Promise<{ readonly host: string; readonly port: number }> {\n if (started) throw new LifecycleDoubleStartError();\n started = true;\n try {\n if (options.hooks?.beforeStart !== undefined) {\n await options.hooks.beforeStart({ config });\n }\n if (options.skipHardening !== true && config.hardening.applyOnStart) {\n applyProcessHardening({\n refuseRoot: config.hardening.refuseRoot,\n umask: config.hardening.umask,\n });\n }\n preBind = await runPreBind({\n config,\n store,\n ...(options.probeCipherPeer !== undefined\n ? { probeCipherPeer: options.probeCipherPeer }\n : {}),\n });\n\n if (\n config.audit.enabled &&\n preBind.auditPassphrase !== undefined &&\n preBind.auditPath !== undefined\n ) {\n ensureStoreAuditBinding();\n auditDb = await openAuditDb({\n path: preBind.auditPath,\n passphrase: preBind.auditPassphrase,\n ...(config.audit.cipher !== undefined ? { cipher: config.audit.cipher } : {}),\n });\n // IP-21: now that the audit chain is open, route the WS dispatcher's\n // commentary-sanitizer decisions into it.\n commentaryAuditSink?.bind(bridgeCommentaryToAudit(auditDb));\n }\n\n if (config.auth.kind === 'token') {\n const pepper = preBind.pepper ?? (await fallbackPepper(options.skipHardening === true));\n pepperHandle = pepper;\n verifier = new TokenVerifier({\n tokenStore: store.authTokens,\n pepper,\n acceptPrefix: config.auth.tokenPrefix,\n acceptEnvironments: config.auth.tokenEnvironments,\n ...(config.auth.perIpFailureThreshold !== undefined\n ? { perIpFailureThreshold: config.auth.perIpFailureThreshold }\n : {}),\n ...(config.auth.perIpLockoutMs !== undefined\n ? { perIpLockoutMs: config.auth.perIpLockoutMs }\n : {}),\n now,\n });\n }\n\n mountRoutes(app, config, {\n version,\n startedAt,\n now,\n agents,\n workflows,\n runs,\n store,\n metricRegistry,\n ...(options.sessions !== undefined ? { sessions: options.sessions } : {}),\n ...(options.memory !== undefined ? { memory: options.memory } : {}),\n ...(options.skills !== undefined ? { skills: options.skills } : {}),\n ...(options.mcp !== undefined ? { mcp: options.mcp } : {}),\n ...(options.audit !== undefined ? { audit: options.audit } : {}),\n ...(options.replay !== undefined ? { replay: options.replay } : {}),\n ...(options.healthProbes !== undefined ? { healthProbes: options.healthProbes } : {}),\n ...(verifier !== undefined ? { verifier } : {}),\n ...(auditDb !== undefined ? { auditDb } : {}),\n ...(pepperHandle !== undefined ? { pepper: pepperHandle } : {}),\n ...(dispatcher !== undefined ? { wsDispatcher: dispatcher } : {}),\n ...(tickets !== undefined ? { wsTickets: tickets } : {}),\n ...(wsAdapter !== undefined ? { wsAdapter } : {}),\n ...(triggersDaemon !== undefined ? { triggersDaemon } : {}),\n ...(consolidatorDaemon !== undefined ? { consolidatorDaemon } : {}),\n });\n\n // Start the consolidator first so it is ready to handle fired\n // triggers, bridge its cron / idle triggers onto the scheduler\n // (MCON-4 — without this nothing pipes triggers into the\n // consolidator and background consolidation never runs), then start\n // the scheduler last so it only fires fully-wired triggers.\n if (consolidatorDaemon !== undefined) {\n await consolidatorDaemon.start();\n if (\n triggersDaemon !== undefined &&\n consolidatorDaemon.consolidator.registerWithScheduler !== undefined\n ) {\n await consolidatorDaemon.consolidator.registerWithScheduler(triggersDaemon.scheduler);\n }\n }\n if (triggersDaemon !== undefined) {\n await triggersDaemon.start();\n }\n\n // Sample a couple of gauges immediately so the very first\n // `/v1/metrics` scrape after start carries non-zero data.\n try {\n const wal = readWalSize(store.connection);\n metricRegistry.set(SERVER_METRIC_NAMES.storageWalSize, wal);\n } catch {\n // Best-effort.\n }\n metricRegistry.set(\n SERVER_METRIC_NAMES.serverUptime,\n Math.max(0, Math.floor((now() - startedAt) / 1000)),\n );\n metricRegistry.set(SERVER_METRIC_NAMES.inflightRuns, runs.runningCount());\n metricRegistry.set(SERVER_METRIC_NAMES.replayBufferEvents, 0);\n\n // IP-16: terminal run records (each holding an AbortController) would\n // otherwise accumulate forever; sweep them on a periodic timer.\n stopRunPruning = scheduleRunPruning(runs, now);\n\n if (options.skipListen !== true) {\n serverInstance = serve({\n fetch: app.fetch.bind(app),\n hostname: config.server.host,\n port: config.server.port,\n });\n if (wsAdapter !== undefined && serverInstance !== undefined) {\n wsAdapter.injectWebSocket(serverInstance);\n }\n await new Promise<void>((resolve) => {\n const server = serverInstance as unknown as {\n once(event: 'listening', cb: () => void): void;\n };\n if ('once' in server && typeof server.once === 'function') {\n server.once('listening', () => resolve());\n } else {\n // serve(...) returned a Node http.Server already bound.\n setImmediate(() => resolve());\n }\n });\n const address = (\n serverInstance as unknown as { address(): AddressInfo | string | null }\n ).address();\n if (address !== null && typeof address === 'object') {\n listening = { host: address.address, port: address.port };\n } else {\n listening = { host: config.server.host, port: config.server.port };\n }\n } else {\n listening = { host: config.server.host, port: config.server.port };\n }\n\n if (options.hooks?.onReady !== undefined) {\n await options.hooks.onReady({ config, listeningOn: listening });\n }\n return listening;\n } catch (err) {\n started = false;\n await emitError(options.hooks, { error: err, phase: 'beforeStart' });\n throw err;\n }\n },\n async stop({ force }: { readonly force?: boolean } = {}): Promise<void> {\n if (!started) throw new LifecycleNotStartedError();\n if (stopped) return;\n stopped = true;\n // IP-16: halt the prune sweep before draining.\n stopRunPruning?.();\n stopRunPruning = undefined;\n const drainTimeoutMs = force === true ? 0 : config.server.shutdown.drainTimeoutMs;\n try {\n if (options.hooks?.beforeShutdown !== undefined) {\n await options.hooks.beforeShutdown({\n config,\n inflight: runs.inflightCount(),\n drainTimeoutMs,\n });\n }\n } catch (err) {\n await emitError(options.hooks, { error: err, phase: 'beforeShutdown' });\n }\n\n if (triggersDaemon !== undefined) {\n try {\n await triggersDaemon.stop();\n } catch (err) {\n await emitError(options.hooks, { error: err, phase: 'beforeShutdown' });\n }\n }\n if (consolidatorDaemon !== undefined) {\n try {\n await consolidatorDaemon.stop();\n } catch (err) {\n await emitError(options.hooks, { error: err, phase: 'beforeShutdown' });\n }\n }\n\n // Pending reservations (e.g. awaited WS subscriptions for the\n // streaming endpoints) hold no work in progress; abort them\n // immediately so the drain only waits for live runs.\n runs.abortPending('server-shutdown');\n const drained = await drainInFlight(runs, drainTimeoutMs, now);\n if (!drained && drainTimeoutMs > 0) {\n // Force-abort everything that didn't drain in time.\n runs.abortAll(new ShutdownTimeoutError(drainTimeoutMs, runs.runningCount()));\n }\n\n if (dispatcher !== undefined) {\n try {\n dispatcher.shutdown();\n } catch {\n // Best-effort during stop().\n }\n }\n\n if (serverInstance !== undefined) {\n await new Promise<void>((resolve) => {\n (serverInstance as unknown as { close(cb: () => void): void }).close(() => resolve());\n });\n serverInstance = undefined;\n }\n if (auditDb !== undefined) {\n await auditDb.close();\n auditDb = undefined;\n }\n await store.close();\n listening = undefined;\n verifier = undefined;\n started = false;\n preBind = undefined;\n dispatcher = undefined;\n tickets = undefined;\n wsAdapter = undefined;\n },\n };\n return handle;\n}\n\ninterface MountRoutesContext {\n readonly version: string;\n readonly startedAt: number;\n readonly now: () => number;\n readonly agents: AgentRegistry;\n readonly workflows: WorkflowRegistry;\n readonly runs: RunStateTracker;\n readonly store: GraphorinSqliteStore;\n readonly metricRegistry: MetricRegistry;\n readonly sessions?: SessionApi;\n readonly memory?: MemoryApi;\n readonly skills?: SkillsApi;\n readonly mcp?: McpApi;\n readonly audit?: AuditApi;\n readonly replay?: ReplayApi;\n readonly healthProbes?: () => HealthCheckOptions | Promise<HealthCheckOptions>;\n readonly verifier?: TokenVerifier;\n readonly auditDb?: AuditDb;\n readonly pepper?: import('@graphorin/security').SecretValue;\n readonly wsDispatcher?: WsDispatcher;\n readonly wsTickets?: WsTicketStore;\n readonly wsAdapter?: ReturnType<typeof createNodeWebSocket>;\n readonly triggersDaemon?: TriggersDaemon;\n readonly consolidatorDaemon?: ConsolidatorDaemon;\n}\n\n/** IP-23: is `host` a loopback interface (so an open /metrics is not exposed)? */\nfunction isLoopbackHost(host: string): boolean {\n const h = host.trim().toLowerCase();\n return h === '127.0.0.1' || h === '::1' || h === '[::1]' || h === 'localhost';\n}\n\nfunction mountRoutes(\n app: Hono<{ Variables: ServerVariables }>,\n config: ServerConfigSpec,\n ctx: MountRoutesContext,\n): void {\n const base = config.server.basePath;\n const probes =\n ctx.healthProbes ??\n (() =>\n buildDefaultHealthProbes(\n ctx.store,\n ctx.triggersDaemon,\n ctx.consolidatorDaemon,\n ctx.wsDispatcher,\n config,\n ));\n const health = createExtendedHealthRoutes({\n version: ctx.version,\n startedAt: ctx.startedAt,\n now: ctx.now,\n probes,\n });\n app.route(`${base}/health`, health);\n\n // IP-13: `auth.kind='none'` disables authentication on every route. It is the\n // documented trusted-loopback / single-operator mode, but binding a\n // non-loopback host with auth off exposes full admin access (including the WS\n // stream) to anyone who can reach it — warn loudly rather than silently.\n if (config.auth.kind === 'none' && !isLoopbackHost(config.server.host)) {\n console.warn(\n `[graphorin/server] WARN: auth.kind='none' disables authentication on every endpoint, ` +\n `but the server binds the non-loopback host '${config.server.host}'. Anyone who can reach ` +\n `it has full admin access — use auth.kind='token' for non-loopback deployments or bind a ` +\n `loopback host.`,\n );\n }\n\n if (config.metrics.enabled) {\n // IP-23: an unauthenticated /metrics endpoint leaks operational intel\n // (trigger ids in labels, consolidator budgets). It is fine on a loopback\n // host, but binding a non-loopback host with auth off silently exposes it.\n if (!config.metrics.requireAuth && !isLoopbackHost(config.server.host)) {\n console.warn(\n `[graphorin/server] WARN: /metrics is unauthenticated (metrics.requireAuth=false) and ` +\n `the server binds the non-loopback host '${config.server.host}'. The exposition leaks ` +\n `operational detail to anyone who can reach it — set metrics.requireAuth=true or bind a ` +\n `loopback host.`,\n );\n }\n const metricsRoute = createMetricsRoutes({\n registry: ctx.metricRegistry,\n requireAuth: config.metrics.requireAuth,\n refresh: () =>\n refreshLiveMetrics({\n registry: ctx.metricRegistry,\n store: ctx.store,\n runs: ctx.runs,\n startedAt: ctx.startedAt,\n now: ctx.now,\n ...(ctx.triggersDaemon !== undefined ? { triggersDaemon: ctx.triggersDaemon } : {}),\n ...(ctx.consolidatorDaemon !== undefined\n ? { consolidatorDaemon: ctx.consolidatorDaemon }\n : {}),\n ...(ctx.wsDispatcher !== undefined ? { wsDispatcher: ctx.wsDispatcher } : {}),\n }),\n });\n app.route(`${base}${config.metrics.path}`, metricsRoute);\n }\n\n // Authenticated subtree begins here. The health endpoint above is\n // intentionally outside the auth boundary so liveness probes work\n // before the token verifier is wired. The WebSocket upgrade path is\n // also exempt: the upgrade handler in `ws/upgrade.ts` performs its\n // own bearer + ticket validation inline (the HTTP auth middleware\n // would otherwise short-circuit the upgrade with a 401 response\n // before the handler can negotiate the subprotocol).\n const wsUpgradePath = config.server.ws.enabled ? `${base}${config.server.ws.path}` : undefined;\n const metricsPath =\n config.metrics.enabled && !config.metrics.requireAuth\n ? `${base}${config.metrics.path}`\n : undefined;\n function shouldSkipAuth(path: string): boolean {\n if (path === `${base}/health` || path === `${base}/health/`) return true;\n if (wsUpgradePath !== undefined) {\n if (path === wsUpgradePath || path === `${wsUpgradePath}/`) return true;\n }\n if (metricsPath !== undefined) {\n if (path === metricsPath || path === `${metricsPath}/`) return true;\n }\n return false;\n }\n // IP-13: in the no-auth loopback mode (`auth.kind='none'`) there is no\n // verifier, but the authenticated subtree must still be reachable — install\n // an anonymous middleware that stamps a fully-authorized principal so the\n // domain routes serve instead of every one of them returning 401.\n const anonymousAuth = ctx.verifier === undefined && config.auth.kind === 'none';\n if (ctx.verifier !== undefined || anonymousAuth) {\n const authMw =\n ctx.verifier !== undefined\n ? createAuthMiddleware({ verifier: ctx.verifier })\n : createAnonymousAuthMiddleware();\n app.use(`${base}/*`, async (c, next) => {\n if (shouldSkipAuth(c.req.path)) {\n await next();\n return;\n }\n return authMw(c, next);\n });\n if (ctx.auditDb !== undefined) {\n const auditMw = createAuditMiddleware({ auditDb: ctx.auditDb, now: ctx.now });\n app.use(`${base}/*`, async (c, next) => {\n if (shouldSkipAuth(c.req.path)) {\n await next();\n return;\n }\n return auditMw(c, next);\n });\n }\n }\n\n // Idempotency middleware — applied to side-effecting endpoints. We\n // mount it once on the authenticated subtree so handlers don't have\n // to duplicate the configuration per-route.\n if (config.server.idempotency.enabled) {\n const idempotencyMw = createIdempotencyMiddleware({\n store: ctx.store.idempotency,\n config: config.server.idempotency,\n now: ctx.now,\n // IP-6: token minting returns a raw secret — never cache it.\n excludeResponseCachePaths: [`${base}/tokens`],\n // IP-15: publish the live cache hit ratio gauge.\n metricRegistry: ctx.metricRegistry,\n });\n app.use(`${base}/*`, async (c, next) => {\n if (shouldSkipAuth(c.req.path)) {\n await next();\n return;\n }\n return idempotencyMw(c, next);\n });\n }\n\n // Mounted AFTER the auth middleware so the scope check has a\n // verified token to inspect; the unauthenticated `/v1/health` GET\n // continues to serve the rollup.\n app.route(`${base}/health/secrets`, createSecretsHealthRoutes());\n\n app.route(\n `${base}/agents`,\n createAgentRoutes({\n agents: ctx.agents,\n runs: ctx.runs,\n // IP-2: the streaming dispatcher reaches the route layer.\n ...(ctx.wsDispatcher !== undefined ? { dispatcher: ctx.wsDispatcher } : {}),\n }),\n );\n app.route(`${base}/runs`, createRunRoutes({ agents: ctx.agents, runs: ctx.runs }));\n app.route(\n `${base}/workflows`,\n createWorkflowRoutes({\n workflows: ctx.workflows,\n runs: ctx.runs,\n ...(ctx.wsDispatcher !== undefined ? { dispatcher: ctx.wsDispatcher } : {}),\n }),\n );\n if (ctx.sessions !== undefined) {\n app.route(`${base}/sessions`, createSessionRoutes({ sessions: ctx.sessions }));\n }\n if (ctx.memory !== undefined) {\n app.route(`${base}/memory`, createMemoryRoutes({ memory: ctx.memory }));\n }\n if (ctx.skills !== undefined) {\n app.route(`${base}/skills`, createSkillsRoutes({ skills: ctx.skills }));\n }\n if (ctx.mcp !== undefined) {\n app.route(`${base}/mcp`, createMcpRoutes({ mcp: ctx.mcp }));\n }\n if (ctx.audit !== undefined) {\n app.route(`${base}/audit`, createAuditRoutes({ audit: ctx.audit }));\n }\n if (ctx.triggersDaemon !== undefined) {\n app.route(`${base}/triggers`, createTriggersRoutes({ daemon: ctx.triggersDaemon }));\n }\n if (ctx.replay !== undefined) {\n app.route(\n `${base}`,\n createReplayRoutes({\n replay: ctx.replay,\n ...(ctx.auditDb !== undefined ? { auditDb: ctx.auditDb } : {}),\n now: ctx.now,\n }),\n );\n }\n if (config.auth.kind === 'token' && ctx.pepper !== undefined) {\n app.route(\n `${base}/tokens`,\n createTokensRoutes({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n defaultEnv: 'live',\n allowedEnvs: config.auth.tokenEnvironments,\n }),\n );\n }\n if (ctx.wsTickets !== undefined) {\n app.route(`${base}`, createAuthRoutes({ tickets: ctx.wsTickets }));\n }\n // IP-13: mount the WS upgrade when a verifier is wired OR auth is disabled\n // (`auth.kind='none'`). The old condition required a verifier, so\n // `ws.enabled: true` under no-auth was silently ignored — the route never\n // mounted and clients got a bare 404 with no explanation.\n if (\n ctx.wsDispatcher !== undefined &&\n ctx.wsTickets !== undefined &&\n ctx.wsAdapter !== undefined &&\n (ctx.verifier !== undefined || anonymousAuth) &&\n config.server.ws.enabled\n ) {\n const dispatcher = ctx.wsDispatcher;\n const tickets = ctx.wsTickets;\n const verifier = ctx.verifier;\n const runs = ctx.runs;\n app.get(\n `${base}${config.server.ws.path}`,\n ctx.wsAdapter.upgradeWebSocket((c) =>\n createWsUpgradeEvents(c, {\n dispatcher,\n tickets,\n ...(verifier !== undefined ? { verifier } : {}),\n anonymous: anonymousAuth,\n runs,\n now: ctx.now,\n }),\n ),\n );\n }\n if (ctx.wsDispatcher !== undefined && config.server.sse.enabled) {\n app.route(\n `${base}${config.server.sse.path}`,\n createSseRoutes({\n dispatcher: ctx.wsDispatcher,\n keepAliveMs: config.server.sse.keepAliveMs,\n // IP-9: bound the per-connection delivery queue.\n perConnectionQueueLimit: config.server.stream.perConnectionQueueLimit,\n now: ctx.now,\n }),\n );\n }\n}\n\nasync function drainInFlight(\n runs: RunStateTracker,\n drainTimeoutMs: number,\n now: () => number,\n): Promise<boolean> {\n if (runs.runningCount() === 0) return true;\n if (drainTimeoutMs <= 0) return runs.runningCount() === 0;\n const deadline = now() + drainTimeoutMs;\n while (now() < deadline) {\n if (runs.runningCount() === 0) return true;\n await new Promise((resolve) => setTimeout(resolve, Math.min(50, deadline - now())));\n }\n return runs.runningCount() === 0;\n}\n\nasync function emitError(hooks: LifecycleHooks | undefined, ctx: OnErrorContext): Promise<void> {\n if (hooks?.onError === undefined) return;\n try {\n await hooks.onError(ctx);\n } catch {\n // onError must never throw further; swallow.\n }\n}\n\ninterface RefreshLiveMetricsOptions {\n readonly registry: MetricRegistry;\n readonly store: GraphorinSqliteStore;\n readonly runs: RunStateTracker;\n readonly startedAt: number;\n readonly now: () => number;\n readonly triggersDaemon?: TriggersDaemon;\n readonly consolidatorDaemon?: ConsolidatorDaemon;\n readonly wsDispatcher?: WsDispatcher;\n}\n\nasync function refreshLiveMetrics(options: RefreshLiveMetricsOptions): Promise<void> {\n const { registry, store, runs, startedAt, now } = options;\n\n try {\n const wal = readWalSize(store.connection);\n registry.set(SERVER_METRIC_NAMES.storageWalSize, wal);\n } catch {\n // Best-effort.\n }\n\n registry.set(\n SERVER_METRIC_NAMES.serverUptime,\n Math.max(0, Math.floor((now() - startedAt) / 1000)),\n );\n registry.set(SERVER_METRIC_NAMES.inflightRuns, runs.runningCount());\n\n if (options.wsDispatcher !== undefined) {\n const sizes = options.wsDispatcher.size();\n registry.set(SERVER_METRIC_NAMES.replayBufferEvents, sizes.subscriptions);\n }\n\n if (options.triggersDaemon !== undefined) {\n const metrics = options.triggersDaemon.metrics();\n for (const [triggerId, counts] of metrics.fires) {\n const sanitized = sanitizeMetricLabelValue(triggerId);\n const successCurrent = readCounter(registry, SERVER_METRIC_NAMES.triggersFiresTotal, {\n trigger_id: sanitized,\n status: 'success',\n });\n const errorCurrent = readCounter(registry, SERVER_METRIC_NAMES.triggersFiresTotal, {\n trigger_id: sanitized,\n status: 'error',\n });\n const successDelta = counts.success - successCurrent;\n const errorDelta = counts.error - errorCurrent;\n if (successDelta > 0) {\n registry.inc(\n SERVER_METRIC_NAMES.triggersFiresTotal,\n { trigger_id: sanitized, status: 'success' },\n successDelta,\n );\n }\n if (errorDelta > 0) {\n registry.inc(\n SERVER_METRIC_NAMES.triggersFiresTotal,\n { trigger_id: sanitized, status: 'error' },\n errorDelta,\n );\n }\n }\n }\n\n if (options.consolidatorDaemon !== undefined) {\n try {\n const status = await options.consolidatorDaemon.status();\n registry.set(SERVER_METRIC_NAMES.consolidatorQueueDepth, status.queueDepth, {\n phase: 'aggregate',\n });\n registry.set(SERVER_METRIC_NAMES.consolidatorDlqSize, status.dlqSize);\n registry.set(SERVER_METRIC_NAMES.consolidatorBudgetRemainingUsd, status.budget.costRemaining);\n registry.set(\n SERVER_METRIC_NAMES.consolidatorBudgetRemainingTokens,\n status.budget.tokensRemaining,\n );\n } catch {\n // Best-effort.\n }\n }\n}\n\n/**\n * Convert an arbitrary user-supplied identifier (trigger id) into a\n * Prometheus-safe label value. Replaces every character outside the\n * `[A-Za-z0-9_:]` range with `_`. This guarantees the cardinality\n * never explodes on UTF-8 sequences while keeping the value\n * recognizable.\n */\nfunction sanitizeMetricLabelValue(value: string): string {\n return value.replace(/[^A-Za-z0-9_:.-]/g, '_').slice(0, 200);\n}\n\nfunction readCounter(\n registry: MetricRegistry,\n name: string,\n labels: Record<string, string>,\n): number {\n const snap = registry.snapshot().counters[name] ?? [];\n for (const entry of snap) {\n if (matchesLabels(entry.labels, labels)) return entry.value;\n }\n return 0;\n}\n\nfunction matchesLabels(\n a: Record<string, string | number | boolean>,\n b: Record<string, string>,\n): boolean {\n for (const k of Object.keys(b)) {\n if (String(a[k]) !== b[k]) return false;\n }\n for (const k of Object.keys(a)) {\n if (!(k in b)) return false;\n }\n return true;\n}\n\nfunction buildDefaultHealthProbes(\n store: GraphorinSqliteStore,\n triggersDaemon: TriggersDaemon | undefined,\n consolidatorDaemon: ConsolidatorDaemon | undefined,\n dispatcher: WsDispatcher | undefined,\n config: ServerConfigSpec,\n): HealthCheckOptions {\n const out: {\n -readonly [K in keyof HealthCheckOptions]?: HealthCheckOptions[K];\n } = {\n store,\n walWarnThresholdBytes: config.health.walWarnThresholdBytes,\n encryptionEnabled: config.storage.encryption.enabled,\n // IP-1: when this process built the encrypted store itself, the\n // keyed open at boot proved the cipher peer — report the fact.\n ...(config.storage.encryption.enabled ? { cipherPeerInstalled: true } : {}),\n };\n if (triggersDaemon !== undefined) out.triggers = triggersDaemon;\n if (consolidatorDaemon !== undefined) out.consolidator = consolidatorDaemon;\n if (dispatcher !== undefined) {\n const sizes = dispatcher.size();\n // The dispatcher only exposes (subscribers, subscriptions); the\n // per-subject replay-buffer occupancy is owned by each\n // subscription, not the dispatcher itself. Surface the active\n // subscription count as a usable proxy + the raw subscriber count\n // so operators can correlate.\n out.replayBuffer = {\n eventsBuffered: sizes.subscriptions,\n subscribers: sizes.subscribers,\n subscriptions: sizes.subscriptions,\n };\n }\n return Object.freeze(out as HealthCheckOptions);\n}\n\nasync function fallbackPepper(skipHardening: boolean) {\n // Test-only path: when `skipHardening` is set we mint an ephemeral\n // pepper so the verifier is constructible without a real keyring.\n if (!skipHardening) {\n throw new Error(\n '[graphorin/server] missing resolved pepper after pre-bind; auth.pepperRef is required when auth.kind = token.',\n );\n }\n return generatePepper();\n}\n\nvoid process;\nvoid ConfigInvalidError;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0GA,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;GAe9D,MAAM,KAAK,IAdA,OAcO,KAAK,KAAK;AAC5B,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;;IAEH,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;;;;;AAoIH,MAAa,UAAU;;;;;;;AAQvB,eAAsB,aAAa,UAA+B,EAAE,EAA4B;CAC9F,MAAMA,SACJ,QAAQ,mBAAmB,kBAAkB,QAAQ,UAAU,EAAE,CAAC;CACpE,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,MAAM,YAAY,KAAK;CACvB,MAAM,UAAU,QAAQ,WAAW;CAOnC,IAAIC;AAGJ,KAAI,QAAQ,UAAU,UAAa,OAAO,QAAQ,WAAW,SAAS;AACpE,MAAI,OAAO,QAAQ,WAAW,kBAAkB,OAC9C,OAAM,IAAI,MACR,4FACD;EAEH,MAAM,EAAE,kBAAkB,MAAM,OAAO;EACvC,MAAM,aAAa,MAAM,cAAc,OAAO,QAAQ,WAAW,cAAc;AAC/E,oBAAkB;GAChB,SAAS;GACT,oBAAoB,YAAY,WAAW,KAAK,MAAM,EAAE;GACzD;;CAEH,MAAM,QACJ,QAAQ,SACP,MAAM,kBAAkB;EACvB,MAAM,OAAO,QAAQ;EACrB,MAAM,OAAO,QAAQ;EACrB,GAAI,oBAAoB,SACpB,EACE,YAAY;GACV,SAAS;GACT,GAAI,OAAO,QAAQ,WAAW,WAAW,SACrC,EAAE,QAAQ,OAAO,QAAQ,WAAW,QAAiB,GACrD,EAAE;GACN,oBAAoB,gBAAgB;GACrC,EACF,GACD,EAAE;EACP,CAAC;CAEJ,MAAM,iBAAiB,QAAQ,kBAAkB,4BAA4B;AAC7E,gBAAe,IAAI,oBAAoB,WAAW,GAAG,EAAE,SAAS,CAAC;CAKjE,MAAM,OACJ,QAAQ,QACR,IAAI,gBAAgB;EAClB;EACA,aAAa,SAAS;AACpB,kBAAe,IAAI,oBAAoB,gBAAgB,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAC/E,OAAI,KAAK,eAAe,OACtB,gBAAe,QAAQ,oBAAoB,kBAAkB,KAAK,aAAa,IAAK;;EAGzF,CAAC;CACJ,MAAM,SAAS,QAAQ,UAAU,IAAI,eAAe;CACpD,MAAM,YAAY,QAAQ,aAAa,IAAI,kBAAkB;CAE7D,MAAM,MAAM,IAAI,MAAsC;AAKtD,KAAI,IACF,KACA,6BAA6B;EAC3B;EACA,GAAI,OAAO,OAAO,aAAa,EAAE,YAAY,MAAM,GAAG,EAAE;EACzD,CAAC,CACH;AACD,KAAI,IAAI,KAAK,qBAAqB,OAAO,OAAO,KAAK,CAAC;AACtD,KAAI,IAAI,KAAK,qBAAqB,OAAO,OAAO,KAAK,CAAC;AACtD,KAAI,IAAI,KAAK,0BAA0B,OAAO,OAAO,WAAW,EAAE,KAAK,CAAC,CAAC;CAEzE,IAAIC;CACJ,IAAIC;CACJ,IAAI,UAAU;CACd,IAAI,UAAU;CAEd,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CAEJ,IAAIC;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;CAGvF,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CAKJ,IAAIC;AACJ,KAAI,OAAO,OAAO,GAAG,SAAS;AAC5B,wBAAsB,+BAA+B;AACrD,eAAa,mBAAmB;GAC9B,YAAY;IACV,QAAQ,OAAO,OAAO,GAAG,uBAAuB;IAChD,eAAe,OAAO,OAAO,GAAG,uBAAuB;IACvD,MAAM;IACP;GACD,cAAc;IACZ,WAAW,OAAO,OAAO,OAAO,aAAa;IAC7C,OAAO,OAAO,OAAO,OAAO,aAAa,aAAa;IACvD;GACD,yBAAyB,OAAO,OAAO,OAAO;GAC9C;GACD,CAAC;AACF,YAAU,oBAAoB;GAC5B,OAAO,OAAO,OAAO,GAAG;GACxB;GACD,CAAC;AACF,cAAY,oBAAoB,EAAE,KAAK,CAAC;EAOxC,MAAM,aACJ,UAAU,IAQV;AACF,aAAW,mBAAmB,cAAc;GAC1C,MAAM,aAAa,qBAAqB,MAAM,KAAK,UAAU,CAAC;AAC9D,OAAI,eAAe,KAAM,QAAO;AAahC,QAAK,MAAM,aAAa,UACtB,KAAI,cAAc,iBAAkB,QAAO;AAE7C,UAAO;;;AAoQX,QAhQgC;EAC9B;EACA;EACA;EACA;EACA;EACA;EACA,SAAS;EACT,IAAI,eAAe;AACjB,UAAO;;EAET,IAAI,YAAY;AACd,UAAO;;EAET,IAAI,WAAW;AACb,UAAO;;EAET,IAAI,eAAe;AACjB,UAAO;;EAET,IAAI,cAAc;AAChB,UAAO;;EAET,MAAM,QAAmE;AACvE,OAAI,QAAS,OAAM,IAAI,2BAA2B;AAClD,aAAU;AACV,OAAI;AACF,QAAI,QAAQ,OAAO,gBAAgB,OACjC,OAAM,QAAQ,MAAM,YAAY,EAAE,QAAQ,CAAC;AAE7C,QAAI,QAAQ,kBAAkB,QAAQ,OAAO,UAAU,aACrD,uBAAsB;KACpB,YAAY,OAAO,UAAU;KAC7B,OAAO,OAAO,UAAU;KACzB,CAAC;AAEJ,cAAU,MAAM,WAAW;KACzB;KACA;KACA,GAAI,QAAQ,oBAAoB,SAC5B,EAAE,iBAAiB,QAAQ,iBAAiB,GAC5C,EAAE;KACP,CAAC;AAEF,QACE,OAAO,MAAM,WACb,QAAQ,oBAAoB,UAC5B,QAAQ,cAAc,QACtB;AACA,8BAAyB;AACzB,eAAU,MAAM,YAAY;MAC1B,MAAM,QAAQ;MACd,YAAY,QAAQ;MACpB,GAAI,OAAO,MAAM,WAAW,SAAY,EAAE,QAAQ,OAAO,MAAM,QAAQ,GAAG,EAAE;MAC7E,CAAC;AAGF,0BAAqB,KAAK,wBAAwB,QAAQ,CAAC;;AAG7D,QAAI,OAAO,KAAK,SAAS,SAAS;KAChC,MAAM,SAAS,QAAQ,UAAW,MAAM,eAAe,QAAQ,kBAAkB,KAAK;AACtF,oBAAe;AACf,gBAAW,IAAI,cAAc;MAC3B,YAAY,MAAM;MAClB;MACA,cAAc,OAAO,KAAK;MAC1B,oBAAoB,OAAO,KAAK;MAChC,GAAI,OAAO,KAAK,0BAA0B,SACtC,EAAE,uBAAuB,OAAO,KAAK,uBAAuB,GAC5D,EAAE;MACN,GAAI,OAAO,KAAK,mBAAmB,SAC/B,EAAE,gBAAgB,OAAO,KAAK,gBAAgB,GAC9C,EAAE;MACN;MACD,CAAC;;AAGJ,gBAAY,KAAK,QAAQ;KACvB;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA,GAAI,QAAQ,aAAa,SAAY,EAAE,UAAU,QAAQ,UAAU,GAAG,EAAE;KACxE,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;KAClE,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;KAClE,GAAI,QAAQ,QAAQ,SAAY,EAAE,KAAK,QAAQ,KAAK,GAAG,EAAE;KACzD,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,OAAO,GAAG,EAAE;KAC/D,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;KAClE,GAAI,QAAQ,iBAAiB,SAAY,EAAE,cAAc,QAAQ,cAAc,GAAG,EAAE;KACpF,GAAI,aAAa,SAAY,EAAE,UAAU,GAAG,EAAE;KAC9C,GAAI,YAAY,SAAY,EAAE,SAAS,GAAG,EAAE;KAC5C,GAAI,iBAAiB,SAAY,EAAE,QAAQ,cAAc,GAAG,EAAE;KAC9D,GAAI,eAAe,SAAY,EAAE,cAAc,YAAY,GAAG,EAAE;KAChE,GAAI,YAAY,SAAY,EAAE,WAAW,SAAS,GAAG,EAAE;KACvD,GAAI,cAAc,SAAY,EAAE,WAAW,GAAG,EAAE;KAChD,GAAI,mBAAmB,SAAY,EAAE,gBAAgB,GAAG,EAAE;KAC1D,GAAI,uBAAuB,SAAY,EAAE,oBAAoB,GAAG,EAAE;KACnE,CAAC;AAOF,QAAI,uBAAuB,QAAW;AACpC,WAAM,mBAAmB,OAAO;AAChC,SACE,mBAAmB,UACnB,mBAAmB,aAAa,0BAA0B,OAE1D,OAAM,mBAAmB,aAAa,sBAAsB,eAAe,UAAU;;AAGzF,QAAI,mBAAmB,OACrB,OAAM,eAAe,OAAO;AAK9B,QAAI;KACF,MAAM,MAAM,YAAY,MAAM,WAAW;AACzC,oBAAe,IAAI,oBAAoB,gBAAgB,IAAI;YACrD;AAGR,mBAAe,IACb,oBAAoB,cACpB,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,GAAG,aAAa,IAAK,CAAC,CACpD;AACD,mBAAe,IAAI,oBAAoB,cAAc,KAAK,cAAc,CAAC;AACzE,mBAAe,IAAI,oBAAoB,oBAAoB,EAAE;AAI7D,qBAAiB,mBAAmB,MAAM,IAAI;AAE9C,QAAI,QAAQ,eAAe,MAAM;AAC/B,sBAAiB,MAAM;MACrB,OAAO,IAAI,MAAM,KAAK,IAAI;MAC1B,UAAU,OAAO,OAAO;MACxB,MAAM,OAAO,OAAO;MACrB,CAAC;AACF,SAAI,cAAc,UAAa,mBAAmB,OAChD,WAAU,gBAAgB,eAAe;AAE3C,WAAM,IAAI,SAAe,YAAY;MACnC,MAAM,SAAS;AAGf,UAAI,UAAU,UAAU,OAAO,OAAO,SAAS,WAC7C,QAAO,KAAK,mBAAmB,SAAS,CAAC;UAGzC,oBAAmB,SAAS,CAAC;OAE/B;KACF,MAAM,UACJ,eACA,SAAS;AACX,SAAI,YAAY,QAAQ,OAAO,YAAY,SACzC,aAAY;MAAE,MAAM,QAAQ;MAAS,MAAM,QAAQ;MAAM;SAEzD,aAAY;MAAE,MAAM,OAAO,OAAO;MAAM,MAAM,OAAO,OAAO;MAAM;UAGpE,aAAY;KAAE,MAAM,OAAO,OAAO;KAAM,MAAM,OAAO,OAAO;KAAM;AAGpE,QAAI,QAAQ,OAAO,YAAY,OAC7B,OAAM,QAAQ,MAAM,QAAQ;KAAE;KAAQ,aAAa;KAAW,CAAC;AAEjE,WAAO;YACA,KAAK;AACZ,cAAU;AACV,UAAM,UAAU,QAAQ,OAAO;KAAE,OAAO;KAAK,OAAO;KAAe,CAAC;AACpE,UAAM;;;EAGV,MAAM,KAAK,EAAE,UAAwC,EAAE,EAAiB;AACtE,OAAI,CAAC,QAAS,OAAM,IAAI,0BAA0B;AAClD,OAAI,QAAS;AACb,aAAU;AAEV,qBAAkB;AAClB,oBAAiB;GACjB,MAAM,iBAAiB,UAAU,OAAO,IAAI,OAAO,OAAO,SAAS;AACnE,OAAI;AACF,QAAI,QAAQ,OAAO,mBAAmB,OACpC,OAAM,QAAQ,MAAM,eAAe;KACjC;KACA,UAAU,KAAK,eAAe;KAC9B;KACD,CAAC;YAEG,KAAK;AACZ,UAAM,UAAU,QAAQ,OAAO;KAAE,OAAO;KAAK,OAAO;KAAkB,CAAC;;AAGzE,OAAI,mBAAmB,OACrB,KAAI;AACF,UAAM,eAAe,MAAM;YACpB,KAAK;AACZ,UAAM,UAAU,QAAQ,OAAO;KAAE,OAAO;KAAK,OAAO;KAAkB,CAAC;;AAG3E,OAAI,uBAAuB,OACzB,KAAI;AACF,UAAM,mBAAmB,MAAM;YACxB,KAAK;AACZ,UAAM,UAAU,QAAQ,OAAO;KAAE,OAAO;KAAK,OAAO;KAAkB,CAAC;;AAO3E,QAAK,aAAa,kBAAkB;AAEpC,OAAI,CADY,MAAM,cAAc,MAAM,gBAAgB,IAAI,IAC9C,iBAAiB,EAE/B,MAAK,SAAS,IAAI,qBAAqB,gBAAgB,KAAK,cAAc,CAAC,CAAC;AAG9E,OAAI,eAAe,OACjB,KAAI;AACF,eAAW,UAAU;WACf;AAKV,OAAI,mBAAmB,QAAW;AAChC,UAAM,IAAI,SAAe,YAAY;AACnC,KAAC,eAA8D,YAAY,SAAS,CAAC;MACrF;AACF,qBAAiB;;AAEnB,OAAI,YAAY,QAAW;AACzB,UAAM,QAAQ,OAAO;AACrB,cAAU;;AAEZ,SAAM,MAAM,OAAO;AACnB,eAAY;AACZ,cAAW;AACX,aAAU;AACV,aAAU;AACV,gBAAa;AACb,aAAU;AACV,eAAY;;EAEf;;;AA+BH,SAAS,eAAe,MAAuB;CAC7C,MAAM,IAAI,KAAK,MAAM,CAAC,aAAa;AACnC,QAAO,MAAM,eAAe,MAAM,SAAS,MAAM,WAAW,MAAM;;AAGpE,SAAS,YACP,KACA,QACA,KACM;CACN,MAAM,OAAO,OAAO,OAAO;CAC3B,MAAM,SACJ,IAAI,uBAEF,yBACE,IAAI,OACJ,IAAI,gBACJ,IAAI,oBACJ,IAAI,cACJ,OACD;CACL,MAAM,SAAS,2BAA2B;EACxC,SAAS,IAAI;EACb,WAAW,IAAI;EACf,KAAK,IAAI;EACT;EACD,CAAC;AACF,KAAI,MAAM,GAAG,KAAK,UAAU,OAAO;AAMnC,KAAI,OAAO,KAAK,SAAS,UAAU,CAAC,eAAe,OAAO,OAAO,KAAK,CACpE,SAAQ,KACN,oIACiD,OAAO,OAAO,KAAK,gIAGrE;AAGH,KAAI,OAAO,QAAQ,SAAS;AAI1B,MAAI,CAAC,OAAO,QAAQ,eAAe,CAAC,eAAe,OAAO,OAAO,KAAK,CACpE,SAAQ,KACN,gIAC6C,OAAO,OAAO,KAAK,+HAGjE;EAEH,MAAM,eAAe,oBAAoB;GACvC,UAAU,IAAI;GACd,aAAa,OAAO,QAAQ;GAC5B,eACE,mBAAmB;IACjB,UAAU,IAAI;IACd,OAAO,IAAI;IACX,MAAM,IAAI;IACV,WAAW,IAAI;IACf,KAAK,IAAI;IACT,GAAI,IAAI,mBAAmB,SAAY,EAAE,gBAAgB,IAAI,gBAAgB,GAAG,EAAE;IAClF,GAAI,IAAI,uBAAuB,SAC3B,EAAE,oBAAoB,IAAI,oBAAoB,GAC9C,EAAE;IACN,GAAI,IAAI,iBAAiB,SAAY,EAAE,cAAc,IAAI,cAAc,GAAG,EAAE;IAC7E,CAAC;GACL,CAAC;AACF,MAAI,MAAM,GAAG,OAAO,OAAO,QAAQ,QAAQ,aAAa;;CAU1D,MAAM,gBAAgB,OAAO,OAAO,GAAG,UAAU,GAAG,OAAO,OAAO,OAAO,GAAG,SAAS;CACrF,MAAM,cACJ,OAAO,QAAQ,WAAW,CAAC,OAAO,QAAQ,cACtC,GAAG,OAAO,OAAO,QAAQ,SACzB;CACN,SAAS,eAAe,MAAuB;AAC7C,MAAI,SAAS,GAAG,KAAK,YAAY,SAAS,GAAG,KAAK,UAAW,QAAO;AACpE,MAAI,kBAAkB,QACpB;OAAI,SAAS,iBAAiB,SAAS,GAAG,cAAc,GAAI,QAAO;;AAErE,MAAI,gBAAgB,QAClB;OAAI,SAAS,eAAe,SAAS,GAAG,YAAY,GAAI,QAAO;;AAEjE,SAAO;;CAMT,MAAM,gBAAgB,IAAI,aAAa,UAAa,OAAO,KAAK,SAAS;AACzE,KAAI,IAAI,aAAa,UAAa,eAAe;EAC/C,MAAM,SACJ,IAAI,aAAa,SACb,qBAAqB,EAAE,UAAU,IAAI,UAAU,CAAC,GAChD,+BAA+B;AACrC,MAAI,IAAI,GAAG,KAAK,KAAK,OAAO,GAAG,SAAS;AACtC,OAAI,eAAe,EAAE,IAAI,KAAK,EAAE;AAC9B,UAAM,MAAM;AACZ;;AAEF,UAAO,OAAO,GAAG,KAAK;IACtB;AACF,MAAI,IAAI,YAAY,QAAW;GAC7B,MAAM,UAAU,sBAAsB;IAAE,SAAS,IAAI;IAAS,KAAK,IAAI;IAAK,CAAC;AAC7E,OAAI,IAAI,GAAG,KAAK,KAAK,OAAO,GAAG,SAAS;AACtC,QAAI,eAAe,EAAE,IAAI,KAAK,EAAE;AAC9B,WAAM,MAAM;AACZ;;AAEF,WAAO,QAAQ,GAAG,KAAK;KACvB;;;AAON,KAAI,OAAO,OAAO,YAAY,SAAS;EACrC,MAAM,gBAAgB,4BAA4B;GAChD,OAAO,IAAI,MAAM;GACjB,QAAQ,OAAO,OAAO;GACtB,KAAK,IAAI;GAET,2BAA2B,CAAC,GAAG,KAAK,SAAS;GAE7C,gBAAgB,IAAI;GACrB,CAAC;AACF,MAAI,IAAI,GAAG,KAAK,KAAK,OAAO,GAAG,SAAS;AACtC,OAAI,eAAe,EAAE,IAAI,KAAK,EAAE;AAC9B,UAAM,MAAM;AACZ;;AAEF,UAAO,cAAc,GAAG,KAAK;IAC7B;;AAMJ,KAAI,MAAM,GAAG,KAAK,kBAAkB,2BAA2B,CAAC;AAEhE,KAAI,MACF,GAAG,KAAK,UACR,kBAAkB;EAChB,QAAQ,IAAI;EACZ,MAAM,IAAI;EAEV,GAAI,IAAI,iBAAiB,SAAY,EAAE,YAAY,IAAI,cAAc,GAAG,EAAE;EAC3E,CAAC,CACH;AACD,KAAI,MAAM,GAAG,KAAK,QAAQ,gBAAgB;EAAE,QAAQ,IAAI;EAAQ,MAAM,IAAI;EAAM,CAAC,CAAC;AAClF,KAAI,MACF,GAAG,KAAK,aACR,qBAAqB;EACnB,WAAW,IAAI;EACf,MAAM,IAAI;EACV,GAAI,IAAI,iBAAiB,SAAY,EAAE,YAAY,IAAI,cAAc,GAAG,EAAE;EAC3E,CAAC,CACH;AACD,KAAI,IAAI,aAAa,OACnB,KAAI,MAAM,GAAG,KAAK,YAAY,oBAAoB,EAAE,UAAU,IAAI,UAAU,CAAC,CAAC;AAEhF,KAAI,IAAI,WAAW,OACjB,KAAI,MAAM,GAAG,KAAK,UAAU,mBAAmB,EAAE,QAAQ,IAAI,QAAQ,CAAC,CAAC;AAEzE,KAAI,IAAI,WAAW,OACjB,KAAI,MAAM,GAAG,KAAK,UAAU,mBAAmB,EAAE,QAAQ,IAAI,QAAQ,CAAC,CAAC;AAEzE,KAAI,IAAI,QAAQ,OACd,KAAI,MAAM,GAAG,KAAK,OAAO,gBAAgB,EAAE,KAAK,IAAI,KAAK,CAAC,CAAC;AAE7D,KAAI,IAAI,UAAU,OAChB,KAAI,MAAM,GAAG,KAAK,SAAS,kBAAkB,EAAE,OAAO,IAAI,OAAO,CAAC,CAAC;AAErE,KAAI,IAAI,mBAAmB,OACzB,KAAI,MAAM,GAAG,KAAK,YAAY,qBAAqB,EAAE,QAAQ,IAAI,gBAAgB,CAAC,CAAC;AAErF,KAAI,IAAI,WAAW,OACjB,KAAI,MACF,GAAG,QACH,mBAAmB;EACjB,QAAQ,IAAI;EACZ,GAAI,IAAI,YAAY,SAAY,EAAE,SAAS,IAAI,SAAS,GAAG,EAAE;EAC7D,KAAK,IAAI;EACV,CAAC,CACH;AAEH,KAAI,OAAO,KAAK,SAAS,WAAW,IAAI,WAAW,OACjD,KAAI,MACF,GAAG,KAAK,UACR,mBAAmB;EACjB,YAAY,IAAI,MAAM;EACtB,QAAQ,IAAI;EACZ,YAAY;EACZ,aAAa,OAAO,KAAK;EAC1B,CAAC,CACH;AAEH,KAAI,IAAI,cAAc,OACpB,KAAI,MAAM,GAAG,QAAQ,iBAAiB,EAAE,SAAS,IAAI,WAAW,CAAC,CAAC;AAMpE,KACE,IAAI,iBAAiB,UACrB,IAAI,cAAc,UAClB,IAAI,cAAc,WACjB,IAAI,aAAa,UAAa,kBAC/B,OAAO,OAAO,GAAG,SACjB;EACA,MAAM,aAAa,IAAI;EACvB,MAAM,UAAU,IAAI;EACpB,MAAM,WAAW,IAAI;EACrB,MAAM,OAAO,IAAI;AACjB,MAAI,IACF,GAAG,OAAO,OAAO,OAAO,GAAG,QAC3B,IAAI,UAAU,kBAAkB,MAC9B,sBAAsB,GAAG;GACvB;GACA;GACA,GAAI,aAAa,SAAY,EAAE,UAAU,GAAG,EAAE;GAC9C,WAAW;GACX;GACA,KAAK,IAAI;GACV,CAAC,CACH,CACF;;AAEH,KAAI,IAAI,iBAAiB,UAAa,OAAO,OAAO,IAAI,QACtD,KAAI,MACF,GAAG,OAAO,OAAO,OAAO,IAAI,QAC5B,gBAAgB;EACd,YAAY,IAAI;EAChB,aAAa,OAAO,OAAO,IAAI;EAE/B,yBAAyB,OAAO,OAAO,OAAO;EAC9C,KAAK,IAAI;EACV,CAAC,CACH;;AAIL,eAAe,cACb,MACA,gBACA,KACkB;AAClB,KAAI,KAAK,cAAc,KAAK,EAAG,QAAO;AACtC,KAAI,kBAAkB,EAAG,QAAO,KAAK,cAAc,KAAK;CACxD,MAAM,WAAW,KAAK,GAAG;AACzB,QAAO,KAAK,GAAG,UAAU;AACvB,MAAI,KAAK,cAAc,KAAK,EAAG,QAAO;AACtC,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,KAAK,IAAI,IAAI,WAAW,KAAK,CAAC,CAAC,CAAC;;AAErF,QAAO,KAAK,cAAc,KAAK;;AAGjC,eAAe,UAAU,OAAmC,KAAoC;AAC9F,KAAI,OAAO,YAAY,OAAW;AAClC,KAAI;AACF,QAAM,MAAM,QAAQ,IAAI;SAClB;;AAgBV,eAAe,mBAAmB,SAAmD;CACnF,MAAM,EAAE,UAAU,OAAO,MAAM,WAAW,QAAQ;AAElD,KAAI;EACF,MAAM,MAAM,YAAY,MAAM,WAAW;AACzC,WAAS,IAAI,oBAAoB,gBAAgB,IAAI;SAC/C;AAIR,UAAS,IACP,oBAAoB,cACpB,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,GAAG,aAAa,IAAK,CAAC,CACpD;AACD,UAAS,IAAI,oBAAoB,cAAc,KAAK,cAAc,CAAC;AAEnE,KAAI,QAAQ,iBAAiB,QAAW;EACtC,MAAM,QAAQ,QAAQ,aAAa,MAAM;AACzC,WAAS,IAAI,oBAAoB,oBAAoB,MAAM,cAAc;;AAG3E,KAAI,QAAQ,mBAAmB,QAAW;EACxC,MAAM,UAAU,QAAQ,eAAe,SAAS;AAChD,OAAK,MAAM,CAAC,WAAW,WAAW,QAAQ,OAAO;GAC/C,MAAM,YAAY,yBAAyB,UAAU;GACrD,MAAM,iBAAiB,YAAY,UAAU,oBAAoB,oBAAoB;IACnF,YAAY;IACZ,QAAQ;IACT,CAAC;GACF,MAAM,eAAe,YAAY,UAAU,oBAAoB,oBAAoB;IACjF,YAAY;IACZ,QAAQ;IACT,CAAC;GACF,MAAM,eAAe,OAAO,UAAU;GACtC,MAAM,aAAa,OAAO,QAAQ;AAClC,OAAI,eAAe,EACjB,UAAS,IACP,oBAAoB,oBACpB;IAAE,YAAY;IAAW,QAAQ;IAAW,EAC5C,aACD;AAEH,OAAI,aAAa,EACf,UAAS,IACP,oBAAoB,oBACpB;IAAE,YAAY;IAAW,QAAQ;IAAS,EAC1C,WACD;;;AAKP,KAAI,QAAQ,uBAAuB,OACjC,KAAI;EACF,MAAM,SAAS,MAAM,QAAQ,mBAAmB,QAAQ;AACxD,WAAS,IAAI,oBAAoB,wBAAwB,OAAO,YAAY,EAC1E,OAAO,aACR,CAAC;AACF,WAAS,IAAI,oBAAoB,qBAAqB,OAAO,QAAQ;AACrE,WAAS,IAAI,oBAAoB,gCAAgC,OAAO,OAAO,cAAc;AAC7F,WAAS,IACP,oBAAoB,mCACpB,OAAO,OAAO,gBACf;SACK;;;;;;;;;AAaZ,SAAS,yBAAyB,OAAuB;AACvD,QAAO,MAAM,QAAQ,qBAAqB,IAAI,CAAC,MAAM,GAAG,IAAI;;AAG9D,SAAS,YACP,UACA,MACA,QACQ;CACR,MAAM,OAAO,SAAS,UAAU,CAAC,SAAS,SAAS,EAAE;AACrD,MAAK,MAAM,SAAS,KAClB,KAAI,cAAc,MAAM,QAAQ,OAAO,CAAE,QAAO,MAAM;AAExD,QAAO;;AAGT,SAAS,cACP,GACA,GACS;AACT,MAAK,MAAM,KAAK,OAAO,KAAK,EAAE,CAC5B,KAAI,OAAO,EAAE,GAAG,KAAK,EAAE,GAAI,QAAO;AAEpC,MAAK,MAAM,KAAK,OAAO,KAAK,EAAE,CAC5B,KAAI,EAAE,KAAK,GAAI,QAAO;AAExB,QAAO;;AAGT,SAAS,yBACP,OACA,gBACA,oBACA,YACA,QACoB;CACpB,MAAMC,MAEF;EACF;EACA,uBAAuB,OAAO,OAAO;EACrC,mBAAmB,OAAO,QAAQ,WAAW;EAG7C,GAAI,OAAO,QAAQ,WAAW,UAAU,EAAE,qBAAqB,MAAM,GAAG,EAAE;EAC3E;AACD,KAAI,mBAAmB,OAAW,KAAI,WAAW;AACjD,KAAI,uBAAuB,OAAW,KAAI,eAAe;AACzD,KAAI,eAAe,QAAW;EAC5B,MAAM,QAAQ,WAAW,MAAM;AAM/B,MAAI,eAAe;GACjB,gBAAgB,MAAM;GACtB,aAAa,MAAM;GACnB,eAAe,MAAM;GACtB;;AAEH,QAAO,OAAO,OAAO,IAA0B;;AAGjD,eAAe,eAAe,eAAwB;AAGpD,KAAI,CAAC,cACH,OAAM,IAAI,MACR,gHACD;AAEH,QAAO,gBAAgB"}
1
+ {"version":3,"file":"app.js","names":["VERSION: string","pkg.version","config: ServerConfigSpec","version","storeEncryption:\n | { enabled: true; cipher?: never; passphraseResolver: () => Promise<string> }\n | undefined","serverInstance: ServerType | undefined","listening: { readonly host: string; readonly port: number } | undefined","stopRunPruning: (() => void) | undefined","auditDb: AuditDb | undefined","preBind: Awaited<ReturnType<typeof runPreBind>> | undefined","verifier: TokenVerifier | undefined","pepperHandle: import('@graphorin/security').SecretValue | undefined","triggersDaemon: TriggersDaemon | undefined","consolidatorDaemon: ConsolidatorDaemon | undefined","dispatcher: WsDispatcher | undefined","tickets: WsTicketStore | undefined","wsAdapter: ReturnType<typeof createNodeWebSocket> | undefined","commentaryAuditSink: LateBoundCommentarySink | undefined","out: {\n -readonly [K in keyof HealthCheckOptions]?: HealthCheckOptions[K];\n }"],"sources":["../src/app.ts"],"sourcesContent":["/**\n * `createServer({...})` - the single programmatic entry point.\n *\n * The factory returns a {@link GraphorinServer} handle the operator\n * controls via `start()` / `stop()`. The same handle is consumed by\n * the `graphorin start` CLI binary in `@graphorin/cli`.\n *\n * Phase 14a covers the full HTTP surface, the lifecycle plumbing,\n * the Hono app composition, and the auth / scope / idempotency /\n * audit middleware stack. Phase 14b/c attach the WebSocket layer +\n * triggers daemon + observability endpoints to the same handle.\n *\n * @packageDocumentation\n */\n\nimport type { AddressInfo } from 'node:net';\nimport process from 'node:process';\nimport { negotiateSubprotocol, SUBPROTOCOL_NAME } from '@graphorin/protocol';\nimport { applyProcessHardening, generatePepper, TokenVerifier } from '@graphorin/security';\nimport { type AuditDb, openAuditDb, registerAuditDbBinding } from '@graphorin/security/audit';\nimport {\n createSqliteStore,\n type GraphorinSqliteStore,\n loadCipherDriver,\n readWalSize,\n} from '@graphorin/store-sqlite';\nimport { type ServerType, serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { Hono } from 'hono';\nimport {\n bridgeCommentaryToAudit,\n createLateBoundCommentarySink,\n type LateBoundCommentarySink,\n} from './commentary/index.js';\nimport { parseServerConfig, type ServerConfigInput, type ServerConfigSpec } from './config.js';\nimport type { ConsolidatorDaemon, ConsolidatorLike } from './consolidator/daemon.js';\nimport { createConsolidatorDaemon } from './consolidator/daemon.js';\nimport {\n ConfigInvalidError,\n LifecycleDoubleStartError,\n LifecycleNotStartedError,\n ShutdownTimeoutError,\n} from './errors/index.js';\nimport {\n createExtendedHealthRoutes,\n createMetricsRoutes,\n createSecretsHealthRoutes,\n type HealthCheckOptions,\n} from './health/index.js';\nimport type { ServerVariables } from './internal/context.js';\nimport { type LifecycleHooks, type OnErrorContext, runPreBind } from './lifecycle/index.js';\nimport { createServerMetricRegistry, SERVER_METRIC_NAMES } from './metrics/catalog.js';\nimport type { MetricRegistry } from './metrics/registry.js';\nimport {\n createAnonymousAuthMiddleware,\n createAuditMiddleware,\n createAuthMiddleware,\n createCorsMiddleware,\n createCsrfMiddleware,\n createIdempotencyMiddleware,\n createRateLimitMiddleware,\n createRequestStateMiddleware,\n} from './middleware/index.js';\nimport { AgentRegistry, WorkflowRegistry } from './registry/index.js';\nimport { createReplayRoutes, type ReplayApi } from './replay/index.js';\nimport {\n type AuditApi,\n createAgentRoutes,\n createAuditRoutes,\n createAuthRoutes,\n createMcpRoutes,\n createMemoryRoutes,\n createRunRoutes,\n createSessionRoutes,\n createSkillsRoutes,\n createTokensRoutes,\n createWorkflowRoutes,\n type McpApi,\n type MemoryApi,\n type SessionApi,\n type SkillsApi,\n} from './routes/index.js';\nimport { RunStateTracker, scheduleRunPruning } from './runtime/run-state.js';\nimport { createSseRoutes } from './sse/index.js';\nimport type { TriggersDaemon } from './triggers/daemon.js';\nimport { createTriggersDaemon } from './triggers/daemon.js';\nimport { createTriggersRoutes } from './triggers/routes.js';\nimport {\n createWsDispatcher,\n createWsTicketStore,\n type WsDispatcher,\n type WsTicketStore,\n} from './ws/index.js';\nimport { createWsUpgradeEvents } from './ws/upgrade.js';\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 const db = new Db(opts.path);\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 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\n/**\n * Public surface returned by {@link createServer}.\n *\n * @stable\n */\nexport interface GraphorinServer {\n readonly version: string;\n readonly config: ServerConfigSpec;\n readonly app: Hono<{ Variables: ServerVariables }>;\n readonly agents: AgentRegistry;\n readonly workflows: WorkflowRegistry;\n readonly runs: RunStateTracker;\n readonly listeningOn: { readonly host: string; readonly port: number } | undefined;\n /**\n * Optional WebSocket dispatcher exposed when `server.ws.enabled =\n * true`. Route handlers + the agent / workflow runtimes call\n * `dispatcher.emit(subject, event)` to fan out events to subscribed\n * clients. Returns `undefined` when WS is disabled.\n */\n readonly wsDispatcher: WsDispatcher | undefined;\n /**\n * Optional WebSocket ticket store exposed when `server.ws.enabled\n * = true`. Surfaced primarily for tests; the\n * `POST /v1/session/ws-ticket` route uses it transparently.\n */\n readonly wsTickets: WsTicketStore | undefined;\n /**\n * Optional triggers daemon - populated when the operator wired a\n * scheduler (or an in-process trigger surface) at construction\n * time. Phase 14c integration.\n */\n readonly triggers: TriggersDaemon | undefined;\n /**\n * Optional consolidator daemon - populated when the operator\n * supplied a `Consolidator` instance via `createServer({\n * consolidator })`. Phase 14c integration.\n */\n readonly consolidator: ConsolidatorDaemon | undefined;\n /**\n * Phase 14c Prometheus registry. Always present; sample updates\n * are observable via `metrics.snapshot()`.\n */\n readonly metrics: MetricRegistry;\n start(): Promise<{ readonly host: string; readonly port: number }>;\n stop(options?: { readonly force?: boolean }): Promise<void>;\n}\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/**\n * @stable\n */\nexport interface CreateServerOptions {\n /** Loaded `graphorin.config.ts` payload - see `defineConfig({...})`. */\n readonly config?: ServerConfigInput;\n /**\n * Optional pre-validated config. When supplied, `config` is ignored\n * and the schema validation step is skipped. Useful for tests + the\n * `graphorin migrate` CLI command which bypasses the listener.\n */\n readonly validatedConfig?: ServerConfigSpec;\n /** Pre-built SQLite store. Tests inject an in-memory store. */\n readonly store?: GraphorinSqliteStore;\n /** Optional pre-built tracker. Tests inject deterministic timing. */\n readonly runs?: RunStateTracker;\n /** Optional pre-built registries. */\n readonly agents?: AgentRegistry;\n readonly workflows?: WorkflowRegistry;\n /** Optional in-process domain adapters wired into REST routes. */\n readonly sessions?: SessionApi;\n readonly memory?: MemoryApi;\n readonly skills?: SkillsApi;\n readonly mcp?: McpApi;\n readonly audit?: AuditApi;\n /**\n * Optional consolidator surface (`@graphorin/memory`). Phase 14c\n * starts/stops the runtime alongside the server lifecycle and\n * surfaces its status through `/v1/health`.\n */\n readonly consolidator?: ConsolidatorLike;\n /**\n * Optional triggers daemon - pass an existing one (e.g. built\n * from `createScheduler`) or a triggers configuration the server\n * should wrap with the daemon adapter.\n */\n readonly triggers?: TriggersDaemonInput;\n /**\n * Optional replay API consumed by the scope-enforced replay\n * endpoints. Phase 14c.\n */\n readonly replay?: ReplayApi;\n /**\n * Optional probes that augment `/v1/health`. Provided by consumer\n * code (e.g. `embedder` provides `embedderLoaded`).\n */\n readonly healthProbes?: () => HealthCheckOptions | Promise<HealthCheckOptions>;\n /**\n * Optional Prometheus metric registry override. When omitted, the\n * server constructs the canonical registry from\n * {@link createServerMetricRegistry}.\n */\n readonly metricRegistry?: MetricRegistry;\n /** Lifecycle hook overrides. */\n readonly hooks?: LifecycleHooks;\n /** Wall-clock provider for tests. */\n readonly now?: () => number;\n /** Override the cipher peer probe. Tests inject a stub. */\n readonly probeCipherPeer?: () => Promise<void>;\n /** Override the package version reported on `/v1/health`. */\n readonly version?: string;\n /** Skip `applyProcessHardening` (tests). */\n readonly skipHardening?: boolean;\n /** Skip starting the actual listener (tests). */\n readonly skipListen?: boolean;\n}\n\n/**\n * @stable\n */\nimport pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\n\n/**\n * Build a fully-wired Graphorin server. The returned handle is\n * inert until `start()` is awaited.\n *\n * @stable\n */\nexport async function createServer(options: CreateServerOptions = {}): Promise<GraphorinServer> {\n const config: ServerConfigSpec =\n options.validatedConfig ?? parseServerConfig(options.config ?? {});\n const now = options.now ?? Date.now;\n const startedAt = now();\n const version = options.version ?? VERSION;\n\n // IP-1: when encryption is configured, resolve the passphrase ref\n // BEFORE constructing the store and thread the encryption config\n // through - `graphorin init --encrypted` produced a config nothing\n // honoured, and a database encrypted via `storage encrypt` could not\n // be opened by the server at all.\n let storeEncryption:\n | { enabled: true; cipher?: never; passphraseResolver: () => Promise<string> }\n | undefined;\n if (options.store === undefined && config.storage.encryption.enabled) {\n if (config.storage.encryption.passphraseRef === undefined) {\n throw new Error(\n '[graphorin/server] storage.encryption.enabled is true but no passphraseRef is configured.',\n );\n }\n const { resolveSecret } = await import('@graphorin/security/secrets');\n const passphrase = await resolveSecret(config.storage.encryption.passphraseRef);\n storeEncryption = {\n enabled: true,\n passphraseResolver: async () => passphrase.use((v) => v),\n } as never;\n }\n const store =\n options.store ??\n (await createSqliteStore({\n path: config.storage.path,\n mode: config.storage.mode,\n ...(storeEncryption !== undefined\n ? {\n encryption: {\n enabled: true,\n ...(config.storage.encryption.cipher !== undefined\n ? { cipher: config.storage.encryption.cipher as never }\n : {}),\n passphraseResolver: storeEncryption.passphraseResolver,\n },\n }\n : {}),\n }));\n\n const metricRegistry = options.metricRegistry ?? createServerMetricRegistry();\n metricRegistry.set(SERVER_METRIC_NAMES.buildInfo, 1, { version });\n\n // IP-15: agent / workflow run completions move graphorin_agent_runs_total +\n // graphorin_agent_run_duration_seconds. The tracker stays metric-agnostic -\n // it fires a terminal callback the server turns into samples.\n const runs =\n options.runs ??\n new RunStateTracker({\n now,\n onTerminal: (info) => {\n metricRegistry.inc(SERVER_METRIC_NAMES.agentRunsTotal, { status: info.status });\n if (info.durationMs !== undefined) {\n metricRegistry.observe(SERVER_METRIC_NAMES.agentRunDuration, info.durationMs / 1000);\n }\n },\n });\n const agents = options.agents ?? new AgentRegistry();\n const workflows = options.workflows ?? new WorkflowRegistry();\n\n const app = new Hono<{ Variables: ServerVariables }>();\n\n // Global middleware in the documented order. Per-route scope +\n // idempotency layers are mounted by the route factories so the\n // composition stays declarative + auditable.\n app.use(\n '*',\n createRequestStateMiddleware({\n now,\n ...(config.server.trustProxy ? { trustProxy: true } : {}),\n }),\n );\n app.use('*', createCorsMiddleware(config.server.cors));\n app.use('*', createCsrfMiddleware(config.server.csrf));\n app.use('*', createRateLimitMiddleware(config.server.rateLimit, { now }));\n\n let serverInstance: ServerType | undefined;\n let listening: { readonly host: string; readonly port: number } | undefined;\n let started = false;\n let stopped = false;\n // IP-16: stops the periodic terminal-run prune sweep on shutdown.\n let stopRunPruning: (() => void) | undefined;\n let auditDb: AuditDb | undefined;\n let preBind: Awaited<ReturnType<typeof runPreBind>> | undefined;\n let verifier: TokenVerifier | undefined;\n let pepperHandle: import('@graphorin/security').SecretValue | undefined;\n\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\n let dispatcher: WsDispatcher | undefined;\n let tickets: WsTicketStore | undefined;\n let wsAdapter: ReturnType<typeof createNodeWebSocket> | undefined;\n // IP-21: the dispatcher is built here, before the audit DB opens in start().\n // Hand it a late-bound commentary sink now and install the audit-writing\n // target once the DB exists - otherwise the sanitizer's documented decisions\n // (wrapped/stripped frames with before/after digests) are dropped.\n let commentaryAuditSink: LateBoundCommentarySink | undefined;\n if (config.server.ws.enabled) {\n commentaryAuditSink = createLateBoundCommentarySink();\n dispatcher = createWsDispatcher({\n commentary: {\n policy: config.server.ws.commentarySanitization.policy,\n applyToEvents: config.server.ws.commentarySanitization.applyToEvents,\n sink: commentaryAuditSink,\n },\n replayBuffer: {\n maxEvents: config.server.stream.replayBuffer.maxEvents,\n ttlMs: config.server.stream.replayBuffer.ttlSeconds * 1000,\n },\n perConnectionQueueLimit: config.server.stream.perConnectionQueueLimit,\n now,\n });\n tickets = createWsTicketStore({\n ttlMs: config.server.ws.ticketTtlMs,\n now,\n });\n wsAdapter = createNodeWebSocket({ app });\n // The WS server inside @hono/node-ws is created without any\n // subprotocol-negotiation policy; without one, the `ws` library\n // never echoes back `Sec-WebSocket-Protocol` and clients that\n // advertised a subprotocol close the connection immediately.\n // Mutate the options to install a Graphorin-aware policy that\n // selects `graphorin.protocol.v1` when the client offered it.\n const wssOptions = (\n wsAdapter.wss as unknown as {\n options: {\n handleProtocols?: (\n protocols: Set<string>,\n request: import('node:http').IncomingMessage,\n ) => string | false;\n };\n }\n ).options;\n wssOptions.handleProtocols = (protocols) => {\n const negotiated = negotiateSubprotocol(Array.from(protocols));\n if (negotiated !== null) return negotiated;\n // Browser ticket flow: the `WebSocket` constructor cannot set an\n // `Authorization` header, so the browser client offers two\n // subprotocol tokens - the canonical `graphorin.protocol.v1`\n // name plus a `ticket.<value>` token (see the wire contract in\n // `@graphorin/protocol`'s `subprotocol.ts`:\n // `SUBPROTOCOL_NAME` / `TICKET_SUBPROTOCOL_PREFIX` /\n // `parseTicketSubprotocol`). The server MUST echo back exactly\n // the canonical name (never the `ticket.*` token) so the\n // handshake's `Sec-WebSocket-Protocol` response stays valid; the\n // ticket value is consumed separately by `createWsUpgradeEvents`\n // via `parseTicketSubprotocol` and exchanged through the\n // single-use `WsTicketStore`.\n for (const candidate of protocols) {\n if (candidate === SUBPROTOCOL_NAME) return SUBPROTOCOL_NAME;\n }\n return false;\n };\n }\n\n const handle: GraphorinServer = {\n version,\n config,\n app,\n agents,\n workflows,\n runs,\n metrics: metricRegistry,\n get wsDispatcher() {\n return dispatcher;\n },\n get wsTickets() {\n return tickets;\n },\n get triggers() {\n return triggersDaemon;\n },\n get consolidator() {\n return consolidatorDaemon;\n },\n get listeningOn() {\n return listening;\n },\n async start(): Promise<{ readonly host: string; readonly port: number }> {\n if (started) throw new LifecycleDoubleStartError();\n started = true;\n try {\n if (options.hooks?.beforeStart !== undefined) {\n await options.hooks.beforeStart({ config });\n }\n if (options.skipHardening !== true && config.hardening.applyOnStart) {\n applyProcessHardening({\n refuseRoot: config.hardening.refuseRoot,\n umask: config.hardening.umask,\n });\n }\n preBind = await runPreBind({\n config,\n store,\n ...(options.probeCipherPeer !== undefined\n ? { probeCipherPeer: options.probeCipherPeer }\n : {}),\n });\n\n if (\n config.audit.enabled &&\n preBind.auditPassphrase !== undefined &&\n preBind.auditPath !== undefined\n ) {\n ensureStoreAuditBinding();\n auditDb = await openAuditDb({\n path: preBind.auditPath,\n passphrase: preBind.auditPassphrase,\n ...(config.audit.cipher !== undefined ? { cipher: config.audit.cipher } : {}),\n });\n // IP-21: now that the audit chain is open, route the WS dispatcher's\n // commentary-sanitizer decisions into it.\n commentaryAuditSink?.bind(bridgeCommentaryToAudit(auditDb));\n }\n\n if (config.auth.kind === 'token') {\n const pepper = preBind.pepper ?? (await fallbackPepper(options.skipHardening === true));\n pepperHandle = pepper;\n verifier = new TokenVerifier({\n tokenStore: store.authTokens,\n pepper,\n acceptPrefix: config.auth.tokenPrefix,\n acceptEnvironments: config.auth.tokenEnvironments,\n ...(config.auth.perIpFailureThreshold !== undefined\n ? { perIpFailureThreshold: config.auth.perIpFailureThreshold }\n : {}),\n ...(config.auth.perIpLockoutMs !== undefined\n ? { perIpLockoutMs: config.auth.perIpLockoutMs }\n : {}),\n now,\n });\n }\n\n mountRoutes(app, config, {\n version,\n startedAt,\n now,\n agents,\n workflows,\n runs,\n store,\n metricRegistry,\n ...(options.sessions !== undefined ? { sessions: options.sessions } : {}),\n ...(options.memory !== undefined ? { memory: options.memory } : {}),\n ...(options.skills !== undefined ? { skills: options.skills } : {}),\n ...(options.mcp !== undefined ? { mcp: options.mcp } : {}),\n ...(options.audit !== undefined ? { audit: options.audit } : {}),\n ...(options.replay !== undefined ? { replay: options.replay } : {}),\n ...(options.healthProbes !== undefined ? { healthProbes: options.healthProbes } : {}),\n ...(verifier !== undefined ? { verifier } : {}),\n ...(auditDb !== undefined ? { auditDb } : {}),\n ...(pepperHandle !== undefined ? { pepper: pepperHandle } : {}),\n ...(dispatcher !== undefined ? { wsDispatcher: dispatcher } : {}),\n ...(tickets !== undefined ? { wsTickets: tickets } : {}),\n ...(wsAdapter !== undefined ? { wsAdapter } : {}),\n ...(triggersDaemon !== undefined ? { triggersDaemon } : {}),\n ...(consolidatorDaemon !== undefined ? { consolidatorDaemon } : {}),\n });\n\n // Start the consolidator first so it is ready to handle fired\n // triggers, bridge its cron / idle triggers onto the scheduler\n // (MCON-4 - without this nothing pipes triggers into the\n // consolidator and background consolidation never runs), then start\n // the scheduler last so it only fires fully-wired triggers.\n if (consolidatorDaemon !== undefined) {\n await consolidatorDaemon.start();\n if (\n triggersDaemon !== undefined &&\n consolidatorDaemon.consolidator.registerWithScheduler !== undefined\n ) {\n await consolidatorDaemon.consolidator.registerWithScheduler(triggersDaemon.scheduler);\n }\n }\n if (triggersDaemon !== undefined) {\n await triggersDaemon.start();\n }\n\n // Sample a couple of gauges immediately so the very first\n // `/v1/metrics` scrape after start carries non-zero data.\n try {\n const wal = readWalSize(store.connection);\n metricRegistry.set(SERVER_METRIC_NAMES.storageWalSize, wal);\n } catch {\n // Best-effort.\n }\n metricRegistry.set(\n SERVER_METRIC_NAMES.serverUptime,\n Math.max(0, Math.floor((now() - startedAt) / 1000)),\n );\n metricRegistry.set(SERVER_METRIC_NAMES.inflightRuns, runs.runningCount());\n metricRegistry.set(SERVER_METRIC_NAMES.replayBufferEvents, 0);\n\n // IP-16: terminal run records (each holding an AbortController) would\n // otherwise accumulate forever; sweep them on a periodic timer.\n stopRunPruning = scheduleRunPruning(runs, now);\n\n if (options.skipListen !== true) {\n serverInstance = serve({\n fetch: app.fetch.bind(app),\n hostname: config.server.host,\n port: config.server.port,\n });\n if (wsAdapter !== undefined && serverInstance !== undefined) {\n wsAdapter.injectWebSocket(serverInstance);\n }\n await new Promise<void>((resolve) => {\n const server = serverInstance as unknown as {\n once(event: 'listening', cb: () => void): void;\n };\n if ('once' in server && typeof server.once === 'function') {\n server.once('listening', () => resolve());\n } else {\n // serve(...) returned a Node http.Server already bound.\n setImmediate(() => resolve());\n }\n });\n const address = (\n serverInstance as unknown as { address(): AddressInfo | string | null }\n ).address();\n if (address !== null && typeof address === 'object') {\n listening = { host: address.address, port: address.port };\n } else {\n listening = { host: config.server.host, port: config.server.port };\n }\n } else {\n listening = { host: config.server.host, port: config.server.port };\n }\n\n if (options.hooks?.onReady !== undefined) {\n await options.hooks.onReady({ config, listeningOn: listening });\n }\n return listening;\n } catch (err) {\n started = false;\n await emitError(options.hooks, { error: err, phase: 'beforeStart' });\n throw err;\n }\n },\n async stop({ force }: { readonly force?: boolean } = {}): Promise<void> {\n if (!started) throw new LifecycleNotStartedError();\n if (stopped) return;\n stopped = true;\n // IP-16: halt the prune sweep before draining.\n stopRunPruning?.();\n stopRunPruning = undefined;\n const drainTimeoutMs = force === true ? 0 : config.server.shutdown.drainTimeoutMs;\n try {\n if (options.hooks?.beforeShutdown !== undefined) {\n await options.hooks.beforeShutdown({\n config,\n inflight: runs.inflightCount(),\n drainTimeoutMs,\n });\n }\n } catch (err) {\n await emitError(options.hooks, { error: err, phase: 'beforeShutdown' });\n }\n\n if (triggersDaemon !== undefined) {\n try {\n await triggersDaemon.stop();\n } catch (err) {\n await emitError(options.hooks, { error: err, phase: 'beforeShutdown' });\n }\n }\n if (consolidatorDaemon !== undefined) {\n try {\n await consolidatorDaemon.stop();\n } catch (err) {\n await emitError(options.hooks, { error: err, phase: 'beforeShutdown' });\n }\n }\n\n // Pending reservations (e.g. awaited WS subscriptions for the\n // streaming endpoints) hold no work in progress; abort them\n // immediately so the drain only waits for live runs.\n runs.abortPending('server-shutdown');\n const drained = await drainInFlight(runs, drainTimeoutMs, now);\n if (!drained && drainTimeoutMs > 0) {\n // Force-abort everything that didn't drain in time.\n runs.abortAll(new ShutdownTimeoutError(drainTimeoutMs, runs.runningCount()));\n }\n\n if (dispatcher !== undefined) {\n try {\n dispatcher.shutdown();\n } catch {\n // Best-effort during stop().\n }\n }\n\n if (serverInstance !== undefined) {\n await new Promise<void>((resolve) => {\n (serverInstance as unknown as { close(cb: () => void): void }).close(() => resolve());\n });\n serverInstance = undefined;\n }\n if (auditDb !== undefined) {\n await auditDb.close();\n auditDb = undefined;\n }\n await store.close();\n listening = undefined;\n verifier = undefined;\n started = false;\n preBind = undefined;\n dispatcher = undefined;\n tickets = undefined;\n wsAdapter = undefined;\n },\n };\n return handle;\n}\n\ninterface MountRoutesContext {\n readonly version: string;\n readonly startedAt: number;\n readonly now: () => number;\n readonly agents: AgentRegistry;\n readonly workflows: WorkflowRegistry;\n readonly runs: RunStateTracker;\n readonly store: GraphorinSqliteStore;\n readonly metricRegistry: MetricRegistry;\n readonly sessions?: SessionApi;\n readonly memory?: MemoryApi;\n readonly skills?: SkillsApi;\n readonly mcp?: McpApi;\n readonly audit?: AuditApi;\n readonly replay?: ReplayApi;\n readonly healthProbes?: () => HealthCheckOptions | Promise<HealthCheckOptions>;\n readonly verifier?: TokenVerifier;\n readonly auditDb?: AuditDb;\n readonly pepper?: import('@graphorin/security').SecretValue;\n readonly wsDispatcher?: WsDispatcher;\n readonly wsTickets?: WsTicketStore;\n readonly wsAdapter?: ReturnType<typeof createNodeWebSocket>;\n readonly triggersDaemon?: TriggersDaemon;\n readonly consolidatorDaemon?: ConsolidatorDaemon;\n}\n\n/** IP-23: is `host` a loopback interface (so an open /metrics is not exposed)? */\nfunction isLoopbackHost(host: string): boolean {\n const h = host.trim().toLowerCase();\n return h === '127.0.0.1' || h === '::1' || h === '[::1]' || h === 'localhost';\n}\n\nfunction mountRoutes(\n app: Hono<{ Variables: ServerVariables }>,\n config: ServerConfigSpec,\n ctx: MountRoutesContext,\n): void {\n const base = config.server.basePath;\n const probes =\n ctx.healthProbes ??\n (() =>\n buildDefaultHealthProbes(\n ctx.store,\n ctx.triggersDaemon,\n ctx.consolidatorDaemon,\n ctx.wsDispatcher,\n config,\n ));\n const health = createExtendedHealthRoutes({\n version: ctx.version,\n startedAt: ctx.startedAt,\n now: ctx.now,\n probes,\n });\n app.route(`${base}/health`, health);\n\n // IP-13: `auth.kind='none'` disables authentication on every route. It is the\n // documented trusted-loopback / single-operator mode, but binding a\n // non-loopback host with auth off exposes full admin access (including the WS\n // stream) to anyone who can reach it - warn loudly rather than silently.\n if (config.auth.kind === 'none' && !isLoopbackHost(config.server.host)) {\n console.warn(\n `[graphorin/server] WARN: auth.kind='none' disables authentication on every endpoint, ` +\n `but the server binds the non-loopback host '${config.server.host}'. Anyone who can reach ` +\n `it has full admin access - use auth.kind='token' for non-loopback deployments or bind a ` +\n `loopback host.`,\n );\n }\n\n if (config.metrics.enabled) {\n // IP-23: an unauthenticated /metrics endpoint leaks operational intel\n // (trigger ids in labels, consolidator budgets). It is fine on a loopback\n // host, but binding a non-loopback host with auth off silently exposes it.\n if (!config.metrics.requireAuth && !isLoopbackHost(config.server.host)) {\n console.warn(\n `[graphorin/server] WARN: /metrics is unauthenticated (metrics.requireAuth=false) and ` +\n `the server binds the non-loopback host '${config.server.host}'. The exposition leaks ` +\n `operational detail to anyone who can reach it - set metrics.requireAuth=true or bind a ` +\n `loopback host.`,\n );\n }\n const metricsRoute = createMetricsRoutes({\n registry: ctx.metricRegistry,\n requireAuth: config.metrics.requireAuth,\n refresh: () =>\n refreshLiveMetrics({\n registry: ctx.metricRegistry,\n store: ctx.store,\n runs: ctx.runs,\n startedAt: ctx.startedAt,\n now: ctx.now,\n ...(ctx.triggersDaemon !== undefined ? { triggersDaemon: ctx.triggersDaemon } : {}),\n ...(ctx.consolidatorDaemon !== undefined\n ? { consolidatorDaemon: ctx.consolidatorDaemon }\n : {}),\n ...(ctx.wsDispatcher !== undefined ? { wsDispatcher: ctx.wsDispatcher } : {}),\n }),\n });\n app.route(`${base}${config.metrics.path}`, metricsRoute);\n }\n\n // Authenticated subtree begins here. The health endpoint above is\n // intentionally outside the auth boundary so liveness probes work\n // before the token verifier is wired. The WebSocket upgrade path is\n // also exempt: the upgrade handler in `ws/upgrade.ts` performs its\n // own bearer + ticket validation inline (the HTTP auth middleware\n // would otherwise short-circuit the upgrade with a 401 response\n // before the handler can negotiate the subprotocol).\n const wsUpgradePath = config.server.ws.enabled ? `${base}${config.server.ws.path}` : undefined;\n const metricsPath =\n config.metrics.enabled && !config.metrics.requireAuth\n ? `${base}${config.metrics.path}`\n : undefined;\n function shouldSkipAuth(path: string): boolean {\n if (path === `${base}/health` || path === `${base}/health/`) return true;\n if (wsUpgradePath !== undefined) {\n if (path === wsUpgradePath || path === `${wsUpgradePath}/`) return true;\n }\n if (metricsPath !== undefined) {\n if (path === metricsPath || path === `${metricsPath}/`) return true;\n }\n return false;\n }\n // IP-13: in the no-auth loopback mode (`auth.kind='none'`) there is no\n // verifier, but the authenticated subtree must still be reachable - install\n // an anonymous middleware that stamps a fully-authorized principal so the\n // domain routes serve instead of every one of them returning 401.\n const anonymousAuth = ctx.verifier === undefined && config.auth.kind === 'none';\n if (ctx.verifier !== undefined || anonymousAuth) {\n const authMw =\n ctx.verifier !== undefined\n ? createAuthMiddleware({ verifier: ctx.verifier })\n : createAnonymousAuthMiddleware();\n app.use(`${base}/*`, async (c, next) => {\n if (shouldSkipAuth(c.req.path)) {\n await next();\n return;\n }\n return authMw(c, next);\n });\n if (ctx.auditDb !== undefined) {\n const auditMw = createAuditMiddleware({ auditDb: ctx.auditDb, now: ctx.now });\n app.use(`${base}/*`, async (c, next) => {\n if (shouldSkipAuth(c.req.path)) {\n await next();\n return;\n }\n return auditMw(c, next);\n });\n }\n }\n\n // Idempotency middleware - applied to side-effecting endpoints. We\n // mount it once on the authenticated subtree so handlers don't have\n // to duplicate the configuration per-route.\n if (config.server.idempotency.enabled) {\n const idempotencyMw = createIdempotencyMiddleware({\n store: ctx.store.idempotency,\n config: config.server.idempotency,\n now: ctx.now,\n // IP-6: token minting returns a raw secret - never cache it.\n excludeResponseCachePaths: [`${base}/tokens`],\n // IP-15: publish the live cache hit ratio gauge.\n metricRegistry: ctx.metricRegistry,\n });\n app.use(`${base}/*`, async (c, next) => {\n if (shouldSkipAuth(c.req.path)) {\n await next();\n return;\n }\n return idempotencyMw(c, next);\n });\n }\n\n // Mounted AFTER the auth middleware so the scope check has a\n // verified token to inspect; the unauthenticated `/v1/health` GET\n // continues to serve the rollup.\n app.route(`${base}/health/secrets`, createSecretsHealthRoutes());\n\n app.route(\n `${base}/agents`,\n createAgentRoutes({\n agents: ctx.agents,\n runs: ctx.runs,\n // IP-2: the streaming dispatcher reaches the route layer.\n ...(ctx.wsDispatcher !== undefined ? { dispatcher: ctx.wsDispatcher } : {}),\n }),\n );\n app.route(`${base}/runs`, createRunRoutes({ agents: ctx.agents, runs: ctx.runs }));\n app.route(\n `${base}/workflows`,\n createWorkflowRoutes({\n workflows: ctx.workflows,\n runs: ctx.runs,\n ...(ctx.wsDispatcher !== undefined ? { dispatcher: ctx.wsDispatcher } : {}),\n }),\n );\n if (ctx.sessions !== undefined) {\n app.route(`${base}/sessions`, createSessionRoutes({ sessions: ctx.sessions }));\n }\n if (ctx.memory !== undefined) {\n app.route(`${base}/memory`, createMemoryRoutes({ memory: ctx.memory }));\n }\n if (ctx.skills !== undefined) {\n app.route(`${base}/skills`, createSkillsRoutes({ skills: ctx.skills }));\n }\n if (ctx.mcp !== undefined) {\n app.route(`${base}/mcp`, createMcpRoutes({ mcp: ctx.mcp }));\n }\n if (ctx.audit !== undefined) {\n app.route(`${base}/audit`, createAuditRoutes({ audit: ctx.audit }));\n }\n if (ctx.triggersDaemon !== undefined) {\n app.route(`${base}/triggers`, createTriggersRoutes({ daemon: ctx.triggersDaemon }));\n }\n if (ctx.replay !== undefined) {\n app.route(\n `${base}`,\n createReplayRoutes({\n replay: ctx.replay,\n ...(ctx.auditDb !== undefined ? { auditDb: ctx.auditDb } : {}),\n now: ctx.now,\n }),\n );\n }\n if (config.auth.kind === 'token' && ctx.pepper !== undefined) {\n app.route(\n `${base}/tokens`,\n createTokensRoutes({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n defaultEnv: 'live',\n allowedEnvs: config.auth.tokenEnvironments,\n }),\n );\n }\n if (ctx.wsTickets !== undefined) {\n app.route(`${base}`, createAuthRoutes({ tickets: ctx.wsTickets }));\n }\n // IP-13: mount the WS upgrade when a verifier is wired OR auth is disabled\n // (`auth.kind='none'`). The old condition required a verifier, so\n // `ws.enabled: true` under no-auth was silently ignored - the route never\n // mounted and clients got a bare 404 with no explanation.\n if (\n ctx.wsDispatcher !== undefined &&\n ctx.wsTickets !== undefined &&\n ctx.wsAdapter !== undefined &&\n (ctx.verifier !== undefined || anonymousAuth) &&\n config.server.ws.enabled\n ) {\n const dispatcher = ctx.wsDispatcher;\n const tickets = ctx.wsTickets;\n const verifier = ctx.verifier;\n const runs = ctx.runs;\n app.get(\n `${base}${config.server.ws.path}`,\n ctx.wsAdapter.upgradeWebSocket((c) =>\n createWsUpgradeEvents(c, {\n dispatcher,\n tickets,\n ...(verifier !== undefined ? { verifier } : {}),\n anonymous: anonymousAuth,\n runs,\n now: ctx.now,\n }),\n ),\n );\n }\n if (ctx.wsDispatcher !== undefined && config.server.sse.enabled) {\n app.route(\n `${base}${config.server.sse.path}`,\n createSseRoutes({\n dispatcher: ctx.wsDispatcher,\n keepAliveMs: config.server.sse.keepAliveMs,\n // IP-9: bound the per-connection delivery queue.\n perConnectionQueueLimit: config.server.stream.perConnectionQueueLimit,\n now: ctx.now,\n }),\n );\n }\n}\n\nasync function drainInFlight(\n runs: RunStateTracker,\n drainTimeoutMs: number,\n now: () => number,\n): Promise<boolean> {\n if (runs.runningCount() === 0) return true;\n if (drainTimeoutMs <= 0) return runs.runningCount() === 0;\n const deadline = now() + drainTimeoutMs;\n while (now() < deadline) {\n if (runs.runningCount() === 0) return true;\n await new Promise((resolve) => setTimeout(resolve, Math.min(50, deadline - now())));\n }\n return runs.runningCount() === 0;\n}\n\nasync function emitError(hooks: LifecycleHooks | undefined, ctx: OnErrorContext): Promise<void> {\n if (hooks?.onError === undefined) return;\n try {\n await hooks.onError(ctx);\n } catch {\n // onError must never throw further; swallow.\n }\n}\n\ninterface RefreshLiveMetricsOptions {\n readonly registry: MetricRegistry;\n readonly store: GraphorinSqliteStore;\n readonly runs: RunStateTracker;\n readonly startedAt: number;\n readonly now: () => number;\n readonly triggersDaemon?: TriggersDaemon;\n readonly consolidatorDaemon?: ConsolidatorDaemon;\n readonly wsDispatcher?: WsDispatcher;\n}\n\nasync function refreshLiveMetrics(options: RefreshLiveMetricsOptions): Promise<void> {\n const { registry, store, runs, startedAt, now } = options;\n\n try {\n const wal = readWalSize(store.connection);\n registry.set(SERVER_METRIC_NAMES.storageWalSize, wal);\n } catch {\n // Best-effort.\n }\n\n registry.set(\n SERVER_METRIC_NAMES.serverUptime,\n Math.max(0, Math.floor((now() - startedAt) / 1000)),\n );\n registry.set(SERVER_METRIC_NAMES.inflightRuns, runs.runningCount());\n\n if (options.wsDispatcher !== undefined) {\n const sizes = options.wsDispatcher.size();\n registry.set(SERVER_METRIC_NAMES.replayBufferEvents, sizes.subscriptions);\n }\n\n if (options.triggersDaemon !== undefined) {\n const metrics = options.triggersDaemon.metrics();\n for (const [triggerId, counts] of metrics.fires) {\n const sanitized = sanitizeMetricLabelValue(triggerId);\n const successCurrent = readCounter(registry, SERVER_METRIC_NAMES.triggersFiresTotal, {\n trigger_id: sanitized,\n status: 'success',\n });\n const errorCurrent = readCounter(registry, SERVER_METRIC_NAMES.triggersFiresTotal, {\n trigger_id: sanitized,\n status: 'error',\n });\n const successDelta = counts.success - successCurrent;\n const errorDelta = counts.error - errorCurrent;\n if (successDelta > 0) {\n registry.inc(\n SERVER_METRIC_NAMES.triggersFiresTotal,\n { trigger_id: sanitized, status: 'success' },\n successDelta,\n );\n }\n if (errorDelta > 0) {\n registry.inc(\n SERVER_METRIC_NAMES.triggersFiresTotal,\n { trigger_id: sanitized, status: 'error' },\n errorDelta,\n );\n }\n }\n }\n\n if (options.consolidatorDaemon !== undefined) {\n try {\n const status = await options.consolidatorDaemon.status();\n registry.set(SERVER_METRIC_NAMES.consolidatorQueueDepth, status.queueDepth, {\n phase: 'aggregate',\n });\n registry.set(SERVER_METRIC_NAMES.consolidatorDlqSize, status.dlqSize);\n registry.set(SERVER_METRIC_NAMES.consolidatorBudgetRemainingUsd, status.budget.costRemaining);\n registry.set(\n SERVER_METRIC_NAMES.consolidatorBudgetRemainingTokens,\n status.budget.tokensRemaining,\n );\n } catch {\n // Best-effort.\n }\n }\n}\n\n/**\n * Convert an arbitrary user-supplied identifier (trigger id) into a\n * Prometheus-safe label value. Replaces every character outside the\n * `[A-Za-z0-9_:]` range with `_`. This guarantees the cardinality\n * never explodes on UTF-8 sequences while keeping the value\n * recognizable.\n */\nfunction sanitizeMetricLabelValue(value: string): string {\n return value.replace(/[^A-Za-z0-9_:.-]/g, '_').slice(0, 200);\n}\n\nfunction readCounter(\n registry: MetricRegistry,\n name: string,\n labels: Record<string, string>,\n): number {\n const snap = registry.snapshot().counters[name] ?? [];\n for (const entry of snap) {\n if (matchesLabels(entry.labels, labels)) return entry.value;\n }\n return 0;\n}\n\nfunction matchesLabels(\n a: Record<string, string | number | boolean>,\n b: Record<string, string>,\n): boolean {\n for (const k of Object.keys(b)) {\n if (String(a[k]) !== b[k]) return false;\n }\n for (const k of Object.keys(a)) {\n if (!(k in b)) return false;\n }\n return true;\n}\n\nfunction buildDefaultHealthProbes(\n store: GraphorinSqliteStore,\n triggersDaemon: TriggersDaemon | undefined,\n consolidatorDaemon: ConsolidatorDaemon | undefined,\n dispatcher: WsDispatcher | undefined,\n config: ServerConfigSpec,\n): HealthCheckOptions {\n const out: {\n -readonly [K in keyof HealthCheckOptions]?: HealthCheckOptions[K];\n } = {\n store,\n walWarnThresholdBytes: config.health.walWarnThresholdBytes,\n encryptionEnabled: config.storage.encryption.enabled,\n // IP-1: when this process built the encrypted store itself, the\n // keyed open at boot proved the cipher peer - report the fact.\n ...(config.storage.encryption.enabled ? { cipherPeerInstalled: true } : {}),\n };\n if (triggersDaemon !== undefined) out.triggers = triggersDaemon;\n if (consolidatorDaemon !== undefined) out.consolidator = consolidatorDaemon;\n if (dispatcher !== undefined) {\n const sizes = dispatcher.size();\n // The dispatcher only exposes (subscribers, subscriptions); the\n // per-subject replay-buffer occupancy is owned by each\n // subscription, not the dispatcher itself. Surface the active\n // subscription count as a usable proxy + the raw subscriber count\n // so operators can correlate.\n out.replayBuffer = {\n eventsBuffered: sizes.subscriptions,\n subscribers: sizes.subscribers,\n subscriptions: sizes.subscriptions,\n };\n }\n return Object.freeze(out as HealthCheckOptions);\n}\n\nasync function fallbackPepper(skipHardening: boolean) {\n // Test-only path: when `skipHardening` is set we mint an ephemeral\n // pepper so the verifier is constructible without a real keyring.\n if (!skipHardening) {\n throw new Error(\n '[graphorin/server] missing resolved pepper after pre-bind; auth.pepperRef is required when auth.kind = token.',\n );\n }\n return generatePepper();\n}\n\nvoid process;\nvoid ConfigInvalidError;\n"],"mappings":"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"}
@@ -28,7 +28,7 @@ interface CommentaryAuditSink extends DeliveryCommentarySink {
28
28
  /**
29
29
  * Build a commentary sink that appends each sanitization decision to the audit
30
30
  * log. Writes serialise through `appendAudit` so concurrent decisions never
31
- * race on `seq`; a failed write is isolated from the wire `onWriteError`
31
+ * race on `seq`; a failed write is isolated from the wire - `onWriteError`
32
32
  * (default: a console warning) runs instead of throwing.
33
33
  *
34
34
  * @stable
@@ -38,7 +38,7 @@ declare function bridgeCommentaryToAudit(db: AuditDb, onWriteError?: (decision:
38
38
  * A {@link DeliveryCommentarySink} whose real target is installed later. The WS
39
39
  * dispatcher is created before the audit DB opens (IP-21); the server hands it
40
40
  * this forwarding sink and calls {@link LateBoundCommentarySink.bind} once the
41
- * audit-writing sink exists. Decisions emitted before binding are dropped the
41
+ * audit-writing sink exists. Decisions emitted before binding are dropped - the
42
42
  * dispatcher only sanitizes once it is live (after `start()`, by which point
43
43
  * the audit DB, if configured, has opened and bound).
44
44
  *
@@ -4,9 +4,9 @@ import { appendAudit } from "@graphorin/security/audit";
4
4
  /**
5
5
  * IP-21: bridge the delivery-commentary sanitizer's audit decisions into the
6
6
  * tamper-evident audit log. `createWsDispatcher` ran the sanitizer without a
7
- * `sink`, so every documented sanitization decision what was wrapped or
7
+ * `sink`, so every documented sanitization decision - what was wrapped or
8
8
  * stripped on the wire, with before/after digests and the matched-pattern
9
- * bucket was silently dropped instead of landing in the audit chain.
9
+ * bucket - was silently dropped instead of landing in the audit chain.
10
10
  *
11
11
  * The WS dispatcher is constructed before the audit DB is unsealed (the DB
12
12
  * opens during `start()`), so the server hands the dispatcher a
@@ -50,7 +50,7 @@ function commentaryDecisionToAuditInput(decision) {
50
50
  /**
51
51
  * Build a commentary sink that appends each sanitization decision to the audit
52
52
  * log. Writes serialise through `appendAudit` so concurrent decisions never
53
- * race on `seq`; a failed write is isolated from the wire `onWriteError`
53
+ * race on `seq`; a failed write is isolated from the wire - `onWriteError`
54
54
  * (default: a console warning) runs instead of throwing.
55
55
  *
56
56
  * @stable