@mmnto/totem 1.106.0 → 1.107.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/capability/schema.d.ts +2 -2
- package/dist/errors.d.ts +3 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +92 -4
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +91 -13
- package/dist/ledger.js.map +1 -1
- package/dist/qbd/compliance.d.ts +216 -0
- package/dist/qbd/compliance.d.ts.map +1 -0
- package/dist/qbd/compliance.js +490 -0
- package/dist/qbd/compliance.js.map +1 -0
- package/dist/qbd/compliance.test.d.ts +2 -0
- package/dist/qbd/compliance.test.d.ts.map +1 -0
- package/dist/qbd/compliance.test.js +539 -0
- package/dist/qbd/compliance.test.js.map +1 -0
- package/dist/qbd/correlation-id.d.ts +130 -0
- package/dist/qbd/correlation-id.d.ts.map +1 -0
- package/dist/qbd/correlation-id.js +224 -0
- package/dist/qbd/correlation-id.js.map +1 -0
- package/dist/qbd/correlation-id.test.d.ts +2 -0
- package/dist/qbd/correlation-id.test.d.ts.map +1 -0
- package/dist/qbd/correlation-id.test.js +136 -0
- package/dist/qbd/correlation-id.test.js.map +1 -0
- package/dist/qbd/record.d.ts +140 -0
- package/dist/qbd/record.d.ts.map +1 -0
- package/dist/qbd/record.js +419 -0
- package/dist/qbd/record.js.map +1 -0
- package/dist/qbd/record.test.d.ts +27 -0
- package/dist/qbd/record.test.d.ts.map +1 -0
- package/dist/qbd/record.test.js +607 -0
- package/dist/qbd/record.test.js.map +1 -0
- package/dist/session-id.d.ts.map +1 -1
- package/dist/session-id.js +19 -3
- package/dist/session-id.js.map +1 -1
- package/dist/session-id.test.js +35 -0
- package/dist/session-id.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query-before-derive (QBD) correlation IDs — the join key between a corpus
|
|
3
|
+
* query event and the derive-class action it grounded (mmnto-ai/totem#2510).
|
|
4
|
+
*
|
|
5
|
+
* ## Why a bespoke ID and not `correlation_id`
|
|
6
|
+
*
|
|
7
|
+
* `LedgerEvent.correlation_id` is ADR-014's orchestrator→MCP trace ID and is a
|
|
8
|
+
* plain UUID. #2510 is explicitly NOT chartered to build the ADR-014 telemetry
|
|
9
|
+
* stack, so this slice carries its own field (`qbd_correlation_id`) with its
|
|
10
|
+
* own format. A UUID carries no mint instant, and the mint instant is exactly
|
|
11
|
+
* what makes falsifier 2 (post-hoc correlation) mechanically checkable.
|
|
12
|
+
*
|
|
13
|
+
* ## Format
|
|
14
|
+
*
|
|
15
|
+
* `qbd1-<mintInstantMs base36>-<16 hex random>` — e.g.
|
|
16
|
+
* `qbd1-2h1s4kbqr-9f2c0a7e51d34b60`.
|
|
17
|
+
*
|
|
18
|
+
* The ID is **self-dating**: it embeds the wall-clock instant at which it was
|
|
19
|
+
* minted. That is the whole point. Validation (below) cross-checks the embedded
|
|
20
|
+
* instant against the timestamp of the row carrying the ID, which turns
|
|
21
|
+
* "IDs must be minted at event-write time" from a convention into a schema
|
|
22
|
+
* constraint (#2510 falsifier 2: "a backfilled ID is a schema violation, not a
|
|
23
|
+
* data point").
|
|
24
|
+
*
|
|
25
|
+
* ## What the validation actually catches — and what it does not
|
|
26
|
+
*
|
|
27
|
+
* Catches (mechanically, at parse time, for every reader and writer):
|
|
28
|
+
* - a query row whose ID predates or postdates its own write instant beyond
|
|
29
|
+
* `QBD_MINT_TOLERANCE_MS` — i.e. an ID minted anywhere other than at the
|
|
30
|
+
* moment the row was written;
|
|
31
|
+
* - a derive row carrying an ID minted *after* the derive happened (an ID that
|
|
32
|
+
* did not exist when the derive ran cannot have grounded it);
|
|
33
|
+
* - a derive row carrying an ID minted longer ago than the correlation window
|
|
34
|
+
* (retroactively dressing an ancient query as this derive's grounding);
|
|
35
|
+
* - `qbd_correlation_id` stamped on an event type that has no query→derive
|
|
36
|
+
* semantics at all.
|
|
37
|
+
*
|
|
38
|
+
* On the tolerance: the checks are "beyond tolerance", NOT categorical. A
|
|
39
|
+
* mismatch inside `QBD_MINT_TOLERANCE_MS` passes in both directions, including
|
|
40
|
+
* a mint instant slightly in the future of its row. That window exists for
|
|
41
|
+
* clock granularity between two reads of the same clock, and it is small
|
|
42
|
+
* relative to the correlation window — but it is a real gap, and calling these
|
|
43
|
+
* checks categorical would overstate them.
|
|
44
|
+
*
|
|
45
|
+
* ## What this does NOT catch, stated precisely
|
|
46
|
+
*
|
|
47
|
+
* This is a per-row consistency check, so it cannot see relationships BETWEEN
|
|
48
|
+
* rows. Two attacks live in that gap:
|
|
49
|
+
*
|
|
50
|
+
* 1. **Backdated appends.** `mintQbdCorrelationId` takes its instant as an
|
|
51
|
+
* argument and is public API, and the ledger is append-only — so coherent
|
|
52
|
+
* query/derive pairs stamped in the past can simply be appended. No
|
|
53
|
+
* whole-file rewrite is needed. Each row passes this check individually.
|
|
54
|
+
* That attack is caught one layer up, by the append-only monotonicity check
|
|
55
|
+
* in `compliance.ts`, which degrades a scan whose timestamps regress. That
|
|
56
|
+
* check takes its reference from EVERY row in the file, not just QBD rows,
|
|
57
|
+
* so an adopted ledger's existing history is the baseline.
|
|
58
|
+
* 2. **A coherent whole-file rewrite** — and its degenerate case, an EMPTY or
|
|
59
|
+
* QBD-only ledger authored wholesale. With no prior history there is no
|
|
60
|
+
* monotonicity reference to violate, so internally consistent fake rows
|
|
61
|
+
* read clean. This is the same adversary as the rewrite: whoever authors
|
|
62
|
+
* the entire file controls its whole timeline. Conceded scope for v1 —
|
|
63
|
+
* nothing short of signing or an append-only server addresses it, and
|
|
64
|
+
* neither is chartered here.
|
|
65
|
+
*
|
|
66
|
+
* The realistic threat — a script or agent attaching correlation IDs to rows
|
|
67
|
+
* already on disk — is caught here. The other two are named rather than
|
|
68
|
+
* claimed solved, per the #2510 discipline.
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* Correlation window: a derive-class action counts as grounded by a query only
|
|
72
|
+
* if that query was minted within this window before it.
|
|
73
|
+
*
|
|
74
|
+
* The VALUE is borrowed from ADR-029 § 2's two-hour session window so this
|
|
75
|
+
* slice does not invent a second time constant. The DECISION to treat that
|
|
76
|
+
* span as a grounding-validity window is this slice's own, not the ADR's —
|
|
77
|
+
* ADR-029 § 2 defines a session-GROUPING heuristic for the recall metric, which
|
|
78
|
+
* is a different question, and it should not be cited as authority for this
|
|
79
|
+
* choice. Owned here as a design call: a shorter window would be a strictly
|
|
80
|
+
* harder test of the same claim, and the value is pinned so the pre-registered
|
|
81
|
+
* threshold is evaluated against a fixed rule rather than a tunable one.
|
|
82
|
+
*
|
|
83
|
+
* Note that consume-on-use (see `record.ts`) does most of the real work: the
|
|
84
|
+
* window bounds how STALE a grounding may be, but a query grounds only one
|
|
85
|
+
* derive regardless, so a generous window cannot inflate the numerator.
|
|
86
|
+
*/
|
|
87
|
+
export declare const QBD_CORRELATION_WINDOW_MS: number;
|
|
88
|
+
/**
|
|
89
|
+
* Tolerance for clock jitter between minting an ID and stamping the row's
|
|
90
|
+
* `timestamp`. These happen microseconds apart in the same function, so this is
|
|
91
|
+
* generous; it exists so a coarse clock or a slow filesystem never manufactures
|
|
92
|
+
* a spurious schema violation.
|
|
93
|
+
*/
|
|
94
|
+
export declare const QBD_MINT_TOLERANCE_MS = 2000;
|
|
95
|
+
/** Why a correlation ID failed validation. Rendered verbatim in accounting. */
|
|
96
|
+
export type QbdIdViolation = 'malformed-shape' | 'undecodable-mint-instant' | 'query-id-not-self-minted' | 'derive-id-minted-after-derive' | 'derive-id-older-than-window' | 'id-on-non-qbd-event';
|
|
97
|
+
export interface QbdIdCheckResult {
|
|
98
|
+
ok: boolean;
|
|
99
|
+
/** Populated when `ok` is false. */
|
|
100
|
+
violation?: QbdIdViolation;
|
|
101
|
+
/** Human-readable detail for accounting output. */
|
|
102
|
+
detail?: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Mint a correlation ID stamped with `mintedAtMs`.
|
|
106
|
+
*
|
|
107
|
+
* Callers MUST pass the same instant they stamp on the event row — see
|
|
108
|
+
* `record.ts`, which derives both from one `Date.now()` read. Every writer path
|
|
109
|
+
* in this slice goes through that module precisely so the two cannot drift.
|
|
110
|
+
*/
|
|
111
|
+
export declare function mintQbdCorrelationId(mintedAtMs: number): string;
|
|
112
|
+
/**
|
|
113
|
+
* Decode the mint instant embedded in a correlation ID.
|
|
114
|
+
* Returns `undefined` when the ID is not a well-formed QBD ID.
|
|
115
|
+
*/
|
|
116
|
+
export declare function decodeQbdMintInstant(id: string): number | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* The minted-at-write-time contract, enforced structurally.
|
|
119
|
+
*
|
|
120
|
+
* `eventType` is the ledger row's `type`; `eventMs` is `Date.parse(row.timestamp)`.
|
|
121
|
+
*
|
|
122
|
+
* - `corpus_query`: the ID must be minted at the row's own instant (± tolerance).
|
|
123
|
+
* A query row is the ID's birth certificate; any other mint instant means the
|
|
124
|
+
* ID was attached later.
|
|
125
|
+
* - `derive_action`: the ID must be minted at or before the derive (± tolerance)
|
|
126
|
+
* and no earlier than one correlation window before it.
|
|
127
|
+
* - any other type: carrying the field at all is a violation.
|
|
128
|
+
*/
|
|
129
|
+
export declare function checkQbdCorrelationId(id: string, eventType: string, eventMs: number): QbdIdCheckResult;
|
|
130
|
+
//# sourceMappingURL=correlation-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation-id.d.ts","sourceRoot":"","sources":["../../src/qbd/correlation-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAeH;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,yBAAyB,QAAqB,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAqB1C,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GACtB,iBAAiB,GACjB,0BAA0B,GAC1B,0BAA0B,GAC1B,+BAA+B,GAC/B,6BAA6B,GAC7B,qBAAqB,CAAC;AAE1B,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,oCAAoC;IACpC,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAO/D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUnE;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,gBAAgB,CAgElB"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query-before-derive (QBD) correlation IDs — the join key between a corpus
|
|
3
|
+
* query event and the derive-class action it grounded (mmnto-ai/totem#2510).
|
|
4
|
+
*
|
|
5
|
+
* ## Why a bespoke ID and not `correlation_id`
|
|
6
|
+
*
|
|
7
|
+
* `LedgerEvent.correlation_id` is ADR-014's orchestrator→MCP trace ID and is a
|
|
8
|
+
* plain UUID. #2510 is explicitly NOT chartered to build the ADR-014 telemetry
|
|
9
|
+
* stack, so this slice carries its own field (`qbd_correlation_id`) with its
|
|
10
|
+
* own format. A UUID carries no mint instant, and the mint instant is exactly
|
|
11
|
+
* what makes falsifier 2 (post-hoc correlation) mechanically checkable.
|
|
12
|
+
*
|
|
13
|
+
* ## Format
|
|
14
|
+
*
|
|
15
|
+
* `qbd1-<mintInstantMs base36>-<16 hex random>` — e.g.
|
|
16
|
+
* `qbd1-2h1s4kbqr-9f2c0a7e51d34b60`.
|
|
17
|
+
*
|
|
18
|
+
* The ID is **self-dating**: it embeds the wall-clock instant at which it was
|
|
19
|
+
* minted. That is the whole point. Validation (below) cross-checks the embedded
|
|
20
|
+
* instant against the timestamp of the row carrying the ID, which turns
|
|
21
|
+
* "IDs must be minted at event-write time" from a convention into a schema
|
|
22
|
+
* constraint (#2510 falsifier 2: "a backfilled ID is a schema violation, not a
|
|
23
|
+
* data point").
|
|
24
|
+
*
|
|
25
|
+
* ## What the validation actually catches — and what it does not
|
|
26
|
+
*
|
|
27
|
+
* Catches (mechanically, at parse time, for every reader and writer):
|
|
28
|
+
* - a query row whose ID predates or postdates its own write instant beyond
|
|
29
|
+
* `QBD_MINT_TOLERANCE_MS` — i.e. an ID minted anywhere other than at the
|
|
30
|
+
* moment the row was written;
|
|
31
|
+
* - a derive row carrying an ID minted *after* the derive happened (an ID that
|
|
32
|
+
* did not exist when the derive ran cannot have grounded it);
|
|
33
|
+
* - a derive row carrying an ID minted longer ago than the correlation window
|
|
34
|
+
* (retroactively dressing an ancient query as this derive's grounding);
|
|
35
|
+
* - `qbd_correlation_id` stamped on an event type that has no query→derive
|
|
36
|
+
* semantics at all.
|
|
37
|
+
*
|
|
38
|
+
* On the tolerance: the checks are "beyond tolerance", NOT categorical. A
|
|
39
|
+
* mismatch inside `QBD_MINT_TOLERANCE_MS` passes in both directions, including
|
|
40
|
+
* a mint instant slightly in the future of its row. That window exists for
|
|
41
|
+
* clock granularity between two reads of the same clock, and it is small
|
|
42
|
+
* relative to the correlation window — but it is a real gap, and calling these
|
|
43
|
+
* checks categorical would overstate them.
|
|
44
|
+
*
|
|
45
|
+
* ## What this does NOT catch, stated precisely
|
|
46
|
+
*
|
|
47
|
+
* This is a per-row consistency check, so it cannot see relationships BETWEEN
|
|
48
|
+
* rows. Two attacks live in that gap:
|
|
49
|
+
*
|
|
50
|
+
* 1. **Backdated appends.** `mintQbdCorrelationId` takes its instant as an
|
|
51
|
+
* argument and is public API, and the ledger is append-only — so coherent
|
|
52
|
+
* query/derive pairs stamped in the past can simply be appended. No
|
|
53
|
+
* whole-file rewrite is needed. Each row passes this check individually.
|
|
54
|
+
* That attack is caught one layer up, by the append-only monotonicity check
|
|
55
|
+
* in `compliance.ts`, which degrades a scan whose timestamps regress. That
|
|
56
|
+
* check takes its reference from EVERY row in the file, not just QBD rows,
|
|
57
|
+
* so an adopted ledger's existing history is the baseline.
|
|
58
|
+
* 2. **A coherent whole-file rewrite** — and its degenerate case, an EMPTY or
|
|
59
|
+
* QBD-only ledger authored wholesale. With no prior history there is no
|
|
60
|
+
* monotonicity reference to violate, so internally consistent fake rows
|
|
61
|
+
* read clean. This is the same adversary as the rewrite: whoever authors
|
|
62
|
+
* the entire file controls its whole timeline. Conceded scope for v1 —
|
|
63
|
+
* nothing short of signing or an append-only server addresses it, and
|
|
64
|
+
* neither is chartered here.
|
|
65
|
+
*
|
|
66
|
+
* The realistic threat — a script or agent attaching correlation IDs to rows
|
|
67
|
+
* already on disk — is caught here. The other two are named rather than
|
|
68
|
+
* claimed solved, per the #2510 discipline.
|
|
69
|
+
*/
|
|
70
|
+
import * as crypto from 'node:crypto';
|
|
71
|
+
// ─── Constants ──────────────────────────────────────────
|
|
72
|
+
/** Format version prefix — bump if the encoding ever changes. */
|
|
73
|
+
const QBD_ID_PREFIX = 'qbd1';
|
|
74
|
+
/** Bytes of randomness in the ID suffix (16 hex chars). */
|
|
75
|
+
const QBD_ID_RANDOM_BYTES = 8;
|
|
76
|
+
/** Radix for the embedded mint instant. */
|
|
77
|
+
const MINT_RADIX = 36;
|
|
78
|
+
/**
|
|
79
|
+
* Correlation window: a derive-class action counts as grounded by a query only
|
|
80
|
+
* if that query was minted within this window before it.
|
|
81
|
+
*
|
|
82
|
+
* The VALUE is borrowed from ADR-029 § 2's two-hour session window so this
|
|
83
|
+
* slice does not invent a second time constant. The DECISION to treat that
|
|
84
|
+
* span as a grounding-validity window is this slice's own, not the ADR's —
|
|
85
|
+
* ADR-029 § 2 defines a session-GROUPING heuristic for the recall metric, which
|
|
86
|
+
* is a different question, and it should not be cited as authority for this
|
|
87
|
+
* choice. Owned here as a design call: a shorter window would be a strictly
|
|
88
|
+
* harder test of the same claim, and the value is pinned so the pre-registered
|
|
89
|
+
* threshold is evaluated against a fixed rule rather than a tunable one.
|
|
90
|
+
*
|
|
91
|
+
* Note that consume-on-use (see `record.ts`) does most of the real work: the
|
|
92
|
+
* window bounds how STALE a grounding may be, but a query grounds only one
|
|
93
|
+
* derive regardless, so a generous window cannot inflate the numerator.
|
|
94
|
+
*/
|
|
95
|
+
export const QBD_CORRELATION_WINDOW_MS = 2 * 60 * 60 * 1000;
|
|
96
|
+
/**
|
|
97
|
+
* Tolerance for clock jitter between minting an ID and stamping the row's
|
|
98
|
+
* `timestamp`. These happen microseconds apart in the same function, so this is
|
|
99
|
+
* generous; it exists so a coarse clock or a slow filesystem never manufactures
|
|
100
|
+
* a spurious schema violation.
|
|
101
|
+
*/
|
|
102
|
+
export const QBD_MINT_TOLERANCE_MS = 2000;
|
|
103
|
+
/** Structural shape of a QBD correlation ID. */
|
|
104
|
+
const QBD_ID_PATTERN = /^qbd1-([0-9a-z]{1,11})-[0-9a-f]{16}$/;
|
|
105
|
+
/**
|
|
106
|
+
* Largest instant a `Date` can represent (±8.64e15 ms). The ID pattern admits
|
|
107
|
+
* up to 11 base36 digits, i.e. ~1.3e17 — comfortably past this bound.
|
|
108
|
+
*
|
|
109
|
+
* That gap was a crash, not a cosmetic issue: an ID like
|
|
110
|
+
* `qbd1-zzzzzzzzzzz-<16 hex>` decoded to a finite, non-negative number, passed
|
|
111
|
+
* every guard, and then reached `new Date(mintedAtMs).toISOString()` in a
|
|
112
|
+
* violation-detail string, which throws `RangeError`. Those details are built
|
|
113
|
+
* inside `LedgerEventSchema`'s refinement, and Zod does NOT trap throws from
|
|
114
|
+
* refinements — so `safeParse` threw rather than returning a failure, and one
|
|
115
|
+
* forged line killed the entire scan. Reject the ID before any `Date` sees it.
|
|
116
|
+
*/
|
|
117
|
+
const MAX_TIME_VALUE_MS = 8.64e15;
|
|
118
|
+
// ─── Mint ───────────────────────────────────────────────
|
|
119
|
+
/**
|
|
120
|
+
* Mint a correlation ID stamped with `mintedAtMs`.
|
|
121
|
+
*
|
|
122
|
+
* Callers MUST pass the same instant they stamp on the event row — see
|
|
123
|
+
* `record.ts`, which derives both from one `Date.now()` read. Every writer path
|
|
124
|
+
* in this slice goes through that module precisely so the two cannot drift.
|
|
125
|
+
*/
|
|
126
|
+
export function mintQbdCorrelationId(mintedAtMs) {
|
|
127
|
+
if (!Number.isFinite(mintedAtMs) || mintedAtMs < 0 || mintedAtMs > MAX_TIME_VALUE_MS) {
|
|
128
|
+
throw new RangeError(`mintQbdCorrelationId: invalid mint instant ${String(mintedAtMs)}`);
|
|
129
|
+
}
|
|
130
|
+
const stamp = Math.floor(mintedAtMs).toString(MINT_RADIX);
|
|
131
|
+
const random = crypto.randomBytes(QBD_ID_RANDOM_BYTES).toString('hex');
|
|
132
|
+
return `${QBD_ID_PREFIX}-${stamp}-${random}`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Decode the mint instant embedded in a correlation ID.
|
|
136
|
+
* Returns `undefined` when the ID is not a well-formed QBD ID.
|
|
137
|
+
*/
|
|
138
|
+
export function decodeQbdMintInstant(id) {
|
|
139
|
+
const match = QBD_ID_PATTERN.exec(id);
|
|
140
|
+
if (match === null)
|
|
141
|
+
return undefined;
|
|
142
|
+
const decoded = parseInt(match[1], MINT_RADIX);
|
|
143
|
+
// The upper bound is load-bearing, not defensive: past it, every downstream
|
|
144
|
+
// `new Date(...)` throws. Returning `undefined` routes such an ID to
|
|
145
|
+
// `undecodable-mint-instant`, which is counted and degrades the read — a data
|
|
146
|
+
// point rather than a crash.
|
|
147
|
+
if (!Number.isFinite(decoded) || decoded < 0 || decoded > MAX_TIME_VALUE_MS)
|
|
148
|
+
return undefined;
|
|
149
|
+
return decoded;
|
|
150
|
+
}
|
|
151
|
+
// ─── Validate (the anti-backfill contract) ──────────────
|
|
152
|
+
/**
|
|
153
|
+
* The minted-at-write-time contract, enforced structurally.
|
|
154
|
+
*
|
|
155
|
+
* `eventType` is the ledger row's `type`; `eventMs` is `Date.parse(row.timestamp)`.
|
|
156
|
+
*
|
|
157
|
+
* - `corpus_query`: the ID must be minted at the row's own instant (± tolerance).
|
|
158
|
+
* A query row is the ID's birth certificate; any other mint instant means the
|
|
159
|
+
* ID was attached later.
|
|
160
|
+
* - `derive_action`: the ID must be minted at or before the derive (± tolerance)
|
|
161
|
+
* and no earlier than one correlation window before it.
|
|
162
|
+
* - any other type: carrying the field at all is a violation.
|
|
163
|
+
*/
|
|
164
|
+
export function checkQbdCorrelationId(id, eventType, eventMs) {
|
|
165
|
+
// Self-contained on its own argument: callers inside this package already
|
|
166
|
+
// validate `eventMs`, but this is public API and the detail strings below
|
|
167
|
+
// feed it straight to `new Date(...)`. An unusable instant is a violation to
|
|
168
|
+
// report, never a throw to leak out of a validation function.
|
|
169
|
+
if (!Number.isFinite(eventMs) || Math.abs(eventMs) > MAX_TIME_VALUE_MS) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
violation: 'undecodable-mint-instant',
|
|
173
|
+
detail: `event timestamp ${String(eventMs)} is not a usable instant`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (QBD_ID_PATTERN.exec(id) === null) {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
violation: 'malformed-shape',
|
|
180
|
+
detail: `'${id}' is not a qbd1-<base36 instant>-<16 hex> correlation ID`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const mintedAtMs = decodeQbdMintInstant(id);
|
|
184
|
+
if (mintedAtMs === undefined) {
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
violation: 'undecodable-mint-instant',
|
|
188
|
+
detail: `'${id}' has no readable mint instant`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (eventType === 'corpus_query') {
|
|
192
|
+
if (Math.abs(eventMs - mintedAtMs) > QBD_MINT_TOLERANCE_MS) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
violation: 'query-id-not-self-minted',
|
|
196
|
+
detail: `query row stamped ${new Date(eventMs).toISOString()} carries an ID minted ${new Date(mintedAtMs).toISOString()} — a query event's ID is minted as the row is written, so this ID was attached after the fact`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return { ok: true };
|
|
200
|
+
}
|
|
201
|
+
if (eventType === 'derive_action') {
|
|
202
|
+
if (mintedAtMs - eventMs > QBD_MINT_TOLERANCE_MS) {
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
violation: 'derive-id-minted-after-derive',
|
|
206
|
+
detail: `derive row stamped ${new Date(eventMs).toISOString()} carries an ID minted ${new Date(mintedAtMs).toISOString()} — a query that had not happened yet cannot have grounded this derive`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (eventMs - mintedAtMs > QBD_CORRELATION_WINDOW_MS) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
violation: 'derive-id-older-than-window',
|
|
213
|
+
detail: `derive row stamped ${new Date(eventMs).toISOString()} carries an ID minted ${new Date(mintedAtMs).toISOString()}, older than the ${QBD_CORRELATION_WINDOW_MS}ms correlation window`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return { ok: true };
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
violation: 'id-on-non-qbd-event',
|
|
221
|
+
detail: `event type '${eventType}' has no query→derive semantics but carries a qbd_correlation_id`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=correlation-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation-id.js","sourceRoot":"","sources":["../../src/qbd/correlation-id.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation-id.test.d.ts","sourceRoot":"","sources":["../../src/qbd/correlation-id.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { LedgerEventSchema } from '../ledger.js';
|
|
3
|
+
import { checkQbdCorrelationId, decodeQbdMintInstant, mintQbdCorrelationId, QBD_CORRELATION_WINDOW_MS, QBD_MINT_TOLERANCE_MS, } from './correlation-id.js';
|
|
4
|
+
const T0 = Date.parse('2026-07-28T12:00:00.000Z');
|
|
5
|
+
describe('mintQbdCorrelationId', () => {
|
|
6
|
+
it('embeds the mint instant so the ID is self-dating', () => {
|
|
7
|
+
const id = mintQbdCorrelationId(T0);
|
|
8
|
+
expect(decodeQbdMintInstant(id)).toBe(T0);
|
|
9
|
+
});
|
|
10
|
+
it('is unique across calls at the same instant', () => {
|
|
11
|
+
const a = mintQbdCorrelationId(T0);
|
|
12
|
+
const b = mintQbdCorrelationId(T0);
|
|
13
|
+
expect(a).not.toBe(b);
|
|
14
|
+
expect(decodeQbdMintInstant(a)).toBe(decodeQbdMintInstant(b));
|
|
15
|
+
});
|
|
16
|
+
it('rejects a nonsense mint instant rather than emitting a bad ID', () => {
|
|
17
|
+
expect(() => mintQbdCorrelationId(Number.NaN)).toThrow(RangeError);
|
|
18
|
+
expect(() => mintQbdCorrelationId(-1)).toThrow(RangeError);
|
|
19
|
+
});
|
|
20
|
+
it('returns undefined decoding a non-QBD string', () => {
|
|
21
|
+
expect(decodeQbdMintInstant('not-an-id')).toBeUndefined();
|
|
22
|
+
expect(decodeQbdMintInstant('550e8400-e29b-41d4-a716-446655440000')).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
it('returns undefined for an instant past the max Date value', () => {
|
|
25
|
+
// The ID pattern admits 11 base36 digits (~1.3e17), well past 8.64e15.
|
|
26
|
+
// Decoding must refuse rather than hand a poison number downstream, where
|
|
27
|
+
// every `new Date(...)` would throw.
|
|
28
|
+
expect(decodeQbdMintInstant(`qbd1-zzzzzzzzzzz-${'a'.repeat(16)}`)).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
it('refuses to MINT an instant past the max Date value', () => {
|
|
31
|
+
expect(() => mintQbdCorrelationId(8.64e15 + 1)).toThrow(RangeError);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('checkQbdCorrelationId — the minted-at-write-time contract', () => {
|
|
35
|
+
it('accepts a query ID minted at its own row instant', () => {
|
|
36
|
+
const id = mintQbdCorrelationId(T0);
|
|
37
|
+
expect(checkQbdCorrelationId(id, 'corpus_query', T0).ok).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it('REJECTS a query row whose ID was minted earlier — the backfill shape', () => {
|
|
40
|
+
// The falsifier-2 case: an ID minted at some past moment, attached later to
|
|
41
|
+
// a query row that was already on disk.
|
|
42
|
+
const id = mintQbdCorrelationId(T0 - 60_000);
|
|
43
|
+
const result = checkQbdCorrelationId(id, 'corpus_query', T0);
|
|
44
|
+
expect(result.ok).toBe(false);
|
|
45
|
+
expect(result.violation).toBe('query-id-not-self-minted');
|
|
46
|
+
});
|
|
47
|
+
it('accepts a derive ID minted earlier, inside the correlation window', () => {
|
|
48
|
+
const id = mintQbdCorrelationId(T0 - 60_000);
|
|
49
|
+
expect(checkQbdCorrelationId(id, 'derive_action', T0).ok).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it('REJECTS a derive ID minted after the derive it claims to have grounded', () => {
|
|
52
|
+
const id = mintQbdCorrelationId(T0 + 60_000);
|
|
53
|
+
const result = checkQbdCorrelationId(id, 'derive_action', T0);
|
|
54
|
+
expect(result.ok).toBe(false);
|
|
55
|
+
expect(result.violation).toBe('derive-id-minted-after-derive');
|
|
56
|
+
});
|
|
57
|
+
it('REJECTS a derive ID older than the correlation window', () => {
|
|
58
|
+
const id = mintQbdCorrelationId(T0 - QBD_CORRELATION_WINDOW_MS - 1000);
|
|
59
|
+
const result = checkQbdCorrelationId(id, 'derive_action', T0);
|
|
60
|
+
expect(result.ok).toBe(false);
|
|
61
|
+
expect(result.violation).toBe('derive-id-older-than-window');
|
|
62
|
+
});
|
|
63
|
+
it('tolerates sub-tolerance clock jitter on a query row', () => {
|
|
64
|
+
const id = mintQbdCorrelationId(T0 - (QBD_MINT_TOLERANCE_MS - 1));
|
|
65
|
+
expect(checkQbdCorrelationId(id, 'corpus_query', T0).ok).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it('reports an out-of-range ID as undecodable rather than throwing', () => {
|
|
68
|
+
const result = checkQbdCorrelationId(`qbd1-zzzzzzzzzzz-${'a'.repeat(16)}`, 'derive_action', T0);
|
|
69
|
+
expect(result.ok).toBe(false);
|
|
70
|
+
expect(result.violation).toBe('undecodable-mint-instant');
|
|
71
|
+
});
|
|
72
|
+
it('reports an unusable EVENT timestamp rather than throwing (public API)', () => {
|
|
73
|
+
const id = mintQbdCorrelationId(T0);
|
|
74
|
+
expect(checkQbdCorrelationId(id, 'derive_action', Number.NaN).ok).toBe(false);
|
|
75
|
+
expect(checkQbdCorrelationId(id, 'derive_action', Number.POSITIVE_INFINITY).ok).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
it('REJECTS a malformed ID', () => {
|
|
78
|
+
const result = checkQbdCorrelationId('qbd1-nope', 'corpus_query', T0);
|
|
79
|
+
expect(result.ok).toBe(false);
|
|
80
|
+
expect(result.violation).toBe('malformed-shape');
|
|
81
|
+
});
|
|
82
|
+
it('REJECTS the field on an event type with no query→derive semantics', () => {
|
|
83
|
+
const id = mintQbdCorrelationId(T0);
|
|
84
|
+
const result = checkQbdCorrelationId(id, 'mcp_call', T0);
|
|
85
|
+
expect(result.ok).toBe(false);
|
|
86
|
+
expect(result.violation).toBe('id-on-non-qbd-event');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('LedgerEventSchema enforces the contract at the schema layer', () => {
|
|
90
|
+
const baseRow = {
|
|
91
|
+
timestamp: new Date(T0).toISOString(),
|
|
92
|
+
type: 'derive_action',
|
|
93
|
+
activity_name: 'spec',
|
|
94
|
+
source: 'lint',
|
|
95
|
+
justification: '',
|
|
96
|
+
};
|
|
97
|
+
it('parses a contract-valid correlated derive row', () => {
|
|
98
|
+
const row = { ...baseRow, qbd_correlation_id: mintQbdCorrelationId(T0 - 60_000) };
|
|
99
|
+
expect(LedgerEventSchema.safeParse(row).success).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
it('parses an UNcorrelated derive row — it is a data point, not an error', () => {
|
|
102
|
+
// Falsifier 1: a derive with no query behind it must survive parsing so it
|
|
103
|
+
// can land in the denominator.
|
|
104
|
+
expect(LedgerEventSchema.safeParse(baseRow).success).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
it('REJECTS a backfilled ID — a schema violation, not a data point', () => {
|
|
107
|
+
const row = { ...baseRow, qbd_correlation_id: mintQbdCorrelationId(T0 + 60_000) };
|
|
108
|
+
const result = LedgerEventSchema.safeParse(row);
|
|
109
|
+
expect(result.success).toBe(false);
|
|
110
|
+
if (!result.success) {
|
|
111
|
+
expect(result.error.issues[0]?.message).toContain('derive-id-minted-after-derive');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
it('REJECTS a correlation ID stapled onto an unrelated event type', () => {
|
|
115
|
+
const row = {
|
|
116
|
+
timestamp: new Date(T0).toISOString(),
|
|
117
|
+
type: 'mcp_call',
|
|
118
|
+
source: 'bot',
|
|
119
|
+
justification: '',
|
|
120
|
+
qbd_correlation_id: mintQbdCorrelationId(T0),
|
|
121
|
+
};
|
|
122
|
+
expect(LedgerEventSchema.safeParse(row).success).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
it('leaves pre-existing event types untouched', () => {
|
|
125
|
+
const legacy = {
|
|
126
|
+
timestamp: new Date(T0).toISOString(),
|
|
127
|
+
type: 'suppress',
|
|
128
|
+
ruleId: 'abc123',
|
|
129
|
+
file: 'packages/core/src/logger.ts',
|
|
130
|
+
source: 'lint',
|
|
131
|
+
justification: 'logger module',
|
|
132
|
+
};
|
|
133
|
+
expect(LedgerEventSchema.safeParse(legacy).success).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
//# sourceMappingURL=correlation-id.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation-id.test.js","sourceRoot":"","sources":["../../src/qbd/correlation-id.test.ts"],"names":[],"mappings":"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"}
|