tickmarkr 2.7.9 → 2.8.1

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.
@@ -5,7 +5,7 @@ export declare function readCodexModelsCache(path?: string): {
5
5
  retirements?: Record<string, ModelRetirement | null>;
6
6
  hidden?: string[];
7
7
  };
8
- export declare const CODEX_GIT_GRANT = "-c \"sandbox_workspace_write.writable_roots=[$(tkr_common=$(git rev-parse --path-format=absolute --git-common-dir); tkr_gitdir=$(git rev-parse --absolute-git-dir); if [ \"$tkr_gitdir\" != \"$tkr_common\" ]; then printf '\"%s\",' \"$tkr_gitdir\"; fi; printf '\"%s/objects\",\"%s/refs\",\"%s/logs\"' \"$tkr_common\" \"$tkr_common\" \"$tkr_common\")]\"";
8
+ export declare const CODEX_GIT_GRANT = "-c \"sandbox_workspace_write.writable_roots=[$(tkr_common=$(git rev-parse --path-format=absolute --git-common-dir && echo /) && tkr_gitdir=$(git rev-parse --absolute-git-dir && echo /) && tkr_common=${tkr_common%?/} && tkr_gitdir=${tkr_gitdir%?/} && { tkr_head=$(git symbolic-ref -q HEAD); [ $? -le 1 ]; } && case $tkr_head in (refs/heads/*) tkr_leaf=${tkr_head%/*};; (*) tkr_leaf=;; esac && [ \"$(printf %s \"$tkr_common$tkr_gitdir$tkr_leaf\" | LC_ALL=C tr -d '\\0-\\37\\177\"\\\\')\" = \"$tkr_common$tkr_gitdir$tkr_leaf\" ] && { [ \"$tkr_gitdir\" = \"$tkr_common\" ] || printf '\"%s\",' \"$tkr_gitdir\"; } && printf '\"%s/objects\"' \"$tkr_common\" && { [ -z \"$tkr_leaf\" ] || printf ',\"%s/%s\",\"%s/logs/%s\"' \"$tkr_common\" \"$tkr_leaf\" \"$tkr_common\" \"$tkr_leaf\"; })]\"";
9
9
  export declare const CODEX_TRUST_DIALOG: TrustDialog;
10
10
  export declare const CODEX_INPUT_BOX: import("./types.js").InputBox;
11
11
  export declare function seedCodexTrust(repoRoot: string, configPath?: string): TrustVerdict;
@@ -106,13 +106,25 @@ function codexRetirement(upgrade) {
106
106
  // worktree cwd, for Codex WORKERS only: v2.6.7 T4 (D-1046) — invoke() (the headless worker form, including
107
107
  // the interactive size fallback) and interactiveCommand carry it; headlessCommand, which judge, review,
108
108
  // consult and the model probe launch, carries no writable_roots at all (those seats never commit).
109
- // v2.6.6 T9 (K, D-912): the grant is exactly what a linked-worktree branch commit writes — the worktree's
110
- // own gitdir (only when it differs from the common dir), common/objects, common/refs, common/logs. The
111
- // shipped [common] grant (and D-910's [common, gitdir]) let a sandboxed seat write <common>/hooks and
112
- // <common>/config, which unsandboxed git later runs. No common-root fallback: an ordinary checkout gets
113
- // no gitdir root and its commit fails closed. packed-refs is not granted (packed-ref ops fail closed).
109
+ // v2.6.6 T9 (K, D-912) → v2.8.0 T1 (rows 17/18, D-910): the grant is a LEAF of what a linked-worktree branch
110
+ // commit writes, never the shared hooks/config (the shipped [common] and D-910's [common, gitdir] let a seat
111
+ // write <common>/hooks and <common>/config, which unsandboxed git later runs) and never whole common/refs and
112
+ // common/logs (which let a seat move main, any branch or tag, or the stash). The closed root table, in order:
113
+ // - the worktree's own gitdir, only when it differs from the common dir (an ordinary checkout gets none);
114
+ // - <common>/objects;
115
+ // - only when HEAD is a symbolic ref refs/heads/<B>: <common>/refs/heads/<D> and <common>/logs/refs/heads/<D>,
116
+ // <D> being <B> without its last slash-separated segment (refs/heads itself when <B> holds no slash);
117
+ // a detached HEAD gets no refs or reflog root.
118
+ // A directory outside any repository, any failed git read, or any root holding a character of the closed
119
+ // forbidden set (U+0000 to U+001F, U+007F DEL, the double quote, the backslash) yields the EMPTY array `[]`:
120
+ // no git write root, so a commit fails closed — never a partial array, an escaped string or codex's
121
+ // literal-string fallback. Every emitted root is therefore a TOML basic string that parses back to the exact
122
+ // path (git's own trailing newline is stripped through a sentinel, never a path's). No common-root fallback;
123
+ // packed-refs is not granted (packed-ref ops fail closed); stash, tags, notes and branches outside the leaf
124
+ // are denied. Declared: sibling refs in the same leaf (other task branches, the run's integration branch
125
+ // under the default prefix) stay writable until 2.8.1's integration identity.
114
126
  // doctor's probe (codex-commit-check.ts) executes these exact bytes; it never rebuilds the grant.
115
- export const CODEX_GIT_GRANT = `-c "sandbox_workspace_write.writable_roots=[$(tkr_common=$(git rev-parse --path-format=absolute --git-common-dir); tkr_gitdir=$(git rev-parse --absolute-git-dir); if [ "$tkr_gitdir" != "$tkr_common" ]; then printf '"%s",' "$tkr_gitdir"; fi; printf '"%s/objects","%s/refs","%s/logs"' "$tkr_common" "$tkr_common" "$tkr_common")]"`;
127
+ export const CODEX_GIT_GRANT = `-c "sandbox_workspace_write.writable_roots=[$(tkr_common=$(git rev-parse --path-format=absolute --git-common-dir && echo /) && tkr_gitdir=$(git rev-parse --absolute-git-dir && echo /) && tkr_common=\${tkr_common%?/} && tkr_gitdir=\${tkr_gitdir%?/} && { tkr_head=$(git symbolic-ref -q HEAD); [ $? -le 1 ]; } && case $tkr_head in (refs/heads/*) tkr_leaf=\${tkr_head%/*};; (*) tkr_leaf=;; esac && [ "$(printf %s "$tkr_common$tkr_gitdir$tkr_leaf" | LC_ALL=C tr -d '\\0-\\37\\177"\\\\')" = "$tkr_common$tkr_gitdir$tkr_leaf" ] && { [ "$tkr_gitdir" = "$tkr_common" ] || printf '"%s",' "$tkr_gitdir"; } && printf '"%s/objects"' "$tkr_common" && { [ -z "$tkr_leaf" ] || printf ',"%s/%s","%s/logs/%s"' "$tkr_common" "$tkr_leaf" "$tkr_common" "$tkr_leaf"; })]"`;
116
128
  // OBS-125: codex 0.144.x gates enabled hooks behind PER-PROJECT-PATH hook trust — a NEW gate distinct
117
129
  // from the directory trust seedCodexTrust seeds. Every worker runs in a fresh ephemeral worktree, an
118
130
  // untrusted project, so codex re-prompts "Hooks need review" and the interactive worker stalls forever
@@ -2,6 +2,40 @@ import { readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { parseWorkerResult } from "./prompt.js";
4
4
  import { OPENCODE_BUSY_FRAME_MARKERS, shq, TokenUsageSchema } from "./types.js";
5
+ /**
6
+ * S23: the first citation of the gate's nonce-framed index — from the LAST line exactly `CITABLE_LINES_BEGIN <nonce>`
7
+ * to the next line exactly `CITABLE_LINES_END <nonce>`. Every entry must be a JSON object with a string path and a
8
+ * non-empty array of non-negative safe-integer lines before any is used; otherwise (no block, no END, an empty or
9
+ * invalid entry anywhere) there is no citation. Never throws.
10
+ */
11
+ function firstCitation(prompt, nonce) {
12
+ if (!nonce)
13
+ return undefined;
14
+ const lines = prompt.split("\n");
15
+ const begin = lines.lastIndexOf(`CITABLE_LINES_BEGIN ${nonce}`);
16
+ const end = begin < 0 ? -1 : lines.indexOf(`CITABLE_LINES_END ${nonce}`, begin + 1);
17
+ if (end < 0)
18
+ return undefined;
19
+ const entries = [];
20
+ for (const line of lines.slice(begin + 1, end)) {
21
+ let entry;
22
+ try {
23
+ entry = JSON.parse(line);
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
29
+ return undefined;
30
+ const { path, lines: cited } = entry;
31
+ if (typeof path !== "string" || !Array.isArray(cited) || cited.length === 0
32
+ || !cited.every((n) => Number.isSafeInteger(n) && n >= 0))
33
+ return undefined;
34
+ entries.push({ path, lines: cited });
35
+ }
36
+ const first = entries[0];
37
+ return first ? { path: first.path, line: first.lines[0] } : undefined;
38
+ }
5
39
  // ponytail: deterministic scripted adapter — the whole integration suite runs on it, zero tokens.
6
40
  export class FakeAdapter {
7
41
  scriptPath;
@@ -77,13 +111,15 @@ export class FakeAdapter {
77
111
  val = { ...verdict, criteria: items.map((criterion) => ({ criterion, met: true, reason: "scripted fake pass" })) };
78
112
  }
79
113
  }
114
+ // S23: a scripted row without evidence cites the first line of the first entry of the gate's own citable
115
+ // index — never a string. An invalid or unterminated index yields no evidence (the gate then fails closed).
80
116
  if (key === "judge" && isJudge && val && typeof val === "object" && !Array.isArray(val)) {
81
117
  const verdict = val;
82
- const line = /```diff\n([\s\S]*?)```/.exec(prompt)?.[1].split("\n").find((candidate) => candidate.trim());
83
- if (line && Array.isArray(verdict.criteria)) {
118
+ const evidence = firstCitation(prompt, nonce);
119
+ if (evidence && Array.isArray(verdict.criteria)) {
84
120
  val = {
85
121
  ...verdict,
86
- criteria: verdict.criteria.map((row) => row && typeof row === "object" && !("evidence" in row) ? { ...row, evidence: line } : row),
122
+ criteria: verdict.criteria.map((row) => row && typeof row === "object" && !("evidence" in row) ? { ...row, evidence } : row),
87
123
  };
88
124
  }
89
125
  }
@@ -152,10 +188,12 @@ export class FakeAdapter {
152
188
  }
153
189
  // the run nonce lives in the prompt writePrompt handed us; echo a matching trailer so
154
190
  // parseWorkerResult(output, nonce) succeeds exactly as before the nonce existed
191
+ // S23: the verdict nonce is the LAST whole `VERDICT_NONCE: <hex>` line — the gate writes its own after the goal,
192
+ // criteria and diff, so a nonce line quoted in any of them never binds.
155
193
  nonceFor(promptFile) {
156
194
  try {
157
195
  const prompt = readFileSync(promptFile, "utf8");
158
- return /VERDICT_NONCE:\s*([0-9a-f]+)/i.exec(prompt)?.[1]
196
+ return prompt.split("\n").map((line) => /^VERDICT_NONCE: ([0-9a-f]+)$/.exec(line)?.[1]).filter(Boolean).at(-1)
159
197
  ?? /TICKMARKR_RESULT_([0-9a-z]+)/.exec(prompt)?.[1]
160
198
  ?? "";
161
199
  }
@@ -5,6 +5,7 @@ import { allAdapters } from "../../adapters/registry.js";
5
5
  import { channelKey } from "../../adapters/types.js";
6
6
  import { loadConfig } from "../../config/config.js";
7
7
  import { integrationBranch } from "../../run/merge.js";
8
+ import { integrationAnchor } from "../../run/daemon.js";
8
9
  import { separabilityErrors, surfaceErrors, taskBudgetErrors } from "../../compile/collateral.js";
9
10
  import { GATE_NAMES } from "../../graph/schema.js";
10
11
  import { applyScopeAmendments, bindingToken, captureOwedCheck, engagementComparable, engagementReleased, failedGateBeforePark, parseBindingToken, physicalLine, ATTEMPT_CAP_RELEASE, GATE_SATISFIED_RELEASE, Journal, RECHECK_RELEASE, REVIEW_UPHELD_RELEASE } from "../../run/journal.js";
@@ -168,6 +169,11 @@ export async function approve(argv, cwd = process.cwd()) {
168
169
  const serialization = await acquireApprovalSerialization(cwd, runId);
169
170
  try {
170
171
  const journal = Journal.open(cwd, runId);
172
+ // 2.8.1 T3 (ANL-023 §7.3): a marked run's recheck counts against its journal anchor; an unreadable one admits nothing.
173
+ const anchor = integrationAnchor(journal.read());
174
+ if (recheck && anchor && "unreadable" in anchor) {
175
+ throw new Error(`refusing to recheck ${taskId} in ${runId}: its journal names no readable published tip (${anchor.unreadable})`);
176
+ }
171
177
  const status = journal.replayStatuses().get(taskId);
172
178
  if (status === undefined) {
173
179
  throw new Error(`task ${taskId} has no events in run ${runId} — unknown task or never dispatched`);
@@ -308,12 +314,12 @@ export async function approve(argv, cwd = process.cwd()) {
308
314
  // daemon's boundary never observes the decision without its obligation (or a second write).
309
315
  // The seats a review discharge may resolve to are this repository's declared channels, recorded on
310
316
  // the row so every later fold reads the same table whatever process or checkout it runs in.
311
- let integrationRef = "unknown";
317
+ let byName = "unknown";
312
318
  let declared;
313
319
  let owedTask;
314
320
  try {
315
321
  const cfg = loadConfig(cwd);
316
- integrationRef = integrationBranch(cfg, runId);
322
+ byName = integrationBranch(cfg, runId);
317
323
  declared = allAdapters().flatMap((a) => a.channels(cfg)).map((c) => ({ key: channelKey(c), vendor: c.vendor }));
318
324
  }
319
325
  catch { /* an unreadable config binds no range and declares no seat: unknown */ }
@@ -321,11 +327,15 @@ export async function approve(argv, cwd = process.cwd()) {
321
327
  owedTask = loadGraph(cwd).tasks.find((t) => t.id === taskId);
322
328
  }
323
329
  catch { /* recorded as unknown criteria and scope */ }
324
- const obligation = captureOwedCheck({
330
+ const integration = integrationBase(journal.read(), () => byName);
331
+ const owed = captureOwedCheck({
325
332
  cwd, runId, taskId, gate: failedGate, events, acceptance: owedTask?.acceptance, files: owedTask?.files, declared,
326
333
  cause: reason ?? `operator waived failed ${failedGate} gate`, // the row's own park field binds the park
327
- taskRef: `${integrationRef}--${taskId}`, integrationRef,
334
+ taskRef: `${integration.branch}--${taskId}`, integrationRef: integration.base,
328
335
  });
336
+ // An unreadable anchor still records the waive, its obligation unknown: the debt is never silently known.
337
+ const obligation = integration.unreadable === undefined ? owed
338
+ : { ...owed, known: false, unknownReason: `its journal names no readable published tip (${integration.unreadable})` };
329
339
  journal.append("task-approved", taskId, {
330
340
  by,
331
341
  ...(reason ? { reason } : {}),
@@ -370,6 +380,20 @@ export async function approve(argv, cwd = process.cwd()) {
370
380
  serialization.release();
371
381
  }
372
382
  }
383
+ /**
384
+ * 2.8.1 T3 (ANL-023 §7.3): the integration branch and base a decision reads. A marked run (run-start
385
+ * `integrationAnchor: "journal"`) takes its branch from run-start, never the current config, and its base is the
386
+ * journal anchor A (`unreadable` when the journal names none); an unmarked run reads the branch by name, as before.
387
+ */
388
+ function integrationBase(events, byName) {
389
+ const anchor = integrationAnchor(events);
390
+ if (!anchor) {
391
+ const branch = byName();
392
+ return { branch, base: branch };
393
+ }
394
+ const branch = String(events.find((e) => e.event === "run-start").data.branch);
395
+ return "anchor" in anchor ? { branch, base: anchor.anchor } : { branch, base: "unknown", unreadable: anchor.unreadable };
396
+ }
373
397
  /** The row a decision on this task answers right now: its newest failure when failed, else its newest park. */
374
398
  function openDecision(journal, taskId) {
375
399
  const { events, sourceIndexes } = readJournalEvents(journal);
@@ -391,11 +415,12 @@ function requireBound(runId, taskId, bound, open, what) {
391
415
  * OBS-1022: what a failed task has landed. A preserved snapshot is authoritative only when it contains
392
416
  * the current task-branch tip; an older preservation can remain in the journal after a later retry has
393
417
  * advanced/rebuilt the branch. Missing task branches may still recover from a preserved ref. The selected
394
- * ref is counted against the run's integration branch; unreadable refs count as zero and fail closed.
418
+ * ref is counted against the run's integration base (integrationBase); unreadable refs count as zero and fail closed.
419
+ * `base` names the branch the text prints; the count reads `integration.base`.
395
420
  */
396
421
  function failedTaskCommitsAhead(cwd, journal, runId, taskId) {
397
- const cfg = loadConfig(cwd);
398
- const base = integrationBranch(cfg, runId);
422
+ const integration = integrationBase(journal.read(), () => integrationBranch(loadConfig(cwd), runId));
423
+ const base = integration.branch;
399
424
  const taskRef = `${base}--${taskId}`;
400
425
  const preserved = [...journal.read()].reverse().find((e) => e.taskId === taskId && e.event === "worktree-preserved" && typeof e.data.ref === "string");
401
426
  const preservedRef = preserved?.data.ref;
@@ -416,7 +441,7 @@ function failedTaskCommitsAhead(cwd, journal, runId, taskId) {
416
441
  }
417
442
  }
418
443
  try {
419
- const out = execFileSync("git", ["rev-list", "--count", `${base}..${ref}`], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
444
+ const out = execFileSync("git", ["rev-list", "--count", `${integration.base}..${ref}`], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
420
445
  return { ref, base, count: Number.parseInt(out.trim(), 10) || 0 };
421
446
  }
422
447
  catch {
@@ -22,7 +22,7 @@ import { sh } from "../../run/git.js";
22
22
  import { auditNamedTestOracles, listVitestTests } from "../../gates/acceptance.js";
23
23
  import { readReviewNoVerdictHistory, reviewNoVerdictRows } from "../../run/journal.js";
24
24
  import { checkModelDiscovery } from "../../adapters/model-discovery-check.js";
25
- import { recordLaunchServices } from "../../run/launchservices-check.js";
25
+ import { LAUNCHSERVICES_PORT_ADVISORY, probeLaunchServicesPorts, recordLaunchServices } from "../../run/launchservices-check.js";
26
26
  /** Where a newer `table_<date>.csv` is discovered — the deployed site builds filenames by
27
27
  * concatenation and publishes no index, so the release listing is the only enumerable surface. */
28
28
  export const LIVEBENCH_RELEASES_URL = "https://api.github.com/repos/LiveBench/livebench.github.io/contents/public";
@@ -670,7 +670,8 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
670
670
  const discovery = await checkModelDiscovery({ cwd, cfg, adapters, health, catalog, resolved: resolvedIdentitySnapshot, mayRefresh: !opts.catalog && !catalogRefreshLine && catalogRefreshAllowed, fetcher: opts.catalogFetcher, now: opts.catalogNow });
671
671
  catalog = discovery.catalog;
672
672
  aliasDriftFindings.push(...discovery.findings);
673
- const launchServices = await recordLaunchServices(readDoctor(cwd), health);
673
+ const portProbe = await probeLaunchServicesPorts();
674
+ const launchServices = await recordLaunchServices(readDoctor(cwd), health, portProbe.observation);
674
675
  writeDoctor(cwd, launchServices.health);
675
676
  const rows = adapters.map((a) => {
676
677
  const h = health[a.id];
@@ -680,6 +681,14 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
680
681
  const healthy = h.installed && (a.id !== kimi.id || h.authed) && !(a.id === codex.id && h.codexCommit === "escape");
681
682
  return alignedStatusRow(healthy ? "pass" : "fail", a.id, state);
682
683
  });
684
+ const ports = portProbe.ports;
685
+ const portLine = ports.status === "non-darwin" ? "launchservicesd ports: not applicable"
686
+ : ports.status === "read" ? `launchservicesd pid ${ports.pid}: ${ports.count} ports`
687
+ : `launchservicesd pid ${ports.status === "unknown" ? ports.pid ?? "unknown" : "unknown"}: ports unknown`;
688
+ if (ports.status !== "skipped") {
689
+ rows.push(alignedStatusRow(ports.status === "read" && ports.count < LAUNCHSERVICES_PORT_ADVISORY || ports.status === "non-darwin"
690
+ ? "pass" : "warn", "launchservicesd", portLine));
691
+ }
683
692
  const reviewDemotions = recentReviewDemotions(cwd);
684
693
  if (reviewDemotions.length) {
685
694
  rows.push(legend("recent review-seat demotions:"));
@@ -13,11 +13,11 @@ import { runGates } from "../../gates/run-gates.js";
13
13
  import { getTask, loadGraph } from "../../graph/graph.js";
14
14
  import { GATE_NAMES } from "../../graph/schema.js";
15
15
  import { executionSignal } from "../../run/execution-budget.js";
16
- import { addDetachedWorktree, linkNodeModules, removeWorktree, shGit, shGitOk } from "../../run/git.js";
16
+ import { addDetachedWorktree, linkNodeModules, reflogWitness, removeWorktree, shGit, shGitOk } from "../../run/git.js";
17
17
  import { foldOwedChecks, integrationMapped, owedCriteria, Journal, OWED_DISCHARGE_EVENT } from "../../run/journal.js";
18
18
  import { describeLeaseWait, withRepositoryLease } from "../../run/lease.js";
19
19
  import { isPidLive } from "../../run/lock.js";
20
- import { recordLaunchServices } from "../../run/launchservices-check.js";
20
+ import { LAUNCHSERVICES_PORT_STOP, probeLaunchServicesPorts, recordLaunchServices } from "../../run/launchservices-check.js";
21
21
  /**
22
22
  * tickmarkr verify — the gate battery as a standalone command (OPERATING-MODEL-2026-08-11 item 3).
23
23
  *
@@ -184,6 +184,14 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
184
184
  },
185
185
  allowPositionals: false,
186
186
  });
187
+ const portProbe = await probeLaunchServicesPorts({ requireTop: true });
188
+ const ports = portProbe.ports;
189
+ if (ports.status === "read" || ports.status === "unknown") {
190
+ console.error(`verify: ports ${ports.status === "read" ? ports.count : "unknown"}`);
191
+ }
192
+ if (ports.status === "read" && ports.count >= LAUNCHSERVICES_PORT_STOP) {
193
+ return { out: `verify refused: launchservicesd has ${ports.count} ports (threshold ${LAUNCHSERVICES_PORT_STOP}); log out or restart to reset launchservicesd before verifying`, code: 1 };
194
+ }
187
195
  const stateRoot = await verifyStateRoot(cwd);
188
196
  const fileRoot = (file) => existsSync(join(cwd, ".tickmarkr", file)) ? cwd : stateRoot;
189
197
  if (resolve(stateRoot) !== resolve(cwd)) {
@@ -192,16 +200,24 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
192
200
  }
193
201
  const cfg = loadConfig(fileRoot("config.yaml"));
194
202
  const head = (await shGitOk("git rev-parse HEAD", cwd)).trim();
195
- // Git appends every move of HEAD to its reflog — one a gate command makes and undoes itself included —
196
- // so the log as of this capture is the subject's witness: any later difference is a move (fail closed).
197
- const headLogPath = resolve(cwd, (await shGitOk("git rev-parse --git-path logs/HEAD", cwd)).trim());
198
- const headLog = () => { try {
199
- return readFileSync(headLogPath, "utf8");
203
+ // The latch reads HEAD's reflog through git as a validated witness, refuses before capture when it
204
+ // is absent or invalid, and compares it line for line at every subject check.
205
+ const witnessFault = (witness, commit) => {
206
+ if (witness.code !== 0)
207
+ return `exit ${witness.code}`;
208
+ const newest = witness.lines[0];
209
+ if (newest === undefined)
210
+ return "no reflog";
211
+ if (newest !== commit)
212
+ return `newest is ${newest.slice(0, 12)}`;
213
+ return undefined;
214
+ };
215
+ const capturedWitness = await reflogWitness(cwd, "HEAD");
216
+ const fault = witnessFault(capturedWitness, head);
217
+ if (fault) {
218
+ throw new Error(`verify refused: HEAD's reflog cannot witness the candidate (${fault}) — record HEAD moves in this checkout, then verify again`);
200
219
  }
201
- catch {
202
- return "";
203
- } };
204
- const headLogAtCapture = headLog();
220
+ const headReflogAtCapture = capturedWitness.lines.join("\n");
205
221
  const baseTip = (await shGitOk(`git rev-parse '${values.base}'`, cwd).catch(() => {
206
222
  throw new Error(`--base ${values.base} is not a resolvable ref — pass --base <ref> naming the branch this diff targets`);
207
223
  })).trim();
@@ -493,9 +509,13 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
493
509
  let subjectLatch;
494
510
  const subjectChange = async () => {
495
511
  const now = await shGitOk("git rev-parse HEAD", cwd).then((out) => out.trim(), () => undefined);
512
+ // The latch reads HEAD's reflog through git as a validated witness, refuses before capture when it
513
+ // is absent or invalid, and compares it line for line at every subject check.
514
+ const witness = now === head ? await reflogWitness(cwd, "HEAD") : undefined;
515
+ const reflogMoved = witness !== undefined && (witness.code !== 0 || witness.lines.join("\n") !== headReflogAtCapture || witness.lines[0] !== head);
496
516
  const why = now === undefined ? "HEAD is unreadable"
497
517
  : now !== head ? `HEAD moved from ${head.slice(0, 12)} to ${now.slice(0, 12)}`
498
- : headLog() !== headLogAtCapture ? `HEAD moved and was restored to ${head.slice(0, 12)} (its reflog recorded the move)`
518
+ : reflogMoved ? `HEAD moved and was restored to ${head.slice(0, 12)} (its reflog recorded the move)`
499
519
  : await candidateDirt();
500
520
  if (why)
501
521
  subjectLatch ??= why;
@@ -12,7 +12,7 @@ export interface JudgeVerdict {
12
12
  criterion: string;
13
13
  met: boolean;
14
14
  reason: string;
15
- evidence: string | EvidenceCitation;
15
+ evidence: EvidenceCitation;
16
16
  }>;
17
17
  comments?: Array<{
18
18
  path: string;