@mmnto/totem 1.105.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/ast-classifier.d.ts +3 -3
- package/dist/ast-classifier.js +12 -3
- package/dist/ast-classifier.js.map +1 -1
- package/dist/ast-classifier.test.js +33 -2
- package/dist/ast-classifier.test.js.map +1 -1
- 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 +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- 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/rule-engine.test.js +85 -0
- package/dist/rule-engine.test.js.map +1 -1
- 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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query-before-derive (QBD) event writers (mmnto-ai/totem#2510).
|
|
3
|
+
*
|
|
4
|
+
* Two row types, one contract:
|
|
5
|
+
*
|
|
6
|
+
* - `recordCorpusQuery` — a corpus query fired. Mints a fresh correlation ID
|
|
7
|
+
* **from the same clock read that stamps the row's `timestamp`**, so the ID
|
|
8
|
+
* and the row are born together. Then parks the ID in a pointer file for the
|
|
9
|
+
* derives that follow.
|
|
10
|
+
* - `recordDeriveAction` — a derive-class action ran. Reads the parked ID and
|
|
11
|
+
* attaches it *only if* it is still inside the correlation window, from this
|
|
12
|
+
* session, and from this seat. Otherwise the row is written with NO
|
|
13
|
+
* correlation ID — an uncorrelated derive is the metric's most important
|
|
14
|
+
* observation, so it is always recorded, never dropped (#2510 falsifier 1).
|
|
15
|
+
*
|
|
16
|
+
* ## Consumption semantics: one query grounds ONE derive
|
|
17
|
+
*
|
|
18
|
+
* The pointer is CONSUMED by the first derive that correlates to it. Without
|
|
19
|
+
* that, a single query would credit every derive for the rest of the
|
|
20
|
+
* correlation window — one query, ten derives, compliance 1.00 — which measures
|
|
21
|
+
* "queried at least once per two hours", not "queried before deriving". The
|
|
22
|
+
* strict 1:1 reading is the one that can actually be falsified, so it is the
|
|
23
|
+
* one implemented. A second derive after a single query is UNCORRELATED, and
|
|
24
|
+
* that is the correct reading, not a miss.
|
|
25
|
+
*
|
|
26
|
+
* ## Correlation is scoped to one seat AND one session, fail-closed
|
|
27
|
+
*
|
|
28
|
+
* Both sides must carry a session id and agree on it, and the seats must match.
|
|
29
|
+
* Cohort seats share one working tree per repo, so a pointer left by another
|
|
30
|
+
* seat is reachable; treating a missing id as a match would let it ground this
|
|
31
|
+
* seat's derive.
|
|
32
|
+
*
|
|
33
|
+
* **Seat comparison is equality including "both unknown".** When neither side
|
|
34
|
+
* is seated (`TOTEM_SELF_AGENT` unset), they compare equal and correlation
|
|
35
|
+
* proceeds. That is the deliberate default for the solo/unseated case, which is
|
|
36
|
+
* the common one — requiring a seat would make the metric unusable for anyone
|
|
37
|
+
* not running a cohort. The cost is disclosed rather than hidden: seat exactly
|
|
38
|
+
* ONE side and correlation stops entirely. That is a live configuration today —
|
|
39
|
+
* the CLI can be seated while the MCP server is not — and it produces a
|
|
40
|
+
* truthful-looking 0.00 whose pre-registered consequence is falsified adherence
|
|
41
|
+
* claims. The scanner therefore emits a seat-mismatch hint (see
|
|
42
|
+
* `compliance.ts`) so the config smell is visible instead of silent. Wiring the
|
|
43
|
+
* MCP server's seat is out of this slice's scope and tracked in
|
|
44
|
+
* mmnto-ai/totem#2530.
|
|
45
|
+
*
|
|
46
|
+
* ## Why minting lives here and nowhere else
|
|
47
|
+
*
|
|
48
|
+
* `mintQbdCorrelationId(now)` takes the instant as an argument, which would let
|
|
49
|
+
* a caller pass any instant it liked. Every production writer goes through this
|
|
50
|
+
* module, where `now` is read once and used for BOTH the ID and the timestamp.
|
|
51
|
+
* A caller that tried to route around it would have to hand-build a row, and the
|
|
52
|
+
* schema refinement in `ledger.ts` rejects any row whose ID could not have been
|
|
53
|
+
* minted when the row was written. Convention here, enforcement in the schema.
|
|
54
|
+
*
|
|
55
|
+
* ## Failure posture (Tenet 13 + ADR-115 § 2)
|
|
56
|
+
*
|
|
57
|
+
* A contract breach — a pointer file holding a forged, malformed, or
|
|
58
|
+
* out-of-window ID — makes the schema reject the row, and these functions THROW
|
|
59
|
+
* (the loud backstop; a backfilled ID is a schema violation, not a data point).
|
|
60
|
+
* An ordinary I/O failure does not throw; it is reported through the returned
|
|
61
|
+
* `warnings` array.
|
|
62
|
+
*
|
|
63
|
+
* Neither may break the instrumented command. That is what the `sense*`
|
|
64
|
+
* wrappers are for: they catch the backstop, convert it to a visible warning,
|
|
65
|
+
* and return. Command call sites use `sense*`; the throwing variants exist so
|
|
66
|
+
* the contract is testable and so a programming error is never silent.
|
|
67
|
+
*/
|
|
68
|
+
/** Which surface fired a `corpus_query`. Stamped as `activity_name`. */
|
|
69
|
+
export type QbdQuerySurface = 'totem_search' | 'search_knowledge';
|
|
70
|
+
/** Which derive-class action ran. Stamped as `activity_name`. */
|
|
71
|
+
export type QbdDeriveSurface = 'spec' | 'orient' | 'review';
|
|
72
|
+
export interface QbdRecordInput {
|
|
73
|
+
/** Absolute path to the resolved `.totem` directory. */
|
|
74
|
+
totemDir: string;
|
|
75
|
+
/** Emitting subsystem: `lint` for CLI commands, `bot` for the MCP server. */
|
|
76
|
+
source: 'lint' | 'bot';
|
|
77
|
+
/** Test seam — production callers omit and the writer reads the clock. */
|
|
78
|
+
nowMs?: number;
|
|
79
|
+
/** Test seam — production callers omit and the writer reads `process.env`. */
|
|
80
|
+
env?: NodeJS.ProcessEnv;
|
|
81
|
+
}
|
|
82
|
+
export interface QbdRecordResult {
|
|
83
|
+
/** True when the ledger row reached disk. */
|
|
84
|
+
written: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* True when nothing was recorded because this is not an instrumented project
|
|
87
|
+
* (no `.totem` directory). Distinguishes "nothing to instrument here" — a
|
|
88
|
+
* normal state — from "the write failed", which is a degradation.
|
|
89
|
+
*/
|
|
90
|
+
skipped?: boolean;
|
|
91
|
+
/** The correlation ID on the row, when it carries one. */
|
|
92
|
+
correlationId?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Per-item accounting. Every non-fatal degradation names itself here; the
|
|
95
|
+
* caller renders these so a sensor failure is visible, never silent
|
|
96
|
+
* (ADR-115 § 2 accounting contract).
|
|
97
|
+
*/
|
|
98
|
+
warnings: string[];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the emitting seat from `TOTEM_SELF_AGENT`, reusing the comma-split
|
|
102
|
+
* precedent of `deriveSearchLogAttribution` / `resolveSelfAgents` — a process
|
|
103
|
+
* runs under one seat, so the first non-empty entry is that seat.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveQbdAgentSource(env: NodeJS.ProcessEnv): string | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Record a corpus query and mint its correlation ID.
|
|
108
|
+
*
|
|
109
|
+
* `nowMs` is read ONCE and used for both the ID's embedded mint instant and the
|
|
110
|
+
* row's `timestamp` — the property the schema refinement checks.
|
|
111
|
+
*/
|
|
112
|
+
export declare function recordCorpusQuery(input: QbdRecordInput & {
|
|
113
|
+
surface: QbdQuerySurface;
|
|
114
|
+
}): QbdRecordResult;
|
|
115
|
+
/**
|
|
116
|
+
* Record a derive-class action, attaching the correlation ID of the query that
|
|
117
|
+
* grounded it when one is in scope.
|
|
118
|
+
*
|
|
119
|
+
* Throws on a correlation-contract breach — AFTER the derive row has been
|
|
120
|
+
* written uncorrelated. The ledger is left correct either way; the throw is the
|
|
121
|
+
* loud backstop that stops a tampered pointer from passing as a data point.
|
|
122
|
+
*/
|
|
123
|
+
export declare function recordDeriveAction(input: QbdRecordInput & {
|
|
124
|
+
surface: QbdDeriveSurface;
|
|
125
|
+
}): QbdRecordResult;
|
|
126
|
+
/** Sensor-safe `recordCorpusQuery`. Command call sites use this. */
|
|
127
|
+
export declare function senseCorpusQuery(input: QbdRecordInput & {
|
|
128
|
+
surface: QbdQuerySurface;
|
|
129
|
+
}, onWarn?: (msg: string) => void): QbdRecordResult;
|
|
130
|
+
/**
|
|
131
|
+
* Sensor-safe `recordDeriveAction`. Command call sites use this.
|
|
132
|
+
*
|
|
133
|
+
* A correlation-contract breach is reported as a warning rather than thrown —
|
|
134
|
+
* and, critically, the derive row it refused to correlate has still been
|
|
135
|
+
* written, so `written` stays true and the denominator is intact.
|
|
136
|
+
*/
|
|
137
|
+
export declare function senseDeriveAction(input: QbdRecordInput & {
|
|
138
|
+
surface: QbdDeriveSurface;
|
|
139
|
+
}, onWarn?: (msg: string) => void): QbdRecordResult;
|
|
140
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/qbd/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AA8BH,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,kBAAkB,CAAC;AAElE,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAI5D,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAID;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,SAAS,CAShF;AA6KD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,GAAG;IAAE,OAAO,EAAE,eAAe,CAAA;CAAE,GACnD,eAAe,CAyCjB;AA6FD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,cAAc,GAAG;IAAE,OAAO,EAAE,gBAAgB,CAAA;CAAE,GACpD,eAAe,CAIjB;AA2BD,oEAAoE;AACpE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,GAAG;IAAE,OAAO,EAAE,eAAe,CAAA;CAAE,EACpD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC7B,eAAe,CAEjB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,GAAG;IAAE,OAAO,EAAE,gBAAgB,CAAA;CAAE,EACrD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC7B,eAAe,CAMjB"}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query-before-derive (QBD) event writers (mmnto-ai/totem#2510).
|
|
3
|
+
*
|
|
4
|
+
* Two row types, one contract:
|
|
5
|
+
*
|
|
6
|
+
* - `recordCorpusQuery` — a corpus query fired. Mints a fresh correlation ID
|
|
7
|
+
* **from the same clock read that stamps the row's `timestamp`**, so the ID
|
|
8
|
+
* and the row are born together. Then parks the ID in a pointer file for the
|
|
9
|
+
* derives that follow.
|
|
10
|
+
* - `recordDeriveAction` — a derive-class action ran. Reads the parked ID and
|
|
11
|
+
* attaches it *only if* it is still inside the correlation window, from this
|
|
12
|
+
* session, and from this seat. Otherwise the row is written with NO
|
|
13
|
+
* correlation ID — an uncorrelated derive is the metric's most important
|
|
14
|
+
* observation, so it is always recorded, never dropped (#2510 falsifier 1).
|
|
15
|
+
*
|
|
16
|
+
* ## Consumption semantics: one query grounds ONE derive
|
|
17
|
+
*
|
|
18
|
+
* The pointer is CONSUMED by the first derive that correlates to it. Without
|
|
19
|
+
* that, a single query would credit every derive for the rest of the
|
|
20
|
+
* correlation window — one query, ten derives, compliance 1.00 — which measures
|
|
21
|
+
* "queried at least once per two hours", not "queried before deriving". The
|
|
22
|
+
* strict 1:1 reading is the one that can actually be falsified, so it is the
|
|
23
|
+
* one implemented. A second derive after a single query is UNCORRELATED, and
|
|
24
|
+
* that is the correct reading, not a miss.
|
|
25
|
+
*
|
|
26
|
+
* ## Correlation is scoped to one seat AND one session, fail-closed
|
|
27
|
+
*
|
|
28
|
+
* Both sides must carry a session id and agree on it, and the seats must match.
|
|
29
|
+
* Cohort seats share one working tree per repo, so a pointer left by another
|
|
30
|
+
* seat is reachable; treating a missing id as a match would let it ground this
|
|
31
|
+
* seat's derive.
|
|
32
|
+
*
|
|
33
|
+
* **Seat comparison is equality including "both unknown".** When neither side
|
|
34
|
+
* is seated (`TOTEM_SELF_AGENT` unset), they compare equal and correlation
|
|
35
|
+
* proceeds. That is the deliberate default for the solo/unseated case, which is
|
|
36
|
+
* the common one — requiring a seat would make the metric unusable for anyone
|
|
37
|
+
* not running a cohort. The cost is disclosed rather than hidden: seat exactly
|
|
38
|
+
* ONE side and correlation stops entirely. That is a live configuration today —
|
|
39
|
+
* the CLI can be seated while the MCP server is not — and it produces a
|
|
40
|
+
* truthful-looking 0.00 whose pre-registered consequence is falsified adherence
|
|
41
|
+
* claims. The scanner therefore emits a seat-mismatch hint (see
|
|
42
|
+
* `compliance.ts`) so the config smell is visible instead of silent. Wiring the
|
|
43
|
+
* MCP server's seat is out of this slice's scope and tracked in
|
|
44
|
+
* mmnto-ai/totem#2530.
|
|
45
|
+
*
|
|
46
|
+
* ## Why minting lives here and nowhere else
|
|
47
|
+
*
|
|
48
|
+
* `mintQbdCorrelationId(now)` takes the instant as an argument, which would let
|
|
49
|
+
* a caller pass any instant it liked. Every production writer goes through this
|
|
50
|
+
* module, where `now` is read once and used for BOTH the ID and the timestamp.
|
|
51
|
+
* A caller that tried to route around it would have to hand-build a row, and the
|
|
52
|
+
* schema refinement in `ledger.ts` rejects any row whose ID could not have been
|
|
53
|
+
* minted when the row was written. Convention here, enforcement in the schema.
|
|
54
|
+
*
|
|
55
|
+
* ## Failure posture (Tenet 13 + ADR-115 § 2)
|
|
56
|
+
*
|
|
57
|
+
* A contract breach — a pointer file holding a forged, malformed, or
|
|
58
|
+
* out-of-window ID — makes the schema reject the row, and these functions THROW
|
|
59
|
+
* (the loud backstop; a backfilled ID is a schema violation, not a data point).
|
|
60
|
+
* An ordinary I/O failure does not throw; it is reported through the returned
|
|
61
|
+
* `warnings` array.
|
|
62
|
+
*
|
|
63
|
+
* Neither may break the instrumented command. That is what the `sense*`
|
|
64
|
+
* wrappers are for: they catch the backstop, convert it to a visible warning,
|
|
65
|
+
* and return. Command call sites use `sense*`; the throwing variants exist so
|
|
66
|
+
* the contract is testable and so a programming error is never silent.
|
|
67
|
+
*/
|
|
68
|
+
import * as fs from 'node:fs';
|
|
69
|
+
import * as path from 'node:path';
|
|
70
|
+
import { TotemError } from '../errors.js';
|
|
71
|
+
import { appendLedgerEvent, LedgerEventSchema } from '../ledger.js';
|
|
72
|
+
import { readSessionId } from '../session-id.js';
|
|
73
|
+
import { checkQbdCorrelationId, mintQbdCorrelationId, QBD_CORRELATION_WINDOW_MS, } from './correlation-id.js';
|
|
74
|
+
// ─── Constants ──────────────────────────────────────────
|
|
75
|
+
const LEDGER_DIR = 'ledger';
|
|
76
|
+
/**
|
|
77
|
+
* Where the most recently minted correlation ID is parked for the derives that
|
|
78
|
+
* follow it. Mirrors the `.session-id` pointer convention deliberately — same
|
|
79
|
+
* directory, same fire-and-forget posture, same "a missing pointer is a normal
|
|
80
|
+
* state" reading.
|
|
81
|
+
*/
|
|
82
|
+
const QBD_POINTER_FILE = '.qbd-correlation';
|
|
83
|
+
/** UUID shape check — `session_id` is `z.string().uuid()` in the ledger schema. */
|
|
84
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
85
|
+
// ─── Env attribution ────────────────────────────────────
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the emitting seat from `TOTEM_SELF_AGENT`, reusing the comma-split
|
|
88
|
+
* precedent of `deriveSearchLogAttribution` / `resolveSelfAgents` — a process
|
|
89
|
+
* runs under one seat, so the first non-empty entry is that seat.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveQbdAgentSource(env) {
|
|
92
|
+
const raw = env.TOTEM_SELF_AGENT;
|
|
93
|
+
if (typeof raw !== 'string')
|
|
94
|
+
return undefined;
|
|
95
|
+
return (raw
|
|
96
|
+
.split(',')
|
|
97
|
+
.map((s) => s.trim())
|
|
98
|
+
.find((s) => s.length > 0) ?? undefined);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the session UUID: the `.session-id` pointer first (the established
|
|
102
|
+
* primitive), then `TOTEM_SESSION_ID`. A value that is not UUID-shaped is
|
|
103
|
+
* dropped rather than stamped — the ledger schema requires a UUID, and stamping
|
|
104
|
+
* a malformed one would make the whole row unparseable, losing the event.
|
|
105
|
+
*/
|
|
106
|
+
function resolveSessionId(totemDir, env) {
|
|
107
|
+
const fromPointer = readSessionId(totemDir);
|
|
108
|
+
if (fromPointer !== undefined)
|
|
109
|
+
return fromPointer;
|
|
110
|
+
const fromEnv = env.TOTEM_SESSION_ID;
|
|
111
|
+
if (typeof fromEnv === 'string' && UUID_PATTERN.test(fromEnv.trim()))
|
|
112
|
+
return fromEnv.trim();
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
function pointerPath(totemDir) {
|
|
116
|
+
return path.join(totemDir, LEDGER_DIR, QBD_POINTER_FILE);
|
|
117
|
+
}
|
|
118
|
+
function writePointer(totemDir, pointer, warnings) {
|
|
119
|
+
try {
|
|
120
|
+
fs.mkdirSync(path.join(totemDir, LEDGER_DIR), { recursive: true });
|
|
121
|
+
fs.writeFileSync(pointerPath(totemDir), JSON.stringify(pointer), 'utf-8');
|
|
122
|
+
// totem-context: intentional cleanup — the correlation pointer is telemetry; a sensor must never fail the command it instruments (Tenet 13). NOT swallowed: recorded on the `warnings` accounting channel and surfaced by the caller.
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// totem-context: intentional cleanup — the correlation pointer is telemetry; a sensor must never fail the command it instruments (Tenet 13). NOT swallowed: recorded on the `warnings` accounting channel and surfaced by the caller.
|
|
126
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
127
|
+
warnings.push(`query-before-derive: correlation pointer write failed: ${msg}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Consume the parked pointer, so one query grounds exactly one derive.
|
|
132
|
+
*
|
|
133
|
+
* Best-effort: if the pointer cannot be removed, the failure is reported on the
|
|
134
|
+
* accounting channel. A surviving pointer would over-credit, so this is a real
|
|
135
|
+
* degradation and is never silent.
|
|
136
|
+
*/
|
|
137
|
+
function clearPointer(totemDir, warnings) {
|
|
138
|
+
try {
|
|
139
|
+
fs.rmSync(pointerPath(totemDir), { force: true });
|
|
140
|
+
// totem-context: intentional cleanup — consuming the correlation pointer is telemetry bookkeeping; a failure is reported on the `warnings` accounting channel rather than thrown, because it must not fail the instrumented command (Tenet 13).
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
// totem-context: intentional cleanup — consuming the correlation pointer is telemetry bookkeeping; a failure is reported on the `warnings` accounting channel rather than thrown, because it must not fail the instrumented command (Tenet 13).
|
|
144
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
145
|
+
warnings.push(`query-before-derive: could not consume the correlation pointer (${msg}) — it may over-credit until removed`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Read the parked correlation pointer. A missing or unreadable pointer is a
|
|
150
|
+
* normal state (first query of a session, fresh repo) and reads as `undefined`.
|
|
151
|
+
*
|
|
152
|
+
* A pointer that is present but structurally wrong is NOT silently ignored — it
|
|
153
|
+
* is returned as-is so the schema check downstream rejects it loudly. Swallowing
|
|
154
|
+
* it here would be exactly the silent-degradation shape ADR-115 § 2 forbids.
|
|
155
|
+
*/
|
|
156
|
+
function readPointer(totemDir, warnings) {
|
|
157
|
+
let raw;
|
|
158
|
+
try {
|
|
159
|
+
raw = fs.readFileSync(pointerPath(totemDir), 'utf-8');
|
|
160
|
+
// totem-context: intentional cleanup — an absent pointer (ENOENT) is a normal state, not a failure; every OTHER errno is recorded on the `warnings` accounting channel rather than thrown, because telemetry must not fail the instrumented command (Tenet 13).
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
// totem-context: intentional cleanup — an absent pointer (ENOENT) is a normal state, not a failure; every OTHER errno is recorded on the `warnings` accounting channel rather than thrown, because telemetry must not fail the instrumented command (Tenet 13).
|
|
164
|
+
const code = typeof err === 'object' && err !== null ? err.code : undefined;
|
|
165
|
+
// ENOTDIR alongside ENOENT: a path COMPONENT being a file means there is no
|
|
166
|
+
// pointer here, same as the file being absent. Same cross-platform
|
|
167
|
+
// divergence `session-id.ts` documents — POSIX says ENOTDIR where Windows
|
|
168
|
+
// says ENOENT for the identical on-disk state — so both must read as
|
|
169
|
+
// "absent", or the derive path warns on one platform and not the other.
|
|
170
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') {
|
|
171
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
172
|
+
warnings.push(`query-before-derive: correlation pointer unreadable: ${msg}`);
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const parsed = JSON.parse(raw);
|
|
178
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
179
|
+
warnings.push('query-before-derive: correlation pointer is not an object — ignoring');
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const rec = parsed;
|
|
183
|
+
if (typeof rec.id !== 'string' || typeof rec.mintedAtMs !== 'number') {
|
|
184
|
+
warnings.push('query-before-derive: correlation pointer is missing id/mintedAtMs — ignoring');
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
id: rec.id,
|
|
189
|
+
mintedAtMs: rec.mintedAtMs,
|
|
190
|
+
sessionId: typeof rec.sessionId === 'string' ? rec.sessionId : null,
|
|
191
|
+
agentSource: typeof rec.agentSource === 'string' ? rec.agentSource : null,
|
|
192
|
+
};
|
|
193
|
+
// totem-context: intentional cleanup — a corrupt pointer degrades to "no correlation in scope" and is reported on the `warnings` accounting channel; throwing would let a hand-edited telemetry file break `totem spec` / `orient` / `review`.
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
// totem-context: intentional cleanup — a corrupt pointer degrades to "no correlation in scope" and is reported on the `warnings` accounting channel; throwing would let a hand-edited telemetry file break `totem spec` / `orient` / `review`.
|
|
197
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
198
|
+
warnings.push(`query-before-derive: correlation pointer is not valid JSON (${msg}) — ignoring`);
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// ─── Shared write path ──────────────────────────────────
|
|
203
|
+
/**
|
|
204
|
+
* Is this an instrumented project at all?
|
|
205
|
+
*
|
|
206
|
+
* The derive-class commands (`orient` especially) run from anywhere, including
|
|
207
|
+
* directories that are not Totem projects. `appendLedgerEvent` creates its
|
|
208
|
+
* directory tree on demand, so without this guard the sensor would materialise a
|
|
209
|
+
* stray `.totem/ledger/` in any directory a command happened to run in.
|
|
210
|
+
*
|
|
211
|
+
* An absent `.totem` is a NORMAL state, not a degradation — it means "nothing
|
|
212
|
+
* here to instrument" — so this path is silent rather than warned. A totemDir
|
|
213
|
+
* that EXISTS but cannot be written to is a different thing entirely, and that
|
|
214
|
+
* still reports through the accounting channel.
|
|
215
|
+
*/
|
|
216
|
+
function isInstrumentedProject(totemDir) {
|
|
217
|
+
try {
|
|
218
|
+
return fs.statSync(totemDir).isDirectory();
|
|
219
|
+
// totem-context: a missing `.totem` is the honest "not a Totem project" state for this sensor — skip silently rather than creating a stray directory in an unrelated cwd.
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Validate against the ledger schema, then append.
|
|
227
|
+
*
|
|
228
|
+
* The schema carries the minted-at-write-time refinement, so this is the single
|
|
229
|
+
* choke point where a backfilled or forged correlation ID becomes a thrown
|
|
230
|
+
* error instead of a persisted "data point".
|
|
231
|
+
*/
|
|
232
|
+
function validateAndAppend(totemDir, event, warnings) {
|
|
233
|
+
const parsed = LedgerEventSchema.safeParse(event);
|
|
234
|
+
if (!parsed.success) {
|
|
235
|
+
const detail = parsed.error.issues.map((i) => i.message).join('; ');
|
|
236
|
+
throw new TotemError('QBD_CORRELATION_CONTRACT', `Refusing to write a query-before-derive row that violates the correlation contract: ${detail}`, 'A correlation ID must be minted at event-write time. Delete the stale `.totem/ledger/.qbd-correlation` pointer if it was hand-edited.');
|
|
237
|
+
}
|
|
238
|
+
let written = true;
|
|
239
|
+
appendLedgerEvent(totemDir, parsed.data, (msg) => {
|
|
240
|
+
written = false;
|
|
241
|
+
warnings.push(`query-before-derive: ${msg}`);
|
|
242
|
+
});
|
|
243
|
+
return { written };
|
|
244
|
+
}
|
|
245
|
+
// ─── Writers ────────────────────────────────────────────
|
|
246
|
+
/**
|
|
247
|
+
* Record a corpus query and mint its correlation ID.
|
|
248
|
+
*
|
|
249
|
+
* `nowMs` is read ONCE and used for both the ID's embedded mint instant and the
|
|
250
|
+
* row's `timestamp` — the property the schema refinement checks.
|
|
251
|
+
*/
|
|
252
|
+
export function recordCorpusQuery(input) {
|
|
253
|
+
const env = input.env ?? process.env;
|
|
254
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
255
|
+
const warnings = [];
|
|
256
|
+
if (!isInstrumentedProject(input.totemDir))
|
|
257
|
+
return { written: false, skipped: true, warnings };
|
|
258
|
+
const correlationId = mintQbdCorrelationId(nowMs);
|
|
259
|
+
const sessionId = resolveSessionId(input.totemDir, env);
|
|
260
|
+
const agentSource = resolveQbdAgentSource(env);
|
|
261
|
+
const event = {
|
|
262
|
+
timestamp: new Date(nowMs).toISOString(),
|
|
263
|
+
type: 'corpus_query',
|
|
264
|
+
activity_name: input.surface,
|
|
265
|
+
source: input.source,
|
|
266
|
+
justification: '',
|
|
267
|
+
qbd_correlation_id: correlationId,
|
|
268
|
+
...(sessionId !== undefined && { session_id: sessionId }),
|
|
269
|
+
...(agentSource !== undefined && { agent_source: agentSource }),
|
|
270
|
+
};
|
|
271
|
+
const { written } = validateAndAppend(input.totemDir, event, warnings);
|
|
272
|
+
// Park the ID for following derives ONLY if the query row actually landed.
|
|
273
|
+
// Parking an ID whose query row failed to write would manufacture orphan
|
|
274
|
+
// correlations — derives pointing at a query that does not exist.
|
|
275
|
+
if (written) {
|
|
276
|
+
writePointer(input.totemDir, {
|
|
277
|
+
id: correlationId,
|
|
278
|
+
mintedAtMs: nowMs,
|
|
279
|
+
sessionId: sessionId ?? null,
|
|
280
|
+
agentSource: agentSource ?? null,
|
|
281
|
+
}, warnings);
|
|
282
|
+
}
|
|
283
|
+
return { written, correlationId, warnings };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Shared derive write path used by both the throwing and the sensor variants.
|
|
287
|
+
*
|
|
288
|
+
* The parked pointer grounds this derive only when it is (a) inside the
|
|
289
|
+
* correlation window, (b) from this session, and (c) contract-valid. Otherwise
|
|
290
|
+
* the row is written WITHOUT an ID — the honest reading being that nothing
|
|
291
|
+
* grounded this derive — and case (c) additionally reports a breach.
|
|
292
|
+
*/
|
|
293
|
+
function buildAndWriteDerive(input) {
|
|
294
|
+
const env = input.env ?? process.env;
|
|
295
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
296
|
+
const warnings = [];
|
|
297
|
+
if (!isInstrumentedProject(input.totemDir)) {
|
|
298
|
+
return { result: { written: false, skipped: true, warnings } };
|
|
299
|
+
}
|
|
300
|
+
const sessionId = resolveSessionId(input.totemDir, env);
|
|
301
|
+
const agentSource = resolveQbdAgentSource(env);
|
|
302
|
+
const pointer = readPointer(input.totemDir, warnings);
|
|
303
|
+
let correlationId;
|
|
304
|
+
let breach;
|
|
305
|
+
if (pointer !== undefined) {
|
|
306
|
+
// Same session, fail-CLOSED. Correlation requires BOTH sides to carry a
|
|
307
|
+
// session id and for them to be equal. The previous "either side missing
|
|
308
|
+
// counts as a match" reading was a fail-open: a hookless run with no session
|
|
309
|
+
// id would inherit any pointer lying around, including another seat's.
|
|
310
|
+
const sameSession = pointer.sessionId !== null && sessionId !== undefined && pointer.sessionId === sessionId;
|
|
311
|
+
// Same seat. Cohort seats share ONE working tree per repo, so without this
|
|
312
|
+
// an agent-A query would ground an agent-B derive — a live scenario here,
|
|
313
|
+
// not a theoretical one. `agent_source` was recorded but never consulted.
|
|
314
|
+
const sameAgent = (pointer.agentSource ?? undefined) === agentSource;
|
|
315
|
+
const withinWindow = nowMs - pointer.mintedAtMs <= QBD_CORRELATION_WINDOW_MS;
|
|
316
|
+
// A stale or foreign pointer is not an error — it simply did not ground
|
|
317
|
+
// this derive. The row is still written, uncorrelated, into the denominator.
|
|
318
|
+
if (sameSession && sameAgent && withinWindow) {
|
|
319
|
+
const check = checkQbdCorrelationId(pointer.id, 'derive_action', nowMs);
|
|
320
|
+
if (check.ok) {
|
|
321
|
+
correlationId = pointer.id;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
// Contract breach: the parked ID could not have been minted at a moment
|
|
325
|
+
// that would let it ground THIS derive (forged, hand-edited, or from a
|
|
326
|
+
// clock that ran backwards). It is refused — but the derive row is still
|
|
327
|
+
// written, uncorrelated, so the denominator never shrinks because the
|
|
328
|
+
// sensor was tampered with (#2510 falsifier 1). The breach is then
|
|
329
|
+
// raised loudly by the caller.
|
|
330
|
+
breach = new TotemError('QBD_CORRELATION_CONTRACT', `Refusing a query-before-derive correlation ID that violates the minted-at-write-time contract (${check.violation ?? 'invalid'}): ${check.detail ?? 'invalid id'}`, 'Delete the `.totem/ledger/.qbd-correlation` pointer; it was hand-edited or written by a clock that disagrees with this one. The derive was recorded as uncorrelated.');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const event = {
|
|
335
|
+
timestamp: new Date(nowMs).toISOString(),
|
|
336
|
+
type: 'derive_action',
|
|
337
|
+
activity_name: input.surface,
|
|
338
|
+
source: input.source,
|
|
339
|
+
justification: '',
|
|
340
|
+
...(correlationId !== undefined && { qbd_correlation_id: correlationId }),
|
|
341
|
+
...(sessionId !== undefined && { session_id: sessionId }),
|
|
342
|
+
...(agentSource !== undefined && { agent_source: agentSource }),
|
|
343
|
+
};
|
|
344
|
+
const { written } = validateAndAppend(input.totemDir, event, warnings);
|
|
345
|
+
// Consume-on-use: a query grounds exactly ONE derive. Without this the parked
|
|
346
|
+
// ID stays live for the whole correlation window, so a single query could
|
|
347
|
+
// credit an unbounded run of derives (measured: 1 query, 10 derives,
|
|
348
|
+
// compliance 1.00). That reading is not "query before derive", it is "query
|
|
349
|
+
// once per two hours", which is precisely the shape the metric exists to
|
|
350
|
+
// detect. Consuming only after a SUCCESSFUL correlated write means a failed
|
|
351
|
+
// append cannot silently burn the query's grounding.
|
|
352
|
+
if (written && correlationId !== undefined)
|
|
353
|
+
clearPointer(input.totemDir, warnings);
|
|
354
|
+
const result = {
|
|
355
|
+
written,
|
|
356
|
+
...(correlationId !== undefined && { correlationId }),
|
|
357
|
+
warnings,
|
|
358
|
+
};
|
|
359
|
+
return breach === undefined ? { result } : { result, breach };
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Record a derive-class action, attaching the correlation ID of the query that
|
|
363
|
+
* grounded it when one is in scope.
|
|
364
|
+
*
|
|
365
|
+
* Throws on a correlation-contract breach — AFTER the derive row has been
|
|
366
|
+
* written uncorrelated. The ledger is left correct either way; the throw is the
|
|
367
|
+
* loud backstop that stops a tampered pointer from passing as a data point.
|
|
368
|
+
*/
|
|
369
|
+
export function recordDeriveAction(input) {
|
|
370
|
+
const { result, breach } = buildAndWriteDerive(input);
|
|
371
|
+
if (breach !== undefined)
|
|
372
|
+
throw breach;
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
// ─── Sensor wrappers (never throw) ──────────────────────
|
|
376
|
+
/**
|
|
377
|
+
* Run a QBD writer as a pure sensor: it may degrade, it may not break the
|
|
378
|
+
* command it instruments (Tenet 13 / #2510 "sensor failure is not command
|
|
379
|
+
* failure").
|
|
380
|
+
*
|
|
381
|
+
* Every degradation — including a thrown contract breach — is surfaced through
|
|
382
|
+
* `onWarn`. The accounting contract is "visible, not swallowed": there is no
|
|
383
|
+
* path here that discards a failure silently.
|
|
384
|
+
*/
|
|
385
|
+
function senseQbd(run, onWarn) {
|
|
386
|
+
try {
|
|
387
|
+
const result = run();
|
|
388
|
+
for (const warning of result.warnings)
|
|
389
|
+
onWarn?.(warning);
|
|
390
|
+
return result;
|
|
391
|
+
// totem-context: instrumentation is a sensor — a telemetry failure must never fail the instrumented command (Tenet 13); the failure is reported through onWarn, never discarded.
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
395
|
+
const warning = `query-before-derive sensor failed: ${msg}`;
|
|
396
|
+
onWarn?.(warning);
|
|
397
|
+
return { written: false, warnings: [warning] };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/** Sensor-safe `recordCorpusQuery`. Command call sites use this. */
|
|
401
|
+
export function senseCorpusQuery(input, onWarn) {
|
|
402
|
+
return senseQbd(() => recordCorpusQuery(input), onWarn);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Sensor-safe `recordDeriveAction`. Command call sites use this.
|
|
406
|
+
*
|
|
407
|
+
* A correlation-contract breach is reported as a warning rather than thrown —
|
|
408
|
+
* and, critically, the derive row it refused to correlate has still been
|
|
409
|
+
* written, so `written` stays true and the denominator is intact.
|
|
410
|
+
*/
|
|
411
|
+
export function senseDeriveAction(input, onWarn) {
|
|
412
|
+
return senseQbd(() => {
|
|
413
|
+
const { result, breach } = buildAndWriteDerive(input);
|
|
414
|
+
if (breach !== undefined)
|
|
415
|
+
result.warnings.push(`query-before-derive: ${breach.message}`);
|
|
416
|
+
return result;
|
|
417
|
+
}, onWarn);
|
|
418
|
+
}
|
|
419
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/qbd/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,2DAA2D;AAE3D,MAAM,UAAU,GAAG,QAAQ,CAAC;AAE5B;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAE5C,mFAAmF;AACnF,MAAM,YAAY,GAAG,iEAAiE,CAAC;AAwCvF,2DAA2D;AAE3D;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAsB;IAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,gBAAgB,CAAC;IACjC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9C,OAAO,CACL,GAAG;SACA,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,SAAS,CAC1C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,QAAgB,EAAE,GAAsB;IAChE,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAClD,MAAM,OAAO,GAAG,GAAG,CAAC,gBAAgB,CAAC;IACrC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IAC5F,OAAO,SAAS,CAAC;AACnB,CAAC;AAYD,SAAS,WAAW,CAAC,QAAgB;IACnC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,YAAY,CAAC,QAAgB,EAAE,OAAmB,EAAE,QAAkB;IAC7E,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1E,sOAAsO;IACxO,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,sOAAsO;QACtO,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,QAAQ,CAAC,IAAI,CAAC,0DAA0D,GAAG,EAAE,CAAC,CAAC;IACjF,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,QAAgB,EAAE,QAAkB;IACxD,IAAI,CAAC;QACH,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,gPAAgP;IAClP,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,gPAAgP;QAChP,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,QAAQ,CAAC,IAAI,CACX,mEAAmE,GAAG,sCAAsC,CAC7G,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,QAAgB,EAAE,QAAkB;IACvD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QACtD,gQAAgQ;IAClQ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,gQAAgQ;QAChQ,MAAM,IAAI,GACR,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAE,GAA6B,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5F,4EAA4E;QAC5E,mEAAmE;QACnE,0EAA0E;QAC1E,qEAAqE;QACrE,wEAAwE;QACxE,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5C,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,QAAQ,CAAC,IAAI,CAAC,wDAAwD,GAAG,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAClD,QAAQ,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;YACtF,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,GAAG,GAAG,MAAiC,CAAC;QAC9C,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;YACrE,QAAQ,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAC;YAC9F,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,SAAS,EAAE,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;YACnE,WAAW,EAAE,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;SAC1E,CAAC;QACF,+OAA+O;IACjP,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,+OAA+O;QAC/O,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,QAAQ,CAAC,IAAI,CAAC,+DAA+D,GAAG,cAAc,CAAC,CAAC;QAChG,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,2DAA2D;AAE3D;;;;;;;;;;;;GAYG;AACH,SAAS,qBAAqB,CAAC,QAAgB;IAC7C,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3C,0KAA0K;IAC5K,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CACxB,QAAgB,EAChB,KAAkB,EAClB,QAAkB;IAElB,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpE,MAAM,IAAI,UAAU,CAClB,0BAA0B,EAC1B,uFAAuF,MAAM,EAAE,EAC/F,uIAAuI,CACxI,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;QAC/C,OAAO,GAAG,KAAK,CAAC;QAChB,QAAQ,CAAC,IAAI,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAED,2DAA2D;AAE3D;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAoD;IAEpD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACxC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAE/F,MAAM,aAAa,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAE/C,MAAM,KAAK,GAAgB;QACzB,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE;QACxC,IAAI,EAAE,cAAc;QACpB,aAAa,EAAE,KAAK,CAAC,OAAO;QAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,aAAa,EAAE,EAAE;QACjB,kBAAkB,EAAE,aAAa;QACjC,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QACzD,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;KAChE,CAAC;IAEF,MAAM,EAAE,OAAO,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAEvE,2EAA2E;IAC3E,yEAAyE;IACzE,kEAAkE;IAClE,IAAI,OAAO,EAAE,CAAC;QACZ,YAAY,CACV,KAAK,CAAC,QAAQ,EACd;YACE,EAAE,EAAE,aAAa;YACjB,UAAU,EAAE,KAAK;YACjB,SAAS,EAAE,SAAS,IAAI,IAAI;YAC5B,WAAW,EAAE,WAAW,IAAI,IAAI;SACjC,EACD,QAAQ,CACT,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,mBAAmB,CAAC,KAAqD;IAIhF,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACxC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACjE,CAAC;IAED,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAEtD,IAAI,aAAiC,CAAC;IACtC,IAAI,MAA8B,CAAC;IAEnC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,wEAAwE;QACxE,yEAAyE;QACzE,6EAA6E;QAC7E,uEAAuE;QACvE,MAAM,WAAW,GACf,OAAO,CAAC,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;QAC3F,2EAA2E;QAC3E,0EAA0E;QAC1E,0EAA0E;QAC1E,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,SAAS,CAAC,KAAK,WAAW,CAAC;QACrE,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO,CAAC,UAAU,IAAI,yBAAyB,CAAC;QAC7E,wEAAwE;QACxE,6EAA6E;QAC7E,IAAI,WAAW,IAAI,SAAS,IAAI,YAAY,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;YACxE,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;gBACb,aAAa,GAAG,OAAO,CAAC,EAAE,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,wEAAwE;gBACxE,uEAAuE;gBACvE,yEAAyE;gBACzE,sEAAsE;gBACtE,mEAAmE;gBACnE,+BAA+B;gBAC/B,MAAM,GAAG,IAAI,UAAU,CACrB,0BAA0B,EAC1B,kGAAkG,KAAK,CAAC,SAAS,IAAI,SAAS,MAAM,KAAK,CAAC,MAAM,IAAI,YAAY,EAAE,EAClK,sKAAsK,CACvK,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAgB;QACzB,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE;QACxC,IAAI,EAAE,eAAe;QACrB,aAAa,EAAE,KAAK,CAAC,OAAO;QAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,aAAa,EAAE,EAAE;QACjB,GAAG,CAAC,aAAa,KAAK,SAAS,IAAI,EAAE,kBAAkB,EAAE,aAAa,EAAE,CAAC;QACzE,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QACzD,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;KAChE,CAAC;IAEF,MAAM,EAAE,OAAO,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAEvE,8EAA8E;IAC9E,0EAA0E;IAC1E,qEAAqE;IACrE,4EAA4E;IAC5E,yEAAyE;IACzE,4EAA4E;IAC5E,qDAAqD;IACrD,IAAI,OAAO,IAAI,aAAa,KAAK,SAAS;QAAE,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAEnF,MAAM,MAAM,GAAoB;QAC9B,OAAO;QACP,GAAG,CAAC,aAAa,KAAK,SAAS,IAAI,EAAE,aAAa,EAAE,CAAC;QACrD,QAAQ;KACT,CAAC;IACF,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAChE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAqD;IAErD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACtD,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,MAAM,CAAC;IACvC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2DAA2D;AAE3D;;;;;;;;GAQG;AACH,SAAS,QAAQ,CAAC,GAA0B,EAAE,MAA8B;IAC1E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC;QACrB,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ;YAAE,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;QACzD,OAAO,MAAM,CAAC;QACd,iLAAiL;IACnL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,sCAAsC,GAAG,EAAE,CAAC;QAC5D,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;QAClB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACjD,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAC9B,KAAoD,EACpD,MAA8B;IAE9B,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAqD,EACrD,MAA8B;IAE9B,OAAO,QAAQ,CAAC,GAAG,EAAE;QACnB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACtD,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,OAAO,MAAM,CAAC;IAChB,CAAC,EAAE,MAAM,CAAC,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writer tests for the query-before-derive sensor (mmnto-ai/totem#2510).
|
|
3
|
+
*
|
|
4
|
+
* The induced-failure blocks follow the ADR-115 § 2 three-pack convention used
|
|
5
|
+
* by `mail-degraded-e4.test.ts`:
|
|
6
|
+
* 1. per-item accounting fires — the specific failing item is NAMED in the
|
|
7
|
+
* warnings channel;
|
|
8
|
+
* 2. the loud backstop throws — for this MUTATING path that is a literal
|
|
9
|
+
* throw from `recordDeriveAction`, and the ledger is left in prior-good
|
|
10
|
+
* state (the derive row present, simply uncorrelated) rather than
|
|
11
|
+
* "presenting as success while partial";
|
|
12
|
+
* 3. a recovery / control assertion — with the fault removed the same call
|
|
13
|
+
* returns to green, and a healthy control never warns.
|
|
14
|
+
*
|
|
15
|
+
* **The two ADR-115 § 2 packs** induce their failures with real errnos (a file
|
|
16
|
+
* where a directory belongs) or real on-disk state (a hand-written pointer),
|
|
17
|
+
* never by mocking the module under test — that is what makes them evidence
|
|
18
|
+
* rather than assertion.
|
|
19
|
+
*
|
|
20
|
+
* Other tests in this file DO stamp errnos onto `fs` via `vi.spyOn` (EACCES,
|
|
21
|
+
* ENOSPC). That is deliberate and scoped: ENOSPC is not inducible on a real
|
|
22
|
+
* filesystem, and those tests cover accounting branches rather than the
|
|
23
|
+
* degraded-path contract. The blanket "never by mocking" claim used to sit at
|
|
24
|
+
* file level and overstated what four of these tests do.
|
|
25
|
+
*/
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=record.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.test.d.ts","sourceRoot":"","sources":["../../src/qbd/record.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG"}
|