@mmnto/totem 1.122.0 → 1.124.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 (45) hide show
  1. package/dist/artifacts/legs.d.ts +430 -0
  2. package/dist/artifacts/legs.d.ts.map +1 -0
  3. package/dist/artifacts/legs.js +577 -0
  4. package/dist/artifacts/legs.js.map +1 -0
  5. package/dist/artifacts/legs.test.d.ts +19 -0
  6. package/dist/artifacts/legs.test.d.ts.map +1 -0
  7. package/dist/artifacts/legs.test.js +628 -0
  8. package/dist/artifacts/legs.test.js.map +1 -0
  9. package/dist/config-schema.d.ts +35 -0
  10. package/dist/config-schema.d.ts.map +1 -1
  11. package/dist/config-schema.js +28 -0
  12. package/dist/config-schema.js.map +1 -1
  13. package/dist/config-schema.test.js +73 -0
  14. package/dist/config-schema.test.js.map +1 -1
  15. package/dist/errors.d.ts +3 -1
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js.map +1 -1
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/routing/legs-owed.d.ts +65 -0
  23. package/dist/routing/legs-owed.d.ts.map +1 -0
  24. package/dist/routing/legs-owed.js +72 -0
  25. package/dist/routing/legs-owed.js.map +1 -0
  26. package/dist/routing/legs-owed.test.d.ts +10 -0
  27. package/dist/routing/legs-owed.test.d.ts.map +1 -0
  28. package/dist/routing/legs-owed.test.js +119 -0
  29. package/dist/routing/legs-owed.test.js.map +1 -0
  30. package/dist/semantic-dedup.d.ts.map +1 -1
  31. package/dist/semantic-dedup.js +2 -5
  32. package/dist/semantic-dedup.js.map +1 -1
  33. package/dist/semantic-dedup.test.js +32 -1
  34. package/dist/semantic-dedup.test.js.map +1 -1
  35. package/dist/store/lance-store.test.js +23 -0
  36. package/dist/store/lance-store.test.js.map +1 -1
  37. package/dist/store/search-lessons.d.ts +18 -0
  38. package/dist/store/search-lessons.d.ts.map +1 -0
  39. package/dist/store/search-lessons.js +18 -0
  40. package/dist/store/search-lessons.js.map +1 -0
  41. package/dist/store/search-lessons.test.d.ts +2 -0
  42. package/dist/store/search-lessons.test.d.ts.map +1 -0
  43. package/dist/store/search-lessons.test.js +48 -0
  44. package/dist/store/search-lessons.test.js.map +1 -0
  45. package/package.json +1 -1
@@ -0,0 +1,577 @@
1
+ /**
2
+ * Leg-deposit contract — the machine-readable record a falsification leg
3
+ * leaves behind after it READ a diff (mmnto-ai/totem#2698, ruled 2026-09-03).
4
+ *
5
+ * The gate's question is deliberately narrow: *was THIS head read by a leg?*
6
+ * So the store is keyed by the READ sha — `<totemDir>/artifacts/legs/<diffSha>.json`
7
+ * — and NOT content-addressed like the verdict/admission/run families. Two
8
+ * legs that read the same head are the same answer to that question; a rerun
9
+ * on the same head supersedes by INTENT, which is why the writer is
10
+ * create-exclusive and a `replace` discloses the `readAt` it overwrote rather
11
+ * than silently deduping (the content-addressed stores' EEXIST-is-dedup rule
12
+ * would be a lie here — the second read is a different observation).
13
+ *
14
+ * Store mechanics still mirror the sibling families where the semantics agree:
15
+ * validate-on-write so a writer bug never poisons the store, a
16
+ * tolerant-within-major reader whose newer-major refusal is NAMED (an older
17
+ * CLI meeting a newer deposit reports "upgrade", never "corrupt"), unknown
18
+ * keys tolerated (forward-minor additive fields), and `0o600` bytes.
19
+ *
20
+ * Two properties are specific to this family:
21
+ *
22
+ * 1. **The loader never throws.** A deposit is a hand-editable JSON file that
23
+ * a pre-push hook reads on every push; one bad file must never take the
24
+ * gate down or hide a valid sibling. Every per-file failure — unreadable,
25
+ * non-JSON, schema-invalid, wrong major, or a filename that disagrees with
26
+ * the stored `diffSha` — becomes a one-line `corrupt` row the caller
27
+ * prints (Tenet 4: loud, never silent).
28
+ * 2. **Every stored string that reaches stdout is control-byte free at the
29
+ * SCHEMA boundary**, at write and at read alike. `verdict`, `claim`,
30
+ * `counterexample` and `file` land inside a hook's `[Totem] …` line; a
31
+ * newline in any of them would forge a second line, so the schema refuses
32
+ * C0 (0-31) and DEL/C1 (127-159) outright. "Single-line" needs no separate
33
+ * rule: LF and CR are inside that band.
34
+ *
35
+ * Core never shells out. Ancestry is supplied by the CLI through the injected
36
+ * {@link LegGitAdapter}, so every resolution rule here is unit-testable
37
+ * without a git fixture.
38
+ */
39
+ import * as crypto from 'node:crypto';
40
+ import * as fs from 'node:fs';
41
+ import * as path from 'node:path';
42
+ import { z } from 'zod';
43
+ import { TotemError } from '../errors.js';
44
+ import { writeFileAtomicSync } from '../fs-atomic.js';
45
+ // ─── Schema version (mirrors the RunArtifact / panel F1 policy) ─────────────
46
+ /** The leg-deposit schemaVersion WRITTEN by this code. Readers accept any 1.x. */
47
+ export const LEG_DEPOSIT_SCHEMA_VERSION = '1.0.0';
48
+ /** The major this reader understands; another major needs a migration entry. */
49
+ export const LEG_DEPOSIT_KNOWN_MAJOR = 1;
50
+ /** Major-1 semver literal — keep in sync with {@link LEG_DEPOSIT_KNOWN_MAJOR}. */
51
+ const LEG_SCHEMA_VERSION_RE = /^1\.\d+\.\d+$/;
52
+ /**
53
+ * Accept any 1.x version; refuse another major with a NAMED reason (the panel
54
+ * precedent). Zod `.regex()` is the validation boundary — not a bare
55
+ * `RegExp.test` — so the ZodError carries the offending value, and the
56
+ * refusal reads the same at write and at read.
57
+ */
58
+ const legSchemaVersionField = z.string().regex(LEG_SCHEMA_VERSION_RE, {
59
+ message: `unsupported leg-deposit schemaVersion — this reader understands major ${LEG_DEPOSIT_KNOWN_MAJOR}.x; a newer major needs a newer @mmnto/cli`,
60
+ });
61
+ /** The read sha: a full 40-char lowercase git object name, never abbreviated. */
62
+ const DIFF_SHA_RE = /^[0-9a-f]{40}$/;
63
+ /** Finding ids are filename-safe and short — they are echoed and cross-referenced. */
64
+ const LEG_FINDING_ID_RE = /^[A-Za-z0-9_-]{1,32}$/;
65
+ // ─── Control-byte discipline ────────────────────────────────────────────────
66
+ /**
67
+ * True when `text` carries any C0 (0-31) or DEL/C1 (127-159) code point.
68
+ *
69
+ * Written as a `charCodeAt` loop on purpose, mirroring the pre-commit hook's
70
+ * `safe()` (`install-hooks.ts`): the equivalent regex character class would
71
+ * have to be authored with `\u`/`\x` escapes, and this repo has a banked
72
+ * incident where an editing tool decoded such an escape into a RAW control
73
+ * byte in the source. The loop states the same rule with no escape to decode.
74
+ * U+0085 (NEL) is inside the band because it breaks a line on some terminals.
75
+ */
76
+ function hasControlByte(text) {
77
+ for (let i = 0; i < text.length; i++) {
78
+ const code = text.charCodeAt(i);
79
+ if (code < 32 || (code >= 127 && code <= 159))
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+ /** Replace every control byte with `?` — used for the one-line `corrupt` reasons. */
85
+ function toSingleLine(text) {
86
+ let out = '';
87
+ for (let i = 0; i < text.length; i++) {
88
+ const code = text.charCodeAt(i);
89
+ const control = code < 32 || (code >= 127 && code <= 159);
90
+ out = out + (control ? '?' : text.charAt(i));
91
+ }
92
+ return out;
93
+ }
94
+ /** A string field that reaches a hook's stdout: no C0/C1 bytes, hence single-line. */
95
+ function controlFreeText(label) {
96
+ return z.string().refine((value) => !hasControlByte(value), {
97
+ message: `${label} must be a single line free of control characters (C0 0-31, DEL/C1 127-159) — the value is echoed on the gate's stdout`,
98
+ });
99
+ }
100
+ /** Same, but also non-empty. */
101
+ function requiredControlFreeText(label) {
102
+ return z
103
+ .string()
104
+ .min(1, `${label} must not be empty`)
105
+ .refine((value) => !hasControlByte(value), {
106
+ message: `${label} must be a single line free of control characters (C0 0-31, DEL/C1 127-159) — the value is echoed on the gate's stdout`,
107
+ });
108
+ }
109
+ // ─── Findings ───────────────────────────────────────────────────────────────
110
+ /**
111
+ * The leg's severity vocabulary. Deliberately NOT the verdict family's
112
+ * CRITICAL/WARN/INFO: a leg deposit answers "what did the leg find", and the
113
+ * doctrine spelling for that is BLOCKING (the fold must land) / MATERIAL (the
114
+ * seat rules) / MINOR (disclosed). Order here is documentation order; the
115
+ * value is data. One spelling — {@link LegFindingSeveritySchema} is built
116
+ * from this array, never a second literal list.
117
+ */
118
+ export const LEG_FINDING_SEVERITIES = ['BLOCKING', 'MATERIAL', 'MINOR'];
119
+ export const LegFindingSeveritySchema = z.enum(LEG_FINDING_SEVERITIES);
120
+ /** The finding id — required, so `folded` can NAME a finding rather than index it. */
121
+ export const LegFindingIdSchema = z
122
+ .string()
123
+ .regex(LEG_FINDING_ID_RE, 'a finding id must be 1-32 characters of [A-Za-z0-9_-]');
124
+ /**
125
+ * One typed finding. `id` is required (mmnto-ai/totem#2698 OQ1, ruled): the
126
+ * contract JSON had none, and without one `folded` cannot reference a finding
127
+ * — positional indexes are fragile under reordering, and a bare count cannot
128
+ * be checked against anything.
129
+ */
130
+ export const LegFindingSchema = z.object({
131
+ /** Unique within the deposit; the referent of every `folded` entry. */
132
+ id: LegFindingIdSchema,
133
+ severity: LegFindingSeveritySchema,
134
+ /** Repo-relative path the finding is about (free-form: core never resolves it). */
135
+ file: requiredControlFreeText('finding.file'),
136
+ /** 0 means "the file, no particular line" — never a sentinel like -1. */
137
+ line: z.number().int().nonnegative(),
138
+ /** The falsifiable claim, one line. */
139
+ claim: requiredControlFreeText('finding.claim'),
140
+ /** The evidence. MAY be empty — a claim with no counterexample is still disclosed. */
141
+ counterexample: controlFreeText('finding.counterexample'),
142
+ });
143
+ // ─── Deposit ────────────────────────────────────────────────────────────────
144
+ /**
145
+ * The deposit itself. Every field is required; the arrays may be empty (a leg
146
+ * that found nothing still deposits — that IS the evidence the gate wants).
147
+ *
148
+ * Unknown keys are TOLERATED, not refused (the run-artifact precedent): a
149
+ * forward-minor writer may add a field this reader strips, and a deposit is
150
+ * evidence a newer leg wrote, not a contract this reader gets to narrow.
151
+ */
152
+ export const LegDepositSchema = z
153
+ .object({
154
+ /** Writer version; readers tolerate any 1.x, refuse another major by name. */
155
+ schemaVersion: legSchemaVersionField,
156
+ /** The head the leg READ — a full 40-hex commit, and the file's own name. */
157
+ diffSha: z.string().regex(DIFF_SHA_RE, 'diffSha must be a 40-character lowercase hex sha'),
158
+ /** The leg's own instant, ISO-8601. Ties are broken on it, so it is contract. */
159
+ readAt: z.string().datetime(),
160
+ findings: z.array(LegFindingSchema),
161
+ /** Ids of findings the seat FOLDED — a subset of `findings[].id` (superRefine). */
162
+ folded: z.array(LegFindingIdSchema),
163
+ /** The leg's one-line disposition; echoed verbatim on the gate's evidence line. */
164
+ verdict: requiredControlFreeText('verdict'),
165
+ })
166
+ .superRefine((deposit, ctx) => {
167
+ const seen = new Set();
168
+ for (const [index, finding] of deposit.findings.entries()) {
169
+ if (seen.has(finding.id)) {
170
+ ctx.addIssue({
171
+ code: z.ZodIssueCode.custom,
172
+ path: ['findings', index, 'id'],
173
+ message: `duplicate finding id "${finding.id}" — ids must be unique within a deposit (folded names them)`,
174
+ });
175
+ }
176
+ seen.add(finding.id);
177
+ }
178
+ for (const [index, id] of deposit.folded.entries()) {
179
+ if (!seen.has(id)) {
180
+ ctx.addIssue({
181
+ code: z.ZodIssueCode.custom,
182
+ path: ['folded', index],
183
+ message: `folded names "${id}", which is not a finding id in this deposit`,
184
+ });
185
+ }
186
+ }
187
+ });
188
+ // ─── Paths ──────────────────────────────────────────────────────────────────
189
+ /** Storage layout segments under the totem dir (mirrors `runsDir`'s idiom). */
190
+ const LEGS_DIR_SEGMENTS = ['artifacts', 'legs'];
191
+ /** Absolute legs directory for a given absolute totem dir. */
192
+ export function legsDir(totemDirAbs) {
193
+ return path.join(totemDirAbs, ...LEGS_DIR_SEGMENTS);
194
+ }
195
+ /** Absolute path of the deposit for `diffSha` — the sha IS the filename stem. */
196
+ export function legDepositPath(totemDirAbs, diffSha) {
197
+ return path.join(legsDir(totemDirAbs), `${diffSha}.json`);
198
+ }
199
+ // ─── Save ───────────────────────────────────────────────────────────────────
200
+ /**
201
+ * Refusal to overwrite an existing deposit without `replace`. Typed so the
202
+ * CLI can print the incumbent's `readAt` and the one-flag cure rather than
203
+ * re-deriving either. `existingReadAt` is `undefined` when the incumbent
204
+ * itself is unreadable/corrupt — the refusal still stands (the seat decides
205
+ * whether to replace it), and the honest report is that its instant could not
206
+ * be read.
207
+ */
208
+ export class LegDepositExistsError extends TotemError {
209
+ diffSha;
210
+ existingReadAt;
211
+ depositPath;
212
+ constructor(diffSha, existingReadAt, depositPath) {
213
+ super('LEG_DEPOSIT_EXISTS', `A leg deposit already exists for ${diffSha} (read ${existingReadAt ?? 'at an unreadable instant'}) at ${depositPath}.`, 'Pass --replace to overwrite it (the replaced readAt is reported), or deposit against the current head.');
214
+ this.name = 'LegDepositExistsError';
215
+ this.diffSha = diffSha;
216
+ this.existingReadAt = existingReadAt;
217
+ this.depositPath = depositPath;
218
+ }
219
+ }
220
+ /** Best-effort read of an incumbent's `readAt` — for disclosure only, never a gate. */
221
+ function peekReadAt(filePath) {
222
+ try {
223
+ const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
224
+ if (typeof raw !== 'object' || raw === null)
225
+ return undefined;
226
+ const readAt = raw['readAt'];
227
+ return typeof readAt === 'string' ? toSingleLine(readAt) : undefined;
228
+ // totem-context: intentional best-effort — the incumbent's instant is DISCLOSURE beside a refusal that happens either way; a corrupt incumbent must not turn "already deposited" into a read error
229
+ }
230
+ catch {
231
+ return undefined;
232
+ }
233
+ }
234
+ /**
235
+ * Persist a deposit at `<legsDir>/<diffSha>.json`, create-exclusive by default.
236
+ *
237
+ * Ordering is contract: the deposit is VALIDATED before the filesystem is
238
+ * touched at all, and the occupancy check precedes the write — so a refused
239
+ * write (schema-invalid, or occupied without `replace`) leaves no file and no
240
+ * temp behind. The bytes go through the shared atomic helper (temp in the same
241
+ * directory, fsync), so a reader never observes a torn deposit.
242
+ *
243
+ * The PUBLISH is exclusive, not merely checked (Greptile P1 on PR
244
+ * mmnto-ai/totem#2745). `existsSync` then rename is check-then-act: two
245
+ * no-replace writers for one sha both see "absent" and the later rename wins
246
+ * silently, overwriting one leg's read with another's. Without `replace` the
247
+ * final name is therefore created by `fs.linkSync`, which is atomic and fails
248
+ * `EEXIST` if the name is taken — the loser refuses exactly as the pre-check
249
+ * does, and the incumbent's bytes are never touched. `replace: true` keeps the
250
+ * rename path, because overwriting is what it asks for.
251
+ *
252
+ * The file's name is DERIVED from `deposit.diffSha`, which is how the store's
253
+ * "the name is the read sha" invariant holds by construction; the loader
254
+ * re-checks it on the way back in, since the name comes off a filesystem a
255
+ * human can rename.
256
+ */
257
+ export function saveLegDeposit(totemDirAbs, deposit, options = {}) {
258
+ // Validate on the way OUT (the run/admission/verdict precedent): a writer bug
259
+ // must never poison a store whose reader would then report it as corrupt.
260
+ const validated = LegDepositSchema.parse(deposit);
261
+ const filePath = legDepositPath(totemDirAbs, validated.diffSha);
262
+ let replaced;
263
+ // The cheap early refusal — it answers the common case without writing a
264
+ // byte. It is NOT the guarantee: two writers can both pass it (the exclusive
265
+ // publish below is what actually decides). Greptile P1 on PR
266
+ // mmnto-ai/totem#2745.
267
+ if (fs.existsSync(filePath)) {
268
+ const existingReadAt = peekReadAt(filePath);
269
+ if (options.replace !== true) {
270
+ throw new LegDepositExistsError(validated.diffSha, existingReadAt, filePath);
271
+ }
272
+ replaced = { readAt: existingReadAt };
273
+ }
274
+ fs.mkdirSync(legsDir(totemDirAbs), { recursive: true });
275
+ const body = JSON.stringify(validated, null, 2);
276
+ if (options.replace === true) {
277
+ // `replace` is ALLOWED to overwrite, so the rename path is correct here:
278
+ // the incumbent's `readAt` is already captured above and reported.
279
+ writeFileAtomicSync(filePath, body, { mode: 0o600 });
280
+ // `replaced` stays absent when `--replace` was passed with nothing to
281
+ // replace: reporting a replacement that did not happen would be a lie the
282
+ // writer's own success line then prints.
283
+ return replaced === undefined ? { path: filePath } : { path: filePath, replaced };
284
+ }
285
+ // Create-exclusive for real. `existsSync` then rename is check-then-act: two
286
+ // no-replace writers for one sha both see "absent", both rename, and the
287
+ // later one silently wins — one leg's read overwritten by another's with no
288
+ // refusal and no disclosure, in a store whose whole contract is that a
289
+ // second read at a head is a different observation.
290
+ //
291
+ // `link` is the exclusive primitive: it creates the final name atomically
292
+ // and fails EEXIST if anything already holds it. Measured on NTFS as well as
293
+ // POSIX for this fix — the loser gets EEXIST, the incumbent's bytes are
294
+ // untouched, and the loser's temp survives to be cleaned up here.
295
+ const tempPath = `${filePath}.${process.pid}-${randomSuffix()}.tmp`;
296
+ try {
297
+ writeFileAtomicSync(tempPath, body, { mode: 0o600 });
298
+ fs.linkSync(tempPath, filePath);
299
+ }
300
+ catch (err) {
301
+ if (err.code === 'EEXIST') {
302
+ // Someone published between the pre-check and here. Same refusal the
303
+ // pre-check raises, carrying the incumbent's instant — read AFTER the
304
+ // loss, so it names the deposit that actually won.
305
+ const existingReadAt = peekReadAt(filePath);
306
+ removeQuietly(tempPath);
307
+ throw new LegDepositExistsError(validated.diffSha, existingReadAt, filePath);
308
+ }
309
+ removeQuietly(tempPath);
310
+ throw err;
311
+ }
312
+ // The link succeeded, so `filePath` and `tempPath` name the same inode; the
313
+ // temp is just the second name and goes away.
314
+ removeQuietly(tempPath);
315
+ return { path: filePath };
316
+ }
317
+ /** Entropy for the publish temp's name — the `writeFileAtomicSync` idiom. */
318
+ function randomSuffix() {
319
+ return crypto.randomUUID().slice(0, 8);
320
+ }
321
+ /** Best-effort unlink of a publish temp. */
322
+ function removeQuietly(target) {
323
+ try {
324
+ fs.rmSync(target, { force: true });
325
+ // totem-context: intentional cleanup — a failed temp unlink must not mask the publish's own outcome, and the file it leaves behind is a `.tmp` sibling the loader already ignores (it is not `.json`)
326
+ }
327
+ catch {
328
+ /* best-effort: the temp is a `.tmp` sibling the loader never reads */
329
+ }
330
+ }
331
+ /** Best-effort major extraction from a raw parsed payload; undefined when absent/garbled. */
332
+ function readMajor(raw) {
333
+ if (typeof raw !== 'object' || raw === null)
334
+ return undefined;
335
+ const version = raw['schemaVersion'];
336
+ if (typeof version !== 'string')
337
+ return undefined;
338
+ const major = Number.parseInt(version.split('.')[0] ?? '', 10);
339
+ return Number.isNaN(major) ? undefined : major;
340
+ }
341
+ /** Flatten a ZodError into one echo-safe line. */
342
+ function oneLineZodReason(zodError) {
343
+ return toSingleLine(zodError.issues
344
+ .map((issue) => {
345
+ const at = issue.path.length > 0 ? issue.path.join('.') : '<root>';
346
+ return `${at}: ${issue.message}`;
347
+ })
348
+ .join('; '));
349
+ }
350
+ /**
351
+ * Read every deposit in the store, TOLERANTLY.
352
+ *
353
+ * Never throws: a missing directory is an empty store, a non-`.json` entry is
354
+ * not a deposit at all (ignored silently — the directory is not the store's
355
+ * inventory), and every other per-file failure is a `corrupt` row carrying a
356
+ * one-line reason. The reading is JSON-AWARE through the schema, so a file
357
+ * that merely *mentions* `diffSha` or `findings` in some other shape — a
358
+ * review artifact copied in, say — is corrupt, never a deposit.
359
+ *
360
+ * The filename is re-checked against the stored `diffSha`: the name is the
361
+ * store's address, a human can rename a file, and a deposit resolved under
362
+ * someone else's sha would answer the gate's question about the wrong head.
363
+ */
364
+ export function loadLegDeposits(totemDirAbs) {
365
+ const dir = legsDir(totemDirAbs);
366
+ const deposits = [];
367
+ const corrupt = [];
368
+ let entries;
369
+ try {
370
+ entries = fs.readdirSync(dir);
371
+ // totem-context: intentional — a missing/unreadable store dir IS the "no deposit" state the gate already has a loud arm for; the loader's contract is that it never throws
372
+ }
373
+ catch {
374
+ return { deposits, corrupt };
375
+ }
376
+ for (const entry of [...entries].sort()) {
377
+ if (!entry.endsWith('.json'))
378
+ continue;
379
+ const filePath = path.join(dir, entry);
380
+ const stem = entry.slice(0, -'.json'.length);
381
+ let raw;
382
+ try {
383
+ raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
384
+ // totem-context: intentional — an unreadable or non-JSON file becomes a NAMED corrupt row (reason carried, control bytes collapsed) and never a throw; the loader's contract is tolerant-per-file so one bad deposit can never mask a valid sibling
385
+ }
386
+ catch (err) {
387
+ corrupt.push({
388
+ file: entry,
389
+ reason: toSingleLine(`unreadable or not JSON: ${err instanceof Error ? err.message : String(err)}`),
390
+ });
391
+ continue;
392
+ }
393
+ const major = readMajor(raw);
394
+ if (major !== undefined && major > LEG_DEPOSIT_KNOWN_MAJOR) {
395
+ corrupt.push({
396
+ file: entry,
397
+ reason: `written by a newer totem (schemaVersion major ${major}; this reader understands ${LEG_DEPOSIT_KNOWN_MAJOR}.x) — upgrade @mmnto/cli to read it, the deposit is valid`,
398
+ });
399
+ continue;
400
+ }
401
+ const parsed = LegDepositSchema.safeParse(raw);
402
+ if (!parsed.success) {
403
+ corrupt.push({ file: entry, reason: `schema-invalid: ${oneLineZodReason(parsed.error)}` });
404
+ continue;
405
+ }
406
+ if (parsed.data.diffSha !== stem) {
407
+ corrupt.push({
408
+ file: entry,
409
+ reason: `filename does not match its stored diffSha ${parsed.data.diffSha} — the name is this store's address`,
410
+ });
411
+ continue;
412
+ }
413
+ deposits.push({ deposit: parsed.data, path: filePath, diffSha: parsed.data.diffSha });
414
+ }
415
+ return { deposits, corrupt };
416
+ }
417
+ /**
418
+ * Resolve which deposit (if any) answers for `headSha`.
419
+ *
420
+ * A deposit read at X answers for every DESCENDANT of X — the contract's
421
+ * ancestor-or-equal rule — so the ranking is: `exact` first, then the NEAREST
422
+ * ancestor (fewest commits since the read), ties to the LATEST `readAt`. The
423
+ * winner carries its `distance` so the caller can print "+N commits since the
424
+ * leg read" rather than pass a stale read off as a fresh one.
425
+ *
426
+ * Since mmnto-ai/totem#2698 fold 3 ancestry is only HALF the question. When a
427
+ * `coverage` query is supplied, each ancestor candidate is also measured on
428
+ * what it could have READ — the branch diff up to its own head, intersected
429
+ * with the owed paths — and a candidate covering NONE of them is stale, not a
430
+ * winner: it satisfies ancestor-or-equal while its leg saw nothing this push
431
+ * proposes (the merge-base deposit that produced the ruling). An EXACT match
432
+ * covers everything by construction and costs no git call.
433
+ *
434
+ * Ranking is unchanged: along one lineage a nearer ancestor's diff is a
435
+ * superset of a farther one's, so nearest-first already orders by coverage.
436
+ *
437
+ * Nothing here decides POLICY beyond that floor: partial coverage still
438
+ * resolves, and a distance of 200 still resolves. Whether either is evidence
439
+ * enough is the gate's disclosure to make and doctrine's re-arm to rule.
440
+ */
441
+ export function findLegDepositForHead(totemDirAbs, headSha, git, coverage) {
442
+ const { deposits, corrupt } = loadLegDeposits(totemDirAbs);
443
+ const stale = [];
444
+ const candidates = [];
445
+ // Deduplicated here as well as by the caller: `owed` is PRINTED as the
446
+ // denominator of `covers K/N`, and one file matching three globs must not
447
+ // inflate N into a number no reader can reconcile with the basis.
448
+ const owedFiles = coverage === undefined ? [] : [...new Set(coverage.owedFiles)];
449
+ const fullCoverage = () => ({
450
+ covered: owedFiles.length,
451
+ owed: owedFiles.length,
452
+ missing: [],
453
+ });
454
+ for (const found of deposits) {
455
+ if (found.diffSha === headSha) {
456
+ // An exact match read THIS head: every owed path is inside the diff it
457
+ // saw, by construction. No git call — and an adapter that would throw on
458
+ // one is never reached, which is the property the test pins.
459
+ candidates.push({
460
+ ...found,
461
+ rank: 'exact',
462
+ distance: 0,
463
+ ...(coverage === undefined ? {} : { coverage: fullCoverage() }),
464
+ });
465
+ continue;
466
+ }
467
+ if (!git.isCommit(found.diffSha)) {
468
+ stale.push({
469
+ diffSha: found.diffSha,
470
+ readAt: found.deposit.readAt,
471
+ reason: 'unknown-commit',
472
+ });
473
+ continue;
474
+ }
475
+ if (!git.isAncestor(found.diffSha, headSha)) {
476
+ stale.push({ diffSha: found.diffSha, readAt: found.deposit.readAt, reason: 'not-ancestor' });
477
+ continue;
478
+ }
479
+ let measured;
480
+ if (coverage !== undefined && owedFiles.length === 0) {
481
+ // Nothing owed: the intersection is empty whatever the candidate reached,
482
+ // so the probe would be a git call whose result cannot change the answer
483
+ // (mmnto-ai/totem#2698 fold 5, Q3). `0/0` is vacuously covered.
484
+ measured = fullCoverage();
485
+ }
486
+ else if (coverage !== undefined) {
487
+ const reachable = new Set(git.changedFiles(coverage.base, found.diffSha));
488
+ const missing = owedFiles.filter((file) => !reachable.has(file));
489
+ measured = { covered: owedFiles.length - missing.length, owed: owedFiles.length, missing };
490
+ // Reached only with a non-empty owed set (the empty case returned above),
491
+ // so covering none of it is a real coverage failure.
492
+ if (measured.covered === 0) {
493
+ stale.push({
494
+ diffSha: found.diffSha,
495
+ readAt: found.deposit.readAt,
496
+ reason: 'no-coverage',
497
+ });
498
+ continue;
499
+ }
500
+ }
501
+ candidates.push({
502
+ ...found,
503
+ rank: 'ancestor',
504
+ // Measured AFTER coverage, so a candidate that is already stale costs no
505
+ // second git call.
506
+ distance: git.distance(found.diffSha, headSha),
507
+ ...(measured === undefined ? {} : { coverage: measured }),
508
+ });
509
+ }
510
+ candidates.sort((a, b) => {
511
+ if (a.rank !== b.rank)
512
+ return a.rank === 'exact' ? -1 : 1;
513
+ if (a.distance !== b.distance)
514
+ return a.distance - b.distance;
515
+ // Equal reach: the LATEST read wins — the later leg saw everything the
516
+ // earlier one did, at the same head.
517
+ return Date.parse(b.deposit.readAt) - Date.parse(a.deposit.readAt);
518
+ });
519
+ const [winner, ...rest] = candidates;
520
+ return {
521
+ ...(winner === undefined ? {} : { winner }),
522
+ superseded: rest.map((candidate) => ({
523
+ diffSha: candidate.diffSha,
524
+ readAt: candidate.deposit.readAt,
525
+ rank: candidate.rank,
526
+ distance: candidate.distance,
527
+ ...(candidate.coverage === undefined ? {} : { coverage: candidate.coverage }),
528
+ })),
529
+ stale,
530
+ corrupt,
531
+ };
532
+ }
533
+ /**
534
+ * Count a deposit's findings by severity, plus how many were folded.
535
+ *
536
+ * A folded finding is still a finding: it is counted in its severity bucket
537
+ * AND in `folded`. The covariate prints both, so a reader can see "3 blocking,
538
+ * 3 folded" (all addressed) apart from "3 blocking, 0 folded" (none were).
539
+ */
540
+ export function countLegFindings(deposit) {
541
+ let blocking = 0;
542
+ let material = 0;
543
+ let minor = 0;
544
+ for (const finding of deposit.findings) {
545
+ if (finding.severity === 'BLOCKING')
546
+ blocking++;
547
+ else if (finding.severity === 'MATERIAL')
548
+ material++;
549
+ else
550
+ minor++;
551
+ }
552
+ return { blocking, material, minor, folded: deposit.folded.length };
553
+ }
554
+ /**
555
+ * Render the covariate line's format-v1.2 `leg:` field — the ONE spelling of
556
+ * this text, exactly:
557
+ *
558
+ * `leg: <sha8> blocking=N material=N folded=N` (a deposit resolved)
559
+ * `leg: none` (none did)
560
+ *
561
+ * The field is COMPOSED BESIDE {@link renderCovariateLine} / `renderAdmissionLine`,
562
+ * never inside them: the v1 shapes stay byte-identical, and a consumer keeps
563
+ * discriminating on the second token. The text is contract — the pilot ledger
564
+ * greps this line — so it is spelled here and nowhere else.
565
+ *
566
+ * `minor` is deliberately absent from the field: the round rules on blocking
567
+ * and material, and `folded` is what says whether they were addressed. The
568
+ * full counts stay available via {@link countLegFindings}.
569
+ */
570
+ export function renderLegField(deposit) {
571
+ if (deposit === undefined)
572
+ return 'leg: none';
573
+ const counts = countLegFindings(deposit);
574
+ const sha8 = deposit.diffSha.slice(0, 8);
575
+ return `leg: ${sha8} blocking=${counts.blocking} material=${counts.material} folded=${counts.folded}`;
576
+ }
577
+ //# sourceMappingURL=legs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legs.js","sourceRoot":"","sources":["../../src/artifacts/legs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Leg-deposit store tests (mmnto-ai/totem#2698).
3
+ *
4
+ * These lock the core-owned half of the design's "invariants to lock in via
5
+ * tests": the store is read JSON-AWARE through the schema (a file that merely
6
+ * quotes `diffSha`/`findings` in some other shape is not a deposit), ancestry
7
+ * ranking is exact > nearest ancestor > latest read, a corrupt file is
8
+ * disclosed by name and reason and never hides a valid sibling,
9
+ * `unknown-commit` and `not-ancestor` are DISTINCT stale reasons, the schema
10
+ * refuses control bytes and a non-subset `folded` at write and at read alike,
11
+ * and the writer is create-exclusive with a disclosed replacement.
12
+ *
13
+ * Every hostile string is built with `String.fromCharCode` on purpose: a
14
+ * literal `\u`/`\x` escape authored through an editing tool has landed as a
15
+ * RAW control byte in this repo before, which would make the test assert
16
+ * something other than what it reads as.
17
+ */
18
+ export {};
19
+ //# sourceMappingURL=legs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legs.test.d.ts","sourceRoot":"","sources":["../../src/artifacts/legs.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}