@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.
- package/CHANGELOG.md +85 -0
- package/README.md +6 -6
- 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 +40 -611
- 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 +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -26
- 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 +6 -0
- package/dist/package.js.map +1 -0
- 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.d.ts.map +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 +26 -19
- package/dist/ws/upgrade.js.map +1 -1
- package/package.json +31 -30
- 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
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stateless delivery-layer commentary-phase trace sanitizer. Walks
|
|
3
|
+
* outbound `ServerMessage` event frames and applies the configured
|
|
4
|
+
* pattern catalogue to the JSON-stringified payload; matched
|
|
5
|
+
* fragments are wrapped in the configured envelope (default) or
|
|
6
|
+
* stripped (per the `'strip'` policy) or passed through (per the
|
|
7
|
+
* `'pass-through'` policy - operator opt-out for trusted
|
|
8
|
+
* deployments).
|
|
9
|
+
*
|
|
10
|
+
* The sanitizer runs AFTER the `ServerMessage` Zod-validation pass
|
|
11
|
+
* in `@graphorin/protocol` and BEFORE the message hits the wire (so
|
|
12
|
+
* the protocol schema is unchanged; the sanitization is a pure
|
|
13
|
+
* transform applied to validated payloads). It is bytes-equal across
|
|
14
|
+
* the WS / SSE / REST transports - the same input + same policy
|
|
15
|
+
* produces the same output regardless of which transport invoked
|
|
16
|
+
* the sanitizer.
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { createHash } from 'node:crypto';
|
|
22
|
+
import type { ServerEventFrame } from '@graphorin/protocol';
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_APPLY_TO_EVENTS,
|
|
26
|
+
DEFAULT_DELIVERY_COMMENTARY_PATTERNS,
|
|
27
|
+
} from './built-in-patterns.js';
|
|
28
|
+
import type {
|
|
29
|
+
DeliveryCommentaryConfig,
|
|
30
|
+
DeliveryCommentaryDecision,
|
|
31
|
+
DeliveryCommentaryPattern,
|
|
32
|
+
DeliveryCommentaryPolicy,
|
|
33
|
+
DeliveryCommentaryReason,
|
|
34
|
+
DeliveryCommentarySink,
|
|
35
|
+
DeliveryCommentaryTransport,
|
|
36
|
+
} from './types.js';
|
|
37
|
+
|
|
38
|
+
const DEFAULT_OPEN = '<<<commentary>>>';
|
|
39
|
+
const DEFAULT_CLOSE = '<<</commentary>>>';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Public surface returned by {@link createDeliveryCommentarySanitizer}.
|
|
43
|
+
*
|
|
44
|
+
* @stable
|
|
45
|
+
*/
|
|
46
|
+
export interface DeliveryCommentarySanitizer {
|
|
47
|
+
readonly policy: DeliveryCommentaryPolicy;
|
|
48
|
+
readonly applyToEvents: ReadonlyArray<string>;
|
|
49
|
+
readonly patterns: ReadonlyArray<DeliveryCommentaryPattern>;
|
|
50
|
+
/**
|
|
51
|
+
* Sanitize the payload of a single `event` frame. Returns the
|
|
52
|
+
* (possibly-replaced) frame; emits an audit decision via the
|
|
53
|
+
* configured sink when the sanitizer mutated the payload.
|
|
54
|
+
*
|
|
55
|
+
* The frame is returned unchanged when:
|
|
56
|
+
* - the policy is `'pass-through'`,
|
|
57
|
+
* - the event type is not in `applyToEvents`,
|
|
58
|
+
* - or no pattern matched the JSON-stringified payload.
|
|
59
|
+
*/
|
|
60
|
+
sanitize(frame: ServerEventFrame, transport: DeliveryCommentaryTransport): ServerEventFrame;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build a stateless delivery-layer sanitizer. Tests can swap the
|
|
65
|
+
* `sink` for an in-memory recorder; production wires the
|
|
66
|
+
* `@graphorin/security/audit` `appendAudit` helper.
|
|
67
|
+
*
|
|
68
|
+
* @stable
|
|
69
|
+
*/
|
|
70
|
+
export function createDeliveryCommentarySanitizer(
|
|
71
|
+
config: DeliveryCommentaryConfig = {},
|
|
72
|
+
): DeliveryCommentarySanitizer {
|
|
73
|
+
const policy: DeliveryCommentaryPolicy = config.policy ?? 'wrap';
|
|
74
|
+
const applyToEvents: ReadonlyArray<string> = config.applyToEvents ?? DEFAULT_APPLY_TO_EVENTS;
|
|
75
|
+
const applyToSet = new Set(applyToEvents);
|
|
76
|
+
const patterns: ReadonlyArray<DeliveryCommentaryPattern> =
|
|
77
|
+
config.patterns ?? DEFAULT_DELIVERY_COMMENTARY_PATTERNS;
|
|
78
|
+
const open = config.wrapOpen ?? DEFAULT_OPEN;
|
|
79
|
+
const close = config.wrapClose ?? DEFAULT_CLOSE;
|
|
80
|
+
const sink: DeliveryCommentarySink | undefined = config.sink;
|
|
81
|
+
|
|
82
|
+
function sanitize(
|
|
83
|
+
frame: ServerEventFrame,
|
|
84
|
+
transport: DeliveryCommentaryTransport,
|
|
85
|
+
): ServerEventFrame {
|
|
86
|
+
if (policy === 'pass-through') return frame;
|
|
87
|
+
if (!applyToSet.has(frame.type)) return frame;
|
|
88
|
+
const before = JSON.stringify(frame.payload ?? null);
|
|
89
|
+
const allReasons = new Set<DeliveryCommentaryReason>();
|
|
90
|
+
let mutated = false;
|
|
91
|
+
const next = walkAndSanitize(frame.payload, (text) => {
|
|
92
|
+
const out = applyToText(text, patterns, policy, open, close);
|
|
93
|
+
for (const reason of out.reasons) allReasons.add(reason);
|
|
94
|
+
if (out.transformed !== text) mutated = true;
|
|
95
|
+
return out.transformed;
|
|
96
|
+
});
|
|
97
|
+
if (!mutated) return frame;
|
|
98
|
+
const after = JSON.stringify(next ?? null);
|
|
99
|
+
const decision: DeliveryCommentaryDecision = {
|
|
100
|
+
transport,
|
|
101
|
+
boundary: 'event-emission',
|
|
102
|
+
policy,
|
|
103
|
+
applied: true,
|
|
104
|
+
reasons: [...allReasons],
|
|
105
|
+
matchedPattern: [...allReasons][0],
|
|
106
|
+
sha256OfBefore: sha256(before),
|
|
107
|
+
sha256OfAfter: sha256(after),
|
|
108
|
+
eventType: frame.type,
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
sink?.onDecision(decision);
|
|
112
|
+
} catch {
|
|
113
|
+
// Sink errors must never block the wire - swallow + continue.
|
|
114
|
+
}
|
|
115
|
+
return Object.freeze({ ...frame, payload: next });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return Object.freeze({
|
|
119
|
+
policy,
|
|
120
|
+
applyToEvents,
|
|
121
|
+
patterns,
|
|
122
|
+
sanitize,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface ApplyResult {
|
|
127
|
+
readonly transformed: string;
|
|
128
|
+
readonly reasons: ReadonlyArray<DeliveryCommentaryReason>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Recursively walks any JSON-shaped value (`null`, primitive, array,
|
|
133
|
+
* object) and applies the supplied transform to every string leaf.
|
|
134
|
+
* Returns a structurally-cloned copy when at least one leaf was
|
|
135
|
+
* rewritten; returns the original input by reference otherwise.
|
|
136
|
+
*/
|
|
137
|
+
function walkAndSanitize(value: unknown, transform: (s: string) => string): unknown {
|
|
138
|
+
if (typeof value === 'string') {
|
|
139
|
+
const next = transform(value);
|
|
140
|
+
return next === value ? value : next;
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
let mutated = false;
|
|
144
|
+
const next: unknown[] = new Array(value.length);
|
|
145
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
146
|
+
const child = walkAndSanitize(value[i], transform);
|
|
147
|
+
if (child !== value[i]) mutated = true;
|
|
148
|
+
next[i] = child;
|
|
149
|
+
}
|
|
150
|
+
return mutated ? next : value;
|
|
151
|
+
}
|
|
152
|
+
if (value !== null && typeof value === 'object') {
|
|
153
|
+
let mutated = false;
|
|
154
|
+
const next: Record<string, unknown> = {};
|
|
155
|
+
for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
|
|
156
|
+
const child = walkAndSanitize(val, transform);
|
|
157
|
+
if (child !== val) mutated = true;
|
|
158
|
+
next[key] = child;
|
|
159
|
+
}
|
|
160
|
+
return mutated ? next : value;
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function applyToText(
|
|
166
|
+
text: string,
|
|
167
|
+
patterns: ReadonlyArray<DeliveryCommentaryPattern>,
|
|
168
|
+
policy: DeliveryCommentaryPolicy,
|
|
169
|
+
open: string,
|
|
170
|
+
close: string,
|
|
171
|
+
): ApplyResult {
|
|
172
|
+
// Split the body into already-wrapped + plain segments so we never
|
|
173
|
+
// re-scan inside an existing wrap envelope. This makes the sanitizer
|
|
174
|
+
// idempotent on the same payload (a second pass produces bytes-equal
|
|
175
|
+
// output, satisfying the cross-cut composition contract with
|
|
176
|
+
// Phase 11 session-output sanitization + Phase 11 replay
|
|
177
|
+
// sanitization).
|
|
178
|
+
const segments = splitByWrapEnvelope(text, open, close);
|
|
179
|
+
const matchedReasons = new Set<DeliveryCommentaryReason>();
|
|
180
|
+
const next: string[] = [];
|
|
181
|
+
for (const segment of segments) {
|
|
182
|
+
if (segment.kind === 'wrapped') {
|
|
183
|
+
next.push(segment.text);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
let segText = segment.text;
|
|
187
|
+
for (const pattern of patterns) {
|
|
188
|
+
const regex = freshRegex(pattern.regex);
|
|
189
|
+
let didMatch = false;
|
|
190
|
+
segText = segText.replace(regex, (match) => {
|
|
191
|
+
didMatch = true;
|
|
192
|
+
if (policy === 'strip') return '';
|
|
193
|
+
return `${open}${match}${close}`;
|
|
194
|
+
});
|
|
195
|
+
if (didMatch) matchedReasons.add(pattern.reason);
|
|
196
|
+
}
|
|
197
|
+
next.push(segText);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
transformed: next.join(''),
|
|
201
|
+
reasons: [...matchedReasons],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function freshRegex(re: RegExp): RegExp {
|
|
206
|
+
return new RegExp(re.source, re.flags);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function splitByWrapEnvelope(
|
|
210
|
+
text: string,
|
|
211
|
+
open: string,
|
|
212
|
+
close: string,
|
|
213
|
+
): ReadonlyArray<{ readonly kind: 'plain' | 'wrapped'; readonly text: string }> {
|
|
214
|
+
const out: { kind: 'plain' | 'wrapped'; text: string }[] = [];
|
|
215
|
+
let cursor = 0;
|
|
216
|
+
while (cursor < text.length) {
|
|
217
|
+
const openAt = text.indexOf(open, cursor);
|
|
218
|
+
if (openAt < 0) {
|
|
219
|
+
out.push({ kind: 'plain', text: text.slice(cursor) });
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
if (openAt > cursor) {
|
|
223
|
+
out.push({ kind: 'plain', text: text.slice(cursor, openAt) });
|
|
224
|
+
}
|
|
225
|
+
const closeAt = text.indexOf(close, openAt + open.length);
|
|
226
|
+
if (closeAt < 0) {
|
|
227
|
+
out.push({ kind: 'plain', text: text.slice(openAt) });
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
out.push({ kind: 'wrapped', text: text.slice(openAt, closeAt + close.length) });
|
|
231
|
+
cursor = closeAt + close.length;
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function sha256(value: string): string {
|
|
237
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
238
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the delivery-layer commentary-phase trace sanitization
|
|
3
|
+
* applied by the WebSocket dispatcher (`@graphorin/server/ws`) and
|
|
4
|
+
* the SSE event-emission boundary (`@graphorin/server/sse`).
|
|
5
|
+
*
|
|
6
|
+
* The catalogue + per-policy enum + audit row shape is intentionally
|
|
7
|
+
* structural so the server's delivery layer stays self-contained. A
|
|
8
|
+
* deployment that wants to share patterns with the session-output
|
|
9
|
+
* boundary in `@graphorin/sessions` can pass that package's
|
|
10
|
+
* `BUILT_IN_COMMENTARY_PATTERNS` through {@link DeliveryCommentaryConfig}.patterns;
|
|
11
|
+
* the shape is bytes-equal across the two layers (the
|
|
12
|
+
* defense-in-depth posture is the load-bearing property - see
|
|
13
|
+
* Phase 11 sanitizer for the storage-write boundary, this module
|
|
14
|
+
* for the wire-emission boundary).
|
|
15
|
+
*
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Operator-facing policy. Identical semantics to the session-output
|
|
21
|
+
* sanitizer in `@graphorin/sessions/commentary` so the two layers
|
|
22
|
+
* are bytes-equal on idempotent re-application.
|
|
23
|
+
*
|
|
24
|
+
* - `'wrap'` (default) - wraps the matched fragment in a
|
|
25
|
+
* `<<<commentary>>>...<<</commentary>>>` envelope so downstream
|
|
26
|
+
* consumers can choose to render or hide based on context.
|
|
27
|
+
* - `'strip'` - removes the matched fragment entirely.
|
|
28
|
+
* - `'pass-through'` - disables the sanitization (operator opt-in
|
|
29
|
+
* for trusted deployments).
|
|
30
|
+
*
|
|
31
|
+
* @stable
|
|
32
|
+
*/
|
|
33
|
+
export type DeliveryCommentaryPolicy = 'wrap' | 'strip' | 'pass-through';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Discriminator surfaced on audit rows + counter labels for the
|
|
37
|
+
* transport that produced the sanitization decision. The cardinality
|
|
38
|
+
* is bounded (3-valued).
|
|
39
|
+
*
|
|
40
|
+
* @stable
|
|
41
|
+
*/
|
|
42
|
+
export type DeliveryCommentaryTransport = 'ws' | 'sse' | 'rest';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Stable label for each detection pattern. Surfaced in the audit row
|
|
46
|
+
* + the counter label cardinality is bounded.
|
|
47
|
+
*
|
|
48
|
+
* @stable
|
|
49
|
+
*/
|
|
50
|
+
export type DeliveryCommentaryReason =
|
|
51
|
+
| 'tool.call.start-payload-signature'
|
|
52
|
+
| 'tool.call.delta-payload-signature'
|
|
53
|
+
| 'tool.call.end-payload-signature'
|
|
54
|
+
| 'tool.execute.end-payload-signature'
|
|
55
|
+
| 'agent.fanout-event-signature'
|
|
56
|
+
| 'context.compacted-event-signature'
|
|
57
|
+
| 'agent.model.fellback-event-signature';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Single pattern entry in the {@link DEFAULT_DELIVERY_COMMENTARY_PATTERNS}
|
|
61
|
+
* catalogue. The `regex` is matched against the JSON payload of the
|
|
62
|
+
* `event` frame (after `JSON.stringify(payload)`); deployments that
|
|
63
|
+
* want to match against the wire-format string instead can supply
|
|
64
|
+
* their own catalogue.
|
|
65
|
+
*
|
|
66
|
+
* @stable
|
|
67
|
+
*/
|
|
68
|
+
export interface DeliveryCommentaryPattern {
|
|
69
|
+
readonly reason: DeliveryCommentaryReason;
|
|
70
|
+
readonly regex: RegExp;
|
|
71
|
+
readonly description: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Per-emission decision recorded by the sanitizer. Mirrored on the
|
|
76
|
+
* audit row + the counter increment.
|
|
77
|
+
*
|
|
78
|
+
* @stable
|
|
79
|
+
*/
|
|
80
|
+
export interface DeliveryCommentaryDecision {
|
|
81
|
+
readonly transport: DeliveryCommentaryTransport;
|
|
82
|
+
readonly boundary: 'event-emission';
|
|
83
|
+
readonly policy: DeliveryCommentaryPolicy;
|
|
84
|
+
readonly applied: boolean;
|
|
85
|
+
readonly reasons: ReadonlyArray<DeliveryCommentaryReason>;
|
|
86
|
+
readonly matchedPattern: string | undefined;
|
|
87
|
+
readonly sha256OfBefore: string;
|
|
88
|
+
readonly sha256OfAfter: string;
|
|
89
|
+
readonly eventType: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Single audit + counter sink consumed by the sanitizer when a
|
|
94
|
+
* decision fires. Wiring is optional - operators that do not need
|
|
95
|
+
* audit telemetry can skip the sink and the sanitizer becomes a
|
|
96
|
+
* pure transform.
|
|
97
|
+
*
|
|
98
|
+
* @stable
|
|
99
|
+
*/
|
|
100
|
+
export interface DeliveryCommentarySink {
|
|
101
|
+
/**
|
|
102
|
+
* Called once per applied decision. Implementations should be
|
|
103
|
+
* non-throwing; the sanitizer wraps the call in `try/catch` so a
|
|
104
|
+
* misbehaving sink never blocks the wire.
|
|
105
|
+
*/
|
|
106
|
+
onDecision(decision: DeliveryCommentaryDecision): void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Public configuration accepted by the WS / SSE / REST event-
|
|
111
|
+
* emission sanitizer. Shape mirrors the per-server
|
|
112
|
+
* `WsConfig.commentarySanitization` field documented in the runtime
|
|
113
|
+
* spec.
|
|
114
|
+
*
|
|
115
|
+
* @stable
|
|
116
|
+
*/
|
|
117
|
+
export interface DeliveryCommentaryConfig {
|
|
118
|
+
readonly policy?: DeliveryCommentaryPolicy;
|
|
119
|
+
/**
|
|
120
|
+
* Whitelist of `event.type` literals to sanitize. The default
|
|
121
|
+
* covers the user-visible commentary surface (`text.delta` plus
|
|
122
|
+
* the two tool-result variants); operators extend the list as
|
|
123
|
+
* their UI rendering boundary expands.
|
|
124
|
+
*/
|
|
125
|
+
readonly applyToEvents?: ReadonlyArray<string>;
|
|
126
|
+
readonly patterns?: ReadonlyArray<DeliveryCommentaryPattern>;
|
|
127
|
+
readonly wrapOpen?: string;
|
|
128
|
+
readonly wrapClose?: string;
|
|
129
|
+
readonly sink?: DeliveryCommentarySink;
|
|
130
|
+
}
|