@mmnto/totem 1.115.0 → 1.116.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.
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Agent-attribution provenance sense (mmnto-ai/totem#2629).
3
+ *
4
+ * On a shared multi-seat machine, a seat session that mints no identity of its
5
+ * own can inherit ANOTHER seat's attribution through two ambient channels:
6
+ * a wider-than-launch-shell `TOTEM_SELF_AGENT` (user/machine scope) and the
7
+ * shared `.totem/ledger/.session-id` pointer. A wrong-seat row corrupts two
8
+ * denominators; an unattributed row only under-counts — so the ruled posture
9
+ * (operator, 2026-08-12, on the issue) is:
10
+ *
11
+ * - **Provenance on every row, not only on mismatch:** `agent_source_provenance`
12
+ * is the two-value `'env' | 'absent'` enum — `'env'` iff `agent_source` was
13
+ * stamped from `TOTEM_SELF_AGENT`, `'absent'` iff no attribution was stamped
14
+ * (the honest stamped-absence default, Tenet 4).
15
+ * - **Fail-closed at the stamp seam:** when the ambient env seat and the
16
+ * session pointer's minting seat disagree, stamp NEITHER — no `agent_source`,
17
+ * plus a conflict disclosure naming both candidates. Fail-closed scopes to
18
+ * the *attribution*, never the *operation*: the sense never throws into its
19
+ * caller, so the query/write it decorates is never refused (Tenet 13).
20
+ * - **Never remembered:** identity derives from process env + on-disk state at
21
+ * call time, per call. No cache at any scope — a session-lifetime cache
22
+ * converts a transient contamination into a whole-session one (the
23
+ * `b8d7aa9b` specimen shape).
24
+ *
25
+ * The minting seat is derived from the pointer session's `session_start` row
26
+ * in `events.ndjson` — written by the SessionStart hook's mint block at the
27
+ * #2625 template, so the lookup needs no new producer. Absence of that row
28
+ * (pre-hook ledger, rotated file, unseated mint) is NOT a disagreement: only
29
+ * positive counter-evidence strips attribution.
30
+ *
31
+ * NAMED LIMIT (stated so coverage is never overread): the conflict arm fires
32
+ * only when two PRESENT candidates disagree — a seated process joining a
33
+ * foreign seat's pointer session. The b8d7aa9b specimen itself had an
34
+ * UNSEATED mint row (its minting process's env carried no var), and a
35
+ * machine-wide ambient var makes every mint agree with every process env —
36
+ * both shapes yield an env stamp with no conflict here: the ruling's
37
+ * fail-closed clause covers disagreement between two PRESENT candidates, and
38
+ * the missing-counter-evidence rule (design invariant 3) covers the rest. The
39
+ * sensors for the ambient-scope class are the provenance field (post-hoc
40
+ * partitioning), doctor arm (d)'s registry scope-sense, and the launch-shell
41
+ * scope protocol — not this predicate.
42
+ */
43
+ /** The ruled two-value provenance enum — `'env' | 'absent'`, nothing else. */
44
+ export declare const AGENT_SOURCE_PROVENANCE_VALUES: readonly ["env", "absent"];
45
+ export type AgentSourceProvenance = (typeof AGENT_SOURCE_PROVENANCE_VALUES)[number];
46
+ /**
47
+ * The fail-closed disclosure: both candidates named, so a post-hoc reader can
48
+ * partition contaminated windows without guessing which seat was real.
49
+ */
50
+ export interface AttributionConflict {
51
+ /** What ambient `TOTEM_SELF_AGENT` claimed (first non-empty comma entry). */
52
+ env_seat: string;
53
+ /** What the pointer session's `session_start` row recorded at mint time. */
54
+ minting_seat: string;
55
+ /** The `.session-id` pointer value the two candidates disagree about. */
56
+ pointer_session_id: string;
57
+ }
58
+ /** The per-call sense result both stamp seams consume. */
59
+ export interface AgentAttributionSense {
60
+ /** Seat to stamp, or `null` — never a guessed value (Tenet 4). */
61
+ agent_source: string | null;
62
+ /** `'env'` iff `agent_source` is non-null — writer-guaranteed biconditional. */
63
+ agent_source_provenance: AgentSourceProvenance;
64
+ /** Present iff the fail-closed rule fired (env seat ≠ minting seat). */
65
+ attribution_conflict?: AttributionConflict;
66
+ /**
67
+ * Present iff the conflict probe failed for a non-benign reason (EACCES-class
68
+ * read failure, torn pointer read throw). The env stamp stands — a broken
69
+ * probe is not counter-evidence — but the degradation names itself on the
70
+ * row rather than vanishing (Tenet 4).
71
+ */
72
+ attribution_probe_error?: string;
73
+ }
74
+ /** Discriminated lookup result — `found: false` is a normal state, not an error. */
75
+ export type SessionMintingSeatLookup = {
76
+ found: true;
77
+ seat: string | null;
78
+ } | {
79
+ found: false;
80
+ };
81
+ /**
82
+ * Derive the minting seat of a session from its `session_start` row in
83
+ * `<totemDir>/ledger/events.ndjson`.
84
+ *
85
+ * Scans BACKWARDS (newest row wins) with a cheap substring prefilter before
86
+ * any `JSON.parse`, so the common case — an active session whose mint row is
87
+ * near the tail — costs a file read plus a short scan. Malformed/torn lines
88
+ * are skipped per line (the `parseSearchLog` tolerance precedent): a torn
89
+ * `session_start` reads as not-found, never as a fabricated conflict.
90
+ *
91
+ * Absent events file (ENOENT class) returns `found: false` — the honest
92
+ * not-instrumented state. Every other read errno throws a `TotemError` so the
93
+ * caller can name the probe failure instead of silently degrading.
94
+ */
95
+ export declare function readSessionMintingSeat(totemDir: string, sessionId: string): SessionMintingSeatLookup;
96
+ export interface AgentAttributionSenseInput {
97
+ /**
98
+ * Resolved `.totem` directory. Omit to run env-only (no conflict probe) —
99
+ * the honest shape for callers with no ledger in reach; provenance is still
100
+ * disclosed.
101
+ */
102
+ totemDir?: string;
103
+ /** Test seam — production callers omit and the sense reads `process.env`. */
104
+ env?: NodeJS.ProcessEnv;
105
+ /**
106
+ * The pointer session id the caller already resolved for the row it is
107
+ * stamping (the manifest builder's shape — one pointer read per row, so the
108
+ * probe cannot race a concurrent rotation into checking a DIFFERENT session
109
+ * than the row joins). Omit and the probe reads the pointer itself.
110
+ */
111
+ pointerSessionId?: string;
112
+ }
113
+ /**
114
+ * Derive the agent-attribution stamp for one row. Per call, never cached —
115
+ * see the module contract. Never throws: every probe failure degrades to the
116
+ * env-only stamp with the failure named on `attribution_probe_error`.
117
+ */
118
+ export declare function senseAgentAttribution(input?: AgentAttributionSenseInput): AgentAttributionSense;
119
+ //# sourceMappingURL=attribution-sense.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution-sense.d.ts","sourceRoot":"","sources":["../src/attribution-sense.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAoBH,8EAA8E;AAC9E,eAAO,MAAM,8BAA8B,4BAA6B,CAAC;AACzE,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpF;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,0DAA0D;AAC1D,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gFAAgF;IAChF,uBAAuB,EAAE,qBAAqB,CAAC;IAC/C,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,mBAAmB,CAAC;IAC3C;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,oFAAoF;AACpF,MAAM,MAAM,wBAAwB,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAE/F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,wBAAwB,CA6C1B;AAED,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,GAAE,0BAA+B,GACrC,qBAAqB,CAgDvB"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Agent-attribution provenance sense (mmnto-ai/totem#2629).
3
+ *
4
+ * On a shared multi-seat machine, a seat session that mints no identity of its
5
+ * own can inherit ANOTHER seat's attribution through two ambient channels:
6
+ * a wider-than-launch-shell `TOTEM_SELF_AGENT` (user/machine scope) and the
7
+ * shared `.totem/ledger/.session-id` pointer. A wrong-seat row corrupts two
8
+ * denominators; an unattributed row only under-counts — so the ruled posture
9
+ * (operator, 2026-08-12, on the issue) is:
10
+ *
11
+ * - **Provenance on every row, not only on mismatch:** `agent_source_provenance`
12
+ * is the two-value `'env' | 'absent'` enum — `'env'` iff `agent_source` was
13
+ * stamped from `TOTEM_SELF_AGENT`, `'absent'` iff no attribution was stamped
14
+ * (the honest stamped-absence default, Tenet 4).
15
+ * - **Fail-closed at the stamp seam:** when the ambient env seat and the
16
+ * session pointer's minting seat disagree, stamp NEITHER — no `agent_source`,
17
+ * plus a conflict disclosure naming both candidates. Fail-closed scopes to
18
+ * the *attribution*, never the *operation*: the sense never throws into its
19
+ * caller, so the query/write it decorates is never refused (Tenet 13).
20
+ * - **Never remembered:** identity derives from process env + on-disk state at
21
+ * call time, per call. No cache at any scope — a session-lifetime cache
22
+ * converts a transient contamination into a whole-session one (the
23
+ * `b8d7aa9b` specimen shape).
24
+ *
25
+ * The minting seat is derived from the pointer session's `session_start` row
26
+ * in `events.ndjson` — written by the SessionStart hook's mint block at the
27
+ * #2625 template, so the lookup needs no new producer. Absence of that row
28
+ * (pre-hook ledger, rotated file, unseated mint) is NOT a disagreement: only
29
+ * positive counter-evidence strips attribution.
30
+ *
31
+ * NAMED LIMIT (stated so coverage is never overread): the conflict arm fires
32
+ * only when two PRESENT candidates disagree — a seated process joining a
33
+ * foreign seat's pointer session. The b8d7aa9b specimen itself had an
34
+ * UNSEATED mint row (its minting process's env carried no var), and a
35
+ * machine-wide ambient var makes every mint agree with every process env —
36
+ * both shapes yield an env stamp with no conflict here: the ruling's
37
+ * fail-closed clause covers disagreement between two PRESENT candidates, and
38
+ * the missing-counter-evidence rule (design invariant 3) covers the rest. The
39
+ * sensors for the ambient-scope class are the provenance field (post-hoc
40
+ * partitioning), doctor arm (d)'s registry scope-sense, and the launch-shell
41
+ * scope protocol — not this predicate.
42
+ */
43
+ import * as fs from 'node:fs';
44
+ import * as path from 'node:path';
45
+ import { TotemError } from './errors.js';
46
+ import { resolveQbdAgentSource } from './qbd/record.js';
47
+ import { readSessionId } from './session-id.js';
48
+ const LEDGER_DIR = 'ledger';
49
+ const EVENTS_FILE = 'events.ndjson';
50
+ /**
51
+ * Errnos that mean "there is no ledger here" rather than "something surprising
52
+ * happened" — the `session-id.ts` absence semantics, minus the permission
53
+ * classes: an EACCES/EPERM/EROFS events file is a real probe failure that must
54
+ * land on the accounting channel, not read as a clean not-instrumented state.
55
+ */
56
+ const LEDGER_ABSENT_ERRNOS = new Set(['ENOENT', 'ENOTDIR']);
57
+ /** The ruled two-value provenance enum — `'env' | 'absent'`, nothing else. */
58
+ export const AGENT_SOURCE_PROVENANCE_VALUES = ['env', 'absent'];
59
+ /**
60
+ * Derive the minting seat of a session from its `session_start` row in
61
+ * `<totemDir>/ledger/events.ndjson`.
62
+ *
63
+ * Scans BACKWARDS (newest row wins) with a cheap substring prefilter before
64
+ * any `JSON.parse`, so the common case — an active session whose mint row is
65
+ * near the tail — costs a file read plus a short scan. Malformed/torn lines
66
+ * are skipped per line (the `parseSearchLog` tolerance precedent): a torn
67
+ * `session_start` reads as not-found, never as a fabricated conflict.
68
+ *
69
+ * Absent events file (ENOENT class) returns `found: false` — the honest
70
+ * not-instrumented state. Every other read errno throws a `TotemError` so the
71
+ * caller can name the probe failure instead of silently degrading.
72
+ */
73
+ export function readSessionMintingSeat(totemDir, sessionId) {
74
+ const eventsPath = path.join(totemDir, LEDGER_DIR, EVENTS_FILE);
75
+ let content;
76
+ try {
77
+ content = fs.readFileSync(eventsPath, 'utf-8');
78
+ }
79
+ catch (err) {
80
+ const code = typeof err === 'object' && err !== null ? err.code : undefined;
81
+ if (code !== undefined && LEDGER_ABSENT_ERRNOS.has(code)) {
82
+ return { found: false };
83
+ }
84
+ throw new TotemError('ATTRIBUTION_SENSE_LEDGER_READ_FAILED', 'Unexpected error reading events.ndjson for the minting-seat lookup', 'Check filesystem permissions for the .totem/ledger/events.ndjson file.', err);
85
+ }
86
+ const lines = content.split('\n');
87
+ for (let i = lines.length - 1; i >= 0; i--) {
88
+ const line = lines[i].trim();
89
+ if (line.length === 0)
90
+ continue;
91
+ // Substring prefilter: a JSON row with type "session_start" and this
92
+ // session id must contain both literals. Skips JSON.parse on the ~all
93
+ // non-matching rows of a large ledger.
94
+ if (!line.includes('"session_start"') || !line.includes(sessionId))
95
+ continue;
96
+ let obj;
97
+ // totem-context: intentional malformed-line tolerance — a torn/corrupt NDJSON line is skipped and the scan continues (the parseSearchLog precedent); a torn mint row must read as not-found, never crash the stamp path or fabricate a conflict.
98
+ try {
99
+ obj = JSON.parse(line);
100
+ // totem-context: intentional degradation — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
101
+ }
102
+ catch {
103
+ continue;
104
+ }
105
+ if (typeof obj !== 'object' || obj === null)
106
+ continue;
107
+ const rec = obj;
108
+ if (rec.type !== 'session_start' || rec.session_id !== sessionId)
109
+ continue;
110
+ const seat = typeof rec.agent_source === 'string' && rec.agent_source.trim().length > 0
111
+ ? rec.agent_source.trim()
112
+ : null;
113
+ return { found: true, seat };
114
+ }
115
+ return { found: false };
116
+ }
117
+ /**
118
+ * Derive the agent-attribution stamp for one row. Per call, never cached —
119
+ * see the module contract. Never throws: every probe failure degrades to the
120
+ * env-only stamp with the failure named on `attribution_probe_error`.
121
+ */
122
+ export function senseAgentAttribution(input = {}) {
123
+ const env = input.env ?? process.env;
124
+ const envSeat = resolveQbdAgentSource(env) ?? null;
125
+ // No env identity → stamped absence. The pointer's minting seat is never
126
+ // promoted into agent_source here: one candidate is not a conflict, and
127
+ // stamping a seat the process did not declare would be a guess (Tenet 4).
128
+ if (envSeat === null) {
129
+ return { agent_source: null, agent_source_provenance: 'absent' };
130
+ }
131
+ if (input.totemDir === undefined) {
132
+ return { agent_source: envSeat, agent_source_provenance: 'env' };
133
+ }
134
+ let probeError;
135
+ // totem-context: intentional degradation — a failed conflict probe keeps the env stamp (a broken probe is not counter-evidence) and names itself on attribution_probe_error; throwing here would let telemetry break the tool call it decorates (Tenet 13, lesson-b1bae311).
136
+ try {
137
+ const pointerId = input.pointerSessionId ?? readSessionId(input.totemDir);
138
+ if (pointerId !== undefined) {
139
+ const minting = readSessionMintingSeat(input.totemDir, pointerId);
140
+ if (minting.found && minting.seat !== null && minting.seat !== envSeat) {
141
+ // Fail-closed: two candidates disagree — stamp neither, name both.
142
+ return {
143
+ agent_source: null,
144
+ agent_source_provenance: 'absent',
145
+ attribution_conflict: {
146
+ env_seat: envSeat,
147
+ minting_seat: minting.seat,
148
+ pointer_session_id: pointerId,
149
+ },
150
+ };
151
+ }
152
+ }
153
+ // totem-context: intentional degradation — see directive above the try; dual placement so the rule fires on either the catch-keyword line or the catch-body line.
154
+ }
155
+ catch (err) {
156
+ // One level of cause unrolled: the wrapper text names the probe step, the
157
+ // cause names the errno — the half a post-hoc reader actually filters on.
158
+ const causeMsg = err instanceof Error && err.cause instanceof Error ? ` (${err.cause.message})` : '';
159
+ probeError = `${err instanceof Error ? err.message : String(err)}${causeMsg}`;
160
+ }
161
+ return {
162
+ agent_source: envSeat,
163
+ agent_source_provenance: 'env',
164
+ ...(probeError !== undefined && { attribution_probe_error: probeError }),
165
+ };
166
+ }
167
+ //# sourceMappingURL=attribution-sense.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution-sense.js","sourceRoot":"","sources":["../src/attribution-sense.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Tests for the agent-attribution provenance sense (mmnto-ai/totem#2629).
3
+ *
4
+ * The invariants under test come verbatim from the design doc
5
+ * (.totem/specs/2629.md § Invariants):
6
+ *
7
+ * - provenance biconditional: `'env'` iff `agent_source` is non-null;
8
+ * - the fail-closed rule fires ONLY on positive disagreement (env seat and
9
+ * minting seat both present, different) — and then withholds attribution,
10
+ * names both candidates, and never throws into the caller;
11
+ * - missing counter-evidence (no pointer, no mint row, unseated mint row,
12
+ * absent ledger) never strips the env stamp;
13
+ * - a failed probe keeps the env stamp and NAMES itself (never silent,
14
+ * never counter-evidence);
15
+ * - derivation is per call — a conflict introduced between two calls is
16
+ * caught on the second (no cache at any layer);
17
+ * - the minting-seat lookup takes the newest matching `session_start`, skips
18
+ * malformed lines, and treats an absent ledger as not-found.
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=attribution-sense.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution-sense.test.d.ts","sourceRoot":"","sources":["../src/attribution-sense.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Tests for the agent-attribution provenance sense (mmnto-ai/totem#2629).
3
+ *
4
+ * The invariants under test come verbatim from the design doc
5
+ * (.totem/specs/2629.md § Invariants):
6
+ *
7
+ * - provenance biconditional: `'env'` iff `agent_source` is non-null;
8
+ * - the fail-closed rule fires ONLY on positive disagreement (env seat and
9
+ * minting seat both present, different) — and then withholds attribution,
10
+ * names both candidates, and never throws into the caller;
11
+ * - missing counter-evidence (no pointer, no mint row, unseated mint row,
12
+ * absent ledger) never strips the env stamp;
13
+ * - a failed probe keeps the env stamp and NAMES itself (never silent,
14
+ * never counter-evidence);
15
+ * - derivation is per call — a conflict introduced between two calls is
16
+ * caught on the second (no cache at any layer);
17
+ * - the minting-seat lookup takes the newest matching `session_start`, skips
18
+ * malformed lines, and treats an absent ledger as not-found.
19
+ */
20
+ import * as os from 'node:os';
21
+ import * as path from 'node:path';
22
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
23
+ // Mock node:fs so spyOn works in ESM — same pattern as session-id.test.ts.
24
+ vi.mock('node:fs', async () => {
25
+ const actual = await vi.importActual('node:fs');
26
+ return { ...actual, default: actual };
27
+ });
28
+ import * as fs from 'node:fs';
29
+ import { readSessionMintingSeat, senseAgentAttribution } from './attribution-sense.js';
30
+ import { TotemError } from './errors.js';
31
+ import { cleanTmpDir } from './test-utils.js';
32
+ const SESSION_A = '550e8400-e29b-41d4-a716-446655440000';
33
+ const SESSION_B = '550e8400-e29b-41d4-a716-446655440009';
34
+ let totemDir;
35
+ function ledgerDir() {
36
+ return path.join(totemDir, 'ledger');
37
+ }
38
+ function writePointer(id) {
39
+ fs.mkdirSync(ledgerDir(), { recursive: true });
40
+ fs.writeFileSync(path.join(ledgerDir(), '.session-id'), id, 'utf-8');
41
+ }
42
+ function eventsPath() {
43
+ return path.join(ledgerDir(), 'events.ndjson');
44
+ }
45
+ function writeEvents(lines) {
46
+ fs.mkdirSync(ledgerDir(), { recursive: true });
47
+ const content = lines
48
+ .map((l) => (typeof l === 'string' ? l : JSON.stringify(l)))
49
+ .join('\n')
50
+ .concat('\n');
51
+ fs.writeFileSync(eventsPath(), content, 'utf-8');
52
+ }
53
+ function sessionStartRow(sessionId, seat) {
54
+ return {
55
+ timestamp: '2026-08-12T03:03:00.000Z',
56
+ type: 'session_start',
57
+ activity_name: 'SessionStart',
58
+ source: 'bot',
59
+ justification: '',
60
+ session_id: sessionId,
61
+ ...(seat !== undefined && { agent_source: seat }),
62
+ };
63
+ }
64
+ beforeEach(() => {
65
+ totemDir = fs.mkdtempSync(path.join(os.tmpdir(), 'attr-sense-'));
66
+ });
67
+ afterEach(() => {
68
+ vi.restoreAllMocks();
69
+ cleanTmpDir(totemDir);
70
+ });
71
+ // ─── readSessionMintingSeat ─────────────────────────────
72
+ describe('readSessionMintingSeat', () => {
73
+ it('finds the mint row and returns its seat', () => {
74
+ writeEvents([sessionStartRow(SESSION_A, 'totem-claude')]);
75
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({
76
+ found: true,
77
+ seat: 'totem-claude',
78
+ });
79
+ });
80
+ it('an unseated mint row is found with seat null (stamped absence, not a fabricated seat)', () => {
81
+ writeEvents([sessionStartRow(SESSION_A)]);
82
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({ found: true, seat: null });
83
+ });
84
+ it('a whitespace-only agent_source on the mint row reads as seat null', () => {
85
+ writeEvents([sessionStartRow(SESSION_A, ' ')]);
86
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({ found: true, seat: null });
87
+ });
88
+ it('returns not-found when the events file is absent (the honest not-instrumented state)', () => {
89
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({ found: false });
90
+ });
91
+ it('returns not-found when no session_start matches the id', () => {
92
+ writeEvents([sessionStartRow(SESSION_B, 'totem-claude')]);
93
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({ found: false });
94
+ });
95
+ it('a non-session_start row carrying the same session id is not a mint row', () => {
96
+ writeEvents([
97
+ {
98
+ timestamp: '2026-08-12T03:04:00.000Z',
99
+ type: 'mcp_call',
100
+ activity_name: 'search_knowledge — "session_start"',
101
+ source: 'bot',
102
+ justification: '',
103
+ session_id: SESSION_A,
104
+ },
105
+ ]);
106
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({ found: false });
107
+ });
108
+ it('the NEWEST matching session_start wins', () => {
109
+ writeEvents([sessionStartRow(SESSION_A, 'seat-old'), sessionStartRow(SESSION_A, 'seat-new')]);
110
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({ found: true, seat: 'seat-new' });
111
+ });
112
+ it('skips torn/malformed lines — a torn mint row reads as not-found, an earlier valid one still resolves', () => {
113
+ // The torn line contains both prefilter literals, so it reaches JSON.parse.
114
+ writeEvents([
115
+ sessionStartRow(SESSION_A, 'totem-claude'),
116
+ `{"type":"session_start","session_id":"${SESSION_A}","agent_source":"torn-`,
117
+ ]);
118
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({
119
+ found: true,
120
+ seat: 'totem-claude',
121
+ });
122
+ writeEvents([`{"type":"session_start","session_id":"${SESSION_A}","agent_source":"torn-`]);
123
+ expect(readSessionMintingSeat(totemDir, SESSION_A)).toEqual({ found: false });
124
+ });
125
+ it('throws a TotemError on a non-benign read failure (EACCES is a probe failure, not absence)', () => {
126
+ writeEvents([sessionStartRow(SESSION_A, 'totem-claude')]);
127
+ vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
128
+ const err = new Error('EACCES: permission denied');
129
+ err.code = 'EACCES';
130
+ throw err;
131
+ });
132
+ try {
133
+ readSessionMintingSeat(totemDir, SESSION_A);
134
+ expect.unreachable('should have thrown');
135
+ }
136
+ catch (err) {
137
+ expect(err).toBeInstanceOf(TotemError);
138
+ expect(err.code).toBe('ATTRIBUTION_SENSE_LEDGER_READ_FAILED');
139
+ }
140
+ });
141
+ });
142
+ // ─── senseAgentAttribution ──────────────────────────────
143
+ describe('senseAgentAttribution', () => {
144
+ it('absent env → stamped absence, even over a foreign-minted pointer (one candidate is not a conflict)', () => {
145
+ writePointer(SESSION_A);
146
+ writeEvents([sessionStartRow(SESSION_A, 'totem-claude')]);
147
+ const sense = senseAgentAttribution({ totemDir, env: {} });
148
+ expect(sense).toEqual({ agent_source: null, agent_source_provenance: 'absent' });
149
+ });
150
+ it('env-only call (no totemDir) stamps env with provenance and touches no fs', () => {
151
+ const readSpy = vi.spyOn(fs, 'readFileSync');
152
+ const statSpy = vi.spyOn(fs, 'statSync');
153
+ const sense = senseAgentAttribution({ env: { TOTEM_SELF_AGENT: 'totem-claude' } });
154
+ expect(sense).toEqual({ agent_source: 'totem-claude', agent_source_provenance: 'env' });
155
+ expect(readSpy).not.toHaveBeenCalled();
156
+ expect(statSpy).not.toHaveBeenCalled();
157
+ });
158
+ it('takes the first non-empty seat from a comma-separated TOTEM_SELF_AGENT', () => {
159
+ const sense = senseAgentAttribution({ env: { TOTEM_SELF_AGENT: ' , totem-gemini , extra' } });
160
+ expect(sense.agent_source).toBe('totem-gemini');
161
+ expect(sense.agent_source_provenance).toBe('env');
162
+ });
163
+ it('no pointer → no counter-candidate — env stamp stands', () => {
164
+ const sense = senseAgentAttribution({ totemDir, env: { TOTEM_SELF_AGENT: 'totem-gemini' } });
165
+ expect(sense).toEqual({ agent_source: 'totem-gemini', agent_source_provenance: 'env' });
166
+ });
167
+ it('pointer minted by the SAME seat → agreement, env stamp, no conflict', () => {
168
+ writePointer(SESSION_A);
169
+ writeEvents([sessionStartRow(SESSION_A, 'totem-claude')]);
170
+ const sense = senseAgentAttribution({ totemDir, env: { TOTEM_SELF_AGENT: 'totem-claude' } });
171
+ expect(sense).toEqual({ agent_source: 'totem-claude', agent_source_provenance: 'env' });
172
+ });
173
+ it('mint row without a seat → no counter-evidence — env stamp stands (the ACTUAL b8d7aa9b specimen shape)', () => {
174
+ // Honest coverage note (leg finding on this PR): the live specimen's mint
175
+ // row carries NO agent_source (minted 03:03Z; the ledger proves only that
176
+ // the minting process's env lacked the var, not why), so in the
177
+ // contamination window this sensor would have stamped the ambient seat
178
+ // with provenance 'env' and NO conflict — ruled item 2 fires only on
179
+ // disagreement between two PRESENT candidates. The ambient-scope class
180
+ // where every mint agrees with the env is sensed by doctor arm (d) + the
181
+ // launch-shell protocol, not by this predicate.
182
+ writePointer(SESSION_A);
183
+ writeEvents([sessionStartRow(SESSION_A)]);
184
+ const sense = senseAgentAttribution({ totemDir, env: { TOTEM_SELF_AGENT: 'totem-gemini' } });
185
+ expect(sense).toEqual({ agent_source: 'totem-gemini', agent_source_provenance: 'env' });
186
+ });
187
+ it('FAIL-CLOSED: env seat ≠ minting seat → stamp neither, name both (a seated foreign-pointer join)', () => {
188
+ writePointer(SESSION_A);
189
+ writeEvents([sessionStartRow(SESSION_A, 'totem-claude')]);
190
+ const sense = senseAgentAttribution({ totemDir, env: { TOTEM_SELF_AGENT: 'totem-gemini' } });
191
+ expect(sense).toEqual({
192
+ agent_source: null,
193
+ agent_source_provenance: 'absent',
194
+ attribution_conflict: {
195
+ env_seat: 'totem-gemini',
196
+ minting_seat: 'totem-claude',
197
+ pointer_session_id: SESSION_A,
198
+ },
199
+ });
200
+ });
201
+ it('a handed pointerSessionId is probed instead of the pointer file (no rotation race)', () => {
202
+ // Pointer file says SESSION_A (minted by the env seat — agreement); the
203
+ // caller hands SESSION_B (minted by a different seat). The probe must
204
+ // check the handed id — the session the caller's row actually joins.
205
+ writePointer(SESSION_A);
206
+ writeEvents([
207
+ sessionStartRow(SESSION_A, 'totem-gemini'),
208
+ sessionStartRow(SESSION_B, 'totem-claude'),
209
+ ]);
210
+ const sense = senseAgentAttribution({
211
+ totemDir,
212
+ env: { TOTEM_SELF_AGENT: 'totem-gemini' },
213
+ pointerSessionId: SESSION_B,
214
+ });
215
+ expect(sense.attribution_conflict).toEqual({
216
+ env_seat: 'totem-gemini',
217
+ minting_seat: 'totem-claude',
218
+ pointer_session_id: SESSION_B,
219
+ });
220
+ });
221
+ it('NEVER REMEMBERED: a conflict introduced between two calls is caught on the second', () => {
222
+ writePointer(SESSION_A);
223
+ writeEvents([sessionStartRow(SESSION_A, 'totem-gemini')]);
224
+ const env = { TOTEM_SELF_AGENT: 'totem-gemini' };
225
+ const first = senseAgentAttribution({ totemDir, env });
226
+ expect(first.agent_source).toBe('totem-gemini');
227
+ expect(first.attribution_conflict).toBeUndefined();
228
+ // A foreign seat's SessionStart rotates the shared pointer mid-session.
229
+ writePointer(SESSION_B);
230
+ writeEvents([
231
+ sessionStartRow(SESSION_A, 'totem-gemini'),
232
+ sessionStartRow(SESSION_B, 'totem-claude'),
233
+ ]);
234
+ const second = senseAgentAttribution({ totemDir, env });
235
+ expect(second.agent_source).toBeNull();
236
+ expect(second.attribution_conflict).toEqual({
237
+ env_seat: 'totem-gemini',
238
+ minting_seat: 'totem-claude',
239
+ pointer_session_id: SESSION_B,
240
+ });
241
+ });
242
+ it('a failed probe keeps the env stamp and NAMES itself — never silent, never counter-evidence, never a throw', () => {
243
+ writePointer(SESSION_A);
244
+ writeEvents([sessionStartRow(SESSION_A, 'totem-claude')]);
245
+ // Only the events.ndjson read is faked; the pointer read stays real (the
246
+ // qbd/record.test.ts selective-fake pattern).
247
+ const realRead = fs.readFileSync.bind(fs);
248
+ vi.spyOn(fs, 'readFileSync').mockImplementation(((target, ...rest) => {
249
+ if (String(target).endsWith('events.ndjson')) {
250
+ const err = new Error('EACCES: permission denied');
251
+ err.code = 'EACCES';
252
+ throw err;
253
+ }
254
+ return realRead(target, ...rest);
255
+ }));
256
+ const sense = senseAgentAttribution({ totemDir, env: { TOTEM_SELF_AGENT: 'totem-gemini' } });
257
+ expect(sense.agent_source).toBe('totem-gemini');
258
+ expect(sense.agent_source_provenance).toBe('env');
259
+ expect(sense.attribution_conflict).toBeUndefined();
260
+ expect(sense.attribution_probe_error).toContain('EACCES');
261
+ });
262
+ it('provenance biconditional holds across every arm above', () => {
263
+ writePointer(SESSION_A);
264
+ writeEvents([sessionStartRow(SESSION_A, 'totem-claude')]);
265
+ const cases = [
266
+ senseAgentAttribution({ env: {} }),
267
+ senseAgentAttribution({ env: { TOTEM_SELF_AGENT: 'totem-claude' } }),
268
+ senseAgentAttribution({ totemDir, env: {} }),
269
+ senseAgentAttribution({ totemDir, env: { TOTEM_SELF_AGENT: 'totem-claude' } }),
270
+ senseAgentAttribution({ totemDir, env: { TOTEM_SELF_AGENT: 'totem-gemini' } }),
271
+ ];
272
+ for (const sense of cases) {
273
+ expect(sense.agent_source_provenance === 'env').toBe(sense.agent_source !== null);
274
+ }
275
+ });
276
+ });
277
+ //# sourceMappingURL=attribution-sense.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution-sense.test.js","sourceRoot":"","sources":["../src/attribution-sense.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzE,2EAA2E;AAC3E,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;IAC5B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,YAAY,CAA2B,SAAS,CAAC,CAAC;IAC1E,OAAO,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC,CAAC,CAAC;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,SAAS,GAAG,sCAAsC,CAAC;AACzD,MAAM,SAAS,GAAG,sCAAsC,CAAC;AAEzD,IAAI,QAAgB,CAAC;AAErB,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,YAAY,CAAC,EAAU;IAC9B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,WAAW,CAAC,KAA8C;IACjE,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,KAAK;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;SAC3D,IAAI,CAAC,IAAI,CAAC;SACV,MAAM,CAAC,IAAI,CAAC,CAAC;IAChB,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB,EAAE,IAAa;IACvD,OAAO;QACL,SAAS,EAAE,0BAA0B;QACrC,IAAI,EAAE,eAAe;QACrB,aAAa,EAAE,cAAc;QAC7B,MAAM,EAAE,KAAK;QACb,aAAa,EAAE,EAAE;QACjB,UAAU,EAAE,SAAS;QACrB,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;KAClD,CAAC;AACJ,CAAC;AAED,UAAU,CAAC,GAAG,EAAE;IACd,QAAQ,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;AACnE,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACrB,WAAW,CAAC,QAAQ,CAAC,CAAC;AACxB,CAAC,CAAC,CAAC;AAEH,2DAA2D;AAE3D,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;YAC1D,KAAK,EAAE,IAAI;YACX,IAAI,EAAE,cAAc;SACrB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uFAAuF,EAAE,GAAG,EAAE;QAC/F,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sFAAsF,EAAE,GAAG,EAAE;QAC9F,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAChF,WAAW,CAAC;YACV;gBACE,SAAS,EAAE,0BAA0B;gBACrC,IAAI,EAAE,UAAU;gBAChB,aAAa,EAAE,oCAAoC;gBACnD,MAAM,EAAE,KAAK;gBACb,aAAa,EAAE,EAAE;gBACjB,UAAU,EAAE,SAAS;aACtB;SACF,CAAC,CAAC;QACH,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,eAAe,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QAC9F,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IACjG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sGAAsG,EAAE,GAAG,EAAE;QAC9G,4EAA4E;QAC5E,WAAW,CAAC;YACV,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC;YAC1C,yCAAyC,SAAS,yBAAyB;SAC5E,CAAC,CAAC;QACH,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;YAC1D,KAAK,EAAE,IAAI;YACX,IAAI,EAAE,cAAc;SACrB,CAAC,CAAC;QAEH,WAAW,CAAC,CAAC,yCAAyC,SAAS,yBAAyB,CAAC,CAAC,CAAC;QAC3F,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2FAA2F,EAAE,GAAG,EAAE;QACnG,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE;YACnD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,2BAA2B,CAA0B,CAAC;YAC5E,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC;YACpB,MAAM,GAAG,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,IAAI,CAAC;YACH,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC5C,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;YACvC,MAAM,CAAE,GAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QAChF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,2DAA2D;AAE3D,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,oGAAoG,EAAE,GAAG,EAAE;QAC5G,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3D,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,uBAAuB,EAAE,QAAQ,EAAE,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QACnF,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,CAAC;QACxF,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACvC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAChF,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,EAAE,CAAC,CAAC;QAC9F,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QAC7F,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QAC7F,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uGAAuG,EAAE,GAAG,EAAE;QAC/G,0EAA0E;QAC1E,0EAA0E;QAC1E,gEAAgE;QAChE,uEAAuE;QACvE,qEAAqE;QACrE,uEAAuE;QACvE,yEAAyE;QACzE,gDAAgD;QAChD,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QAC7F,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iGAAiG,EAAE,GAAG,EAAE;QACzG,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QAC7F,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;YACpB,YAAY,EAAE,IAAI;YAClB,uBAAuB,EAAE,QAAQ;YACjC,oBAAoB,EAAE;gBACpB,QAAQ,EAAE,cAAc;gBACxB,YAAY,EAAE,cAAc;gBAC5B,kBAAkB,EAAE,SAAS;aAC9B;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oFAAoF,EAAE,GAAG,EAAE;QAC5F,wEAAwE;QACxE,sEAAsE;QACtE,qEAAqE;QACrE,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC;YACV,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC;YAC1C,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC;SAC3C,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,qBAAqB,CAAC;YAClC,QAAQ;YACR,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE;YACzC,gBAAgB,EAAE,SAAS;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,OAAO,CAAC;YACzC,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,cAAc;YAC5B,kBAAkB,EAAE,SAAS;SAC9B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,GAAG,EAAE;QAC3F,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,GAAG,GAAG,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QACvD,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,aAAa,EAAE,CAAC;QAEnD,wEAAwE;QACxE,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC;YACV,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC;YAC1C,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC;SAC3C,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,OAAO,CAAC;YAC1C,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,cAAc;YAC5B,kBAAkB,EAAE,SAAS;SAC9B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2GAA2G,EAAE,GAAG,EAAE;QACnH,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,yEAAyE;QACzE,8CAA8C;QAC9C,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1C,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAe,EAAE,GAAG,IAAe,EAAE,EAAE;YACvF,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;gBAC7C,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,2BAA2B,CAA0B,CAAC;gBAC5E,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC;gBACpB,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,OAAQ,QAA4C,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;QACxE,CAAC,CAAU,CAAC,CAAC;QAEb,MAAM,KAAK,GAAG,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QAC7F,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,aAAa,EAAE,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,WAAW,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG;YACZ,qBAAqB,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;YAClC,qBAAqB,CAAC,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC;YACpE,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;YAC5C,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC;YAC9E,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC;SAC/E,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,CAAC,KAAK,CAAC,uBAAuB,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC;QACpF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/dist/errors.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * - A recoveryHint telling the user exactly how to fix it
7
7
  * - A code for programmatic handling
8
8
  */
9
- export type TotemErrorCode = 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'DATABASE_CORRUPT' | 'DATABASE_MISMATCH' | 'EMBEDDING_UNAVAILABLE' | 'ORCHESTRATOR_UNAVAILABLE' | 'COMPILE_FAILED' | 'PARSE_FAILED' | 'SYNC_FAILED' | 'GIT_FAILED' | 'NO_LESSONS' | 'NO_LESSONS_DIR' | 'NO_RULES' | 'SHIELD_FAILED' | 'LINT_LESSONS_FAILED' | 'DRIFT_FAILED' | 'TEST_FAILED' | 'CHECK_FAILED' | 'MCP_ERROR' | 'UPGRADE_HASH_NOT_FOUND' | 'UPGRADE_HASH_AMBIGUOUS' | 'STAGED_READ_FAILED' | 'UPGRADE_CLOUD_UNSUPPORTED' | 'STALE_MANIFEST' | 'FLAG_CONFLICT' | 'HOOKS_LOAD_FAILED' | 'SESSION_ID_WRITE_FAILED' | 'SESSION_ID_READ_FAILED' | 'BADGE_VERIFICATION_FAILED' | 'CLAIM_DISCIPLINE_FAILED' | 'GATE_INVALID' | 'PARITY_DRIFT_DETECTED' | 'MAIL_SEND_FAILED' | 'PR_MERGE_FAILED' | 'BASH_RESOLUTION_FAILED'
9
+ export type TotemErrorCode = 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'DATABASE_CORRUPT' | 'DATABASE_MISMATCH' | 'EMBEDDING_UNAVAILABLE' | 'ORCHESTRATOR_UNAVAILABLE' | 'COMPILE_FAILED' | 'PARSE_FAILED' | 'SYNC_FAILED' | 'GIT_FAILED' | 'NO_LESSONS' | 'NO_LESSONS_DIR' | 'NO_RULES' | 'SHIELD_FAILED' | 'LINT_LESSONS_FAILED' | 'DRIFT_FAILED' | 'TEST_FAILED' | 'CHECK_FAILED' | 'MCP_ERROR' | 'UPGRADE_HASH_NOT_FOUND' | 'UPGRADE_HASH_AMBIGUOUS' | 'STAGED_READ_FAILED' | 'UPGRADE_CLOUD_UNSUPPORTED' | 'STALE_MANIFEST' | 'FLAG_CONFLICT' | 'HOOKS_LOAD_FAILED' | 'SESSION_ID_WRITE_FAILED' | 'SESSION_ID_READ_FAILED' | 'ATTRIBUTION_SENSE_LEDGER_READ_FAILED' | 'BADGE_VERIFICATION_FAILED' | 'CLAIM_DISCIPLINE_FAILED' | 'GATE_INVALID' | 'PARITY_DRIFT_DETECTED' | 'MAIL_SEND_FAILED' | 'PR_MERGE_FAILED' | 'BASH_RESOLUTION_FAILED'
10
10
  /** Query-before-derive correlation contract breach (mmnto-ai/totem#2510). */
11
11
  | 'QBD_CORRELATION_CONTRACT'
12
12
  /** Eject's loud backstop: every attempted cleanup mutation failed (mmnto-ai/totem#2620). */