@noodleseed/one 0.12.0 → 0.14.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 (84) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +5 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/analytics-ops.d.ts +3 -0
  5. package/dist/commands/analytics-ops.d.ts.map +1 -0
  6. package/dist/commands/analytics-ops.js +250 -0
  7. package/dist/commands/analytics-ops.js.map +1 -0
  8. package/dist/commands/shared.d.ts.map +1 -1
  9. package/dist/commands/shared.js +9 -0
  10. package/dist/commands/shared.js.map +1 -1
  11. package/dist/metrics-render.d.ts +99 -0
  12. package/dist/metrics-render.d.ts.map +1 -0
  13. package/dist/metrics-render.js +181 -0
  14. package/dist/metrics-render.js.map +1 -0
  15. package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.d.ts.map +1 -1
  16. package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +10 -0
  17. package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js.map +1 -1
  18. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.d.ts.map +1 -1
  19. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +2 -0
  20. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js.map +1 -1
  21. package/node_modules/@noodle-borg/agent-kit/dist/skill-content.d.ts.map +1 -1
  22. package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js +4 -0
  23. package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js.map +1 -1
  24. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  25. package/node_modules/@noodle-borg/module/dist/contract.d.ts +61 -0
  26. package/node_modules/@noodle-borg/module/dist/contract.d.ts.map +1 -1
  27. package/node_modules/@noodle-borg/module/dist/contract.js +1 -0
  28. package/node_modules/@noodle-borg/module/dist/contract.js.map +1 -1
  29. package/node_modules/@noodle-borg/protocol/dist/index.d.ts +1 -1
  30. package/node_modules/@noodle-borg/protocol/dist/index.d.ts.map +1 -1
  31. package/node_modules/@noodle-borg/protocol/dist/index.js +1 -1
  32. package/node_modules/@noodle-borg/protocol/dist/index.js.map +1 -1
  33. package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts +19 -0
  34. package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts.map +1 -1
  35. package/node_modules/@noodle-borg/protocol/dist/sdk-server.js +74 -16
  36. package/node_modules/@noodle-borg/protocol/dist/sdk-server.js.map +1 -1
  37. package/node_modules/@noodle-borg/service/dist/index.d.ts +4 -0
  38. package/node_modules/@noodle-borg/service/dist/index.d.ts.map +1 -1
  39. package/node_modules/@noodle-borg/service/dist/index.js +4 -0
  40. package/node_modules/@noodle-borg/service/dist/index.js.map +1 -1
  41. package/node_modules/@noodle-borg/service/dist/options.d.ts +17 -0
  42. package/node_modules/@noodle-borg/service/dist/options.d.ts.map +1 -1
  43. package/node_modules/@noodle-borg/service/dist/request-event-metrics.d.ts +63 -0
  44. package/node_modules/@noodle-borg/service/dist/request-event-metrics.d.ts.map +1 -0
  45. package/node_modules/@noodle-borg/service/dist/request-event-metrics.js +103 -0
  46. package/node_modules/@noodle-borg/service/dist/request-event-metrics.js.map +1 -0
  47. package/node_modules/@noodle-borg/service/dist/routes/analytics.d.ts +8 -0
  48. package/node_modules/@noodle-borg/service/dist/routes/analytics.d.ts.map +1 -0
  49. package/node_modules/@noodle-borg/service/dist/routes/analytics.js +109 -0
  50. package/node_modules/@noodle-borg/service/dist/routes/analytics.js.map +1 -0
  51. package/node_modules/@noodle-borg/service/dist/routes/paths.d.ts +8 -1
  52. package/node_modules/@noodle-borg/service/dist/routes/paths.d.ts.map +1 -1
  53. package/node_modules/@noodle-borg/service/dist/routes/paths.js +25 -0
  54. package/node_modules/@noodle-borg/service/dist/routes/paths.js.map +1 -1
  55. package/node_modules/@noodle-borg/service/dist/serve.d.ts.map +1 -1
  56. package/node_modules/@noodle-borg/service/dist/serve.js +27 -0
  57. package/node_modules/@noodle-borg/service/dist/serve.js.map +1 -1
  58. package/node_modules/@noodle-borg/service/dist/service.d.ts.map +1 -1
  59. package/node_modules/@noodle-borg/service/dist/service.js +51 -1
  60. package/node_modules/@noodle-borg/service/dist/service.js.map +1 -1
  61. package/node_modules/@noodle-borg/service/dist/store/postgres-schema.d.ts.map +1 -1
  62. package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js +2 -0
  63. package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js.map +1 -1
  64. package/node_modules/@noodle-borg/service/dist/store/request-event-buffer.d.ts +23 -0
  65. package/node_modules/@noodle-borg/service/dist/store/request-event-buffer.d.ts.map +1 -0
  66. package/node_modules/@noodle-borg/service/dist/store/request-event-buffer.js +62 -0
  67. package/node_modules/@noodle-borg/service/dist/store/request-event-buffer.js.map +1 -0
  68. package/node_modules/@noodle-borg/service/dist/store/request-events-postgres.d.ts +27 -0
  69. package/node_modules/@noodle-borg/service/dist/store/request-events-postgres.d.ts.map +1 -0
  70. package/node_modules/@noodle-borg/service/dist/store/request-events-postgres.js +188 -0
  71. package/node_modules/@noodle-borg/service/dist/store/request-events-postgres.js.map +1 -0
  72. package/node_modules/@noodle-borg/service/dist/store/request-events.d.ts +14 -0
  73. package/node_modules/@noodle-borg/service/dist/store/request-events.d.ts.map +1 -0
  74. package/node_modules/@noodle-borg/service/dist/store/request-events.js +85 -0
  75. package/node_modules/@noodle-borg/service/dist/store/request-events.js.map +1 -0
  76. package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +8 -1
  77. package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts.map +1 -1
  78. package/node_modules/@noodle-borg/transport-http/dist/handler.js +16 -32
  79. package/node_modules/@noodle-borg/transport-http/dist/handler.js.map +1 -1
  80. package/node_modules/@noodle-borg/transport-http/dist/request-capture.d.ts +49 -0
  81. package/node_modules/@noodle-borg/transport-http/dist/request-capture.d.ts.map +1 -0
  82. package/node_modules/@noodle-borg/transport-http/dist/request-capture.js +116 -0
  83. package/node_modules/@noodle-borg/transport-http/dist/request-capture.js.map +1 -0
  84. package/package.json +2 -2
@@ -0,0 +1,62 @@
1
+ const DEFAULT_CAP = 1024;
2
+ /**
3
+ * Bounded async write-behind buffer between the transport's synchronous capture hook and a durable
4
+ * {@link RequestEventSink} (ADR 0121). `capture()` is enqueue-only — it never awaits a store write, so
5
+ * the MCP response path pays no durable-write latency. Under backpressure the **newest** events drop
6
+ * (`droppedTotal` counts them); store failures are swallowed and counted (`failedTotal`); `close()`
7
+ * drains what is queued. Analytics is strictly best-effort by contract.
8
+ */
9
+ export class RequestEventBuffer {
10
+ #sink;
11
+ #cap;
12
+ #queue = [];
13
+ #draining = Promise.resolve();
14
+ #inFlight = false;
15
+ #closed = false;
16
+ #dropped = 0;
17
+ #failed = 0;
18
+ constructor(sink, options = {}) {
19
+ this.#sink = sink;
20
+ this.#cap = options.cap ?? DEFAULT_CAP;
21
+ }
22
+ /** Enqueue one event; drops (and counts) when closed or the queue is full. Never throws. */
23
+ capture(event) {
24
+ if (this.#closed || this.#queue.length >= this.#cap) {
25
+ this.#dropped += 1;
26
+ return;
27
+ }
28
+ this.#queue.push(event);
29
+ if (!this.#inFlight) {
30
+ this.#inFlight = true;
31
+ this.#draining = this.#drain();
32
+ }
33
+ }
34
+ async #drain() {
35
+ while (this.#queue.length > 0) {
36
+ const event = this.#queue.shift();
37
+ if (event === undefined)
38
+ break;
39
+ try {
40
+ await this.#sink.emit(event);
41
+ }
42
+ catch {
43
+ this.#failed += 1; // best-effort: a down store must not spill into request handling
44
+ }
45
+ }
46
+ this.#inFlight = false;
47
+ }
48
+ /** Events dropped because the buffer was full or closed. */
49
+ get droppedTotal() {
50
+ return this.#dropped;
51
+ }
52
+ /** Events whose durable write failed (swallowed). */
53
+ get failedTotal() {
54
+ return this.#failed;
55
+ }
56
+ /** Stop accepting events and wait for the queued ones to flush. */
57
+ async close() {
58
+ this.#closed = true;
59
+ await this.#draining;
60
+ }
61
+ }
62
+ //# sourceMappingURL=request-event-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-event-buffer.js","sourceRoot":"","sources":["../../src/store/request-event-buffer.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB;;;;;;GAMG;AACH,MAAM,OAAO,kBAAkB;IACpB,KAAK,CAAmB;IACxB,IAAI,CAAS;IACb,MAAM,GAAwB,EAAE,CAAC;IAC1C,SAAS,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7C,SAAS,GAAG,KAAK,CAAC;IAClB,OAAO,GAAG,KAAK,CAAC;IAChB,QAAQ,GAAG,CAAC,CAAC;IACb,OAAO,GAAG,CAAC,CAAC;IAEZ,YAAY,IAAsB,EAAE,UAA4B,EAAE;QAChE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC;IACzC,CAAC;IAED,4FAA4F;IAC5F,OAAO,CAAC,KAAwB;QAC9B,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACpD,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACjC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM;YAC/B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,iEAAiE;YACtF,CAAC;QACH,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC;IAED,4DAA4D;IAC5D,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,qDAAqD;IACrD,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,mEAAmE;IACnE,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ import { type RequestEvent, type RequestEventFilter, type RequestEventInput, type RequestEventStore } from '@noodle-borg/module';
2
+ import type { Pool } from 'pg';
3
+ /**
4
+ * Create the append-only `request_events` table if absent (idempotent; run once at startup). Like
5
+ * `audit_events`, it has **no foreign key to `environments`** — the analytics stream carries plain
6
+ * `org_slug`/`app_slug`/`environment` text so history survives tenant/app/env deletion and is pruned
7
+ * only by retention (ADR 0121). `seq` (bigserial) gives stable newest-first order and a future cursor;
8
+ * `details` is `jsonb` holding the already-redacted scalar bag.
9
+ */
10
+ export declare function ensureRequestEventSchema(pool: Pool): Promise<void>;
11
+ /**
12
+ * Relational system of record for the tenant-facing analytics stream (parallels
13
+ * {@link InMemoryRequestEventStore}). The `pg.Pool` is injected; pure SQL, unit-testable against any
14
+ * local Postgres (`DATABASE_URL_TEST`).
15
+ */
16
+ export declare class PostgresRequestEventStore implements RequestEventStore {
17
+ #private;
18
+ constructor(pool: Pool, options?: {
19
+ now?: () => Date;
20
+ });
21
+ ensureSchema(): Promise<void>;
22
+ emit(input: RequestEventInput): Promise<void>;
23
+ list(filter: RequestEventFilter): Promise<readonly RequestEvent[]>;
24
+ /** Delete events older than the retention window (Stage-A default retention; called by the sweeper). */
25
+ prune(olderThan: Date): Promise<number>;
26
+ }
27
+ //# sourceMappingURL=request-events-postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-events-postgres.d.ts","sourceRoot":"","sources":["../../src/store/request-events-postgres.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EAMvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAM/B;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CxE;AAiCD;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;;gBAIrD,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;KAAO;IAK1D,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0C7C,IAAI,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAmCxE,wGAAwG;IAClG,KAAK,CAAC,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;CAO9C"}
@@ -0,0 +1,188 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { REQUEST_EVENT_SCHEMA_VERSION, redactDetails, } from '@noodle-borg/module';
3
+ /** Reads are always bounded: the metrics scan is the largest sanctioned window. */
4
+ const MAX_LIST_LIMIT = 50_000;
5
+ const DEFAULT_LIST_LIMIT = 10_000;
6
+ /**
7
+ * Create the append-only `request_events` table if absent (idempotent; run once at startup). Like
8
+ * `audit_events`, it has **no foreign key to `environments`** — the analytics stream carries plain
9
+ * `org_slug`/`app_slug`/`environment` text so history survives tenant/app/env deletion and is pruned
10
+ * only by retention (ADR 0121). `seq` (bigserial) gives stable newest-first order and a future cursor;
11
+ * `details` is `jsonb` holding the already-redacted scalar bag.
12
+ */
13
+ export async function ensureRequestEventSchema(pool) {
14
+ await pool.query(`
15
+ CREATE TABLE IF NOT EXISTS request_events (
16
+ seq bigserial PRIMARY KEY,
17
+ id uuid NOT NULL,
18
+ schema_version int NOT NULL,
19
+ created_at timestamptz NOT NULL,
20
+ org_slug text NOT NULL,
21
+ app_slug text,
22
+ environment text,
23
+ deployment_id text,
24
+ server_version text,
25
+ sdk_protocol_version text,
26
+ request_id text NOT NULL,
27
+ session_id text,
28
+ session_source text NOT NULL,
29
+ client_name text,
30
+ client_version text,
31
+ access_mode text,
32
+ subject_kind text NOT NULL,
33
+ method text NOT NULL,
34
+ kind text NOT NULL,
35
+ tool_name text,
36
+ resource_name text,
37
+ prompt_name text,
38
+ outcome text NOT NULL,
39
+ error_kind text,
40
+ duration_ms double precision NOT NULL,
41
+ output_tokens_est int,
42
+ country text,
43
+ details jsonb
44
+ )
45
+ `);
46
+ await pool.query(`
47
+ CREATE INDEX IF NOT EXISTS request_events_tenant_time_idx
48
+ ON request_events (org_slug, app_slug, environment, seq DESC)
49
+ `);
50
+ await pool.query(`
51
+ CREATE INDEX IF NOT EXISTS request_events_session_idx
52
+ ON request_events (org_slug, session_id)
53
+ WHERE session_id IS NOT NULL
54
+ `);
55
+ // Retention prune deletes by `created_at < cutoff`; without this index every sweep is a full scan.
56
+ await pool.query(`
57
+ CREATE INDEX IF NOT EXISTS request_events_created_at_idx
58
+ ON request_events (created_at)
59
+ `);
60
+ }
61
+ /**
62
+ * Relational system of record for the tenant-facing analytics stream (parallels
63
+ * {@link InMemoryRequestEventStore}). The `pg.Pool` is injected; pure SQL, unit-testable against any
64
+ * local Postgres (`DATABASE_URL_TEST`).
65
+ */
66
+ export class PostgresRequestEventStore {
67
+ #pool;
68
+ #now;
69
+ constructor(pool, options = {}) {
70
+ this.#pool = pool;
71
+ this.#now = options.now ?? (() => new Date());
72
+ }
73
+ ensureSchema() {
74
+ return ensureRequestEventSchema(this.#pool);
75
+ }
76
+ async emit(input) {
77
+ const details = redactDetails(input.details);
78
+ await this.#pool.query(`INSERT INTO request_events
79
+ (id, schema_version, created_at, org_slug, app_slug, environment, deployment_id,
80
+ server_version, sdk_protocol_version, request_id, session_id, session_source,
81
+ client_name, client_version, access_mode, subject_kind, method, kind,
82
+ tool_name, resource_name, prompt_name, outcome, error_kind, duration_ms,
83
+ output_tokens_est, country, details)
84
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27)`, [
85
+ randomUUID(),
86
+ REQUEST_EVENT_SCHEMA_VERSION,
87
+ this.#now(),
88
+ input.org,
89
+ input.app ?? null,
90
+ input.env ?? null,
91
+ input.deploymentId ?? null,
92
+ input.serverVersion ?? null,
93
+ input.sdkProtocolVersion ?? null,
94
+ input.requestId,
95
+ input.sessionId ?? null,
96
+ input.sessionSource,
97
+ input.clientName ?? null,
98
+ input.clientVersion ?? null,
99
+ input.accessMode ?? null,
100
+ input.subjectKind,
101
+ input.method,
102
+ input.kind,
103
+ input.toolName ?? null,
104
+ input.resourceName ?? null,
105
+ input.promptName ?? null,
106
+ input.outcome,
107
+ input.errorKind ?? null,
108
+ input.durationMs,
109
+ input.outputTokensEst ?? null,
110
+ input.country ?? null,
111
+ details !== undefined ? JSON.stringify(details) : null,
112
+ ]);
113
+ }
114
+ async list(filter) {
115
+ const clauses = ['org_slug = $1'];
116
+ const values = [filter.org];
117
+ const add = (column, value) => {
118
+ values.push(value);
119
+ clauses.push(`${column} = $${values.length}`);
120
+ };
121
+ if (filter.app !== undefined)
122
+ add('app_slug', filter.app);
123
+ if (filter.env !== undefined)
124
+ add('environment', filter.env);
125
+ if (filter.outcome !== undefined)
126
+ add('outcome', filter.outcome);
127
+ if (filter.toolName !== undefined)
128
+ add('tool_name', filter.toolName);
129
+ if (filter.clientName !== undefined)
130
+ add('client_name', filter.clientName);
131
+ if (filter.sessionId !== undefined)
132
+ add('session_id', filter.sessionId);
133
+ if (filter.since !== undefined) {
134
+ values.push(filter.since);
135
+ clauses.push(`created_at >= $${values.length}`);
136
+ }
137
+ if (filter.until !== undefined) {
138
+ values.push(filter.until);
139
+ clauses.push(`created_at <= $${values.length}`);
140
+ }
141
+ // Always bounded: a missing limit gets the server-side default, and a malformed one can never
142
+ // reach the SQL text (a raw `Number(...)` here would produce `LIMIT NaN`).
143
+ const requested = Math.floor(Number(filter.limit));
144
+ const limit = Number.isFinite(requested) && requested >= 0
145
+ ? Math.min(requested, MAX_LIST_LIMIT)
146
+ : DEFAULT_LIST_LIMIT;
147
+ const { rows } = await this.#pool.query(`SELECT * FROM request_events WHERE ${clauses.join(' AND ')} ORDER BY seq DESC LIMIT ${limit}`, values);
148
+ return rows.map(rowToEvent);
149
+ }
150
+ /** Delete events older than the retention window (Stage-A default retention; called by the sweeper). */
151
+ async prune(olderThan) {
152
+ const { rowCount } = await this.#pool.query('DELETE FROM request_events WHERE created_at < $1', [olderThan]);
153
+ return rowCount ?? 0;
154
+ }
155
+ }
156
+ function rowToEvent(row) {
157
+ return {
158
+ seq: Number(row.seq),
159
+ id: row.id,
160
+ schemaVersion: row.schema_version,
161
+ createdAt: row.created_at.toISOString(),
162
+ org: row.org_slug,
163
+ requestId: row.request_id,
164
+ sessionSource: row.session_source,
165
+ subjectKind: row.subject_kind,
166
+ method: row.method,
167
+ kind: row.kind,
168
+ outcome: row.outcome,
169
+ durationMs: row.duration_ms,
170
+ ...(row.app_slug !== null ? { app: row.app_slug } : {}),
171
+ ...(row.environment !== null ? { env: row.environment } : {}),
172
+ ...(row.deployment_id !== null ? { deploymentId: row.deployment_id } : {}),
173
+ ...(row.server_version !== null ? { serverVersion: row.server_version } : {}),
174
+ ...(row.sdk_protocol_version !== null ? { sdkProtocolVersion: row.sdk_protocol_version } : {}),
175
+ ...(row.session_id !== null ? { sessionId: row.session_id } : {}),
176
+ ...(row.client_name !== null ? { clientName: row.client_name } : {}),
177
+ ...(row.client_version !== null ? { clientVersion: row.client_version } : {}),
178
+ ...(row.access_mode !== null ? { accessMode: row.access_mode } : {}),
179
+ ...(row.tool_name !== null ? { toolName: row.tool_name } : {}),
180
+ ...(row.resource_name !== null ? { resourceName: row.resource_name } : {}),
181
+ ...(row.prompt_name !== null ? { promptName: row.prompt_name } : {}),
182
+ ...(row.error_kind !== null ? { errorKind: row.error_kind } : {}),
183
+ ...(row.output_tokens_est !== null ? { outputTokensEst: row.output_tokens_est } : {}),
184
+ ...(row.country !== null ? { country: row.country } : {}),
185
+ ...(row.details !== null ? { details: row.details } : {}),
186
+ };
187
+ }
188
+ //# sourceMappingURL=request-events-postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-events-postgres.js","sourceRoot":"","sources":["../../src/store/request-events-postgres.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type RequestEvent, type RequestEventFilter, type RequestEventInput, type RequestEventStore } from '@noodle-borg/module';
2
+ /**
3
+ * In-memory {@link RequestEventStore} for local dev and tests: one **global** bounded drop-oldest
4
+ * ring (a busy tenant can evict another's events — acceptable for a dev/test-only store; the durable
5
+ * Postgres store is the real system of record with per-window retention). Mirrors the audit store's
6
+ * redaction discipline (scalar-only `details`).
7
+ */
8
+ export declare class InMemoryRequestEventStore implements RequestEventStore {
9
+ #private;
10
+ constructor(cap?: number);
11
+ emit(input: RequestEventInput): Promise<void>;
12
+ list(filter: RequestEventFilter): Promise<readonly RequestEvent[]>;
13
+ }
14
+ //# sourceMappingURL=request-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-events.d.ts","sourceRoot":"","sources":["../../src/store/request-events.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EAEvB,MAAM,qBAAqB,CAAC;AAmD7B;;;;;GAKG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;;gBAIrD,GAAG,GAAE,MAAoB;IAIrC,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7C,IAAI,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;CAQnE"}
@@ -0,0 +1,85 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { REQUEST_EVENT_SCHEMA_VERSION, redactDetails, } from '@noodle-borg/module';
3
+ const DEFAULT_CAP = 10_000;
4
+ function toRequestEvent(input) {
5
+ const details = redactDetails(input.details);
6
+ return {
7
+ id: randomUUID(),
8
+ schemaVersion: REQUEST_EVENT_SCHEMA_VERSION,
9
+ createdAt: new Date().toISOString(),
10
+ org: input.org,
11
+ requestId: input.requestId,
12
+ sessionSource: input.sessionSource,
13
+ subjectKind: input.subjectKind,
14
+ method: input.method,
15
+ kind: input.kind,
16
+ outcome: input.outcome,
17
+ durationMs: input.durationMs,
18
+ ...(input.app !== undefined ? { app: input.app } : {}),
19
+ ...(input.env !== undefined ? { env: input.env } : {}),
20
+ ...(input.deploymentId !== undefined ? { deploymentId: input.deploymentId } : {}),
21
+ ...(input.serverVersion !== undefined ? { serverVersion: input.serverVersion } : {}),
22
+ ...(input.sdkProtocolVersion !== undefined
23
+ ? { sdkProtocolVersion: input.sdkProtocolVersion }
24
+ : {}),
25
+ ...(input.sessionId !== undefined ? { sessionId: input.sessionId } : {}),
26
+ ...(input.clientName !== undefined ? { clientName: input.clientName } : {}),
27
+ ...(input.clientVersion !== undefined ? { clientVersion: input.clientVersion } : {}),
28
+ ...(input.accessMode !== undefined ? { accessMode: input.accessMode } : {}),
29
+ ...(input.toolName !== undefined ? { toolName: input.toolName } : {}),
30
+ ...(input.resourceName !== undefined ? { resourceName: input.resourceName } : {}),
31
+ ...(input.promptName !== undefined ? { promptName: input.promptName } : {}),
32
+ ...(input.errorKind !== undefined ? { errorKind: input.errorKind } : {}),
33
+ ...(input.outputTokensEst !== undefined ? { outputTokensEst: input.outputTokensEst } : {}),
34
+ ...(input.country !== undefined ? { country: input.country } : {}),
35
+ ...(details !== undefined ? { details } : {}),
36
+ };
37
+ }
38
+ function matches(event, filter) {
39
+ if (filter.app !== undefined && event.app !== filter.app)
40
+ return false;
41
+ if (filter.env !== undefined && event.env !== filter.env)
42
+ return false;
43
+ if (filter.outcome !== undefined && event.outcome !== filter.outcome)
44
+ return false;
45
+ if (filter.toolName !== undefined && event.toolName !== filter.toolName)
46
+ return false;
47
+ if (filter.clientName !== undefined && event.clientName !== filter.clientName)
48
+ return false;
49
+ if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId)
50
+ return false;
51
+ if (filter.since !== undefined && event.createdAt < filter.since)
52
+ return false;
53
+ if (filter.until !== undefined && event.createdAt > filter.until)
54
+ return false;
55
+ return true;
56
+ }
57
+ /**
58
+ * In-memory {@link RequestEventStore} for local dev and tests: one **global** bounded drop-oldest
59
+ * ring (a busy tenant can evict another's events — acceptable for a dev/test-only store; the durable
60
+ * Postgres store is the real system of record with per-window retention). Mirrors the audit store's
61
+ * redaction discipline (scalar-only `details`).
62
+ */
63
+ export class InMemoryRequestEventStore {
64
+ #events = [];
65
+ #cap;
66
+ constructor(cap = DEFAULT_CAP) {
67
+ this.#cap = cap;
68
+ }
69
+ emit(input) {
70
+ this.#events.push(toRequestEvent(input));
71
+ if (this.#events.length > this.#cap) {
72
+ this.#events.splice(0, this.#events.length - this.#cap);
73
+ }
74
+ return Promise.resolve();
75
+ }
76
+ list(filter) {
77
+ const matched = this.#events.filter((e) => e.org === filter.org && matches(e, filter));
78
+ matched.reverse(); // newest-first
79
+ // Guard malformed limits (NaN would make slice() silently return []).
80
+ const raw = filter.limit !== undefined ? Math.floor(filter.limit) : undefined;
81
+ const limit = raw !== undefined && Number.isFinite(raw) ? Math.max(0, raw) : undefined;
82
+ return Promise.resolve(limit !== undefined ? matched.slice(0, limit) : matched);
83
+ }
84
+ }
85
+ //# sourceMappingURL=request-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-events.js","sourceRoot":"","sources":["../../src/store/request-events.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import type { AccessMode, AdmissionGate, DataPlaneIdentityAuthorizer, OrgMembershipSource, OwnerTokenVerifier } from '@noodle-borg/module';
2
+ import type { AccessMode, AdmissionGate, DataPlaneIdentityAuthorizer, OrgMembershipSource, OwnerTokenVerifier, RequestEventInput } from '@noodle-borg/module';
3
3
  import { type ServedArtifact } from '@noodle-borg/protocol';
4
4
  import { type TlsPosture } from './front-door.js';
5
5
  import { type Logger } from './logging.js';
@@ -41,6 +41,13 @@ export interface HttpHandlerOptions {
41
41
  * execution. The default is allow; injected policy can log, suspend, quota, or deny per request.
42
42
  */
43
43
  readonly admissionGate?: AdmissionGate;
44
+ /**
45
+ * Analytics capture hook (ADR 0121): called with one scalar-only {@link RequestEventInput} per served
46
+ * MCP request, after the response is written — so it can never add latency to the response path. The
47
+ * callback must be synchronous enqueue-only (the service wraps a buffered sink); throws are swallowed.
48
+ * Requests without a tenant org (single/local dev targets) are not captured.
49
+ */
50
+ readonly captureRequestEvent?: (event: RequestEventInput) => void;
44
51
  }
45
52
  export interface TenantRouteRef {
46
53
  readonly org: string;
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { serverVersionFromPathSegment } from '@noodle-borg/module';
2
2
  import { handleStatelessHttp, JSON_RPC, } from '@noodle-borg/protocol';
3
3
  import { applySecurityHeaders, effectiveProto, enforceHttps, } from './front-door.js';
4
4
  import { noopLogger } from './logging.js';
5
+ import { emitRequestEvent, header, rpcMethod, rpcTargetName, safeRpcId, } from './request-capture.js';
5
6
  const DEFAULT_ENDPOINT = '/mcp';
6
7
  const DEFAULT_MAX_BODY = 1 << 20;
7
8
  /**
@@ -39,6 +40,7 @@ export function createMcpHttpHandler(target, options = {}) {
39
40
  verifyOwnerToken: options.verifyOwnerToken,
40
41
  authorizeDataPlaneIdentity: options.authorizeDataPlaneIdentity,
41
42
  admissionGate: options.admissionGate,
43
+ captureRequestEvent: options.captureRequestEvent,
42
44
  logger,
43
45
  trustProxy: tls.trustProxy ?? false,
44
46
  }, 'single', maxBody, options.allowedOrigins)).then(() => logRequest(logger, 'single', req, res, start));
@@ -97,6 +99,7 @@ export function createMcpRouter(lookup, options = {}) {
97
99
  verifyOwnerToken: resolvedTarget.verifyToken ?? options.verifyOwnerToken,
98
100
  authorizeDataPlaneIdentity: options.authorizeDataPlaneIdentity,
99
101
  admissionGate: options.admissionGate,
102
+ captureRequestEvent: options.captureRequestEvent,
100
103
  logger,
101
104
  trustProxy: tls.trustProxy ?? false,
102
105
  }, routeId, maxBody, options.allowedOrigins, tenant);
@@ -195,6 +198,7 @@ function guard(res, fn) {
195
198
  * the full response (including `202` for a notification). Path matching is done by the caller.
196
199
  */
197
200
  async function serveRequest(req, res, target, auth, routeId, maxBody, allowed, tenant) {
201
+ const startedAt = Date.now();
198
202
  const origin = header(req, 'origin');
199
203
  if (origin !== undefined && !originAllowed(origin, allowed)) {
200
204
  return sendJson(res, 403, rpcError(JSON_RPC.INVALID_REQUEST, 'origin not allowed'));
@@ -276,7 +280,19 @@ async function serveRequest(req, res, target, auth, routeId, maxBody, allowed, t
276
280
  }
277
281
  // The SDK stateless transport owns JSON-RPC framing, version negotiation, and the response (200 for a
278
282
  // request, 202 for a notification). We pass the already-read body so the body-size guard still applies.
283
+ // The analytics observation hook is only attached when capture is configured, so the open loopback path
284
+ // is untouched; the event is emitted after the response is written (zero response-path latency).
285
+ let observed;
286
+ if (auth.captureRequestEvent !== undefined) {
287
+ protocolContext = {
288
+ ...protocolContext,
289
+ observe: (observation) => {
290
+ observed = observation;
291
+ },
292
+ };
293
+ }
279
294
  await handleStatelessHttp(target, req, res, parsed, protocolContext);
295
+ emitRequestEvent({ req, res, target, auth, tenant, parsed, subject, observed, startedAt });
280
296
  }
281
297
  function admissionContexts(parsed, base) {
282
298
  const items = Array.isArray(parsed) ? parsed : [parsed];
@@ -292,34 +308,6 @@ function admissionContexts(parsed, base) {
292
308
  };
293
309
  });
294
310
  }
295
- function safeRpcId(value) {
296
- if (typeof value !== 'object' || value === null)
297
- return {};
298
- if (!Object.hasOwn(value, 'id'))
299
- return {};
300
- const id = value.id;
301
- if (typeof id === 'string' || typeof id === 'number' || id === null)
302
- return { requestId: id };
303
- return {};
304
- }
305
- function rpcMethod(value) {
306
- if (typeof value !== 'object' || value === null)
307
- return 'unknown';
308
- const method = value.method;
309
- return typeof method === 'string' ? method : 'unknown';
310
- }
311
- function rpcTargetName(value, method) {
312
- if (typeof value !== 'object' || value === null)
313
- return undefined;
314
- const params = value.params;
315
- if (typeof params !== 'object' || params === null)
316
- return undefined;
317
- const name = params.name;
318
- if (typeof name === 'string')
319
- return name;
320
- const uri = params.uri;
321
- return typeof uri === 'string' && method.startsWith('resources/') ? uri : undefined;
322
- }
323
311
  function admissionCategory(method) {
324
312
  if (method === 'tools/list' ||
325
313
  method === 'resources/list' ||
@@ -334,10 +322,6 @@ function admissionCategory(method) {
334
322
  return 'execute';
335
323
  return 'protocol';
336
324
  }
337
- function header(req, name) {
338
- const value = req.headers[name];
339
- return Array.isArray(value) ? value[0] : value;
340
- }
341
325
  function bearerToken(req) {
342
326
  const auth = header(req, 'authorization');
343
327
  if (!auth)