@graphorin/server 0.6.1 → 0.7.0

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