tickmarkr 2.7.8 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/codex.d.ts +1 -1
- package/dist/adapters/codex.js +18 -6
- package/dist/adapters/fake.js +42 -4
- package/dist/cli/commands/doctor.js +11 -2
- package/dist/cli/commands/fleet.js +206 -18
- package/dist/cli/commands/verify.js +9 -1
- package/dist/gates/acceptance.d.ts +1 -1
- package/dist/gates/acceptance.js +75 -68
- package/dist/gates/baseline.d.ts +42 -9
- package/dist/gates/baseline.js +261 -47
- package/dist/gates/operation-facts.d.ts +47 -0
- package/dist/gates/operation-facts.js +84 -0
- package/dist/gates/run-gates.js +20 -11
- package/dist/gates/test-manifest.js +2 -2
- package/dist/run/git.d.ts +5 -1
- package/dist/run/git.js +7 -3
- package/dist/run/journal.d.ts +1 -1
- package/dist/run/launchservices-check.d.ts +23 -1
- package/dist/run/launchservices-check.js +88 -3
- package/dist/run/merge.js +26 -12
- package/package.json +1 -1
package/dist/gates/acceptance.js
CHANGED
|
@@ -12,6 +12,7 @@ import { classifyVerdictCause } from "./verdict-cause.js";
|
|
|
12
12
|
import { reviewableLogicDiff } from "./artifact-manifest.js";
|
|
13
13
|
import { diffSidePath } from "./git-paths.js";
|
|
14
14
|
import { classifyFailureOutput } from "./baseline.js";
|
|
15
|
+
import { operationFactsAtHead, readOperationFacts } from "./operation-facts.js";
|
|
15
16
|
// Fable F4: acceptance judge shares review's 900s timeout — 300s default killed frontier judges on cap-sized diffs.
|
|
16
17
|
const JUDGE_TIMEOUT_MS = 900_000;
|
|
17
18
|
const CitationSchema = z.object({ path: z.string(), line: z.number().int() });
|
|
@@ -19,9 +20,8 @@ const JudgeVerdictRowSchema = z.object({
|
|
|
19
20
|
criterion: z.string(),
|
|
20
21
|
met: z.boolean(),
|
|
21
22
|
reason: z.string(),
|
|
22
|
-
// Required
|
|
23
|
-
|
|
24
|
-
evidence: z.union([CitationSchema, z.string()]),
|
|
23
|
+
// Required: a row omitting evidence is malformed and fails closed like any other shape violation.
|
|
24
|
+
evidence: CitationSchema,
|
|
25
25
|
});
|
|
26
26
|
const JudgeVerdictSchema = z.object({
|
|
27
27
|
pass: z.boolean(),
|
|
@@ -33,15 +33,7 @@ export function judgeCriterionId(index) {
|
|
|
33
33
|
function renderJudgeCriterion(item, index) {
|
|
34
34
|
return `[${judgeCriterionId(index)}] ${renderAcceptanceItem(item)}`;
|
|
35
35
|
}
|
|
36
|
-
function checkJudgeVerdict(
|
|
37
|
-
const parsed = JudgeVerdictSchema.safeParse(raw);
|
|
38
|
-
if (!parsed.success) {
|
|
39
|
-
return {
|
|
40
|
-
verdict: { pass: false, criteria: [] },
|
|
41
|
-
inconsistencies: ["judge verdict inconsistent: malformed verdict shape"],
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
const v = parsed.data;
|
|
36
|
+
function checkJudgeVerdict(v, expectedIds) {
|
|
45
37
|
const inconsistencies = [];
|
|
46
38
|
const counts = new Map();
|
|
47
39
|
for (const row of v.criteria) {
|
|
@@ -65,7 +57,14 @@ function checkJudgeVerdict(raw, expectedIds) {
|
|
|
65
57
|
if (!v.pass && v.criteria.every((row) => row.met) && inconsistencies.length === 0 && v.criteria.length === expectedIds.length) {
|
|
66
58
|
inconsistencies.push("judge verdict inconsistent: pass=false contradicts all criteria met");
|
|
67
59
|
}
|
|
68
|
-
return
|
|
60
|
+
return inconsistencies;
|
|
61
|
+
}
|
|
62
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
// S23: the RAW verdict's citation shape, checked before the schema so a string, null or absent evidence, or a
|
|
64
|
+
// non-array criteria, is a protocol failure rather than a charged red. Reads fields only, never converts one.
|
|
65
|
+
function rawCitationsWellFormed(raw) {
|
|
66
|
+
return Array.isArray(raw.criteria) && raw.criteria.every((row) => isRecord(row) && isRecord(row.evidence)
|
|
67
|
+
&& typeof row.evidence.path === "string" && Number.isSafeInteger(row.evidence.line));
|
|
69
68
|
}
|
|
70
69
|
const isCommand = (a) => typeof a === "object" && a.oracle === "command";
|
|
71
70
|
const isTest = (a) => typeof a === "object" && a.oracle === "test";
|
|
@@ -288,24 +287,20 @@ export function changedLinesByFile(diff) {
|
|
|
288
287
|
set.add(newLine);
|
|
289
288
|
newLine++;
|
|
290
289
|
}
|
|
291
|
-
else if (c !== "-") {
|
|
292
|
-
|
|
290
|
+
else if (c !== "-" && c !== "\\") {
|
|
291
|
+
// S23 (A04): "" annotates the row above it and is skipped — it never ends the
|
|
292
|
+
// hunk, so a replaced last line's `+` row after `-old` and its marker stays citable.
|
|
293
|
+
inHunk = false; // a trailing blank or the next section — hunk body ended
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
296
|
return byFile;
|
|
296
297
|
}
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
while (j + 1 < sorted.length && sorted[j + 1] === sorted[j] + 1)
|
|
304
|
-
j++;
|
|
305
|
-
parts.push(i === j ? `${sorted[i]}` : `${sorted[i]}-${sorted[j]}`);
|
|
306
|
-
i = j + 1;
|
|
307
|
-
}
|
|
308
|
-
return parts.join(", ");
|
|
298
|
+
// S23: the citable index is one JSON line per changed path between nonce-framed markers. JSON escapes every
|
|
299
|
+
// line terminator but U+2028/U+2029, which are written as their six-character ASCII escapes, so no name can end
|
|
300
|
+
// an entry line, forge a marker or a heading, and JSON.parse of the line gives the name back exactly.
|
|
301
|
+
function citableIndexEntry(path, lines) {
|
|
302
|
+
return JSON.stringify({ path, lines: [...lines].sort((a, b) => a - b) })
|
|
303
|
+
.replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
309
304
|
}
|
|
310
305
|
// Queue rows 128/129: "\n"-only line semantics — the m flag would also split a line at U+2028/U+2029.
|
|
311
306
|
const DIFF_SECTIONS = /(?<=^|\n)(?=diff --git )/;
|
|
@@ -315,17 +310,17 @@ function deletedPath(section) {
|
|
|
315
310
|
return null;
|
|
316
311
|
const oldPath = /(?<=^|\n)--- ([^\r\n]+)/.exec(section)?.[1]
|
|
317
312
|
?? /(?<=^|\n)Binary files ([^\r\n]+) and \/dev\/null differ(?=[\r\n]|$)/.exec(section)?.[1];
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const unquoted = oldPath.startsWith('"') && oldPath.endsWith('"') ? oldPath.slice(1, -1) : oldPath;
|
|
321
|
-
return unquoted.replace(/^a\//, "");
|
|
313
|
+
// S23 (A05): decoded by the shared side reader (C-quoting, the tab after a spaced name), as artifact-manifest does.
|
|
314
|
+
return oldPath ? diffSidePath(oldPath) : null;
|
|
322
315
|
}
|
|
323
316
|
// v1.82 T1 clause 7: the two shapes this task creates carry no changed hunk, so a judge asked to cite a
|
|
324
317
|
// changed line has nothing to cite — three review rounds died here. Each yields ONE citable operation
|
|
325
|
-
// fact, `{path, line: 0}`, read back from the judged text itself.
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
318
|
+
// fact, `{path, line: 0}`, read back from the judged text itself. Queue row 142 adds the three typed git
|
|
319
|
+
// facts (operation-facts.ts: a pure rename, an empty add, a mode-only change), measured from NUL-delimited
|
|
320
|
+
// raw output and never from this text. Only these RECORDED facts make line 0 citable; a zero line on any
|
|
321
|
+
// other path is still rejected as fabricated. Ordinary whole-file deletions stay uncitable deliberately:
|
|
322
|
+
// the relevance filter collapses them before this runs, and repairing that pre-existing limitation is
|
|
323
|
+
// outside this contract.
|
|
329
324
|
const OPERATION_FACT_LINE = 0;
|
|
330
325
|
function operationFactPaths(diff) {
|
|
331
326
|
return diff.split(DIFF_SECTIONS).flatMap((section) => {
|
|
@@ -336,12 +331,9 @@ function operationFactPaths(diff) {
|
|
|
336
331
|
return path && isProtectedEvidence(path) ? [path] : [];
|
|
337
332
|
});
|
|
338
333
|
}
|
|
339
|
-
// A citation is valid evidence iff the
|
|
340
|
-
// changed-hunk span, not exact-added-line)
|
|
341
|
-
|
|
342
|
-
function citesChangedLocation(evidence, changed, diff) {
|
|
343
|
-
if (typeof evidence === "string")
|
|
344
|
-
return evidence.trim().length > 0 && diff.includes(evidence);
|
|
334
|
+
// A citation is valid evidence iff its pair is in the changed map: a line inside a changed hunk of the cited file
|
|
335
|
+
// (OBS-129: changed-hunk span, not exact-added-line) or line 0 of a recorded operation fact.
|
|
336
|
+
function citesChangedLocation(evidence, changed) {
|
|
345
337
|
return changed.get(evidence.path)?.has(evidence.line) ?? false;
|
|
346
338
|
}
|
|
347
339
|
export async function acceptanceGate(task, worktree, baseRef,
|
|
@@ -389,6 +381,9 @@ judge, via, opts = {}) {
|
|
|
389
381
|
const warn = onlyJudge
|
|
390
382
|
? "WARNING: only judge oracles — no deterministic command/test oracle guards this task (deterministic preferred, spec §2).\n"
|
|
391
383
|
: "";
|
|
384
|
+
// queue row 142: the typed facts are bound to the commit HEAD resolves to BEFORE the judged diff is read,
|
|
385
|
+
// and kept below only while HEAD still resolves to it after that read (fetchTaskDiff reads HEAD itself).
|
|
386
|
+
const typed = await readOperationFacts(worktree, baseRef);
|
|
392
387
|
const fetched = await fetchTaskDiff(worktree, baseRef);
|
|
393
388
|
const diff = reviewableLogicDiff(fetched.full);
|
|
394
389
|
const diffCap = opts.diffCap ?? DEFAULT_DIFF_CAP;
|
|
@@ -400,8 +395,9 @@ judge, via, opts = {}) {
|
|
|
400
395
|
// citation in a real changed line instead of miscounting. Validated below against this same `changed`.
|
|
401
396
|
const changed = changedLinesByFile(diff);
|
|
402
397
|
// clause 7: fold the recorded operation facts into the same index the validator checks, so a change
|
|
403
|
-
// made only of set-aside captures
|
|
404
|
-
|
|
398
|
+
// made only of set-aside captures, removed protected evidence or a typed git fact still yields citable evidence.
|
|
399
|
+
const typedPaths = (await operationFactsAtHead(worktree, typed)).map((fact) => fact.path);
|
|
400
|
+
for (const path of [...operationFactPaths(diff), ...typedPaths]) {
|
|
405
401
|
let set = changed.get(path);
|
|
406
402
|
if (!set) {
|
|
407
403
|
set = new Set();
|
|
@@ -409,8 +405,11 @@ judge, via, opts = {}) {
|
|
|
409
405
|
}
|
|
410
406
|
set.add(OPERATION_FACT_LINE);
|
|
411
407
|
}
|
|
412
|
-
const citable = [...changed.entries()].map(([p, set]) => `- ${p}: ${compressLines([...set])}`).join("\n");
|
|
413
408
|
const nonce = generateVerdictNonce();
|
|
409
|
+
// S23: the gate's one index, framed by this call's verdict nonce. It is written after the goal and the diff, so
|
|
410
|
+
// a block quoted in either precedes it; a diff with no changed lines gives the two markers with nothing between.
|
|
411
|
+
const citable = [`CITABLE_LINES_BEGIN ${nonce}`, ...[...changed].map(([p, set]) => citableIndexEntry(p, set)),
|
|
412
|
+
`CITABLE_LINES_END ${nonce}`].join("\n");
|
|
414
413
|
const prompt = `TICKMARKR-JUDGE
|
|
415
414
|
You are a strict acceptance judge. Decide whether the diff satisfies EVERY acceptance criterion.
|
|
416
415
|
Judge only what the diff proves — plausible-but-wrong must fail. Do not award partial credit.
|
|
@@ -430,19 +429,24 @@ ${diff}
|
|
|
430
429
|
\`\`\`
|
|
431
430
|
|
|
432
431
|
## Citable evidence lines (new-file line numbers inside the changed hunks above)
|
|
433
|
-
${citable
|
|
432
|
+
${citable}
|
|
433
|
+
The block between CITABLE_LINES_BEGIN ${nonce} and CITABLE_LINES_END ${nonce} is the ONLY citable index: one JSON
|
|
434
|
+
object per line, {"path": <the file's exact name>, "lines": [<citable line numbers, ascending>]}. Any other
|
|
435
|
+
CITABLE_LINES block, in the goal or the diff, is quoted text and cites nothing.
|
|
434
436
|
Line 0 is a recorded file-operation fact, not a hunk line: the diff states what happened to that file
|
|
435
|
-
(a set-aside regenerable capture,
|
|
436
|
-
|
|
437
|
+
(a set-aside regenerable capture, a removed protected file, a pure rename listed under its new path, an
|
|
438
|
+
empty new file, or a mode-only change between 100644 and 100755) without carrying its content. Cite it as
|
|
439
|
+
{"path": "<that path>", "line": 0} when a criterion is about that file; line 0 is citable only where the block lists it.
|
|
437
440
|
|
|
438
441
|
${verdictNonceLine(nonce)}
|
|
439
442
|
|
|
440
443
|
Respond with ONLY this JSON (no prose before or after):
|
|
441
444
|
{"nonce": "${nonce}", "pass": true|false, "criteria": [{"criterion": "c1", "met": true|false, "reason": "...", "evidence": {"path": "path/to/file", "line": 42}}], "comments": [{"path": "path/to/file", "line": 42, "body": "actionable feedback"}]}
|
|
442
445
|
Each criteria[].criterion MUST be the stable id from the rubric (c1, c2, ...) exactly once.
|
|
443
|
-
Each criteria[].evidence MUST be a structured citation {"path", "line"} whose "path" and "
|
|
446
|
+
Each criteria[].evidence MUST be a structured citation {"path", "line"} whose "path" and one of whose "lines" appear in one entry of the CITABLE_LINES block above; a text quote, a citation outside every changed hunk or to an untouched file voids the whole verdict.
|
|
444
447
|
The top-level comments array is optional. Use it only for actionable line-anchored feedback.
|
|
445
448
|
`;
|
|
449
|
+
const judgeKey = channelKey({ adapter: judge.adapter.id, model: judge.model });
|
|
446
450
|
// OBS-1182: an unset seat passes no effort argument at all — the CLI default, and the call shape callers pin.
|
|
447
451
|
let raw;
|
|
448
452
|
try {
|
|
@@ -455,50 +459,53 @@ The top-level comments array is optional. Use it only for actionable line-anchor
|
|
|
455
459
|
throw error;
|
|
456
460
|
// OBS-1168(b): no seat, no verdict — run-gates re-routes the judge or parks infra; never a worker charge.
|
|
457
461
|
return { gate: "acceptance", pass: false, details: `${warn}${detBlock}judge dispatch failed — ${error.message} — failing closed`,
|
|
458
|
-
meta: { unparseable: true, cause: "seat-launch-failed", judge:
|
|
462
|
+
meta: { unparseable: true, cause: "seat-launch-failed", judge: judgeKey } };
|
|
459
463
|
}
|
|
460
464
|
const extracted = extractVerdictJson(raw, nonce);
|
|
461
465
|
if (!extracted) {
|
|
462
466
|
const cause = classifyVerdictCause(raw, nonce, "pass");
|
|
463
467
|
// GATE-09: structured meta names the flaked judge channel (mirrors review.ts:99 meta precedent) so
|
|
464
468
|
// run-gates can retry the judge on a failover channel without string-matching details (D-03).
|
|
465
|
-
// The parsed-verdict paths below are untouched — the flake signal is exactly extractJson→null here.
|
|
466
469
|
const failure = cause === "malformed-verdict"
|
|
467
470
|
? "judge output unparseable — failing closed"
|
|
468
471
|
: "judge dispatch failed — no structurally valid nonce-bound response; output unparseable — failing closed";
|
|
469
472
|
return { gate: "acceptance", pass: false,
|
|
470
473
|
details: warn + detBlock + failure,
|
|
471
|
-
meta: { unparseable: true, cause, judge:
|
|
474
|
+
meta: { unparseable: true, cause, judge: judgeKey } };
|
|
472
475
|
}
|
|
473
|
-
|
|
474
|
-
//
|
|
475
|
-
// the
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
return
|
|
482
|
-
|
|
483
|
-
|
|
476
|
+
// S23: every protocol failure — the raw citation shape, the schema, or a citation outside `changed` (the exact
|
|
477
|
+
// index embedded in the prompt above, never the worktree) — is one malformed verdict: unparseable, so GATE-09
|
|
478
|
+
// retries the judge on a failover channel, and named by THIS call's own dispatch arguments, never by model
|
|
479
|
+
// output. The diagnostics are constant: no model value is converted to text, so none can throw here.
|
|
480
|
+
const malformed = (why) => ({ gate: "acceptance", pass: false,
|
|
481
|
+
details: `${warn}${detBlock}${why} — treating as unparseable, failing closed`,
|
|
482
|
+
meta: { unparseable: true, cause: "malformed-verdict", judge: judgeKey } });
|
|
483
|
+
if (!rawCitationsWellFormed(extracted))
|
|
484
|
+
return malformed("judge verdict malformed: malformed verdict shape (each criterion's evidence must be a {path, line} citation)");
|
|
485
|
+
const parsed = JudgeVerdictSchema.safeParse(extracted);
|
|
486
|
+
if (!parsed.success)
|
|
487
|
+
return malformed("judge verdict malformed: malformed verdict shape");
|
|
488
|
+
const v = parsed.data;
|
|
489
|
+
if (!v.criteria.every((row) => citesChangedLocation(row.evidence, changed))) {
|
|
490
|
+
return malformed("judge verdict cites evidence absent from the judged diff");
|
|
484
491
|
}
|
|
492
|
+
const inconsistencies = checkJudgeVerdict(v, expectedIds);
|
|
485
493
|
const pass = v.pass === true && inconsistencies.length === 0 && v.criteria.every((row) => row.met);
|
|
486
494
|
const lines = v.criteria.map((row) => `${row.met ? "✓" : "✗"} ${row.criterion}: ${row.reason}`);
|
|
487
495
|
if (!v.pass)
|
|
488
496
|
lines.push("judge verdict pass=false");
|
|
489
497
|
lines.push(...inconsistencies);
|
|
490
498
|
const prose = warn + detBlock + (lines.join("\n") || "judge passed");
|
|
491
|
-
// OBS-1151: each criterion's canonical text, ruling and the
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
// a comparable subject, and as an adjudicator it can confirm nothing — run-gates parks it infra.
|
|
499
|
+
// OBS-1151: each criterion's canonical text, ruling and the path its evidence cites. An inconsistent verdict
|
|
500
|
+
// (a duplicate row ruling both ways, a missing id) carries NO judgment: it is not a comparable subject, and as
|
|
501
|
+
// an adjudicator it can confirm nothing — run-gates parks it infra.
|
|
495
502
|
const judgment = inconsistencies.length ? undefined : v.criteria.flatMap((row) => {
|
|
496
503
|
const index = expectedIds.indexOf(row.criterion);
|
|
497
504
|
if (index < 0)
|
|
498
505
|
return [];
|
|
499
506
|
return [{ id: row.criterion, criterion: renderAcceptanceItem(judgeItems[index]), met: row.met,
|
|
500
|
-
paths:
|
|
507
|
+
paths: [row.evidence.path] }];
|
|
501
508
|
});
|
|
502
509
|
return { gate: "acceptance", pass, details: appendAnchoredReview(prose, extracted),
|
|
503
|
-
meta: { judge:
|
|
510
|
+
meta: { judge: judgeKey, ...(judgment ? { judgment } : {}) } };
|
|
504
511
|
}
|
package/dist/gates/baseline.d.ts
CHANGED
|
@@ -39,11 +39,11 @@ export type RedactionCounts = NonNullable<GateEvidenceReceipt["redaction"]["coun
|
|
|
39
39
|
* span counted once under its highest-priority member — token, assignment, secret environment, benign
|
|
40
40
|
* HOME/TMPDIR — so precedence never depends on which match starts first. Counts only: never a value.
|
|
41
41
|
*/
|
|
42
|
-
export declare function classifyGateOutput(text: string, env: NodeJS.ProcessEnv): {
|
|
42
|
+
export declare function classifyGateOutput(text: string, env: NodeJS.ProcessEnv, command?: string, inheritedEnv?: NodeJS.ProcessEnv): {
|
|
43
43
|
text: string;
|
|
44
44
|
counts: RedactionCounts;
|
|
45
45
|
};
|
|
46
|
-
export declare function redactGateOutput(text: string, env: NodeJS.ProcessEnv): string;
|
|
46
|
+
export declare function redactGateOutput(text: string, env: NodeJS.ProcessEnv, command?: string): string;
|
|
47
47
|
/** Material means bytes were withheld; a benign location substitution withholds nothing. */
|
|
48
48
|
export declare const redactionMaterial: (c: RedactionCounts) => boolean;
|
|
49
49
|
/** Resolve a snapshot reference against actual bytes: eviction never fabricates a live artifact. */
|
|
@@ -103,6 +103,11 @@ export interface BaselineCommand {
|
|
|
103
103
|
*/
|
|
104
104
|
exitCode?: number;
|
|
105
105
|
fingerprints: string[];
|
|
106
|
+
/**
|
|
107
|
+
* S22: `2` when captureBaseline wrote one table-T / layout-L fingerprint per occurrence. Absent on every
|
|
108
|
+
* earlier and every hand-built entry (LEGACY, compared exactly as 2.7.8 did); any other value is unreadable.
|
|
109
|
+
*/
|
|
110
|
+
fingerprintVersion?: number;
|
|
106
111
|
missingCommand?: boolean;
|
|
107
112
|
/** Why a capture returned no verdict. */
|
|
108
113
|
invalidCause?: "ceiling-kill" | "resource-exhaustion" | "infra";
|
|
@@ -174,6 +179,8 @@ export interface BaselineWarning {
|
|
|
174
179
|
commands: string[];
|
|
175
180
|
reason: string;
|
|
176
181
|
}
|
|
182
|
+
/** D1's signal vocabulary, one copy for the gate battery and the tip (the daemon keeps its own). */
|
|
183
|
+
export declare const SIGNAL_EXIT_RE: RegExp;
|
|
177
184
|
export type FailureClassification = "regression" | "infra";
|
|
178
185
|
/**
|
|
179
186
|
* What a nonzero runner exit is evidence OF. `undefined` when the output names neither — the
|
|
@@ -186,16 +193,42 @@ export declare function classifyFailureOutput(output: string): FailureClassifica
|
|
|
186
193
|
export declare function classifiedFileDurations(entry: BaselineCommand | undefined): BaselineFileDuration[] | null | undefined;
|
|
187
194
|
export declare const UNRECOGNIZED_FAILURE = "<unrecognized failure output>";
|
|
188
195
|
export declare function fingerprint(output: string): string[];
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
*/
|
|
194
|
-
export declare function freshFailures(entry: BaselineCommand | undefined, raw: string): {
|
|
196
|
+
/** Which reading of a baseline entry decided forgiveness (S22). Only `legacy` and `v2` carry a forgivable verdict. */
|
|
197
|
+
export type BaselineEntryState = "missing" | "unreadable" | "legacy" | "v2" | "no-verdict";
|
|
198
|
+
export interface FreshFailureVerdict {
|
|
199
|
+
/** Fresh failure identities, each once. */
|
|
195
200
|
failing: string[];
|
|
201
|
+
/** The output carried no recognizable failure shape at all. */
|
|
196
202
|
unreadable: boolean;
|
|
203
|
+
/** Baseline-recorded identities this output repeated, each once. */
|
|
197
204
|
forgiven: string[];
|
|
198
|
-
|
|
205
|
+
/** The ORIGINAL runner lines behind the fresh identities: classification, host-starved and diagnostics read these, never the keys. */
|
|
206
|
+
failingLines: string[];
|
|
207
|
+
state: BaselineEntryState;
|
|
208
|
+
/** Why an `unreadable` entry could not be read. */
|
|
209
|
+
why?: string;
|
|
210
|
+
/** The one shared forgiveness decision both readers extend: the entry recorded a red VERDICT. */
|
|
211
|
+
baselineRed: boolean;
|
|
212
|
+
/** S19m4: the first kill line, when the caller read kill text (the test gate alone). A forgiven red beside one is infra. */
|
|
213
|
+
killLine?: string;
|
|
214
|
+
}
|
|
215
|
+
/** The red reason a missing or unreadable entry owes in BOTH readers; undefined for every other state. */
|
|
216
|
+
export declare function baselineEntryRefusal(gate: string, verdict: Pick<FreshFailureVerdict, "state" | "why">): string | undefined;
|
|
217
|
+
/**
|
|
218
|
+
* Q121s / S22: the battery's forgiveness math, exported so tip verify applies the IDENTICAL rule, and
|
|
219
|
+
* the one place an entry's state is decided — each reader renders its reason, neither re-derives it.
|
|
220
|
+
*
|
|
221
|
+
* A LEGACY entry (no fingerprintVersion) compares exactly as 2.7.8 did: set membership over lines whose
|
|
222
|
+
* every digit run is masked. A v2 entry is a MULTISET under two closed grammars (ANL-021 S22):
|
|
223
|
+
* - table T: only whole tokens of a closed volatile shape (durations, numbers, positions, ratios, ISO
|
|
224
|
+
* times, key=number, hex addresses) lose their digits; every other token keeps them. Declared cost: a
|
|
225
|
+
* volatile token outside T stays verbatim, so its change reads fresh (a closed false red, not a defect).
|
|
226
|
+
* - layout L: a stylish error row is keyed by its stream label and the header layout L proves (blank or
|
|
227
|
+
* nothing before it, rows right after it). Declared costs: a row outside every proven block is never
|
|
228
|
+
* forgiven, and a header printed under a realpath variant of the cwd compares as a different header.
|
|
229
|
+
* f is fresh when the candidate repeats it more often than the capture recorded it.
|
|
230
|
+
*/
|
|
231
|
+
export declare function freshFailures(entry: BaselineCommand | undefined, raw: string, readKillText?: boolean): FreshFailureVerdict;
|
|
199
232
|
export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
|
200
233
|
/**
|
|
201
234
|
* Operator directive 2026-08-12 (D-OBS-10): tickmarkr is package-manager agnostic.
|