@mmnto/totem 1.114.0 → 1.115.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/errors.d.ts +5 -1
  2. package/dist/errors.d.ts.map +1 -1
  3. package/dist/errors.js.map +1 -1
  4. package/dist/fs-atomic.d.ts +63 -0
  5. package/dist/fs-atomic.d.ts.map +1 -0
  6. package/dist/fs-atomic.js +114 -0
  7. package/dist/fs-atomic.js.map +1 -0
  8. package/dist/fs-atomic.test.d.ts +2 -0
  9. package/dist/fs-atomic.test.d.ts.map +1 -0
  10. package/dist/fs-atomic.test.js +162 -0
  11. package/dist/fs-atomic.test.js.map +1 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +7 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/ingest/pipeline.d.ts.map +1 -1
  17. package/dist/ingest/pipeline.js +5 -5
  18. package/dist/ingest/pipeline.js.map +1 -1
  19. package/dist/orchestration-resolver.d.ts +19 -0
  20. package/dist/orchestration-resolver.d.ts.map +1 -1
  21. package/dist/orchestration-resolver.js +5 -1
  22. package/dist/orchestration-resolver.js.map +1 -1
  23. package/dist/seat-lifecycle.d.ts +118 -0
  24. package/dist/seat-lifecycle.d.ts.map +1 -0
  25. package/dist/seat-lifecycle.js +197 -0
  26. package/dist/seat-lifecycle.js.map +1 -0
  27. package/dist/seat-lifecycle.test.d.ts +11 -0
  28. package/dist/seat-lifecycle.test.d.ts.map +1 -0
  29. package/dist/seat-lifecycle.test.js +210 -0
  30. package/dist/seat-lifecycle.test.js.map +1 -0
  31. package/dist/selection-manifest.d.ts +416 -0
  32. package/dist/selection-manifest.d.ts.map +1 -0
  33. package/dist/selection-manifest.js +444 -0
  34. package/dist/selection-manifest.js.map +1 -0
  35. package/dist/selection-manifest.test.d.ts +26 -0
  36. package/dist/selection-manifest.test.d.ts.map +1 -0
  37. package/dist/selection-manifest.test.js +331 -0
  38. package/dist/selection-manifest.test.js.map +1 -0
  39. package/dist/spine/ledgers.d.ts +12 -12
  40. package/dist/verification-outcomes.d.ts.map +1 -1
  41. package/dist/verification-outcomes.js +6 -12
  42. package/dist/verification-outcomes.js.map +1 -1
  43. package/dist/worktree-registry.d.ts.map +1 -1
  44. package/dist/worktree-registry.js +4 -4
  45. package/dist/worktree-registry.js.map +1 -1
  46. package/package.json +15 -11
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-manifest.d.ts","sourceRoot":"","sources":["../src/selection-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAMH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;CAGvB,CAAC;AAEX,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,0DAA2D,CAAC;AAC3F,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAInE;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;IAEjC;;;;;;OAMG;;IAEH,uEAAuE;;IAEvE;;;;;OAKG;;IAKH,2EAA2E;;IAE3E,6DAA6D;;IAE7D;;;OAGG;;IAEH;;;OAGG;;IAEH,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCpE,CAAC;AAEL,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,qLAkBzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAY1E,4EAA4E;AAC5E,eAAO,MAAM,0BAA0B;;IAGnC,+CAA+C;;;IAG/C,gFAAgF;;IAEhF,wFAAwF;;IAExF,yEAAyE;;;IAGzE;;;;OAIG;;;;;;;;;;;;IAQH;;;OAGG;;;;;;;;;;;;;;;QA/IH;;;;;;WAMG;;QAEH,uEAAuE;;QAEvE;;;;;WAKG;;QAKH,2EAA2E;;QAE3E,6DAA6D;;QAE7D;;;WAGG;;QAEH;;;WAGG;;QAEH,oEAAoE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAuHpE,yEAAyE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGlE,CAAC;AAEZ,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEnE;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB,CAGA;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,WAAW,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG;IAAE,SAAS,EAAE,kBAAkB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CA4BtD;AAID,MAAM,WAAW,sBAAsB;IACrC,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA4BD,mEAAmE;AACnE,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,oBAAoB,CAuB7F;AAID;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,sBAAsB,GAAG,uBAAuB,CAoC9F;AAsBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,sBAAsB,EAC7B,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC7B,uBAAuB,CAYzB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC7B,oBAAoB,EAAE,CAmCxB"}
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Per-seat selection manifests — the Prop 308 / strategy#467 M1 instrument
3
+ * (mmnto-ai/totem#2468).
4
+ *
5
+ * A selection manifest is a read-only record of one selection event: which
6
+ * candidates a consumer (session-start hook, `search_knowledge`, `totem
7
+ * orient`) considered on a seat's behalf, which it selected or excluded, what
8
+ * each cost, and the policy's own stated reason. It is a record of what each
9
+ * policy did — never a ranker, and it never adjudicates between policies.
10
+ *
11
+ * ## Contract (binding, from the Prop 308 round + the armed #467 pre-registration)
12
+ *
13
+ * - **No shared relevance score, no common weights** (Prop 308 F1, unanimous;
14
+ * lc-codex refute condition). Both schema levels are `.strict()` so a shared
15
+ * score field cannot be added without failing parse of existing rows — the
16
+ * guard is structural, not a promise. Per-candidate `reason` strings carry
17
+ * each policy's OWN words (a floor value in a search reason is that policy's
18
+ * stated reason, not a common weight).
19
+ * - **Senses, never gates** (Tenet 13). Emission must never block, filter, or
20
+ * alter a selection. Command call sites use {@link senseSelectionManifest};
21
+ * the throwing writer exists so the contract is testable and a programming
22
+ * error is never silent (the `qbd/record.ts` posture, mirrored).
23
+ * - **Named cost basis** (#467 outcome 3's naming requirement): every figure
24
+ * names its aggregation — {@link SELECTION_COST_BASIS} is a fixed literal
25
+ * stamped on every row. What the manifest emits is a PAYLOAD-BYTE census
26
+ * with a declared token approximation; it is NOT the ruled `<total>` basis
27
+ * (measured cache-aware consumption), and whether it may substitute for a
28
+ * `<total>` surface is the pre-registration's call via versioned revision
29
+ * (#467 §9), never this module's claim.
30
+ * - **No fabricated measurement:** a manifest records only what the policy
31
+ * observed. Candidates the policy never read (recency-excluded journals,
32
+ * first-match-losing proposals) are recorded id-only — no fingerprint, no
33
+ * bytes. Emitters never over-fetch to manufacture exclusions, which would
34
+ * alter the thing being measured.
35
+ * - **Recorded absence, never silent skip** (the #2466 clause): the
36
+ * denominator lives in `events.ndjson` rows (`session_start`, `mcp_call`,
37
+ * `derive_action`) that ship independently of this instrument. A denominator
38
+ * row with no matching manifest row IS the recorded absence; a failed write
39
+ * additionally warns on the emitter's accounting channel.
40
+ */
41
+ import { createHash } from 'node:crypto';
42
+ import * as fs from 'node:fs';
43
+ import * as path from 'node:path';
44
+ import { z } from 'zod';
45
+ import { TotemError } from './errors.js';
46
+ import { resolveQbdAgentSource } from './qbd/record.js';
47
+ import { readSessionId } from './session-id.js';
48
+ // ─── Constants ──────────────────────────────────────────
49
+ const LEDGER_DIR = 'ledger';
50
+ /**
51
+ * Sidecar file, NOT rows in `events.ndjson` (ruled 2026-08-10, #2468 OQ1):
52
+ * per-candidate arrays are bulky, and the event stream's existing consumers
53
+ * (QBD scanner, stats) keep their scan economics. Same directory, same
54
+ * append-only + gitignored conventions; growth is bounded the same way
55
+ * `events.ndjson` is (janitorial rotation is a separate, pre-existing class).
56
+ */
57
+ export const SELECTION_MANIFESTS_FILE = 'selection-manifests.ndjson';
58
+ /**
59
+ * The named aggregation stamped on every row (#467 outcome-3 requirement:
60
+ * a measure without its basis reports NOT OBSERVED — so the basis rides the
61
+ * data). `bytes` is the UTF-8 byte length of the exact content the policy
62
+ * considered; `approxTokens` is declared as the chars/4 approximation, never
63
+ * presented as a measured token count.
64
+ */
65
+ export const SELECTION_COST_BASIS = {
66
+ bytes: 'utf8-length',
67
+ approxTokens: 'ceil(bytes/4) approximation',
68
+ };
69
+ /** Emitting consumers — the three selection surfaces named by #2468. */
70
+ export const SELECTION_EMITTERS = ['session-start', 'search_knowledge', 'orient'];
71
+ // ─── Schema ─────────────────────────────────────────────
72
+ /**
73
+ * One candidate the policy observed. `.strict()` — see the module contract:
74
+ * no field can be added silently, which is the structural half of the Prop 308
75
+ * refute-condition guard.
76
+ */
77
+ export const SelectionCandidateSchema = z
78
+ .object({
79
+ /**
80
+ * Candidate identity: repo-relative path or logical id. The coarse join
81
+ * key for the overlap number — a whole-file fingerprint and a chunk
82
+ * fingerprint of the same source will not collide, so cross-emitter joins
83
+ * anchor on `id` first and use `fingerprint` for exactness. Join policy
84
+ * belongs to the measurement pass, declared here rather than solved.
85
+ */
86
+ id: z.string().trim().min(1),
87
+ /** Linked-repo origin for federated hits; absent for primary/local. */
88
+ sourceRepo: z.string().trim().min(1).optional(),
89
+ /**
90
+ * sha256 hex, first 16 chars, over the EXACT bytes the policy considered.
91
+ * Absent when the policy never read the content (id-only exclusion) or
92
+ * when hashing failed (which also appends to `warnings` — an absent
93
+ * fingerprint is honest, a fabricated one never is).
94
+ */
95
+ fingerprint: z
96
+ .string()
97
+ .regex(/^[0-9a-f]{16}$/)
98
+ .optional(),
99
+ /** UTF-8 byte length of the considered content. Absent on id-only rows. */
100
+ bytes: z.number().int().nonnegative().optional(),
101
+ /** ceil(bytes/4), under the declared approximation basis. */
102
+ approxTokens: z.number().int().nonnegative().optional(),
103
+ /**
104
+ * What the policy did with this candidate. `truncated` = selected but
105
+ * partially delivered (`deliveredBytes` carries what actually shipped).
106
+ */
107
+ disposition: z.enum(['selected', 'truncated', 'excluded']),
108
+ /**
109
+ * The policy's own stated reason — the why-selected / why-excluded half
110
+ * that is invisible today. Free text owned by the emitting policy.
111
+ */
112
+ reason: z.string().trim().min(1),
113
+ /** For `truncated` rows: bytes actually delivered after the cut. */
114
+ deliveredBytes: z.number().int().nonnegative().optional(),
115
+ })
116
+ .strict()
117
+ // Measured-candidate invariants (PR #2625 CR round): the builders produce
118
+ // exactly two shapes — id-only (nothing measured) or fully measured — so
119
+ // the schema refuses the partial states in between, and `deliveredBytes`
120
+ // is meaningful only on a truncated row and can never exceed the full
121
+ // measurement it was cut from.
122
+ .superRefine((c, ctx) => {
123
+ const measuredPresent = [c.fingerprint, c.bytes, c.approxTokens].filter((v) => v !== undefined).length;
124
+ if (measuredPresent !== 0 && measuredPresent !== 3) {
125
+ ctx.addIssue({
126
+ code: z.ZodIssueCode.custom,
127
+ message: 'measured fields (fingerprint, bytes, approxTokens) travel together — a candidate is id-only or fully measured, never partial',
128
+ });
129
+ }
130
+ if (c.deliveredBytes !== undefined) {
131
+ if (c.disposition !== 'truncated') {
132
+ ctx.addIssue({
133
+ code: z.ZodIssueCode.custom,
134
+ path: ['deliveredBytes'],
135
+ message: 'deliveredBytes is only meaningful on a truncated candidate',
136
+ });
137
+ }
138
+ if (c.bytes !== undefined && c.deliveredBytes > c.bytes) {
139
+ ctx.addIssue({
140
+ code: z.ZodIssueCode.custom,
141
+ path: ['deliveredBytes'],
142
+ message: 'deliveredBytes cannot exceed the full measured bytes',
143
+ });
144
+ }
145
+ }
146
+ });
147
+ /**
148
+ * The CLOSED set of per-emitter context keys. Closing the key space is the
149
+ * context-layer half of the Prop 308 refute-condition guard (PR #2625 CR
150
+ * round): with an open record, `context.sharedRelevanceScore` would have
151
+ * parsed cleanly and the "no shared score without a schema change" claim held
152
+ * only at the row/candidate layers. Adding a context key is now a deliberate
153
+ * schema change, exactly like adding a field.
154
+ */
155
+ export const SELECTION_CONTEXT_KEYS = [
156
+ // search_knowledge
157
+ 'query',
158
+ 'boundary',
159
+ 'typeFilter',
160
+ 'floor',
161
+ 'finalLimit',
162
+ 'method',
163
+ 'status',
164
+ 'storesFailed',
165
+ 'storesUnavailable',
166
+ // orient
167
+ 'render',
168
+ // session-start (V2 hook + published templates)
169
+ 'branch',
170
+ 'ticket',
171
+ 'selfAgent',
172
+ 'template',
173
+ ];
174
+ /**
175
+ * Per-emitter context: a FLAT scalar map over the closed key set. Flat by
176
+ * design — a nested structure is where a shared relevance model would grow,
177
+ * and the schema refuses it room; closed-keyed so it cannot grow one either.
178
+ */
179
+ const SelectionContextSchema = z.record(z.enum(SELECTION_CONTEXT_KEYS), z.union([z.string(), z.number(), z.boolean(), z.null()]));
180
+ /** One selection event — one NDJSON row in `selection-manifests.ndjson`. */
181
+ export const SelectionManifestRowSchema = z
182
+ .object({
183
+ schemaVersion: z.literal(1),
184
+ /** ISO 8601 instant of the selection event. */
185
+ timestamp: z.string().datetime(),
186
+ emitter: z.enum(SELECTION_EMITTERS),
187
+ /** Session UUID from `.totem/ledger/.session-id` — the denominator join key. */
188
+ session_id: z.string().uuid().optional(),
189
+ /** Seat id from TOTEM_SELF_AGENT (ADR-078). Absent = stamped absence, never guessed. */
190
+ agent_source: z.string().trim().min(1).optional(),
191
+ /** Emitting package version, when the emitter can resolve it cheaply. */
192
+ cli_version: z.string().trim().min(1).optional(),
193
+ context: SelectionContextSchema,
194
+ /**
195
+ * Names the observable candidate pool (e.g. `store-returned-pool
196
+ * perStoreLimit=5`) — the honest boundary: exclusions are recorded only
197
+ * over candidates the emitter actually saw.
198
+ */
199
+ universe: z.string().trim().min(1),
200
+ costBasis: z
201
+ .object({
202
+ bytes: z.literal(SELECTION_COST_BASIS.bytes),
203
+ approxTokens: z.literal(SELECTION_COST_BASIS.approxTokens),
204
+ })
205
+ .strict(),
206
+ /**
207
+ * The session-start hook's global char slice — string-level, so its cut
208
+ * is NOT attributed per-candidate (declared limitation, not a gap).
209
+ */
210
+ finalTruncation: z
211
+ .object({
212
+ cap: z.number().int().positive(),
213
+ totalChars: z.number().int().nonnegative(),
214
+ applied: z.boolean(),
215
+ })
216
+ .strict()
217
+ .optional(),
218
+ candidates: z.array(SelectionCandidateSchema),
219
+ /** Per-row accounting: every non-fatal degradation names itself here. */
220
+ warnings: z.array(z.string()),
221
+ })
222
+ .strict();
223
+ // ─── Measurement helpers ────────────────────────────────
224
+ /**
225
+ * Fingerprint the exact bytes a policy considered: sha256 hex, first 16.
226
+ * 64 bits of collision resistance is ample for a per-workspace corpus join
227
+ * key; the full digest would double every row's bulk for no analytical gain.
228
+ */
229
+ export function fingerprintContent(content) {
230
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
231
+ }
232
+ /** Cost of a considered candidate under {@link SELECTION_COST_BASIS}. */
233
+ export function measureCandidateCost(content) {
234
+ const bytes = typeof content === 'string' ? Buffer.byteLength(content, 'utf-8') : content.length;
235
+ return { bytes, approxTokens: Math.ceil(bytes / 4) };
236
+ }
237
+ /**
238
+ * Build a fully-measured candidate from content the policy actually read.
239
+ *
240
+ * TOTAL over its input space (leg round 1, H-7): a non-string/non-Buffer
241
+ * `content` (a store row whose unchecked cast lied) or a hash failure
242
+ * degrades to an ID-ONLY row with the failure named on the returned warning —
243
+ * it never throws into the emitting command, and it never persists a
244
+ * fabricated or NaN measurement.
245
+ */
246
+ export function buildMeasuredCandidate(input) {
247
+ const idOnly = {
248
+ id: input.id,
249
+ disposition: input.disposition,
250
+ reason: input.reason,
251
+ ...(input.sourceRepo !== undefined && { sourceRepo: input.sourceRepo }),
252
+ ...(input.deliveredBytes !== undefined && { deliveredBytes: input.deliveredBytes }),
253
+ };
254
+ const content = input.content;
255
+ if (typeof content !== 'string' && !Buffer.isBuffer(content)) {
256
+ return {
257
+ candidate: idOnly,
258
+ warning: `selection-manifest: unmeasurable content for ${input.id} (got ${typeof content}) — recorded id-only`,
259
+ };
260
+ }
261
+ try {
262
+ const { bytes, approxTokens } = measureCandidateCost(content);
263
+ return {
264
+ candidate: { ...idOnly, bytes, approxTokens, fingerprint: fingerprintContent(content) },
265
+ };
266
+ // totem-context: intentional degradation — a measurement/hash failure records the candidate ID-ONLY and names the failure on the accounting channel; fabricating or dropping the row would both lie (Tenet 13).
267
+ }
268
+ catch (err) {
269
+ const msg = err instanceof Error ? err.message : String(err);
270
+ return {
271
+ candidate: idOnly,
272
+ warning: `selection-manifest: measurement failed for ${input.id}: ${msg} — recorded id-only`,
273
+ };
274
+ }
275
+ }
276
+ /**
277
+ * Is this an instrumented project at all? Same convention as
278
+ * `qbd/record.ts`: an absent `.totem` (ENOENT, or ENOTDIR — a path component
279
+ * being a file, the cross-platform divergence `session-id.ts` documents) is
280
+ * the honest "nothing here to instrument" state — skip silently rather than
281
+ * materialising a stray `.totem/ledger/` in whatever cwd an instrumented
282
+ * command ran from. Every OTHER errno (EACCES, I/O) is a real probe failure
283
+ * that must not masquerade as not-a-project (PR #2625 CR round): it returns a
284
+ * named warning so the skip lands on the accounting channel.
285
+ */
286
+ function probeInstrumentedProject(totemDir) {
287
+ try {
288
+ return { instrumented: fs.statSync(totemDir).isDirectory() };
289
+ // totem-context: intentional degradation — a missing `.totem` is the honest "not a Totem project" state; any OTHER errno is reported via the returned warning (the accounting channel), never swallowed.
290
+ }
291
+ catch (err) {
292
+ const code = typeof err === 'object' && err !== null ? err.code : undefined;
293
+ if (code === 'ENOENT' || code === 'ENOTDIR')
294
+ return { instrumented: false };
295
+ const msg = err instanceof Error ? err.message : String(err);
296
+ return {
297
+ instrumented: false,
298
+ warning: `selection-manifest: .totem probe failed (${msg}) — not recording`,
299
+ };
300
+ }
301
+ }
302
+ /** Stamp attribution + basis onto an emitter's assembled input. */
303
+ export function buildSelectionManifestRow(input) {
304
+ const env = input.env ?? process.env;
305
+ const nowMs = input.nowMs ?? Date.now();
306
+ // An emitter that MINTED the session id this event belongs to passes it
307
+ // directly (the session-start hooks) — immune to the shared-pointer being
308
+ // rotated by a concurrent session between mint and emission (PR #2625 CR
309
+ // round, partial). Everyone else joins via the pointer.
310
+ const sessionId = input.sessionId ?? readSessionId(input.totemDir);
311
+ const agentSource = resolveQbdAgentSource(env);
312
+ return {
313
+ schemaVersion: 1,
314
+ timestamp: new Date(nowMs).toISOString(),
315
+ emitter: input.emitter,
316
+ ...(sessionId !== undefined && { session_id: sessionId }),
317
+ ...(agentSource !== undefined && { agent_source: agentSource }),
318
+ ...(input.cliVersion !== undefined && { cli_version: input.cliVersion }),
319
+ context: input.context,
320
+ universe: input.universe,
321
+ costBasis: { ...SELECTION_COST_BASIS },
322
+ ...(input.finalTruncation !== undefined && { finalTruncation: input.finalTruncation }),
323
+ candidates: input.candidates,
324
+ warnings: input.warnings ?? [],
325
+ };
326
+ }
327
+ // ─── Writers ────────────────────────────────────────────
328
+ /**
329
+ * Validate and append one manifest row.
330
+ *
331
+ * Throws `SELECTION_MANIFEST_CONTRACT` on a schema-invalid row — that is a
332
+ * programming error in an emitter, and persisting it would hand the
333
+ * measurement pass a row `readSelectionManifests` silently skips (data loss
334
+ * dressed as data). I/O failures do not throw; they report through the
335
+ * returned `warnings` (the accounting channel).
336
+ */
337
+ export function appendSelectionManifest(input) {
338
+ const warnings = [...(input.warnings ?? [])];
339
+ const probe = probeInstrumentedProject(input.totemDir);
340
+ if (!probe.instrumented) {
341
+ if (probe.warning !== undefined)
342
+ warnings.push(probe.warning);
343
+ return { written: false, skipped: true, warnings };
344
+ }
345
+ const row = buildSelectionManifestRow({ ...input, warnings });
346
+ const parsed = SelectionManifestRowSchema.safeParse(row);
347
+ if (!parsed.success) {
348
+ const detail = parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
349
+ throw new TotemError('SELECTION_MANIFEST_CONTRACT', `Refusing to write a selection-manifest row that violates the schema: ${detail}`, 'This is an emitter programming error — the manifest schema is strict by contract (Prop 308 F1 refute-condition guard); fix the emitter rather than widening the schema.');
350
+ }
351
+ let written = true;
352
+ try {
353
+ const ledgerDir = path.join(input.totemDir, LEDGER_DIR);
354
+ fs.mkdirSync(ledgerDir, { recursive: true });
355
+ fs.appendFileSync(path.join(ledgerDir, SELECTION_MANIFESTS_FILE), JSON.stringify(parsed.data) + '\n', 'utf-8');
356
+ // totem-context: intentional degradation — the manifest is telemetry; an I/O failure is recorded on the `warnings` accounting channel and the absence stays detectable via the denominator ledger rows (never a silent skip).
357
+ }
358
+ catch (err) {
359
+ written = false;
360
+ const msg = err instanceof Error ? err.message : String(err);
361
+ warnings.push(`selection-manifest: append failed: ${msg}`);
362
+ }
363
+ return { written, warnings };
364
+ }
365
+ /**
366
+ * Sensor-safe `appendSelectionManifest` — command call sites use this.
367
+ * Every degradation, including a thrown contract breach, surfaces through
368
+ * `onWarn`; there is no path that discards a failure silently, and no path
369
+ * that fails the instrumented command (Tenet 13).
370
+ */
371
+ /**
372
+ * Deliver a warning to the caller's sink without letting the sink's own
373
+ * failure escape — an accounting-channel exception altering the instrumented
374
+ * command would invert Tenet 13 at the last step (PR #2625 CR round).
375
+ */
376
+ function safeWarn(onWarn, msg) {
377
+ try {
378
+ onWarn?.(msg);
379
+ // totem-context: intentional swallow — a throwing warning SINK must not fail the instrumented command; the warning also remains in the returned `warnings` array, so nothing is lost.
380
+ }
381
+ catch (err) {
382
+ void err;
383
+ }
384
+ }
385
+ export function senseSelectionManifest(input, onWarn) {
386
+ try {
387
+ const result = appendSelectionManifest(input);
388
+ for (const warning of result.warnings)
389
+ safeWarn(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 = `selection-manifest sensor failed: ${msg}`;
396
+ safeWarn(onWarn, warning);
397
+ return { written: false, warnings: [warning] };
398
+ }
399
+ }
400
+ /**
401
+ * Read all manifest rows, skipping schema-invalid lines (the generic-consumer
402
+ * convention shared with `readLedgerEvents`; a measurement pass that must
403
+ * count rejects reads the file raw — same division of labor as QBD's
404
+ * compliance scanner).
405
+ */
406
+ export function readSelectionManifests(totemDir, onWarn) {
407
+ const filePath = path.join(totemDir, LEDGER_DIR, SELECTION_MANIFESTS_FILE);
408
+ let content;
409
+ try {
410
+ content = fs.readFileSync(filePath, 'utf-8');
411
+ // totem-context: intentional degradation — ENOENT is the normal no-manifests-yet state; every other read failure is REPORTED through onWarn (the accounting channel), never swallowed, and the reader degrades to empty rather than failing its caller (Tenet 13 sensor posture, mirroring readLedgerEvents).
412
+ }
413
+ catch (err) {
414
+ const code = typeof err === 'object' && err !== null ? err.code : undefined;
415
+ if (code === 'ENOENT')
416
+ return [];
417
+ const msg = err instanceof Error ? err.message : String(err);
418
+ onWarn?.(`selection-manifest read failed: ${msg}`);
419
+ return [];
420
+ }
421
+ const rows = [];
422
+ let malformed = 0;
423
+ for (const line of content.split('\n')) {
424
+ if (line.trim().length === 0)
425
+ continue;
426
+ try {
427
+ const parsed = SelectionManifestRowSchema.safeParse(JSON.parse(line));
428
+ if (parsed.success)
429
+ rows.push(parsed.data);
430
+ // totem-context: intentional skip of malformed lines — generic consumers mirror readLedgerEvents; the AGGREGATE count is reported through onWarn below, and reject-counting consumers read the file raw by contract.
431
+ }
432
+ catch (err) {
433
+ void err;
434
+ malformed += 1;
435
+ }
436
+ }
437
+ // One aggregate line, not one per row — a torn or hand-edited file must not
438
+ // turn the accounting channel into a firehose (PR #2625 CR round).
439
+ if (malformed > 0) {
440
+ onWarn?.(`selection-manifest: skipped ${malformed} malformed line(s) — reject-counting consumers read the file raw`);
441
+ }
442
+ return rows;
443
+ }
444
+ //# sourceMappingURL=selection-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-manifest.js","sourceRoot":"","sources":["../src/selection-manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Writer + schema tests for the selection-manifest instrument
3
+ * (mmnto-ai/totem#2468 — the Prop 308 / strategy#467 M1 emitter substrate).
4
+ *
5
+ * The invariants under test come verbatim from the design doc
6
+ * (.totem/specs/2468-selection-manifests.md):
7
+ *
8
+ * - the schema is strict at BOTH levels — the structural refute-condition
9
+ * guard (Prop 308 F1: no shared score field can appear silently);
10
+ * - a fingerprint covers exactly the considered bytes: stable on identical
11
+ * content, changed on changed content, ABSENT (with a named warning) on
12
+ * hash failure — never fabricated;
13
+ * - attribution is stamped absence: no env/seat/session → fields absent,
14
+ * never guessed;
15
+ * - emission is a pure sensor: a failed append leaves the caller unbroken
16
+ * and the failure named on the accounting channel; a schema-invalid row
17
+ * THROWS from the writer (emitter programming error) and is converted to
18
+ * a warning by the sense wrapper;
19
+ * - a `.totem`-less cwd is a silent normal-state skip that materialises no
20
+ * directory.
21
+ *
22
+ * The EACCES induced-failure uses vi.spyOn on `node:fs` (accounting-branch
23
+ * coverage, same scoping rationale as qbd/record.test.ts documents).
24
+ */
25
+ export {};
26
+ //# sourceMappingURL=selection-manifest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-manifest.test.d.ts","sourceRoot":"","sources":["../src/selection-manifest.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}