tickmarkr 2.8.0 → 2.8.2
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.js +3 -2
- package/dist/cli/commands/approve.js +33 -8
- package/dist/cli/commands/verify.js +23 -11
- package/dist/gates/baseline.js +3 -3
- package/dist/gates/run-gates.js +61 -1
- package/dist/run/daemon.d.ts +77 -13
- package/dist/run/daemon.js +350 -95
- package/dist/run/git.d.ts +16 -3
- package/dist/run/git.js +19 -6
- package/dist/run/merge.d.ts +114 -7
- package/dist/run/merge.js +127 -34
- package/dist/run/wall-budget.js +6 -1
- package/package.json +1 -2
- package/skills/tickmarkr-overseer/SKILL.md +42 -20
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +162 -166
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +0 -133
package/dist/adapters/codex.js
CHANGED
|
@@ -121,8 +121,9 @@ function codexRetirement(upgrade) {
|
|
|
121
121
|
// literal-string fallback. Every emitted root is therefore a TOML basic string that parses back to the exact
|
|
122
122
|
// path (git's own trailing newline is stripped through a sentinel, never a path's). No common-root fallback;
|
|
123
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
|
|
125
|
-
// under the default prefix) stay writable
|
|
124
|
+
// are denied. Declared (queue row 151): sibling refs in the same leaf (other task branches, the run's integration
|
|
125
|
+
// branch under the default prefix) stay writable; since 2.8.1 a write to the integration ref parks the merge and is
|
|
126
|
+
// never published.
|
|
126
127
|
// doctor's probe (codex-commit-check.ts) executes these exact bytes; it never rebuilds the grant.
|
|
127
128
|
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"; })]"`;
|
|
128
129
|
// OBS-125: codex 0.144.x gates enabled hooks behind PER-PROJECT-PATH hook trust — a NEW gate distinct
|
|
@@ -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
|
|
317
|
+
let byName = "unknown";
|
|
312
318
|
let declared;
|
|
313
319
|
let owedTask;
|
|
314
320
|
try {
|
|
315
321
|
const cfg = loadConfig(cwd);
|
|
316
|
-
|
|
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
|
|
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: `${
|
|
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
|
|
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
|
|
398
|
-
const base =
|
|
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 {
|
|
@@ -13,7 +13,7 @@ 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";
|
|
@@ -200,16 +200,24 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
|
|
|
200
200
|
}
|
|
201
201
|
const cfg = loadConfig(fileRoot("config.yaml"));
|
|
202
202
|
const head = (await shGitOk("git rev-parse HEAD", cwd)).trim();
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
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`);
|
|
208
219
|
}
|
|
209
|
-
|
|
210
|
-
return "";
|
|
211
|
-
} };
|
|
212
|
-
const headLogAtCapture = headLog();
|
|
220
|
+
const headReflogAtCapture = capturedWitness.lines.join("\n");
|
|
213
221
|
const baseTip = (await shGitOk(`git rev-parse '${values.base}'`, cwd).catch(() => {
|
|
214
222
|
throw new Error(`--base ${values.base} is not a resolvable ref — pass --base <ref> naming the branch this diff targets`);
|
|
215
223
|
})).trim();
|
|
@@ -501,9 +509,13 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
|
|
|
501
509
|
let subjectLatch;
|
|
502
510
|
const subjectChange = async () => {
|
|
503
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);
|
|
504
516
|
const why = now === undefined ? "HEAD is unreadable"
|
|
505
517
|
: now !== head ? `HEAD moved from ${head.slice(0, 12)} to ${now.slice(0, 12)}`
|
|
506
|
-
:
|
|
518
|
+
: reflogMoved ? `HEAD moved and was restored to ${head.slice(0, 12)} (its reflog recorded the move)`
|
|
507
519
|
: await candidateDirt();
|
|
508
520
|
if (why)
|
|
509
521
|
subjectLatch ??= why;
|
package/dist/gates/baseline.js
CHANGED
|
@@ -363,9 +363,9 @@ const VOCAB_RE = /\b(?:error|fail(?:ed|ure|ing)?)\b/i;
|
|
|
363
363
|
// OBS-791: `[vitest-worker]: Timeout calling "<method>"` is the SAME birpc mechanism one layer up.
|
|
364
364
|
// Vitest's bundled birpc has a fixed 60s DEFAULT_TIMEOUT with no override (fixed upstream only in
|
|
365
365
|
// vitest 4.x), so a suite whose tests all pass can still die at teardown when the worker->host RPC
|
|
366
|
-
// window closes.
|
|
367
|
-
//
|
|
368
|
-
//
|
|
366
|
+
// window closes. Public CI's proof forgives this exact fingerprint (the RPC-only rule; since v2.8.2 decided
|
|
367
|
+
// from the run's own record, queue rows 148 and 149) and this classifier once called it a regression — the
|
|
368
|
+
// product charged a worker for the failure its own release proof forgives. The METHOD NAME IS DELIBERATELY NOT PINNED: the timeout is a
|
|
369
369
|
// property of the RPC window, not of `onTaskUpdate`, and pinning one method would forgive a run and
|
|
370
370
|
// charge its sibling for the same infrastructure event. This stays a CLOSED signature — the
|
|
371
371
|
// `[vitest-worker]: ` prefix is vitest's own RPC layer and never user assertion text — and the
|
package/dist/gates/run-gates.js
CHANGED
|
@@ -19,7 +19,7 @@ import { discoverTestManifest, evaluateManifestedTest, isVitestTestCommand, mani
|
|
|
19
19
|
import { RUNNER_INFRA_DIAGNOSTIC_RE, timeoutShaped } from "./timeout-shaped.js";
|
|
20
20
|
import { executionSignal } from "../run/execution-budget.js";
|
|
21
21
|
import { failureDisposition } from "../run/recovery.js";
|
|
22
|
-
import { dependencyLinkRefusal, preserveWorktree, producerFields, readCapacity, shGit, resolvedCapacity, sameCapacity, verificationProtocol } from "../run/git.js";
|
|
22
|
+
import { dependencyLinkRefusal, preserveWorktree, producerFields, readCapacity, reflogWitness, shGit, resolvedCapacity, sameCapacity, verificationProtocol } from "../run/git.js";
|
|
23
23
|
import { withJudgeInvocationEvidence } from "../run/journal.js";
|
|
24
24
|
import { computeVerificationIdentity, verificationIdentityKey, formatReusedRow, getVerdictStore, isInfraResult, resolveStateDir, reusedIdentity, } from "./cache.js";
|
|
25
25
|
const productionLoadProvider = () => loadavg()[0] ?? 0;
|
|
@@ -437,6 +437,49 @@ export async function judgeContradictions(worktree, head, fresh, priors) {
|
|
|
437
437
|
}
|
|
438
438
|
return found;
|
|
439
439
|
}
|
|
440
|
+
const SUBJECT_LEG_TEXT = {
|
|
441
|
+
moved: "moved", restored: "moved and restored", witness: "could not be witnessed", unreadable: "is unreadable",
|
|
442
|
+
};
|
|
443
|
+
/** ANL-023 §7.4 S0, read-only: HEAD's symbolic state and its reflog W(HEAD) — plus, when attached, its branch's
|
|
444
|
+
* W(R) — each valid for the sampled `commit`. A move made inside the checkout writes both logs, a branch move from
|
|
445
|
+
* another worktree only the branch's, a same-commit retarget only HEAD's, so both are kept. */
|
|
446
|
+
async function openSubjectWitness(dir, commit) {
|
|
447
|
+
const sym = await shGit("git symbolic-ref -q HEAD", dir);
|
|
448
|
+
if (sym.code !== 0 && sym.code !== 1)
|
|
449
|
+
return { leg: "unreadable" };
|
|
450
|
+
const ref = sym.code === 0 ? sym.stdout.trim() : undefined;
|
|
451
|
+
const logs = [];
|
|
452
|
+
for (const name of ref ? ["HEAD", ref] : ["HEAD"]) {
|
|
453
|
+
const w = await reflogWitness(dir, name);
|
|
454
|
+
if (w.code !== 0 || w.lines[0] !== commit)
|
|
455
|
+
return { leg: "witness" };
|
|
456
|
+
logs.push(w.lines.join("\n"));
|
|
457
|
+
}
|
|
458
|
+
return { ref, logs };
|
|
459
|
+
}
|
|
460
|
+
/** ANL-023 §7.4, after the seat returns: HEAD = `commit`, its symbolic state and referent unchanged, and both logs
|
|
461
|
+
* line for line as S0 found them — or the leg that changed, with the HEAD it found. */
|
|
462
|
+
async function adjudicationSubjectChange(dir, commit, s0) {
|
|
463
|
+
const head = await shGit("git rev-parse HEAD", dir);
|
|
464
|
+
if (head.code !== 0)
|
|
465
|
+
return { leg: "unreadable", found: "unreadable" };
|
|
466
|
+
const found = head.stdout.trim();
|
|
467
|
+
if (found !== commit)
|
|
468
|
+
return { leg: "moved", found };
|
|
469
|
+
const sym = await shGit("git symbolic-ref -q HEAD", dir);
|
|
470
|
+
if (sym.code !== 0 && sym.code !== 1)
|
|
471
|
+
return { leg: "unreadable", found: "unreadable" };
|
|
472
|
+
if ((sym.code === 0 ? sym.stdout.trim() : undefined) !== s0.ref)
|
|
473
|
+
return { leg: "moved", found };
|
|
474
|
+
for (const [i, name] of (s0.ref ? ["HEAD", s0.ref] : ["HEAD"]).entries()) {
|
|
475
|
+
const w = await reflogWitness(dir, name);
|
|
476
|
+
if (w.code !== 0)
|
|
477
|
+
return { leg: "unreadable", found: "unreadable" };
|
|
478
|
+
if (w.lines.join("\n") !== s0.logs[i])
|
|
479
|
+
return { leg: "restored", found };
|
|
480
|
+
}
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
440
483
|
export async function runGates(task, ctx) {
|
|
441
484
|
const results = [];
|
|
442
485
|
const evidence = {
|
|
@@ -1147,6 +1190,11 @@ export async function runGates(task, ctx) {
|
|
|
1147
1190
|
// reused. Only a criterion that REVERSES the newest prior ruling on the same subject key over
|
|
1148
1191
|
// identical cited blobs needs a second, distinct judge; agreement stands (a sound FAIL included),
|
|
1149
1192
|
// and a split, no distinct eligible seat or an unreadable adjudication parks for the operator.
|
|
1193
|
+
// 2.8.1 T4 (ANL-023 §7.4): the record names the commit it judged, under a validated subject witness. Only when
|
|
1194
|
+
// a second seat is about to run, the subject witness (HEAD's reflog and, when attached, its branch's reflog) is
|
|
1195
|
+
// validated for the sampled HEAD before the second seat runs, read-only, and re-read after it returns with HEAD
|
|
1196
|
+
// and its symbolic state; a move, a move-and-restore or an unwitnessable (or unreadable) subject parks infra with
|
|
1197
|
+
// no record stamped and no worker charge.
|
|
1150
1198
|
const adjudicate = async (fresh) => {
|
|
1151
1199
|
const primary = String(fresh.meta?.judge ?? channelKey({ adapter: ctx.cfg.judge.adapter, model: ctx.cfg.judge.model }));
|
|
1152
1200
|
const head = await shGit("git rev-parse HEAD", ctx.worktree);
|
|
@@ -1179,7 +1227,19 @@ export async function runGates(task, ctx) {
|
|
|
1179
1227
|
const seatVia = via
|
|
1180
1228
|
? { driver: via.driver, keep: via.keep, onSlot: via.onSlot, name: via.nameFor("judge", seatAdapter.id) + "-r2", label: via.labelFor("judge") }
|
|
1181
1229
|
: undefined;
|
|
1230
|
+
const subjectPark = (leg, found, ran) => ({
|
|
1231
|
+
gate: "acceptance", pass: false,
|
|
1232
|
+
details: `the adjudication subject ${SUBJECT_LEG_TEXT[leg]}: sampled ${commit.slice(0, 12)}, found ${found.slice(0, 12)} (${leg}); parked for an operator ruling, no worker charge`,
|
|
1233
|
+
meta: { classification: "infra", infra: true, retryable: false, cause: "subject-moved", judge: primary,
|
|
1234
|
+
adjudication: { primary, ...(ran ? { adjudicator: ran } : {}), sampled: commit, found, leg } },
|
|
1235
|
+
});
|
|
1236
|
+
const s0 = await openSubjectWitness(ctx.worktree, commit);
|
|
1237
|
+
if ("leg" in s0)
|
|
1238
|
+
return subjectPark(s0.leg, s0.leg === "unreadable" ? "unreadable" : commit);
|
|
1182
1239
|
const second = await invokeJudge(seatAdapter, seat.model, seatVia, configuredEffort(ctx.cfg, seat));
|
|
1240
|
+
const changed = await adjudicationSubjectChange(ctx.worktree, commit, s0);
|
|
1241
|
+
if (changed)
|
|
1242
|
+
return subjectPark(changed.leg, changed.found, adjudicator);
|
|
1183
1243
|
const rulings = Array.isArray(second.meta?.judgment) ? second.meta.judgment : undefined;
|
|
1184
1244
|
// A citation-less ruling cannot be compared, so it confirms nothing (invented evidence is already unparseable,
|
|
1185
1245
|
// and an internally inconsistent verdict carries no judgment rows at all).
|
package/dist/run/daemon.d.ts
CHANGED
|
@@ -5,8 +5,9 @@ import { type ExecutorDriver, type Slot } from "../drivers/types.js";
|
|
|
5
5
|
import { type Baseline, type BaselineProvenance, type GateEvidenceOptions } from "../gates/baseline.js";
|
|
6
6
|
import type { GateResult } from "../gates/types.js";
|
|
7
7
|
import { type RunGraph, type Task } from "../graph/schema.js";
|
|
8
|
-
import { type PreserveProducer } from "./git.js";
|
|
8
|
+
import { type PreserveProducer, type RunCapacity } from "./git.js";
|
|
9
9
|
import { Journal, type JournalEvent } from "./journal.js";
|
|
10
|
+
import { type IntegrationLeg, type IntegrationRefState, type TipVerifyResult } from "./merge.js";
|
|
10
11
|
export declare function closeLiveSlot(liveSlots: Set<Slot>, driver: Pick<ExecutorDriver, "close">, slot: Slot): Promise<void>;
|
|
11
12
|
export declare function setContactUnreadableDeadlineMsForTests(ms: number): void;
|
|
12
13
|
export declare function resetContactUnreadableDeadlineMsForTests(): void;
|
|
@@ -246,23 +247,86 @@ export interface TipProof {
|
|
|
246
247
|
export declare function runEndTipProof(events: readonly JournalEvent[]): TipProof;
|
|
247
248
|
/** The close notification's statement of the proof — one clause per kind. */
|
|
248
249
|
export declare function formatTipProof(p: TipProof): string;
|
|
250
|
+
/** 2.8.1 T1 (ANL-023 §7.1): what run end checks the integration ref against; `expected` absent = the opening read. */
|
|
251
|
+
export interface TipIdentity {
|
|
252
|
+
repo: string;
|
|
253
|
+
branch: string;
|
|
254
|
+
expected?: string;
|
|
255
|
+
}
|
|
256
|
+
export type IdentityLeg = IntegrationLeg | "setup" | "witness" | "checkout";
|
|
257
|
+
type DeferredVerdict = NonNullable<TipVerifyResult["deferredVerdict"]>;
|
|
258
|
+
/** One close under an identity: `expected` is what the closing check must find; `identityFailed` skips it. */
|
|
259
|
+
export interface TipClose {
|
|
260
|
+
failed: boolean;
|
|
261
|
+
expected?: string;
|
|
262
|
+
identityFailed: boolean;
|
|
263
|
+
deferred: DeferredVerdict[];
|
|
264
|
+
}
|
|
265
|
+
interface TipVerifyOptions {
|
|
266
|
+
lastMergedTask?: string;
|
|
267
|
+
baseline?: Baseline;
|
|
268
|
+
signal?: AbortSignal;
|
|
269
|
+
evidence?: GateEvidenceOptions;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* 2.8.1 T3 (ANL-023 §7.3): anchor A of a run whose run-start carries `integrationAnchor: "journal"` — the newest merge
|
|
273
|
+
* row's commit (that row must carry a `from`), else the run-start base; any of them not an object id is unreadable.
|
|
274
|
+
* No marker: undefined (legacy-ref). Resume and approve both read it.
|
|
275
|
+
*/
|
|
276
|
+
export declare function integrationAnchor(events: readonly JournalEvent[]): {
|
|
277
|
+
anchor: string;
|
|
278
|
+
} | {
|
|
279
|
+
unreadable: string;
|
|
280
|
+
} | undefined;
|
|
281
|
+
/** The ref legs' details: the leg, both commits, the symbolic target and the repair text. */
|
|
282
|
+
export declare function identityDetails(leg: IntegrationLeg, state: IntegrationRefState, branch: string, runId: string, expected: string | undefined): string;
|
|
283
|
+
/**
|
|
284
|
+
* The one closed outcome row for every identity leg: an integration `tip-verify-failed` (preceded by a start row
|
|
285
|
+
* with no gates when this close appended none), which runEndTipProof and every consumer already read as failure.
|
|
286
|
+
*/
|
|
287
|
+
export declare function appendIdentityFailure(journal: Journal, f: {
|
|
288
|
+
started: boolean;
|
|
289
|
+
branch: string;
|
|
290
|
+
expected?: string;
|
|
291
|
+
leg: IdentityLeg;
|
|
292
|
+
details: string;
|
|
293
|
+
state?: IntegrationRefState;
|
|
294
|
+
cmdHash: string;
|
|
295
|
+
capacity: RunCapacity;
|
|
296
|
+
infra?: boolean;
|
|
297
|
+
}): void;
|
|
298
|
+
/** O: I(expected) — or, with no expected value, the ref must be regular and its commit becomes the expected. */
|
|
299
|
+
export declare function openingIdentity(identity: TipIdentity, journal: Journal): Promise<{
|
|
300
|
+
ok: true;
|
|
301
|
+
expected: string;
|
|
302
|
+
} | {
|
|
303
|
+
ok: false;
|
|
304
|
+
leg: IntegrationLeg;
|
|
305
|
+
state: IntegrationRefState;
|
|
306
|
+
expected?: string;
|
|
307
|
+
details: string;
|
|
308
|
+
}>;
|
|
249
309
|
/**
|
|
250
310
|
* OBS-34's strict tip verify, but it stops re-paying for an unmoved tip (~334m corpus-wide; 69.5m in
|
|
251
311
|
* one park-heavy run whose 18 resume cycles merged nothing new). The verify journals the SHA it
|
|
252
312
|
* verified and the hash of the command set, so a later run-end can recognize the same verified state:
|
|
253
|
-
*
|
|
254
|
-
* `tip-verify-cached` and skip. ANY doubt — moved
|
|
255
|
-
* from that cycle's green set, a failure recorded in it, a cycle older than the last one —
|
|
256
|
-
* full verify.
|
|
257
|
-
*
|
|
258
|
-
*
|
|
313
|
+
* the expected commit equals the LAST green verified SHA and the commands are unchanged → journal
|
|
314
|
+
* `tip-verify-cached` and skip, with no checkout. ANY doubt — moved tip, changed commands, a gate
|
|
315
|
+
* missing from that cycle's green set, a failure recorded in it, a cycle older than the last one —
|
|
316
|
+
* runs the full verify. 2.8.1 T1 (ANL-023 §7.1): with `identity` (the daemon always passes it) run end
|
|
317
|
+
* verifies a fresh detached checkout of the expected commit under a validated HEAD reflog, never the
|
|
318
|
+
* integration checkout; it checks tracked content after the battery (the battery-end check also proves
|
|
319
|
+
* the checkout's HEAD and its reflog unchanged), defers verdict-store writes to the caller, and the
|
|
320
|
+
* caller closes on an identity check taken after the approval serialization with no await before the
|
|
321
|
+
* run-end row. The opening check reads the ref before the cycle decision, so a cached green is carried
|
|
322
|
+
* only for the commit the ref holds now. A caller without `identity` keeps the integration checkout's
|
|
323
|
+
* head and clean tree as the cache key, exactly as before.
|
|
324
|
+
* Returns whether the tip is failing (without `identity`) or the close's TipClose (with it).
|
|
259
325
|
*/
|
|
260
|
-
export declare function verifyIntegrationTipCached(intWt: string, commands: Record<string, string>, journal: Journal, opts
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
evidence?: GateEvidenceOptions;
|
|
265
|
-
}): Promise<boolean>;
|
|
326
|
+
export declare function verifyIntegrationTipCached(intWt: string, commands: Record<string, string>, journal: Journal, opts: TipVerifyOptions & {
|
|
327
|
+
identity: TipIdentity;
|
|
328
|
+
}): Promise<TipClose>;
|
|
329
|
+
export declare function verifyIntegrationTipCached(intWt: string, commands: Record<string, string>, journal: Journal, opts?: TipVerifyOptions): Promise<boolean>;
|
|
266
330
|
type WorkerProcessTree = "empty" | "running" | "unmeasurable";
|
|
267
331
|
export declare function observeWorkerProcessTree(marker: string, cwd: string): Promise<WorkerProcessTree>;
|
|
268
332
|
type SuitePidProbe = (pid: number) => number | undefined;
|