@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.
- package/CHANGELOG.md +71 -0
- package/README.md +3 -3
- package/dist/app-audit-binding.d.ts +15 -0
- package/dist/app-audit-binding.d.ts.map +1 -0
- package/dist/app-audit-binding.js +136 -0
- package/dist/app-audit-binding.js.map +1 -0
- package/dist/app-daemons.d.ts +29 -0
- package/dist/app-daemons.d.ts.map +1 -0
- package/dist/app-daemons.js +27 -0
- package/dist/app-daemons.js.map +1 -0
- package/dist/app-lifecycle.js +272 -0
- package/dist/app-lifecycle.js.map +1 -0
- package/dist/app-metrics.js +79 -0
- package/dist/app-metrics.js.map +1 -0
- package/dist/app-middleware.js +92 -0
- package/dist/app-middleware.js.map +1 -0
- package/dist/app-routes.js +131 -0
- package/dist/app-routes.js.map +1 -0
- package/dist/app-ws.js +65 -0
- package/dist/app-ws.js.map +1 -0
- package/dist/app.d.ts +16 -16
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +35 -607
- package/dist/app.js.map +1 -1
- package/dist/commentary/built-in-patterns.d.ts +1 -1
- package/dist/commentary/built-in-patterns.js +1 -1
- package/dist/commentary/built-in-patterns.js.map +1 -1
- package/dist/config.d.ts +100 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -2
- package/dist/config.js.map +1 -1
- package/dist/health/checks.d.ts +15 -1
- package/dist/health/checks.d.ts.map +1 -1
- package/dist/health/checks.js +21 -0
- package/dist/health/checks.js.map +1 -1
- package/dist/health/routes.js +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -24
- package/dist/index.js.map +1 -1
- package/dist/internal/context.d.ts +2 -2
- package/dist/internal/wire-error.js +20 -0
- package/dist/internal/wire-error.js.map +1 -0
- package/dist/lifecycle/pre-bind.d.ts +1 -1
- package/dist/metrics/catalog.d.ts.map +1 -1
- package/dist/metrics/catalog.js.map +1 -1
- package/dist/metrics/tools-bridge.d.ts +15 -0
- package/dist/metrics/tools-bridge.d.ts.map +1 -0
- package/dist/metrics/tools-bridge.js +103 -0
- package/dist/metrics/tools-bridge.js.map +1 -0
- package/dist/middleware/audit.d.ts +1 -1
- package/dist/middleware/auth.js +1 -1
- package/dist/middleware/csrf.js +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/scope.d.ts.map +1 -1
- package/dist/middleware/scope.js +44 -4
- package/dist/middleware/scope.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/registry/index.d.ts +33 -0
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js.map +1 -1
- package/dist/replay/routes.d.ts +1 -1
- package/dist/replay/routes.js +2 -2
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +64 -18
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/auth.js +2 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/sessions.js +5 -5
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/tokens.d.ts +1 -1
- package/dist/routes/tokens.d.ts.map +1 -1
- package/dist/routes/tokens.js +21 -1
- package/dist/routes/tokens.js.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/routes/workflows.js +165 -19
- package/dist/routes/workflows.js.map +1 -1
- package/dist/runtime/retention.d.ts +105 -0
- package/dist/runtime/retention.d.ts.map +1 -0
- package/dist/runtime/retention.js +154 -0
- package/dist/runtime/retention.js.map +1 -0
- package/dist/runtime/run-state.d.ts +2 -0
- package/dist/runtime/run-state.d.ts.map +1 -1
- package/dist/runtime/run-state.js +34 -2
- package/dist/runtime/run-state.js.map +1 -1
- package/dist/sse/events.js +3 -4
- package/dist/sse/events.js.map +1 -1
- package/dist/tools-audit-bridge.d.ts +29 -0
- package/dist/tools-audit-bridge.d.ts.map +1 -0
- package/dist/tools-audit-bridge.js +103 -0
- package/dist/tools-audit-bridge.js.map +1 -0
- package/dist/workflows/timer-daemon.d.ts +69 -0
- package/dist/workflows/timer-daemon.d.ts.map +1 -0
- package/dist/workflows/timer-daemon.js +37 -0
- package/dist/workflows/timer-daemon.js.map +1 -0
- package/dist/ws/dispatcher.d.ts +1 -1
- package/dist/ws/dispatcher.js +19 -12
- package/dist/ws/dispatcher.js.map +1 -1
- package/dist/ws/index.d.ts +2 -2
- package/dist/ws/index.js +3 -3
- package/dist/ws/replay-buffer.d.ts +35 -1
- package/dist/ws/replay-buffer.d.ts.map +1 -1
- package/dist/ws/replay-buffer.js +35 -3
- package/dist/ws/replay-buffer.js.map +1 -1
- package/dist/ws/upgrade.d.ts.map +1 -1
- package/dist/ws/upgrade.js +24 -18
- package/dist/ws/upgrade.js.map +1 -1
- package/package.json +30 -29
- package/src/app-audit-binding.ts +227 -0
- package/src/app-daemons.ts +73 -0
- package/src/app-lifecycle.ts +476 -0
- package/src/app-metrics.ts +144 -0
- package/src/app-middleware.ts +149 -0
- package/src/app-routes.ts +305 -0
- package/src/app-ws.ts +111 -0
- package/src/app.ts +317 -0
- package/src/commentary/audit-bridge.ts +135 -0
- package/src/commentary/built-in-patterns.ts +79 -0
- package/src/commentary/index.ts +32 -0
- package/src/commentary/sanitizer.ts +238 -0
- package/src/commentary/types.ts +130 -0
- package/src/config.ts +472 -0
- package/src/consolidator/daemon.ts +141 -0
- package/src/consolidator/index.ts +14 -0
- package/src/errors/index.ts +247 -0
- package/src/health/checks.ts +322 -0
- package/src/health/index.ts +34 -0
- package/src/health/routes.ts +154 -0
- package/src/index.ts +243 -0
- package/src/internal/context.ts +77 -0
- package/src/internal/ids.ts +17 -0
- package/src/internal/json.ts +47 -0
- package/src/internal/wire-error.ts +44 -0
- package/src/lifecycle/hooks.ts +66 -0
- package/src/lifecycle/index.ts +16 -0
- package/src/lifecycle/pre-bind.ts +138 -0
- package/src/metrics/catalog.ts +112 -0
- package/src/metrics/index.ts +12 -0
- package/src/metrics/registry.ts +337 -0
- package/src/metrics/tools-bridge.ts +124 -0
- package/src/middleware/audit.ts +114 -0
- package/src/middleware/auth.ts +195 -0
- package/src/middleware/cors.ts +72 -0
- package/src/middleware/csrf.ts +98 -0
- package/src/middleware/idempotency.ts +336 -0
- package/src/middleware/index.ts +38 -0
- package/src/middleware/rate-limit.ts +71 -0
- package/src/middleware/request-state.ts +79 -0
- package/src/middleware/scope.ts +161 -0
- package/src/registry/index.ts +278 -0
- package/src/replay/index.ts +14 -0
- package/src/replay/routes.ts +255 -0
- package/src/routes/agents.ts +363 -0
- package/src/routes/audit.ts +207 -0
- package/src/routes/auth.ts +80 -0
- package/src/routes/health.ts +42 -0
- package/src/routes/index.ts +16 -0
- package/src/routes/mcp.ts +97 -0
- package/src/routes/memory.ts +152 -0
- package/src/routes/sessions.ts +250 -0
- package/src/routes/skills.ts +91 -0
- package/src/routes/tokens.ts +166 -0
- package/src/routes/workflows.ts +616 -0
- package/src/runtime/retention.ts +284 -0
- package/src/runtime/run-state.ts +422 -0
- package/src/sse/events.ts +303 -0
- package/src/sse/index.ts +7 -0
- package/src/tools-audit-bridge.ts +120 -0
- package/src/triggers/daemon.ts +198 -0
- package/src/triggers/index.ts +16 -0
- package/src/triggers/routes.ts +139 -0
- package/src/workflows/timer-daemon.ts +102 -0
- package/src/ws/dispatcher.ts +537 -0
- package/src/ws/index.ts +45 -0
- package/src/ws/replay-buffer.ts +209 -0
- package/src/ws/subjects.ts +162 -0
- package/src/ws/ticket.ts +174 -0
- package/src/ws/upgrade.ts +507 -0
- package/dist/lifecycle/index.js +0 -3
- package/dist/routes/index.js +0 -12
package/src/config.ts
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strongly-typed configuration loader for the Graphorin server.
|
|
3
|
+
*
|
|
4
|
+
* Operators ship a `graphorin.config.ts` (or `.js` / `.mjs` / JSON)
|
|
5
|
+
* file that calls {@link defineConfig} to construct a typed config
|
|
6
|
+
* object; the server runtime accepts either the raw object, a path
|
|
7
|
+
* to such a file, or a function that returns one.
|
|
8
|
+
*
|
|
9
|
+
* Validation is performed via Zod so user-facing errors include the
|
|
10
|
+
* exact path of every failing field, not just a generic
|
|
11
|
+
* "TypeError: undefined".
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
|
|
18
|
+
import { ConfigInvalidError } from './errors/index.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* String literal that flags a value as a `SecretRef` URI. The
|
|
22
|
+
* server's pre-bind step resolves every `*Ref` field through the
|
|
23
|
+
* `@graphorin/security` resolver registry before binding the
|
|
24
|
+
* listener; an unresolvable ref fails fast with
|
|
25
|
+
* `PrebindSecretUnresolvableError`.
|
|
26
|
+
*
|
|
27
|
+
* @stable
|
|
28
|
+
*/
|
|
29
|
+
export type SecretRefString = string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Selector for which `SecretsStore` flavour the server activates.
|
|
33
|
+
* Mirrors `--secrets-source` from DEC-136.
|
|
34
|
+
*
|
|
35
|
+
* @stable
|
|
36
|
+
*/
|
|
37
|
+
export type SecretsSource = 'auto' | 'keyring' | 'encrypted-file' | 'env';
|
|
38
|
+
|
|
39
|
+
/** @stable */
|
|
40
|
+
export type IdempotencyRequireKeyMode = 'off' | 'warn' | 'enforce';
|
|
41
|
+
|
|
42
|
+
/** @stable */
|
|
43
|
+
export type DeliveryCommentaryPolicyConfig = 'wrap' | 'strip' | 'pass-through';
|
|
44
|
+
|
|
45
|
+
/** @stable */
|
|
46
|
+
export interface ServerConfigSpec {
|
|
47
|
+
readonly server: {
|
|
48
|
+
readonly host: string;
|
|
49
|
+
readonly port: number;
|
|
50
|
+
readonly basePath: string;
|
|
51
|
+
readonly cors: {
|
|
52
|
+
readonly allowOrigins: ReadonlyArray<string>;
|
|
53
|
+
readonly allowCredentials: boolean;
|
|
54
|
+
readonly allowMethods: ReadonlyArray<string>;
|
|
55
|
+
readonly allowHeaders: ReadonlyArray<string>;
|
|
56
|
+
readonly maxAgeSeconds: number;
|
|
57
|
+
};
|
|
58
|
+
readonly csrf: {
|
|
59
|
+
readonly enabled: boolean;
|
|
60
|
+
readonly cookieName: string;
|
|
61
|
+
readonly headerName: string;
|
|
62
|
+
readonly safeMethods: ReadonlyArray<string>;
|
|
63
|
+
};
|
|
64
|
+
readonly rateLimit: {
|
|
65
|
+
readonly enabled: boolean;
|
|
66
|
+
readonly windowMs: number;
|
|
67
|
+
readonly perIpRequests: number;
|
|
68
|
+
};
|
|
69
|
+
readonly idempotency: {
|
|
70
|
+
readonly enabled: boolean;
|
|
71
|
+
readonly requireKey: IdempotencyRequireKeyMode;
|
|
72
|
+
readonly ttlSeconds: number;
|
|
73
|
+
readonly checkBodyFingerprint: boolean;
|
|
74
|
+
readonly lruCacheSize: number;
|
|
75
|
+
};
|
|
76
|
+
readonly shutdown: {
|
|
77
|
+
readonly drainTimeoutMs: number;
|
|
78
|
+
};
|
|
79
|
+
readonly trustProxy: boolean;
|
|
80
|
+
readonly stream: {
|
|
81
|
+
readonly disconnectPolicy: 'continue' | 'pause-on-disconnect' | 'abort-on-disconnect';
|
|
82
|
+
readonly disconnectGracePeriodMs: number;
|
|
83
|
+
readonly replayBuffer: {
|
|
84
|
+
readonly maxEvents: number;
|
|
85
|
+
readonly ttlSeconds: number;
|
|
86
|
+
readonly pruneIntervalSeconds: number;
|
|
87
|
+
};
|
|
88
|
+
readonly perConnectionQueueLimit: number;
|
|
89
|
+
};
|
|
90
|
+
readonly ws: {
|
|
91
|
+
readonly enabled: boolean;
|
|
92
|
+
readonly path: string;
|
|
93
|
+
readonly ticketTtlMs: number;
|
|
94
|
+
readonly commentarySanitization: {
|
|
95
|
+
readonly policy: DeliveryCommentaryPolicyConfig;
|
|
96
|
+
readonly applyToEvents: ReadonlyArray<string>;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
readonly sse: {
|
|
100
|
+
readonly enabled: boolean;
|
|
101
|
+
readonly path: string;
|
|
102
|
+
readonly keepAliveMs: number;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
readonly storage: {
|
|
106
|
+
readonly path: string;
|
|
107
|
+
readonly mode: 'lib' | 'server';
|
|
108
|
+
readonly walCheckpointIntervalMs?: number;
|
|
109
|
+
readonly encryption: {
|
|
110
|
+
readonly enabled: boolean;
|
|
111
|
+
readonly cipher?: string;
|
|
112
|
+
readonly passphraseRef?: SecretRefString;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
readonly retention: {
|
|
116
|
+
readonly enabled: boolean;
|
|
117
|
+
readonly intervalMs: number;
|
|
118
|
+
readonly spansDays: number;
|
|
119
|
+
readonly consolidatorRunsDays: number;
|
|
120
|
+
readonly dlqExhaustedDays: number;
|
|
121
|
+
readonly idempotency: boolean;
|
|
122
|
+
readonly sessionsDays?: number;
|
|
123
|
+
readonly sessionsClosedOnly: boolean;
|
|
124
|
+
readonly memoryHistoryDays?: number;
|
|
125
|
+
readonly workflowThreadsDays?: number;
|
|
126
|
+
readonly auditDays?: number;
|
|
127
|
+
};
|
|
128
|
+
readonly audit: {
|
|
129
|
+
readonly enabled: boolean;
|
|
130
|
+
readonly path?: string;
|
|
131
|
+
readonly passphraseRef?: SecretRefString;
|
|
132
|
+
readonly cipher?: string;
|
|
133
|
+
/**
|
|
134
|
+
* W-051: which tools/MCP audit-bus events land in the audit chain.
|
|
135
|
+
* `'security'` (default) writes the security-significant subset
|
|
136
|
+
* (dataflow flagged/blocked/declassified, sanitization, approvals,
|
|
137
|
+
* collisions, cap-disabled); `'all'` adds per-call
|
|
138
|
+
* `tool:execute:*` chatter; `'off'` disables the bridge.
|
|
139
|
+
*/
|
|
140
|
+
readonly toolEvents: 'security' | 'all' | 'off';
|
|
141
|
+
};
|
|
142
|
+
readonly secrets: {
|
|
143
|
+
readonly source: SecretsSource;
|
|
144
|
+
readonly strict: boolean;
|
|
145
|
+
};
|
|
146
|
+
readonly auth: {
|
|
147
|
+
readonly kind: 'token' | 'none';
|
|
148
|
+
readonly pepperRef?: SecretRefString;
|
|
149
|
+
readonly tokenPrefix: string;
|
|
150
|
+
readonly tokenEnvironments: ReadonlyArray<string>;
|
|
151
|
+
readonly perIpFailureThreshold?: number;
|
|
152
|
+
readonly perIpLockoutMs?: number;
|
|
153
|
+
};
|
|
154
|
+
readonly observability: {
|
|
155
|
+
readonly logger: 'json' | 'pretty' | 'silent';
|
|
156
|
+
};
|
|
157
|
+
readonly hardening: {
|
|
158
|
+
readonly applyOnStart: boolean;
|
|
159
|
+
readonly refuseRoot: boolean;
|
|
160
|
+
readonly umask: number;
|
|
161
|
+
};
|
|
162
|
+
readonly metrics: {
|
|
163
|
+
readonly enabled: boolean;
|
|
164
|
+
readonly path: string;
|
|
165
|
+
readonly requireAuth: boolean;
|
|
166
|
+
};
|
|
167
|
+
readonly health: {
|
|
168
|
+
readonly walWarnThresholdBytes: number;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const corsSchema = z
|
|
173
|
+
.object({
|
|
174
|
+
allowOrigins: z.array(z.string()).default([]),
|
|
175
|
+
allowCredentials: z.boolean().default(false),
|
|
176
|
+
allowMethods: z.array(z.string()).default(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']),
|
|
177
|
+
allowHeaders: z
|
|
178
|
+
.array(z.string())
|
|
179
|
+
.default(['Authorization', 'Content-Type', 'Idempotency-Key', 'X-CSRF-Token']),
|
|
180
|
+
maxAgeSeconds: z.number().int().nonnegative().default(600),
|
|
181
|
+
})
|
|
182
|
+
.strict()
|
|
183
|
+
.default({});
|
|
184
|
+
|
|
185
|
+
const csrfSchema = z
|
|
186
|
+
.object({
|
|
187
|
+
enabled: z.boolean().default(true),
|
|
188
|
+
cookieName: z.string().default('graphorin_csrf'),
|
|
189
|
+
headerName: z.string().default('X-CSRF-Token'),
|
|
190
|
+
safeMethods: z.array(z.string()).default(['GET', 'HEAD', 'OPTIONS']),
|
|
191
|
+
})
|
|
192
|
+
.strict()
|
|
193
|
+
.default({});
|
|
194
|
+
|
|
195
|
+
const rateLimitSchema = z
|
|
196
|
+
.object({
|
|
197
|
+
enabled: z.boolean().default(true),
|
|
198
|
+
windowMs: z.number().int().positive().default(60_000),
|
|
199
|
+
perIpRequests: z.number().int().positive().default(60),
|
|
200
|
+
})
|
|
201
|
+
.strict()
|
|
202
|
+
.default({});
|
|
203
|
+
|
|
204
|
+
const idempotencySchema = z
|
|
205
|
+
.object({
|
|
206
|
+
enabled: z.boolean().default(true),
|
|
207
|
+
requireKey: z.enum(['off', 'warn', 'enforce']).default('warn'),
|
|
208
|
+
ttlSeconds: z.number().int().positive().default(86_400),
|
|
209
|
+
checkBodyFingerprint: z.boolean().default(true),
|
|
210
|
+
lruCacheSize: z.number().int().positive().default(1_000),
|
|
211
|
+
})
|
|
212
|
+
.strict()
|
|
213
|
+
.default({});
|
|
214
|
+
|
|
215
|
+
const shutdownSchema = z
|
|
216
|
+
.object({
|
|
217
|
+
drainTimeoutMs: z.number().int().positive().default(30_000),
|
|
218
|
+
})
|
|
219
|
+
.strict()
|
|
220
|
+
.default({});
|
|
221
|
+
|
|
222
|
+
const streamReplayBufferSchema = z
|
|
223
|
+
.object({
|
|
224
|
+
maxEvents: z.number().int().positive().default(1_000),
|
|
225
|
+
ttlSeconds: z.number().int().positive().default(300),
|
|
226
|
+
// W-028: interval of the periodic TTL sweep that releases
|
|
227
|
+
// finished-run subjects with no further push/replay activity.
|
|
228
|
+
pruneIntervalSeconds: z.number().int().positive().default(60),
|
|
229
|
+
})
|
|
230
|
+
.strict()
|
|
231
|
+
.default({});
|
|
232
|
+
|
|
233
|
+
const streamSchema = z
|
|
234
|
+
.object({
|
|
235
|
+
disconnectPolicy: z
|
|
236
|
+
.enum(['continue', 'pause-on-disconnect', 'abort-on-disconnect'])
|
|
237
|
+
.default('continue'),
|
|
238
|
+
disconnectGracePeriodMs: z.number().int().nonnegative().default(10_000),
|
|
239
|
+
replayBuffer: streamReplayBufferSchema,
|
|
240
|
+
perConnectionQueueLimit: z.number().int().positive().default(1_000),
|
|
241
|
+
})
|
|
242
|
+
.strict()
|
|
243
|
+
.default({});
|
|
244
|
+
|
|
245
|
+
const wsCommentarySchema = z
|
|
246
|
+
.object({
|
|
247
|
+
policy: z.enum(['wrap', 'strip', 'pass-through']).default('wrap'),
|
|
248
|
+
applyToEvents: z
|
|
249
|
+
.array(z.string().min(1))
|
|
250
|
+
.default(['tool.execute.end', 'tool.execute.error', 'text.delta']),
|
|
251
|
+
})
|
|
252
|
+
.strict()
|
|
253
|
+
.default({});
|
|
254
|
+
|
|
255
|
+
const wsSchema = z
|
|
256
|
+
.object({
|
|
257
|
+
enabled: z.boolean().default(true),
|
|
258
|
+
path: z.string().default('/ws'),
|
|
259
|
+
ticketTtlMs: z
|
|
260
|
+
.number()
|
|
261
|
+
.int()
|
|
262
|
+
.positive()
|
|
263
|
+
.default(5 * 60_000),
|
|
264
|
+
commentarySanitization: wsCommentarySchema,
|
|
265
|
+
})
|
|
266
|
+
.strict()
|
|
267
|
+
.default({});
|
|
268
|
+
|
|
269
|
+
const sseSchema = z
|
|
270
|
+
.object({
|
|
271
|
+
enabled: z.boolean().default(true),
|
|
272
|
+
path: z.string().default('/sessions'),
|
|
273
|
+
keepAliveMs: z.number().int().positive().default(15_000),
|
|
274
|
+
})
|
|
275
|
+
.strict()
|
|
276
|
+
.default({});
|
|
277
|
+
|
|
278
|
+
const serverSchema = z
|
|
279
|
+
.object({
|
|
280
|
+
host: z.string().min(1).default('127.0.0.1'),
|
|
281
|
+
port: z.number().int().min(0).max(65_535).default(8_080),
|
|
282
|
+
basePath: z.string().default('/v1'),
|
|
283
|
+
cors: corsSchema,
|
|
284
|
+
csrf: csrfSchema,
|
|
285
|
+
rateLimit: rateLimitSchema,
|
|
286
|
+
idempotency: idempotencySchema,
|
|
287
|
+
shutdown: shutdownSchema,
|
|
288
|
+
trustProxy: z.boolean().default(false),
|
|
289
|
+
stream: streamSchema,
|
|
290
|
+
ws: wsSchema,
|
|
291
|
+
sse: sseSchema,
|
|
292
|
+
})
|
|
293
|
+
.strict()
|
|
294
|
+
.default({});
|
|
295
|
+
|
|
296
|
+
const encryptionSchema = z
|
|
297
|
+
.object({
|
|
298
|
+
enabled: z.boolean().default(false),
|
|
299
|
+
cipher: z.string().optional(),
|
|
300
|
+
passphraseRef: z.string().optional(),
|
|
301
|
+
})
|
|
302
|
+
.strict()
|
|
303
|
+
.default({});
|
|
304
|
+
|
|
305
|
+
const storageSchema = z
|
|
306
|
+
.object({
|
|
307
|
+
path: z.string().min(1).default('./.graphorin/data.db'),
|
|
308
|
+
mode: z.enum(['lib', 'server']).default('server'),
|
|
309
|
+
walCheckpointIntervalMs: z.number().int().positive().optional(),
|
|
310
|
+
encryption: encryptionSchema,
|
|
311
|
+
})
|
|
312
|
+
.strict()
|
|
313
|
+
.default({});
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* W-010 / W-008 retention policy. Default-on ONLY for derived or
|
|
317
|
+
* recoverable data (span telemetry, consolidator run counters, the
|
|
318
|
+
* exhausted consolidator DLQ, expired idempotency response bodies).
|
|
319
|
+
* Primary user content - sessions, memory history, workflow threads,
|
|
320
|
+
* the session audit trail - is never touched unless the operator sets
|
|
321
|
+
* the matching `*Days` window explicitly.
|
|
322
|
+
*/
|
|
323
|
+
const retentionSchema = z
|
|
324
|
+
.object({
|
|
325
|
+
enabled: z.boolean().default(true),
|
|
326
|
+
intervalMs: z
|
|
327
|
+
.number()
|
|
328
|
+
.int()
|
|
329
|
+
.positive()
|
|
330
|
+
.default(6 * 60 * 60 * 1000),
|
|
331
|
+
spansDays: z.number().positive().default(30),
|
|
332
|
+
consolidatorRunsDays: z.number().positive().default(90),
|
|
333
|
+
dlqExhaustedDays: z.number().positive().default(30),
|
|
334
|
+
idempotency: z.boolean().default(true),
|
|
335
|
+
sessionsDays: z.number().positive().optional(),
|
|
336
|
+
sessionsClosedOnly: z.boolean().default(true),
|
|
337
|
+
memoryHistoryDays: z.number().positive().optional(),
|
|
338
|
+
workflowThreadsDays: z.number().positive().optional(),
|
|
339
|
+
auditDays: z.number().positive().optional(),
|
|
340
|
+
})
|
|
341
|
+
.strict()
|
|
342
|
+
.default({});
|
|
343
|
+
|
|
344
|
+
const auditSchema = z
|
|
345
|
+
.object({
|
|
346
|
+
enabled: z.boolean().default(false),
|
|
347
|
+
path: z.string().optional(),
|
|
348
|
+
passphraseRef: z.string().optional(),
|
|
349
|
+
cipher: z.string().optional(),
|
|
350
|
+
// W-051: tools/MCP audit-bus bridge volume policy.
|
|
351
|
+
toolEvents: z.enum(['security', 'all', 'off']).default('security'),
|
|
352
|
+
})
|
|
353
|
+
.strict()
|
|
354
|
+
.default({});
|
|
355
|
+
|
|
356
|
+
const secretsSchema = z
|
|
357
|
+
.object({
|
|
358
|
+
source: z.enum(['auto', 'keyring', 'encrypted-file', 'env']).default('auto'),
|
|
359
|
+
strict: z.boolean().default(false),
|
|
360
|
+
})
|
|
361
|
+
.strict()
|
|
362
|
+
.default({});
|
|
363
|
+
|
|
364
|
+
const authSchema = z
|
|
365
|
+
.object({
|
|
366
|
+
kind: z.enum(['token', 'none']).default('token'),
|
|
367
|
+
pepperRef: z.string().optional(),
|
|
368
|
+
tokenPrefix: z.string().default('gph'),
|
|
369
|
+
tokenEnvironments: z.array(z.string()).default(['live', 'test', 'local']),
|
|
370
|
+
perIpFailureThreshold: z.number().int().positive().optional(),
|
|
371
|
+
perIpLockoutMs: z.number().int().positive().optional(),
|
|
372
|
+
})
|
|
373
|
+
.strict()
|
|
374
|
+
.default({});
|
|
375
|
+
|
|
376
|
+
const observabilitySchema = z
|
|
377
|
+
.object({
|
|
378
|
+
logger: z.enum(['json', 'pretty', 'silent']).default('json'),
|
|
379
|
+
})
|
|
380
|
+
.strict()
|
|
381
|
+
.default({});
|
|
382
|
+
|
|
383
|
+
const hardeningSchema = z
|
|
384
|
+
.object({
|
|
385
|
+
applyOnStart: z.boolean().default(true),
|
|
386
|
+
refuseRoot: z.boolean().default(true),
|
|
387
|
+
umask: z.number().int().nonnegative().default(0o077),
|
|
388
|
+
})
|
|
389
|
+
.strict()
|
|
390
|
+
.default({});
|
|
391
|
+
|
|
392
|
+
const metricsSchema = z
|
|
393
|
+
.object({
|
|
394
|
+
enabled: z.boolean().default(true),
|
|
395
|
+
path: z.string().default('/metrics'),
|
|
396
|
+
requireAuth: z.boolean().default(false),
|
|
397
|
+
})
|
|
398
|
+
.strict()
|
|
399
|
+
.default({});
|
|
400
|
+
|
|
401
|
+
const healthSchema = z
|
|
402
|
+
.object({
|
|
403
|
+
walWarnThresholdBytes: z
|
|
404
|
+
.number()
|
|
405
|
+
.int()
|
|
406
|
+
.positive()
|
|
407
|
+
.default(50 * 1024 * 1024),
|
|
408
|
+
})
|
|
409
|
+
.strict()
|
|
410
|
+
.default({});
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Zod schema for the resolved {@link ServerConfigSpec}. Exposed for
|
|
414
|
+
* advanced users that want to validate other config sources (env-only
|
|
415
|
+
* launch, CLI overrides, etc.).
|
|
416
|
+
*
|
|
417
|
+
* @stable
|
|
418
|
+
*/
|
|
419
|
+
export const ServerConfigSchema = z
|
|
420
|
+
.object({
|
|
421
|
+
server: serverSchema,
|
|
422
|
+
storage: storageSchema,
|
|
423
|
+
retention: retentionSchema,
|
|
424
|
+
audit: auditSchema,
|
|
425
|
+
secrets: secretsSchema,
|
|
426
|
+
auth: authSchema,
|
|
427
|
+
observability: observabilitySchema,
|
|
428
|
+
hardening: hardeningSchema,
|
|
429
|
+
metrics: metricsSchema,
|
|
430
|
+
health: healthSchema,
|
|
431
|
+
})
|
|
432
|
+
.strict()
|
|
433
|
+
.default({});
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Input shape accepted by {@link defineConfig}. Every field is
|
|
437
|
+
* optional; missing values fall back to a documented default.
|
|
438
|
+
*
|
|
439
|
+
* @stable
|
|
440
|
+
*/
|
|
441
|
+
export type ServerConfigInput = z.input<typeof ServerConfigSchema>;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Helper for `graphorin.config.ts` files. Pure pass-through that
|
|
445
|
+
* provides editor autocomplete; the actual parsing happens at server
|
|
446
|
+
* startup so callers always see the same error path regardless of
|
|
447
|
+
* which loader (TS / JS / JSON) the operator picked.
|
|
448
|
+
*
|
|
449
|
+
* @stable
|
|
450
|
+
*/
|
|
451
|
+
export function defineConfig(input: ServerConfigInput): ServerConfigInput {
|
|
452
|
+
return input;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Parse + validate user input. Returns a strongly-typed
|
|
457
|
+
* {@link ServerConfigSpec}; throws {@link ConfigInvalidError} on
|
|
458
|
+
* any invalid field with a flattened issue list.
|
|
459
|
+
*
|
|
460
|
+
* @stable
|
|
461
|
+
*/
|
|
462
|
+
export function parseServerConfig(input: unknown): ServerConfigSpec {
|
|
463
|
+
const result = ServerConfigSchema.safeParse(input ?? {});
|
|
464
|
+
if (!result.success) {
|
|
465
|
+
const issues = result.error.issues.map((issue) => ({
|
|
466
|
+
path: issue.path,
|
|
467
|
+
message: issue.message,
|
|
468
|
+
}));
|
|
469
|
+
throw new ConfigInvalidError(issues, result.error);
|
|
470
|
+
}
|
|
471
|
+
return result.data as unknown as ServerConfigSpec;
|
|
472
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle wrapper around an externally-built `Consolidator`. Phase
|
|
3
|
+
* 14c wires the runtime into `beforeStart` / `beforeShutdown` so the
|
|
4
|
+
* background pipeline starts and stops cleanly with the daemon. The
|
|
5
|
+
* adapter does not own the consolidator construction - operators
|
|
6
|
+
* supply the instance through the `createServer({ consolidator })`
|
|
7
|
+
* option so the framework stays decoupled from the heavy
|
|
8
|
+
* `@graphorin/memory` package.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Structurally-typed view of the `@graphorin/memory` Consolidator
|
|
15
|
+
* surface. Importing the full type would force a hard dependency on
|
|
16
|
+
* `@graphorin/memory`; the structural subset captured here is enough
|
|
17
|
+
* for the lifecycle integration + the `/v1/health` aggregator.
|
|
18
|
+
*
|
|
19
|
+
* @stable
|
|
20
|
+
*/
|
|
21
|
+
export interface ConsolidatorLike {
|
|
22
|
+
start(): Promise<void>;
|
|
23
|
+
stop(): Promise<void>;
|
|
24
|
+
status(): Promise<ConsolidatorStatusLike>;
|
|
25
|
+
setTier?(tier: string): Promise<void>;
|
|
26
|
+
pause?(): Promise<void>;
|
|
27
|
+
resume?(): Promise<void>;
|
|
28
|
+
drainDlq?(): Promise<number>;
|
|
29
|
+
/**
|
|
30
|
+
* Register the consolidator's cron / idle triggers with the server's
|
|
31
|
+
* triggers scheduler so background consolidation actually fires (MCON-4).
|
|
32
|
+
* Called from `beforeStart` when both a consolidator and a triggers daemon
|
|
33
|
+
* are configured. Optional so a consolidator without a `defaultScope` (or a
|
|
34
|
+
* custom surface) simply opts out.
|
|
35
|
+
*/
|
|
36
|
+
registerWithScheduler?(scheduler: import('@graphorin/triggers').Scheduler): Promise<unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Subset of `ConsolidatorStatus` the server health endpoint and the
|
|
41
|
+
* Prometheus metrics consume. The full struct lives in
|
|
42
|
+
* `@graphorin/memory/consolidator`.
|
|
43
|
+
*
|
|
44
|
+
* @stable
|
|
45
|
+
*/
|
|
46
|
+
export interface ConsolidatorStatusLike {
|
|
47
|
+
readonly tier: string;
|
|
48
|
+
readonly running: boolean;
|
|
49
|
+
readonly paused: boolean;
|
|
50
|
+
readonly queueDepth: number;
|
|
51
|
+
readonly dlqSize: number;
|
|
52
|
+
readonly deferredRuns: number;
|
|
53
|
+
readonly emptyExtractions: number;
|
|
54
|
+
readonly budget: {
|
|
55
|
+
readonly tokensUsedToday: number;
|
|
56
|
+
readonly costUsedToday: number;
|
|
57
|
+
readonly tokensRemaining: number;
|
|
58
|
+
readonly costRemaining: number;
|
|
59
|
+
readonly resetAt: string;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @stable
|
|
65
|
+
*/
|
|
66
|
+
export interface CreateConsolidatorDaemonOptions {
|
|
67
|
+
readonly consolidator: ConsolidatorLike;
|
|
68
|
+
/** Hard timeout on `consolidator.stop()`. Defaults to 10 s. */
|
|
69
|
+
readonly stopTimeoutMs?: number;
|
|
70
|
+
readonly warn?: (message: string) => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @stable
|
|
75
|
+
*/
|
|
76
|
+
export interface ConsolidatorDaemon {
|
|
77
|
+
start(): Promise<void>;
|
|
78
|
+
stop(): Promise<void>;
|
|
79
|
+
status(): Promise<ConsolidatorStatusLike>;
|
|
80
|
+
readonly consolidator: ConsolidatorLike;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @stable
|
|
85
|
+
*/
|
|
86
|
+
export function createConsolidatorDaemon(
|
|
87
|
+
options: CreateConsolidatorDaemonOptions,
|
|
88
|
+
): ConsolidatorDaemon {
|
|
89
|
+
const stopTimeoutMs = options.stopTimeoutMs ?? 10_000;
|
|
90
|
+
const warn = options.warn ?? ((m: string) => process.stderr.write(`${m}\n`));
|
|
91
|
+
let started = false;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
get consolidator() {
|
|
95
|
+
return options.consolidator;
|
|
96
|
+
},
|
|
97
|
+
async start() {
|
|
98
|
+
if (started) return;
|
|
99
|
+
started = true;
|
|
100
|
+
await options.consolidator.start();
|
|
101
|
+
},
|
|
102
|
+
async stop() {
|
|
103
|
+
if (!started) return;
|
|
104
|
+
started = false;
|
|
105
|
+
try {
|
|
106
|
+
await withTimeout(options.consolidator.stop(), stopTimeoutMs);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
warn(
|
|
109
|
+
`[graphorin/server] consolidator daemon: stop() exceeded ${stopTimeoutMs}ms; force-aborting (${describeError(err)}).`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
async status() {
|
|
114
|
+
return options.consolidator.status();
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
|
|
120
|
+
return new Promise<T>((resolve, reject) => {
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
reject(new Error(`timeout after ${ms}ms`));
|
|
123
|
+
}, ms);
|
|
124
|
+
p.then(
|
|
125
|
+
(value) => {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
resolve(value);
|
|
128
|
+
},
|
|
129
|
+
(err) => {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
reject(err);
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function describeError(err: unknown): string {
|
|
138
|
+
if (err === null || err === undefined) return 'unknown';
|
|
139
|
+
if (err instanceof Error) return err.message;
|
|
140
|
+
return String(err);
|
|
141
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@graphorin/server/consolidator` - Phase 14c lifecycle adapter for
|
|
3
|
+
* the consolidator runtime.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
type ConsolidatorDaemon,
|
|
10
|
+
type ConsolidatorLike,
|
|
11
|
+
type ConsolidatorStatusLike,
|
|
12
|
+
type CreateConsolidatorDaemonOptions,
|
|
13
|
+
createConsolidatorDaemon,
|
|
14
|
+
} from './daemon.js';
|