@noodleseed/one 0.13.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.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/analytics-ops.d.ts +3 -0
- package/dist/commands/analytics-ops.d.ts.map +1 -0
- package/dist/commands/analytics-ops.js +250 -0
- package/dist/commands/analytics-ops.js.map +1 -0
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/shared.js +9 -0
- package/dist/commands/shared.js.map +1 -1
- package/dist/metrics-render.d.ts +99 -0
- package/dist/metrics-render.d.ts.map +1 -0
- package/dist/metrics-render.js +181 -0
- package/dist/metrics-render.js.map +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.d.ts.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +10 -0
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.d.ts.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +2 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.d.ts.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js +4 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +61 -0
- package/node_modules/@noodle-borg/module/dist/contract.d.ts.map +1 -1
- package/node_modules/@noodle-borg/module/dist/contract.js +1 -0
- package/node_modules/@noodle-borg/module/dist/contract.js.map +1 -1
- package/node_modules/@noodle-borg/protocol/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/protocol/dist/index.d.ts.map +1 -1
- package/node_modules/@noodle-borg/protocol/dist/index.js +1 -1
- package/node_modules/@noodle-borg/protocol/dist/index.js.map +1 -1
- package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts +19 -0
- package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts.map +1 -1
- package/node_modules/@noodle-borg/protocol/dist/sdk-server.js +74 -16
- package/node_modules/@noodle-borg/protocol/dist/sdk-server.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/index.d.ts +4 -0
- package/node_modules/@noodle-borg/service/dist/index.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/index.js +4 -0
- package/node_modules/@noodle-borg/service/dist/index.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/options.d.ts +17 -0
- package/node_modules/@noodle-borg/service/dist/options.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/request-event-metrics.d.ts +63 -0
- package/node_modules/@noodle-borg/service/dist/request-event-metrics.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/request-event-metrics.js +103 -0
- package/node_modules/@noodle-borg/service/dist/request-event-metrics.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/analytics.d.ts +8 -0
- package/node_modules/@noodle-borg/service/dist/routes/analytics.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js +109 -0
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/paths.d.ts +8 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.js +25 -0
- package/node_modules/@noodle-borg/service/dist/routes/paths.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/serve.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/serve.js +27 -0
- package/node_modules/@noodle-borg/service/dist/serve.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service.js +51 -1
- package/node_modules/@noodle-borg/service/dist/service.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js +2 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/request-event-buffer.d.ts +23 -0
- package/node_modules/@noodle-borg/service/dist/store/request-event-buffer.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/request-event-buffer.js +62 -0
- package/node_modules/@noodle-borg/service/dist/store/request-event-buffer.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/request-events-postgres.d.ts +27 -0
- package/node_modules/@noodle-borg/service/dist/store/request-events-postgres.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/request-events-postgres.js +188 -0
- package/node_modules/@noodle-borg/service/dist/store/request-events-postgres.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/request-events.d.ts +14 -0
- package/node_modules/@noodle-borg/service/dist/store/request-events.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/request-events.js +85 -0
- package/node_modules/@noodle-borg/service/dist/store/request-events.js.map +1 -0
- package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +8 -1
- package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts.map +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/handler.js +16 -32
- package/node_modules/@noodle-borg/transport-http/dist/handler.js.map +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/request-capture.d.ts +49 -0
- package/node_modules/@noodle-borg/transport-http/dist/request-capture.d.ts.map +1 -0
- package/node_modules/@noodle-borg/transport-http/dist/request-capture.js +116 -0
- package/node_modules/@noodle-borg/transport-http/dist/request-capture.js.map +1 -0
- 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":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EACV,UAAU,EAGV,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,kBAAkB,
|
|
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)
|