@graphorin/server 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +6 -6
  3. package/dist/app-audit-binding.d.ts +15 -0
  4. package/dist/app-audit-binding.d.ts.map +1 -0
  5. package/dist/app-audit-binding.js +136 -0
  6. package/dist/app-audit-binding.js.map +1 -0
  7. package/dist/app-daemons.d.ts +29 -0
  8. package/dist/app-daemons.d.ts.map +1 -0
  9. package/dist/app-daemons.js +27 -0
  10. package/dist/app-daemons.js.map +1 -0
  11. package/dist/app-lifecycle.js +272 -0
  12. package/dist/app-lifecycle.js.map +1 -0
  13. package/dist/app-metrics.js +79 -0
  14. package/dist/app-metrics.js.map +1 -0
  15. package/dist/app-middleware.js +92 -0
  16. package/dist/app-middleware.js.map +1 -0
  17. package/dist/app-routes.js +131 -0
  18. package/dist/app-routes.js.map +1 -0
  19. package/dist/app-ws.js +65 -0
  20. package/dist/app-ws.js.map +1 -0
  21. package/dist/app.d.ts +16 -16
  22. package/dist/app.d.ts.map +1 -1
  23. package/dist/app.js +40 -611
  24. package/dist/app.js.map +1 -1
  25. package/dist/commentary/built-in-patterns.d.ts +1 -1
  26. package/dist/commentary/built-in-patterns.js +1 -1
  27. package/dist/commentary/built-in-patterns.js.map +1 -1
  28. package/dist/config.d.ts +100 -1
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +30 -2
  31. package/dist/config.js.map +1 -1
  32. package/dist/health/checks.d.ts +15 -1
  33. package/dist/health/checks.d.ts.map +1 -1
  34. package/dist/health/checks.js +21 -0
  35. package/dist/health/checks.js.map +1 -1
  36. package/dist/health/routes.js +1 -1
  37. package/dist/index.d.ts +9 -4
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +22 -26
  40. package/dist/index.js.map +1 -1
  41. package/dist/internal/context.d.ts +2 -2
  42. package/dist/internal/wire-error.js +20 -0
  43. package/dist/internal/wire-error.js.map +1 -0
  44. package/dist/lifecycle/pre-bind.d.ts +1 -1
  45. package/dist/metrics/catalog.d.ts.map +1 -1
  46. package/dist/metrics/catalog.js.map +1 -1
  47. package/dist/metrics/tools-bridge.d.ts +15 -0
  48. package/dist/metrics/tools-bridge.d.ts.map +1 -0
  49. package/dist/metrics/tools-bridge.js +103 -0
  50. package/dist/metrics/tools-bridge.js.map +1 -0
  51. package/dist/middleware/audit.d.ts +1 -1
  52. package/dist/middleware/auth.js +1 -1
  53. package/dist/middleware/csrf.js +1 -1
  54. package/dist/middleware/index.js +1 -1
  55. package/dist/middleware/scope.d.ts.map +1 -1
  56. package/dist/middleware/scope.js +44 -4
  57. package/dist/middleware/scope.js.map +1 -1
  58. package/dist/package.js +6 -0
  59. package/dist/package.js.map +1 -0
  60. package/dist/registry/index.d.ts +33 -0
  61. package/dist/registry/index.d.ts.map +1 -1
  62. package/dist/registry/index.js.map +1 -1
  63. package/dist/replay/routes.d.ts +1 -1
  64. package/dist/replay/routes.js +2 -2
  65. package/dist/routes/agents.d.ts.map +1 -1
  66. package/dist/routes/agents.js +64 -18
  67. package/dist/routes/agents.js.map +1 -1
  68. package/dist/routes/auth.js +2 -2
  69. package/dist/routes/auth.js.map +1 -1
  70. package/dist/routes/sessions.js +5 -5
  71. package/dist/routes/sessions.js.map +1 -1
  72. package/dist/routes/tokens.d.ts +1 -1
  73. package/dist/routes/tokens.d.ts.map +1 -1
  74. package/dist/routes/tokens.js +21 -1
  75. package/dist/routes/tokens.js.map +1 -1
  76. package/dist/routes/workflows.d.ts.map +1 -1
  77. package/dist/routes/workflows.js +165 -19
  78. package/dist/routes/workflows.js.map +1 -1
  79. package/dist/runtime/retention.d.ts +105 -0
  80. package/dist/runtime/retention.d.ts.map +1 -0
  81. package/dist/runtime/retention.js +154 -0
  82. package/dist/runtime/retention.js.map +1 -0
  83. package/dist/runtime/run-state.d.ts +2 -0
  84. package/dist/runtime/run-state.d.ts.map +1 -1
  85. package/dist/runtime/run-state.js +34 -2
  86. package/dist/runtime/run-state.js.map +1 -1
  87. package/dist/sse/events.js +3 -4
  88. package/dist/sse/events.js.map +1 -1
  89. package/dist/tools-audit-bridge.d.ts +29 -0
  90. package/dist/tools-audit-bridge.d.ts.map +1 -0
  91. package/dist/tools-audit-bridge.js +103 -0
  92. package/dist/tools-audit-bridge.js.map +1 -0
  93. package/dist/workflows/timer-daemon.d.ts +69 -0
  94. package/dist/workflows/timer-daemon.d.ts.map +1 -0
  95. package/dist/workflows/timer-daemon.js +37 -0
  96. package/dist/workflows/timer-daemon.js.map +1 -0
  97. package/dist/ws/dispatcher.d.ts +1 -1
  98. package/dist/ws/dispatcher.d.ts.map +1 -1
  99. package/dist/ws/dispatcher.js +19 -12
  100. package/dist/ws/dispatcher.js.map +1 -1
  101. package/dist/ws/index.d.ts +2 -2
  102. package/dist/ws/index.js +3 -3
  103. package/dist/ws/replay-buffer.d.ts +35 -1
  104. package/dist/ws/replay-buffer.d.ts.map +1 -1
  105. package/dist/ws/replay-buffer.js +35 -3
  106. package/dist/ws/replay-buffer.js.map +1 -1
  107. package/dist/ws/upgrade.d.ts.map +1 -1
  108. package/dist/ws/upgrade.js +26 -19
  109. package/dist/ws/upgrade.js.map +1 -1
  110. package/package.json +31 -30
  111. package/src/app-audit-binding.ts +227 -0
  112. package/src/app-daemons.ts +73 -0
  113. package/src/app-lifecycle.ts +476 -0
  114. package/src/app-metrics.ts +144 -0
  115. package/src/app-middleware.ts +149 -0
  116. package/src/app-routes.ts +305 -0
  117. package/src/app-ws.ts +111 -0
  118. package/src/app.ts +317 -0
  119. package/src/commentary/audit-bridge.ts +135 -0
  120. package/src/commentary/built-in-patterns.ts +79 -0
  121. package/src/commentary/index.ts +32 -0
  122. package/src/commentary/sanitizer.ts +238 -0
  123. package/src/commentary/types.ts +130 -0
  124. package/src/config.ts +472 -0
  125. package/src/consolidator/daemon.ts +141 -0
  126. package/src/consolidator/index.ts +14 -0
  127. package/src/errors/index.ts +247 -0
  128. package/src/health/checks.ts +322 -0
  129. package/src/health/index.ts +34 -0
  130. package/src/health/routes.ts +154 -0
  131. package/src/index.ts +243 -0
  132. package/src/internal/context.ts +77 -0
  133. package/src/internal/ids.ts +17 -0
  134. package/src/internal/json.ts +47 -0
  135. package/src/internal/wire-error.ts +44 -0
  136. package/src/lifecycle/hooks.ts +66 -0
  137. package/src/lifecycle/index.ts +16 -0
  138. package/src/lifecycle/pre-bind.ts +138 -0
  139. package/src/metrics/catalog.ts +112 -0
  140. package/src/metrics/index.ts +12 -0
  141. package/src/metrics/registry.ts +337 -0
  142. package/src/metrics/tools-bridge.ts +124 -0
  143. package/src/middleware/audit.ts +114 -0
  144. package/src/middleware/auth.ts +195 -0
  145. package/src/middleware/cors.ts +72 -0
  146. package/src/middleware/csrf.ts +98 -0
  147. package/src/middleware/idempotency.ts +336 -0
  148. package/src/middleware/index.ts +38 -0
  149. package/src/middleware/rate-limit.ts +71 -0
  150. package/src/middleware/request-state.ts +79 -0
  151. package/src/middleware/scope.ts +161 -0
  152. package/src/registry/index.ts +278 -0
  153. package/src/replay/index.ts +14 -0
  154. package/src/replay/routes.ts +255 -0
  155. package/src/routes/agents.ts +363 -0
  156. package/src/routes/audit.ts +207 -0
  157. package/src/routes/auth.ts +80 -0
  158. package/src/routes/health.ts +42 -0
  159. package/src/routes/index.ts +16 -0
  160. package/src/routes/mcp.ts +97 -0
  161. package/src/routes/memory.ts +152 -0
  162. package/src/routes/sessions.ts +250 -0
  163. package/src/routes/skills.ts +91 -0
  164. package/src/routes/tokens.ts +166 -0
  165. package/src/routes/workflows.ts +616 -0
  166. package/src/runtime/retention.ts +284 -0
  167. package/src/runtime/run-state.ts +422 -0
  168. package/src/sse/events.ts +303 -0
  169. package/src/sse/index.ts +7 -0
  170. package/src/tools-audit-bridge.ts +120 -0
  171. package/src/triggers/daemon.ts +198 -0
  172. package/src/triggers/index.ts +16 -0
  173. package/src/triggers/routes.ts +139 -0
  174. package/src/workflows/timer-daemon.ts +102 -0
  175. package/src/ws/dispatcher.ts +537 -0
  176. package/src/ws/index.ts +45 -0
  177. package/src/ws/replay-buffer.ts +209 -0
  178. package/src/ws/subjects.ts +162 -0
  179. package/src/ws/ticket.ts +174 -0
  180. package/src/ws/upgrade.ts +507 -0
  181. package/dist/lifecycle/index.js +0 -3
  182. package/dist/routes/index.js +0 -12
package/dist/app.js CHANGED
@@ -1,148 +1,21 @@
1
- import { bridgeCommentaryToAudit, createLateBoundCommentarySink } from "./commentary/audit-bridge.js";
2
- import "./commentary/index.js";
3
- import { LifecycleDoubleStartError, LifecycleNotStartedError, ShutdownTimeoutError } from "./errors/index.js";
4
- import { parseServerConfig } from "./config.js";
5
- import { createConsolidatorDaemon } from "./consolidator/daemon.js";
6
- import { createExtendedHealthRoutes, createMetricsRoutes, createSecretsHealthRoutes } from "./health/routes.js";
7
- import "./health/index.js";
8
- import { runPreBind } from "./lifecycle/pre-bind.js";
9
- import "./lifecycle/index.js";
1
+ import { version } from "./package.js";
2
+ import { buildDaemons } from "./app-daemons.js";
3
+ import { ensureStoreAuditBinding } from "./app-audit-binding.js";
10
4
  import { SERVER_METRIC_NAMES, createServerMetricRegistry } from "./metrics/catalog.js";
11
- import { createAuditMiddleware } from "./middleware/audit.js";
12
- import { createAnonymousAuthMiddleware, createAuthMiddleware } from "./middleware/auth.js";
13
- import { createCorsMiddleware } from "./middleware/cors.js";
14
- import { createCsrfMiddleware } from "./middleware/csrf.js";
15
- import { createIdempotencyMiddleware } from "./middleware/idempotency.js";
16
- import { createRateLimitMiddleware } from "./middleware/rate-limit.js";
17
- import { createRequestStateMiddleware } from "./middleware/request-state.js";
18
- import "./middleware/index.js";
5
+ import { attachGlobalMiddleware } from "./app-middleware.js";
6
+ import { RunStateTracker } from "./runtime/run-state.js";
7
+ import { createLifecycle } from "./app-lifecycle.js";
8
+ import { buildWsLayer } from "./app-ws.js";
9
+ import { parseServerConfig } from "./config.js";
19
10
  import { AgentRegistry, WorkflowRegistry } from "./registry/index.js";
20
- import { createReplayRoutes } from "./replay/routes.js";
21
- import "./replay/index.js";
22
- import { createAgentRoutes, createRunRoutes } from "./routes/agents.js";
23
- import { createAuditRoutes } from "./routes/audit.js";
24
- import { createAuthRoutes } from "./routes/auth.js";
25
- import { createMcpRoutes } from "./routes/mcp.js";
26
- import { createMemoryRoutes } from "./routes/memory.js";
27
- import { createSessionRoutes } from "./routes/sessions.js";
28
- import { createSkillsRoutes } from "./routes/skills.js";
29
- import { createTokensRoutes } from "./routes/tokens.js";
30
- import { createWorkflowRoutes } from "./routes/workflows.js";
31
- import "./routes/index.js";
32
- import { RunStateTracker, scheduleRunPruning } from "./runtime/run-state.js";
33
- import { createSseRoutes } from "./sse/events.js";
34
- import "./sse/index.js";
35
- import { createTriggersDaemon } from "./triggers/daemon.js";
36
- import { createTriggersRoutes } from "./triggers/routes.js";
37
- import { createWsDispatcher } from "./ws/dispatcher.js";
38
- import { createWsTicketStore } from "./ws/ticket.js";
39
- import { createWsUpgradeEvents } from "./ws/upgrade.js";
40
- import "./ws/index.js";
41
- import { SUBPROTOCOL_NAME, negotiateSubprotocol } from "@graphorin/protocol";
42
- import { TokenVerifier, applyProcessHardening, generatePepper } from "@graphorin/security";
43
- import { openAuditDb, registerAuditDbBinding } from "@graphorin/security/audit";
44
- import { createSqliteStore, loadCipherDriver, readWalSize } from "@graphorin/store-sqlite";
45
- import { serve } from "@hono/node-server";
46
- import { createNodeWebSocket } from "@hono/node-ws";
11
+ import { createSqliteStore } from "@graphorin/store-sqlite";
47
12
  import { Hono } from "hono";
48
13
 
49
14
  //#region src/app.ts
50
15
  /**
51
- * Pre-built audit-db binding shipped from `@graphorin/store-sqlite`.
52
- * Registered exactly once per process so {@link openAuditDb} can find
53
- * a default binding without forcing operators to wire it manually.
54
- *
55
- * Exported as {@link ensureStoreAuditBinding} so the CLI (Phase 15
56
- * `graphorin audit verify | prune | export`) can reach into the same
57
- * binding without booting the HTTP listener.
58
- *
59
- * @stable
60
- */
61
- let storeAuditBindingRegistered = false;
62
- function ensureStoreAuditBinding() {
63
- if (storeAuditBindingRegistered) return;
64
- registerAuditDbBinding({
65
- id: "better-sqlite3-multiple-ciphers",
66
- description: "Default audit-db binding shipped by @graphorin/store-sqlite.",
67
- open: async (opts) => {
68
- const driver = await loadCipherDriver();
69
- const passphrase = await opts.passphrase.use((value) => value);
70
- const db = new driver(opts.path);
71
- db.pragma(`key = '${passphrase.replace(/'/g, "''")}'`);
72
- db.pragma("journal_mode = WAL");
73
- db.pragma("synchronous = NORMAL");
74
- db.pragma("busy_timeout = 5000");
75
- db.pragma("foreign_keys = ON");
76
- db.exec(`CREATE TABLE IF NOT EXISTS audit_log (
77
- seq INTEGER PRIMARY KEY,
78
- ts INTEGER NOT NULL,
79
- actor_json TEXT NOT NULL,
80
- action TEXT NOT NULL,
81
- target TEXT NOT NULL,
82
- decision TEXT NOT NULL,
83
- context_json TEXT,
84
- metadata_json TEXT,
85
- prev_hash TEXT NOT NULL,
86
- hash TEXT NOT NULL UNIQUE
87
- ) WITHOUT ROWID;`);
88
- return {
89
- binding: "better-sqlite3-multiple-ciphers",
90
- path: opts.path,
91
- async insert(entry) {
92
- db.prepare(`INSERT INTO audit_log (
93
- seq, ts, actor_json, action, target, decision,
94
- context_json, metadata_json, prev_hash, hash
95
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(entry.seq, entry.ts, JSON.stringify(entry.actor), entry.action, entry.target, entry.decision, entry.context !== void 0 ? JSON.stringify(entry.context) : null, entry.metadata !== void 0 ? JSON.stringify(entry.metadata) : null, entry.prevHash, entry.hash);
96
- return entry;
97
- },
98
- async latest() {
99
- const row = db.prepare("SELECT * FROM audit_log ORDER BY seq DESC LIMIT 1").get();
100
- if (row === void 0) return void 0;
101
- return rowToEntry(row);
102
- },
103
- async *iterate(bounds) {
104
- const lo = bounds?.fromSeq ?? 1;
105
- const hi = bounds?.toSeq ?? Number.MAX_SAFE_INTEGER;
106
- const iter = db.prepare("SELECT * FROM audit_log WHERE seq BETWEEN ? AND ? ORDER BY seq ASC").iterate(lo, hi);
107
- for (const row of iter) yield rowToEntry(row);
108
- },
109
- async count() {
110
- return db.prepare("SELECT COUNT(*) AS n FROM audit_log").get().n;
111
- },
112
- async deleteUpTo(threshold) {
113
- const before = db.prepare("SELECT COUNT(*) AS n FROM audit_log WHERE seq <= ?").get(threshold);
114
- db.prepare("DELETE FROM audit_log WHERE seq <= ?").run(threshold);
115
- return before.n;
116
- },
117
- async replaceEntry(entry) {
118
- db.prepare(`UPDATE audit_log SET prev_hash = ?, hash = ? WHERE seq = ?`).run(entry.prevHash, entry.hash, entry.seq);
119
- },
120
- async close() {
121
- if (db.open) db.close();
122
- }
123
- };
124
- }
125
- }, { setAsDefault: true });
126
- storeAuditBindingRegistered = true;
127
- }
128
- function rowToEntry(row) {
129
- return {
130
- seq: row.seq,
131
- ts: row.ts,
132
- actor: JSON.parse(row.actor_json),
133
- action: row.action,
134
- target: row.target,
135
- decision: row.decision,
136
- ...row.context_json !== null ? { context: JSON.parse(row.context_json) } : {},
137
- ...row.metadata_json !== null ? { metadata: JSON.parse(row.metadata_json) } : {},
138
- prevHash: row.prev_hash,
139
- hash: row.hash
140
- };
141
- }
142
- /**
143
16
  * @stable
144
17
  */
145
- const VERSION = "0.6.0";
18
+ const VERSION = version;
146
19
  /**
147
20
  * Build a fully-wired Graphorin server. The returned handle is
148
21
  * inert until `start()` is awaited.
@@ -153,7 +26,7 @@ async function createServer(options = {}) {
153
26
  const config = options.validatedConfig ?? parseServerConfig(options.config ?? {});
154
27
  const now = options.now ?? Date.now;
155
28
  const startedAt = now();
156
- const version = options.version ?? VERSION;
29
+ const version$1 = options.version ?? VERSION;
157
30
  let storeEncryption;
158
31
  if (options.store === void 0 && config.storage.encryption.enabled) {
159
32
  if (config.storage.encryption.passphraseRef === void 0) throw new Error("[graphorin/server] storage.encryption.enabled is true but no passphraseRef is configured.");
@@ -174,7 +47,7 @@ async function createServer(options = {}) {
174
47
  } } : {}
175
48
  });
176
49
  const metricRegistry = options.metricRegistry ?? createServerMetricRegistry();
177
- metricRegistry.set(SERVER_METRIC_NAMES.buildInfo, 1, { version });
50
+ metricRegistry.set(SERVER_METRIC_NAMES.buildInfo, 1, { version: version$1 });
178
51
  const runs = options.runs ?? new RunStateTracker({
179
52
  now,
180
53
  onTerminal: (info) => {
@@ -185,61 +58,27 @@ async function createServer(options = {}) {
185
58
  const agents = options.agents ?? new AgentRegistry();
186
59
  const workflows = options.workflows ?? new WorkflowRegistry();
187
60
  const app = new Hono();
188
- app.use("*", createRequestStateMiddleware({
61
+ attachGlobalMiddleware(app, config, now);
62
+ const { triggersDaemon, consolidatorDaemon, workflowTimerDaemon } = buildDaemons(options);
63
+ const lifecycle = createLifecycle({
64
+ options,
65
+ config,
66
+ store,
67
+ app,
68
+ metricRegistry,
69
+ runs,
70
+ agents,
71
+ workflows,
72
+ version: version$1,
73
+ startedAt,
189
74
  now,
190
- ...config.server.trustProxy ? { trustProxy: true } : {}
191
- }));
192
- app.use("*", createCorsMiddleware(config.server.cors));
193
- app.use("*", createCsrfMiddleware(config.server.csrf));
194
- app.use("*", createRateLimitMiddleware(config.server.rateLimit, { now }));
195
- let serverInstance;
196
- let listening;
197
- let started = false;
198
- let stopped = false;
199
- let stopRunPruning;
200
- let auditDb;
201
- let preBind;
202
- let verifier;
203
- let pepperHandle;
204
- let triggersDaemon;
205
- if (options.triggers !== void 0) if ("daemon" in options.triggers) triggersDaemon = options.triggers.daemon;
206
- else triggersDaemon = createTriggersDaemon({ scheduler: options.triggers.scheduler });
207
- let consolidatorDaemon;
208
- if (options.consolidator !== void 0) consolidatorDaemon = createConsolidatorDaemon({ consolidator: options.consolidator });
209
- let dispatcher;
210
- let tickets;
211
- let wsAdapter;
212
- let commentaryAuditSink;
213
- if (config.server.ws.enabled) {
214
- commentaryAuditSink = createLateBoundCommentarySink();
215
- dispatcher = createWsDispatcher({
216
- commentary: {
217
- policy: config.server.ws.commentarySanitization.policy,
218
- applyToEvents: config.server.ws.commentarySanitization.applyToEvents,
219
- sink: commentaryAuditSink
220
- },
221
- replayBuffer: {
222
- maxEvents: config.server.stream.replayBuffer.maxEvents,
223
- ttlMs: config.server.stream.replayBuffer.ttlSeconds * 1e3
224
- },
225
- perConnectionQueueLimit: config.server.stream.perConnectionQueueLimit,
226
- now
227
- });
228
- tickets = createWsTicketStore({
229
- ttlMs: config.server.ws.ticketTtlMs,
230
- now
231
- });
232
- wsAdapter = createNodeWebSocket({ app });
233
- const wssOptions = wsAdapter.wss.options;
234
- wssOptions.handleProtocols = (protocols) => {
235
- const negotiated = negotiateSubprotocol(Array.from(protocols));
236
- if (negotiated !== null) return negotiated;
237
- for (const candidate of protocols) if (candidate === SUBPROTOCOL_NAME) return SUBPROTOCOL_NAME;
238
- return false;
239
- };
240
- }
75
+ ws: buildWsLayer(app, config, now),
76
+ triggersDaemon,
77
+ consolidatorDaemon,
78
+ workflowTimerDaemon
79
+ });
241
80
  return {
242
- version,
81
+ version: version$1,
243
82
  config,
244
83
  app,
245
84
  agents,
@@ -247,10 +86,10 @@ async function createServer(options = {}) {
247
86
  runs,
248
87
  metrics: metricRegistry,
249
88
  get wsDispatcher() {
250
- return dispatcher;
89
+ return lifecycle.wsDispatcher;
251
90
  },
252
91
  get wsTickets() {
253
- return tickets;
92
+ return lifecycle.wsTickets;
254
93
  },
255
94
  get triggers() {
256
95
  return triggersDaemon;
@@ -258,427 +97,17 @@ async function createServer(options = {}) {
258
97
  get consolidator() {
259
98
  return consolidatorDaemon;
260
99
  },
261
- get listeningOn() {
262
- return listening;
100
+ get workflowTimers() {
101
+ return workflowTimerDaemon;
263
102
  },
264
- async start() {
265
- if (started) throw new LifecycleDoubleStartError();
266
- started = true;
267
- try {
268
- if (options.hooks?.beforeStart !== void 0) await options.hooks.beforeStart({ config });
269
- if (options.skipHardening !== true && config.hardening.applyOnStart) applyProcessHardening({
270
- refuseRoot: config.hardening.refuseRoot,
271
- umask: config.hardening.umask
272
- });
273
- preBind = await runPreBind({
274
- config,
275
- store,
276
- ...options.probeCipherPeer !== void 0 ? { probeCipherPeer: options.probeCipherPeer } : {}
277
- });
278
- if (config.audit.enabled && preBind.auditPassphrase !== void 0 && preBind.auditPath !== void 0) {
279
- ensureStoreAuditBinding();
280
- auditDb = await openAuditDb({
281
- path: preBind.auditPath,
282
- passphrase: preBind.auditPassphrase,
283
- ...config.audit.cipher !== void 0 ? { cipher: config.audit.cipher } : {}
284
- });
285
- commentaryAuditSink?.bind(bridgeCommentaryToAudit(auditDb));
286
- }
287
- if (config.auth.kind === "token") {
288
- const pepper = preBind.pepper ?? await fallbackPepper(options.skipHardening === true);
289
- pepperHandle = pepper;
290
- verifier = new TokenVerifier({
291
- tokenStore: store.authTokens,
292
- pepper,
293
- acceptPrefix: config.auth.tokenPrefix,
294
- acceptEnvironments: config.auth.tokenEnvironments,
295
- ...config.auth.perIpFailureThreshold !== void 0 ? { perIpFailureThreshold: config.auth.perIpFailureThreshold } : {},
296
- ...config.auth.perIpLockoutMs !== void 0 ? { perIpLockoutMs: config.auth.perIpLockoutMs } : {},
297
- now
298
- });
299
- }
300
- mountRoutes(app, config, {
301
- version,
302
- startedAt,
303
- now,
304
- agents,
305
- workflows,
306
- runs,
307
- store,
308
- metricRegistry,
309
- ...options.sessions !== void 0 ? { sessions: options.sessions } : {},
310
- ...options.memory !== void 0 ? { memory: options.memory } : {},
311
- ...options.skills !== void 0 ? { skills: options.skills } : {},
312
- ...options.mcp !== void 0 ? { mcp: options.mcp } : {},
313
- ...options.audit !== void 0 ? { audit: options.audit } : {},
314
- ...options.replay !== void 0 ? { replay: options.replay } : {},
315
- ...options.healthProbes !== void 0 ? { healthProbes: options.healthProbes } : {},
316
- ...verifier !== void 0 ? { verifier } : {},
317
- ...auditDb !== void 0 ? { auditDb } : {},
318
- ...pepperHandle !== void 0 ? { pepper: pepperHandle } : {},
319
- ...dispatcher !== void 0 ? { wsDispatcher: dispatcher } : {},
320
- ...tickets !== void 0 ? { wsTickets: tickets } : {},
321
- ...wsAdapter !== void 0 ? { wsAdapter } : {},
322
- ...triggersDaemon !== void 0 ? { triggersDaemon } : {},
323
- ...consolidatorDaemon !== void 0 ? { consolidatorDaemon } : {}
324
- });
325
- if (consolidatorDaemon !== void 0) {
326
- await consolidatorDaemon.start();
327
- if (triggersDaemon !== void 0 && consolidatorDaemon.consolidator.registerWithScheduler !== void 0) await consolidatorDaemon.consolidator.registerWithScheduler(triggersDaemon.scheduler);
328
- }
329
- if (triggersDaemon !== void 0) await triggersDaemon.start();
330
- try {
331
- const wal = readWalSize(store.connection);
332
- metricRegistry.set(SERVER_METRIC_NAMES.storageWalSize, wal);
333
- } catch {}
334
- metricRegistry.set(SERVER_METRIC_NAMES.serverUptime, Math.max(0, Math.floor((now() - startedAt) / 1e3)));
335
- metricRegistry.set(SERVER_METRIC_NAMES.inflightRuns, runs.runningCount());
336
- metricRegistry.set(SERVER_METRIC_NAMES.replayBufferEvents, 0);
337
- stopRunPruning = scheduleRunPruning(runs, now);
338
- if (options.skipListen !== true) {
339
- serverInstance = serve({
340
- fetch: app.fetch.bind(app),
341
- hostname: config.server.host,
342
- port: config.server.port
343
- });
344
- if (wsAdapter !== void 0 && serverInstance !== void 0) wsAdapter.injectWebSocket(serverInstance);
345
- await new Promise((resolve) => {
346
- const server = serverInstance;
347
- if ("once" in server && typeof server.once === "function") server.once("listening", () => resolve());
348
- else setImmediate(() => resolve());
349
- });
350
- const address = serverInstance.address();
351
- if (address !== null && typeof address === "object") listening = {
352
- host: address.address,
353
- port: address.port
354
- };
355
- else listening = {
356
- host: config.server.host,
357
- port: config.server.port
358
- };
359
- } else listening = {
360
- host: config.server.host,
361
- port: config.server.port
362
- };
363
- if (options.hooks?.onReady !== void 0) await options.hooks.onReady({
364
- config,
365
- listeningOn: listening
366
- });
367
- return listening;
368
- } catch (err) {
369
- started = false;
370
- await emitError(options.hooks, {
371
- error: err,
372
- phase: "beforeStart"
373
- });
374
- throw err;
375
- }
103
+ get listeningOn() {
104
+ return lifecycle.listeningOn;
376
105
  },
377
- async stop({ force } = {}) {
378
- if (!started) throw new LifecycleNotStartedError();
379
- if (stopped) return;
380
- stopped = true;
381
- stopRunPruning?.();
382
- stopRunPruning = void 0;
383
- const drainTimeoutMs = force === true ? 0 : config.server.shutdown.drainTimeoutMs;
384
- try {
385
- if (options.hooks?.beforeShutdown !== void 0) await options.hooks.beforeShutdown({
386
- config,
387
- inflight: runs.inflightCount(),
388
- drainTimeoutMs
389
- });
390
- } catch (err) {
391
- await emitError(options.hooks, {
392
- error: err,
393
- phase: "beforeShutdown"
394
- });
395
- }
396
- if (triggersDaemon !== void 0) try {
397
- await triggersDaemon.stop();
398
- } catch (err) {
399
- await emitError(options.hooks, {
400
- error: err,
401
- phase: "beforeShutdown"
402
- });
403
- }
404
- if (consolidatorDaemon !== void 0) try {
405
- await consolidatorDaemon.stop();
406
- } catch (err) {
407
- await emitError(options.hooks, {
408
- error: err,
409
- phase: "beforeShutdown"
410
- });
411
- }
412
- runs.abortPending("server-shutdown");
413
- if (!await drainInFlight(runs, drainTimeoutMs, now) && drainTimeoutMs > 0) runs.abortAll(new ShutdownTimeoutError(drainTimeoutMs, runs.runningCount()));
414
- if (dispatcher !== void 0) try {
415
- dispatcher.shutdown();
416
- } catch {}
417
- if (serverInstance !== void 0) {
418
- await new Promise((resolve) => {
419
- serverInstance.close(() => resolve());
420
- });
421
- serverInstance = void 0;
422
- }
423
- if (auditDb !== void 0) {
424
- await auditDb.close();
425
- auditDb = void 0;
426
- }
427
- await store.close();
428
- listening = void 0;
429
- verifier = void 0;
430
- started = false;
431
- preBind = void 0;
432
- dispatcher = void 0;
433
- tickets = void 0;
434
- wsAdapter = void 0;
435
- }
106
+ start: lifecycle.start,
107
+ stop: lifecycle.stop
436
108
  };
437
109
  }
438
- /** IP-23: is `host` a loopback interface (so an open /metrics is not exposed)? */
439
- function isLoopbackHost(host) {
440
- const h = host.trim().toLowerCase();
441
- return h === "127.0.0.1" || h === "::1" || h === "[::1]" || h === "localhost";
442
- }
443
- function mountRoutes(app, config, ctx) {
444
- const base = config.server.basePath;
445
- const probes = ctx.healthProbes ?? (() => buildDefaultHealthProbes(ctx.store, ctx.triggersDaemon, ctx.consolidatorDaemon, ctx.wsDispatcher, config));
446
- const health = createExtendedHealthRoutes({
447
- version: ctx.version,
448
- startedAt: ctx.startedAt,
449
- now: ctx.now,
450
- probes
451
- });
452
- 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.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
- const metricsRoute = createMetricsRoutes({
457
- registry: ctx.metricRegistry,
458
- requireAuth: config.metrics.requireAuth,
459
- refresh: () => refreshLiveMetrics({
460
- registry: ctx.metricRegistry,
461
- store: ctx.store,
462
- runs: ctx.runs,
463
- startedAt: ctx.startedAt,
464
- now: ctx.now,
465
- ...ctx.triggersDaemon !== void 0 ? { triggersDaemon: ctx.triggersDaemon } : {},
466
- ...ctx.consolidatorDaemon !== void 0 ? { consolidatorDaemon: ctx.consolidatorDaemon } : {},
467
- ...ctx.wsDispatcher !== void 0 ? { wsDispatcher: ctx.wsDispatcher } : {}
468
- })
469
- });
470
- app.route(`${base}${config.metrics.path}`, metricsRoute);
471
- }
472
- const wsUpgradePath = config.server.ws.enabled ? `${base}${config.server.ws.path}` : void 0;
473
- const metricsPath = config.metrics.enabled && !config.metrics.requireAuth ? `${base}${config.metrics.path}` : void 0;
474
- function shouldSkipAuth(path) {
475
- if (path === `${base}/health` || path === `${base}/health/`) return true;
476
- if (wsUpgradePath !== void 0) {
477
- if (path === wsUpgradePath || path === `${wsUpgradePath}/`) return true;
478
- }
479
- if (metricsPath !== void 0) {
480
- if (path === metricsPath || path === `${metricsPath}/`) return true;
481
- }
482
- return false;
483
- }
484
- const anonymousAuth = ctx.verifier === void 0 && config.auth.kind === "none";
485
- if (ctx.verifier !== void 0 || anonymousAuth) {
486
- const authMw = ctx.verifier !== void 0 ? createAuthMiddleware({ verifier: ctx.verifier }) : createAnonymousAuthMiddleware();
487
- app.use(`${base}/*`, async (c, next) => {
488
- if (shouldSkipAuth(c.req.path)) {
489
- await next();
490
- return;
491
- }
492
- return authMw(c, next);
493
- });
494
- if (ctx.auditDb !== void 0) {
495
- const auditMw = createAuditMiddleware({
496
- auditDb: ctx.auditDb,
497
- now: ctx.now
498
- });
499
- app.use(`${base}/*`, async (c, next) => {
500
- if (shouldSkipAuth(c.req.path)) {
501
- await next();
502
- return;
503
- }
504
- return auditMw(c, next);
505
- });
506
- }
507
- }
508
- if (config.server.idempotency.enabled) {
509
- const idempotencyMw = createIdempotencyMiddleware({
510
- store: ctx.store.idempotency,
511
- config: config.server.idempotency,
512
- now: ctx.now,
513
- excludeResponseCachePaths: [`${base}/tokens`],
514
- metricRegistry: ctx.metricRegistry
515
- });
516
- app.use(`${base}/*`, async (c, next) => {
517
- if (shouldSkipAuth(c.req.path)) {
518
- await next();
519
- return;
520
- }
521
- return idempotencyMw(c, next);
522
- });
523
- }
524
- app.route(`${base}/health/secrets`, createSecretsHealthRoutes());
525
- app.route(`${base}/agents`, createAgentRoutes({
526
- agents: ctx.agents,
527
- runs: ctx.runs,
528
- ...ctx.wsDispatcher !== void 0 ? { dispatcher: ctx.wsDispatcher } : {}
529
- }));
530
- app.route(`${base}/runs`, createRunRoutes({
531
- agents: ctx.agents,
532
- runs: ctx.runs
533
- }));
534
- app.route(`${base}/workflows`, createWorkflowRoutes({
535
- workflows: ctx.workflows,
536
- runs: ctx.runs,
537
- ...ctx.wsDispatcher !== void 0 ? { dispatcher: ctx.wsDispatcher } : {}
538
- }));
539
- if (ctx.sessions !== void 0) app.route(`${base}/sessions`, createSessionRoutes({ sessions: ctx.sessions }));
540
- if (ctx.memory !== void 0) app.route(`${base}/memory`, createMemoryRoutes({ memory: ctx.memory }));
541
- if (ctx.skills !== void 0) app.route(`${base}/skills`, createSkillsRoutes({ skills: ctx.skills }));
542
- if (ctx.mcp !== void 0) app.route(`${base}/mcp`, createMcpRoutes({ mcp: ctx.mcp }));
543
- if (ctx.audit !== void 0) app.route(`${base}/audit`, createAuditRoutes({ audit: ctx.audit }));
544
- if (ctx.triggersDaemon !== void 0) app.route(`${base}/triggers`, createTriggersRoutes({ daemon: ctx.triggersDaemon }));
545
- if (ctx.replay !== void 0) app.route(`${base}`, createReplayRoutes({
546
- replay: ctx.replay,
547
- ...ctx.auditDb !== void 0 ? { auditDb: ctx.auditDb } : {},
548
- now: ctx.now
549
- }));
550
- if (config.auth.kind === "token" && ctx.pepper !== void 0) app.route(`${base}/tokens`, createTokensRoutes({
551
- tokenStore: ctx.store.authTokens,
552
- pepper: ctx.pepper,
553
- defaultEnv: "live",
554
- allowedEnvs: config.auth.tokenEnvironments
555
- }));
556
- if (ctx.wsTickets !== void 0) app.route(`${base}`, createAuthRoutes({ tickets: ctx.wsTickets }));
557
- if (ctx.wsDispatcher !== void 0 && ctx.wsTickets !== void 0 && ctx.wsAdapter !== void 0 && (ctx.verifier !== void 0 || anonymousAuth) && config.server.ws.enabled) {
558
- const dispatcher = ctx.wsDispatcher;
559
- const tickets = ctx.wsTickets;
560
- const verifier = ctx.verifier;
561
- const runs = ctx.runs;
562
- app.get(`${base}${config.server.ws.path}`, ctx.wsAdapter.upgradeWebSocket((c) => createWsUpgradeEvents(c, {
563
- dispatcher,
564
- tickets,
565
- ...verifier !== void 0 ? { verifier } : {},
566
- anonymous: anonymousAuth,
567
- runs,
568
- now: ctx.now
569
- })));
570
- }
571
- if (ctx.wsDispatcher !== void 0 && config.server.sse.enabled) app.route(`${base}${config.server.sse.path}`, createSseRoutes({
572
- dispatcher: ctx.wsDispatcher,
573
- keepAliveMs: config.server.sse.keepAliveMs,
574
- perConnectionQueueLimit: config.server.stream.perConnectionQueueLimit,
575
- now: ctx.now
576
- }));
577
- }
578
- async function drainInFlight(runs, drainTimeoutMs, now) {
579
- if (runs.runningCount() === 0) return true;
580
- if (drainTimeoutMs <= 0) return runs.runningCount() === 0;
581
- const deadline = now() + drainTimeoutMs;
582
- while (now() < deadline) {
583
- if (runs.runningCount() === 0) return true;
584
- await new Promise((resolve) => setTimeout(resolve, Math.min(50, deadline - now())));
585
- }
586
- return runs.runningCount() === 0;
587
- }
588
- async function emitError(hooks, ctx) {
589
- if (hooks?.onError === void 0) return;
590
- try {
591
- await hooks.onError(ctx);
592
- } catch {}
593
- }
594
- async function refreshLiveMetrics(options) {
595
- const { registry, store, runs, startedAt, now } = options;
596
- try {
597
- const wal = readWalSize(store.connection);
598
- registry.set(SERVER_METRIC_NAMES.storageWalSize, wal);
599
- } catch {}
600
- registry.set(SERVER_METRIC_NAMES.serverUptime, Math.max(0, Math.floor((now() - startedAt) / 1e3)));
601
- registry.set(SERVER_METRIC_NAMES.inflightRuns, runs.runningCount());
602
- if (options.wsDispatcher !== void 0) {
603
- const sizes = options.wsDispatcher.size();
604
- registry.set(SERVER_METRIC_NAMES.replayBufferEvents, sizes.subscriptions);
605
- }
606
- if (options.triggersDaemon !== void 0) {
607
- const metrics = options.triggersDaemon.metrics();
608
- for (const [triggerId, counts] of metrics.fires) {
609
- const sanitized = sanitizeMetricLabelValue(triggerId);
610
- const successCurrent = readCounter(registry, SERVER_METRIC_NAMES.triggersFiresTotal, {
611
- trigger_id: sanitized,
612
- status: "success"
613
- });
614
- const errorCurrent = readCounter(registry, SERVER_METRIC_NAMES.triggersFiresTotal, {
615
- trigger_id: sanitized,
616
- status: "error"
617
- });
618
- const successDelta = counts.success - successCurrent;
619
- const errorDelta = counts.error - errorCurrent;
620
- if (successDelta > 0) registry.inc(SERVER_METRIC_NAMES.triggersFiresTotal, {
621
- trigger_id: sanitized,
622
- status: "success"
623
- }, successDelta);
624
- if (errorDelta > 0) registry.inc(SERVER_METRIC_NAMES.triggersFiresTotal, {
625
- trigger_id: sanitized,
626
- status: "error"
627
- }, errorDelta);
628
- }
629
- }
630
- if (options.consolidatorDaemon !== void 0) try {
631
- const status = await options.consolidatorDaemon.status();
632
- registry.set(SERVER_METRIC_NAMES.consolidatorQueueDepth, status.queueDepth, { phase: "aggregate" });
633
- registry.set(SERVER_METRIC_NAMES.consolidatorDlqSize, status.dlqSize);
634
- registry.set(SERVER_METRIC_NAMES.consolidatorBudgetRemainingUsd, status.budget.costRemaining);
635
- registry.set(SERVER_METRIC_NAMES.consolidatorBudgetRemainingTokens, status.budget.tokensRemaining);
636
- } catch {}
637
- }
638
- /**
639
- * Convert an arbitrary user-supplied identifier (trigger id) into a
640
- * Prometheus-safe label value. Replaces every character outside the
641
- * `[A-Za-z0-9_:]` range with `_`. This guarantees the cardinality
642
- * never explodes on UTF-8 sequences while keeping the value
643
- * recognizable.
644
- */
645
- function sanitizeMetricLabelValue(value) {
646
- return value.replace(/[^A-Za-z0-9_:.-]/g, "_").slice(0, 200);
647
- }
648
- function readCounter(registry, name, labels) {
649
- const snap = registry.snapshot().counters[name] ?? [];
650
- for (const entry of snap) if (matchesLabels(entry.labels, labels)) return entry.value;
651
- return 0;
652
- }
653
- function matchesLabels(a, b) {
654
- for (const k of Object.keys(b)) if (String(a[k]) !== b[k]) return false;
655
- for (const k of Object.keys(a)) if (!(k in b)) return false;
656
- return true;
657
- }
658
- function buildDefaultHealthProbes(store, triggersDaemon, consolidatorDaemon, dispatcher, config) {
659
- const out = {
660
- store,
661
- walWarnThresholdBytes: config.health.walWarnThresholdBytes,
662
- encryptionEnabled: config.storage.encryption.enabled,
663
- ...config.storage.encryption.enabled ? { cipherPeerInstalled: true } : {}
664
- };
665
- if (triggersDaemon !== void 0) out.triggers = triggersDaemon;
666
- if (consolidatorDaemon !== void 0) out.consolidator = consolidatorDaemon;
667
- if (dispatcher !== void 0) {
668
- const sizes = dispatcher.size();
669
- out.replayBuffer = {
670
- eventsBuffered: sizes.subscriptions,
671
- subscribers: sizes.subscribers,
672
- subscriptions: sizes.subscriptions
673
- };
674
- }
675
- return Object.freeze(out);
676
- }
677
- async function fallbackPepper(skipHardening) {
678
- if (!skipHardening) throw new Error("[graphorin/server] missing resolved pepper after pre-bind; auth.pepperRef is required when auth.kind = token.");
679
- return generatePepper();
680
- }
681
110
 
682
111
  //#endregion
683
- export { createServer, ensureStoreAuditBinding };
112
+ export { createServer };
684
113
  //# sourceMappingURL=app.js.map