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.
package/dist/run/git.d.ts CHANGED
@@ -154,6 +154,8 @@ export interface ShResult {
154
154
  durationMs?: number;
155
155
  /** T7: the capacity THIS child ran under, stamped where its environment was built (see `shell`). */
156
156
  capacity?: RunCapacity;
157
+ /** Queue row 142: stdout exactly as the child wrote it, present only when the own-git caller asked for bytes. */
158
+ stdoutBytes?: Buffer;
157
159
  }
158
160
  export declare const setSpawnForTests: (fn: typeof spawn) => void;
159
161
  export declare const resetSpawnForTests: () => void;
@@ -168,6 +170,8 @@ export interface ShellOptions {
168
170
  onReceipt?: (receipt: ShellReceipt) => void;
169
171
  /** Called once per invocation (1-based), including each pre-spawn retry. */
170
172
  receiptAttribution?: (invocation: number) => CommandReceiptAttribution;
173
+ /** Queue row 142: also keep stdout as raw bytes (ShResult.stdoutBytes); only shGit's byte readers opt in. */
174
+ bytes?: boolean;
171
175
  }
172
176
  /** Shared command seam, including invocation-bound manifested runners. A PAYLOAD entry: its children keep the
173
177
  * operator's git config (hooks, fsmonitor) and get no checkout pin — ownership follows the entry, never `login`
@@ -180,7 +184,7 @@ export declare function shell(cmd: string, cwd: string, timeoutMs: number, login
180
184
  */
181
185
  export type OwnGitOptions = OwnGitCapability;
182
186
  export declare function sh(cmd: string, cwd: string, timeoutMs?: number, options?: ShellOptions): Promise<ShResult>;
183
- export declare function shGit(cmd: string, cwd: string, timeoutMs?: number, own?: OwnGitOptions): Promise<ShResult>;
187
+ export declare function shGit(cmd: string, cwd: string, timeoutMs?: number, own?: OwnGitOptions, options?: Pick<ShellOptions, "bytes">): Promise<ShResult>;
184
188
  export declare function shOk(cmd: string, cwd: string): Promise<string>;
185
189
  export declare function shGitOk(cmd: string, cwd: string, own?: OwnGitOptions): Promise<string>;
186
190
  /**
@@ -194,16 +198,29 @@ export declare function shGitOk(cmd: string, cwd: string, own?: OwnGitOptions):
194
198
  * A step raced by a commondir change is refused by name before the next one runs, and what the add created is rolled
195
199
  * back through the trusted common directory derived here once (dropWorktree), even while that commondir stays hostile.
196
200
  */
197
- export declare function addDetachedWorktree(cwd: string, dir: string, commitish: string, timeoutMs?: number): Promise<ShResult>;
201
+ export declare function addDetachedWorktree(cwd: string, dir: string, commitish: string, timeoutMs?: number, { logHead }?: {
202
+ logHead?: boolean | undefined;
203
+ }): Promise<ShResult>;
204
+ /**
205
+ * 2.8.1 T1 (ANL-023 §7.1): the reflog witness W(dir, ref) — read-only, the newest entry first. It is valid for a commit
206
+ * x only when the exit is 0, at least one line came back and the first is x: exit 0 with no lines is git's answer for
207
+ * a missing or unreadable log, so an empty witness is a refusal, never an accepted empty.
208
+ */
209
+ export declare function reflogWitness(dir: string, ref: string): Promise<{
210
+ code: number;
211
+ lines: string[];
212
+ }>;
198
213
  export declare function npmDependencyManifestChanged(cwd: string, baselineRef: string): Promise<boolean>;
199
214
  export declare function npmDependencyInstallCommand(cwd: string): string;
200
215
  export declare function gitHead(cwd: string): Promise<string>;
201
216
  export declare const sanitizeBranch: (branch: string) => string;
202
217
  export declare const WORKTREES_DIR = "worktrees.noindex";
203
218
  export declare const worktreePath: (repo: string, branch: string) => string;
204
- /** OBS-28: remove this run's recorded worktrees; tolerates already-gone paths. */
219
+ /**
220
+ * OBS-28: remove this run's recorded task worktrees; tolerates already-gone paths. 2.8.1 T2: a run keeps no
221
+ * integration checkout, and a 2.8.0 run's legacy one is never removed here (it may hold the only view of a merge).
222
+ */
205
223
  export declare function cleanupRunWorktrees(repo: string, branch: string, opts: {
206
- removeIntegration: boolean;
207
224
  removeTaskIds: string[];
208
225
  }): Promise<void>;
209
226
  export declare function resolveIntegrationBranch(_repo: string, branch: string): Promise<string>;
package/dist/run/git.js CHANGED
@@ -386,6 +386,7 @@ function executeShell(cmd, cwd, timeoutMs, login, options, observation, ownGit)
386
386
  throw error;
387
387
  }
388
388
  let stdout = "", stderr = "";
389
+ const stdoutChunks = [];
389
390
  const stdoutDecoder = new StringDecoder("utf8");
390
391
  const stderrDecoder = new StringDecoder("utf8");
391
392
  let timedOut = false, reapedGroup = false, done = false, started = false, outputSeen = false;
@@ -419,6 +420,7 @@ function executeShell(cmd, cwd, timeoutMs, login, options, observation, ownGit)
419
420
  timedOut,
420
421
  durationMs: Date.now() - startedAt,
421
422
  capacity,
423
+ ...(options.bytes ? { stdoutBytes: Buffer.concat(stdoutChunks) } : {}),
422
424
  ...(reapedGroup ? { reapedGroup: true } : {}),
423
425
  ...(reapError ? { reapError } : {}),
424
426
  });
@@ -485,6 +487,8 @@ function executeShell(cmd, cwd, timeoutMs, login, options, observation, ownGit)
485
487
  p.stdout.on("data", (d) => {
486
488
  if (d.length > 0)
487
489
  outputSeen = true;
490
+ if (options.bytes)
491
+ stdoutChunks.push(d);
488
492
  stdout += stdoutDecoder.write(d);
489
493
  });
490
494
  p.stderr.on("data", (d) => {
@@ -576,9 +580,9 @@ export function sh(cmd, cwd, timeoutMs = DEFAULT_SHELL_TIMEOUT_MS, options = {})
576
580
  }
577
581
  // Git plumbing never needs an operator profile; skip login-shell startup and its side effects.
578
582
  // v2.6.8 T2: the explicit OWN-GIT entry — forced inert hooks/fsmonitor and, in a linked checkout, the trusted-path and
579
- // ref-store pin.
580
- export function shGit(cmd, cwd, timeoutMs = DEFAULT_SHELL_TIMEOUT_MS, own = {}) {
581
- return shellEntry(cmd, cwd, timeoutMs, false, {}, own);
583
+ // ref-store pin. Queue row 142: `bytes` is the one opt-in raw-stdout capture; every other caller's output is unchanged.
584
+ export function shGit(cmd, cwd, timeoutMs = DEFAULT_SHELL_TIMEOUT_MS, own = {}, options = {}) {
585
+ return shellEntry(cmd, cwd, timeoutMs, false, options, own);
582
586
  }
583
587
  export async function shOk(cmd, cwd) {
584
588
  const r = await sh(cmd, cwd);
@@ -603,7 +607,10 @@ export async function shGitOk(cmd, cwd, own = {}) {
603
607
  * A step raced by a commondir change is refused by name before the next one runs, and what the add created is rolled
604
608
  * back through the trusted common directory derived here once (dropWorktree), even while that commondir stays hostile.
605
609
  */
606
- export async function addDetachedWorktree(cwd, dir, commitish, timeoutMs = DEFAULT_SHELL_TIMEOUT_MS) {
610
+ export async function addDetachedWorktree(cwd, dir, commitish, timeoutMs = DEFAULT_SHELL_TIMEOUT_MS, { logHead = false } = {}) {
611
+ // 2.8.1 T1 (ANL-023 §7.1): `logHead` writes the new checkout's HEAD reflog whatever the repository's
612
+ // core.logAllRefUpdates says, so a later HEAD move by any command is appended to an established witness.
613
+ const git = logHead ? "git -c core.logAllRefUpdates=always" : "git";
607
614
  // the authority this operation keeps, rollback included: derived once from the trusted layout, never a commondir
608
615
  const store = trustedCommonDir(cwd);
609
616
  const resolved = await shGit(`git rev-parse --verify --end-of-options ${shq(`${commitish}^{commit}`)}`, cwd, timeoutMs);
@@ -612,12 +619,12 @@ export async function addDetachedWorktree(cwd, dir, commitish, timeoutMs = DEFAU
612
619
  const oid = resolved.stdout.trim();
613
620
  // under a ref payload git also resolves its new-HEAD stub into a shared refs/heads/.invalid; it is deleted again
614
621
  const rollback = { dir, branch: { name: ".invalid" } };
615
- const added = await shellEntry(`git worktree add --no-checkout --detach ${shq(dir)} ${shq(oid)}`, cwd, timeoutMs, false, {}, { createsWorktree: "detached", rollback });
622
+ const added = await shellEntry(`${git} worktree add --no-checkout --detach ${shq(dir)} ${shq(oid)}`, cwd, timeoutMs, false, {}, { createsWorktree: "detached", rollback });
616
623
  if (added.code !== 0)
617
624
  return added;
618
625
  let repaired;
619
626
  try {
620
- repaired = await shGit(`git update-ref --no-deref HEAD ${shq(oid)} && git update-ref -d refs/heads/.invalid && git reset -q --hard --no-recurse-submodules`, dir, timeoutMs);
627
+ repaired = await shGit(`${git} update-ref --no-deref HEAD ${shq(oid)} && ${git} update-ref -d refs/heads/.invalid && ${git} reset -q --hard --no-recurse-submodules`, dir, timeoutMs);
621
628
  }
622
629
  catch (error) {
623
630
  await dropWorktree(store, rollback, timeoutMs).catch((failure) => { throw rollbackFailed(error, failure); });
@@ -627,6 +634,15 @@ export async function addDetachedWorktree(cwd, dir, commitish, timeoutMs = DEFAU
627
634
  await dropWorktree(store, rollback, timeoutMs);
628
635
  return repaired;
629
636
  }
637
+ /**
638
+ * 2.8.1 T1 (ANL-023 §7.1): the reflog witness W(dir, ref) — read-only, the newest entry first. It is valid for a commit
639
+ * x only when the exit is 0, at least one line came back and the first is x: exit 0 with no lines is git's answer for
640
+ * a missing or unreadable log, so an empty witness is a refusal, never an accepted empty.
641
+ */
642
+ export async function reflogWitness(dir, ref) {
643
+ const r = await shGit(`git reflog show --no-abbrev --format=%H ${shq(ref)}`, dir);
644
+ return { code: r.code, lines: r.stdout.split("\n").map((line) => line.trim()).filter(Boolean) };
645
+ }
630
646
  /** A failed rollback is never swallowed: it rides on the error the operation already throws (a refusal stays one). */
631
647
  function rollbackFailed(error, failure) {
632
648
  const why = `; its rollback failed, so what it created may remain: ${failure instanceof Error ? failure.message : String(failure)}`;
@@ -734,10 +750,11 @@ const sanitize = sanitizeBranch;
734
750
  // once after this rename — the same one-time per-install cost as the v1.38 state-dir rename.
735
751
  export const WORKTREES_DIR = "worktrees.noindex";
736
752
  export const worktreePath = (repo, branch) => join(tickmarkrDir(repo), WORKTREES_DIR, sanitize(branch));
737
- /** OBS-28: remove this run's recorded worktrees; tolerates already-gone paths. */
753
+ /**
754
+ * OBS-28: remove this run's recorded task worktrees; tolerates already-gone paths. 2.8.1 T2: a run keeps no
755
+ * integration checkout, and a 2.8.0 run's legacy one is never removed here (it may hold the only view of a merge).
756
+ */
738
757
  export async function cleanupRunWorktrees(repo, branch, opts) {
739
- if (opts.removeIntegration)
740
- await removeWorktree(repo, worktreePath(repo, branch));
741
758
  for (const id of opts.removeTaskIds)
742
759
  await removeWorktree(repo, worktreePath(repo, `${branch}--${id}`));
743
760
  }
@@ -33,12 +33,34 @@ export type ProbeTimer = (fire: () => void, ms: number) => () => void;
33
33
  interface ProbeSeam {
34
34
  platform: NodeJS.Platform;
35
35
  spawn: typeof spawn;
36
+ spawnTop?: typeof spawn;
36
37
  setTimer: ProbeTimer;
37
38
  signal?: AbortSignal;
38
39
  }
39
40
  export declare const setLaunchServicesProbeForTests: (s: Partial<ProbeSeam>) => void;
40
41
  export declare const resetLaunchServicesProbeForTests: () => void;
41
42
  export declare function observeLaunchServices(s?: ProbeSeam): Promise<LaunchServicesObservation>;
43
+ export declare const LAUNCHSERVICES_PORT_ADVISORY = 100000;
44
+ export declare const LAUNCHSERVICES_PORT_STOP = 200000;
45
+ export type LaunchServicesPorts = {
46
+ status: "skipped" | "non-darwin";
47
+ } | {
48
+ status: "unknown";
49
+ pid?: number;
50
+ } | {
51
+ status: "read";
52
+ pid: number;
53
+ count: number;
54
+ };
55
+ /** Closed top format: PID COMMAND #PORTS, then the next non-empty row for this pid and launchservicesd. */
56
+ export declare function parseTopPortCount(stdout: string, pid: number): number | null;
57
+ /** One identity read and, only for a readable Darwin identity, one bounded top child. */
58
+ export declare function probeLaunchServicesPorts(options?: {
59
+ requireTop?: boolean;
60
+ }): Promise<{
61
+ observation?: LaunchServicesObservation;
62
+ ports: LaunchServicesPorts;
63
+ }>;
42
64
  export declare function launchServicesAdvisory(prior: LaunchServicesIdentity, now: LaunchServicesIdentity): string;
43
65
  /** Pure: the table row for one observation against the held record, and the record to hold next. */
44
66
  export declare function classifyLaunchServices(prior: Partial<LaunchServicesRecord> | undefined, observed: LaunchServicesObservation): {
@@ -48,7 +70,7 @@ export declare function classifyLaunchServices(prior: Partial<LaunchServicesReco
48
70
  };
49
71
  /** Observe, classify against the first record of `prior`, and hold the result on the first record of `health`.
50
72
  * Returns `health` itself when nothing changed, else a copy — the argument is never mutated. */
51
- export declare function recordLaunchServices(prior: Record<string, AuthHealth> | null, health: Record<string, AuthHealth>): Promise<{
73
+ export declare function recordLaunchServices(prior: Record<string, AuthHealth> | null, health: Record<string, AuthHealth>, observation?: LaunchServicesObservation): Promise<{
52
74
  status: LaunchServicesStatus;
53
75
  health: Record<string, AuthHealth>;
54
76
  line?: string;
@@ -27,12 +27,14 @@ export function parsePsIdentity(stdout) {
27
27
  const production = () => ({
28
28
  platform: process.platform,
29
29
  spawn,
30
+ spawnTop: spawn,
30
31
  setTimer: (fire, ms) => { const t = setTimeout(fire, ms); return () => clearTimeout(t); },
31
32
  });
32
33
  // ponytail: default-off under the test runner (the catalogRefreshAllowed precedent) so an unowned doctor
33
34
  // test never takes a real host reading or changes its doctor.json bytes; tests opt in through the seam.
35
+ // The legacy identity-only seam leaves spawnTop unset, so old /bin/ps fixtures never touch the host top.
34
36
  let seam;
35
- export const setLaunchServicesProbeForTests = (s) => { seam = { ...production(), ...s }; };
37
+ export const setLaunchServicesProbeForTests = (s) => { seam = { ...production(), ...s, spawnTop: s.spawnTop }; };
36
38
  export const resetLaunchServicesProbeForTests = () => { seam = undefined; };
37
39
  const activeSeam = () => seam ?? (process.env.VITEST === "true" ? undefined : production());
38
40
  /** One /bin/ps call that owns only its child, killed at the ceiling or on cancellation and reaped before it resolves. */
@@ -78,6 +80,88 @@ function readIdentity(s) {
78
80
  export function observeLaunchServices(s = production()) {
79
81
  return s.platform === "darwin" ? readIdentity(s) : Promise.resolve({ status: "non-darwin" });
80
82
  }
83
+ export const LAUNCHSERVICES_PORT_ADVISORY = 100_000;
84
+ export const LAUNCHSERVICES_PORT_STOP = 200_000;
85
+ const TOP_BYTES = 1024 * 1024;
86
+ /** Closed top format: PID COMMAND #PORTS, then the next non-empty row for this pid and launchservicesd. */
87
+ export function parseTopPortCount(stdout, pid) {
88
+ const lines = stdout.split(/\r?\n/);
89
+ const header = lines.findIndex((line) => line.trim().split(/\s+/).join(" ") === "PID COMMAND #PORTS");
90
+ if (header < 0)
91
+ return null;
92
+ const row = lines.slice(header + 1).find((line) => line.trim() !== "");
93
+ if (!row)
94
+ return null;
95
+ const tokens = row.trim().split(/\s+/);
96
+ if (tokens.length !== 3 || tokens[0] !== String(pid) || tokens[1] !== "launchservicesd" || !/^[0-9]+[+-]?$/.test(tokens[2]))
97
+ return null;
98
+ const count = Number(tokens[2].replace(/[+-]$/, ""));
99
+ return Number.isSafeInteger(count) ? count : null;
100
+ }
101
+ // The captured macOS top layout is closed: only a matching three-token row after the exact header
102
+ // yields a count. 100000 ports is a doctor advisory; 200000 stops standalone verify before gates.
103
+ // Unknown (including an unreadable identity, malformed top output, or a killed child) never stops.
104
+ function readPortCount(s, pid) {
105
+ return new Promise((resolve) => {
106
+ const unknown = () => resolve({ status: "unknown", pid });
107
+ if (s.signal?.aborted) {
108
+ unknown();
109
+ return;
110
+ }
111
+ if (!s.spawnTop) {
112
+ unknown();
113
+ return;
114
+ }
115
+ let child;
116
+ try {
117
+ child = s.spawnTop("/usr/bin/top", ["-l", "1", "-stats", "pid,command,ports", "-pid", String(pid)], { stdio: ["ignore", "pipe", "ignore"], env: { ...process.env, LC_ALL: "C" } });
118
+ }
119
+ catch {
120
+ unknown();
121
+ return;
122
+ }
123
+ let out = "";
124
+ let bytes = 0;
125
+ let failed = false;
126
+ const retire = () => { if (!failed) {
127
+ failed = true;
128
+ child.kill("SIGKILL");
129
+ } };
130
+ const clear = s.setTimer(retire, LAUNCHSERVICES_PROBE_CEILING_MS);
131
+ s.signal?.addEventListener("abort", retire, { once: true });
132
+ child.stdout?.on("data", (chunk) => {
133
+ if (failed)
134
+ return;
135
+ bytes += chunk.length;
136
+ if (bytes > TOP_BYTES) {
137
+ retire();
138
+ return;
139
+ }
140
+ out += chunk.toString("utf8");
141
+ });
142
+ child.once("error", () => { failed = true; });
143
+ child.once("close", (code) => {
144
+ clear();
145
+ s.signal?.removeEventListener("abort", retire);
146
+ if (failed || code !== 0)
147
+ return unknown();
148
+ const count = parseTopPortCount(out, pid);
149
+ return count === null ? unknown() : resolve({ status: "read", pid, count });
150
+ });
151
+ });
152
+ }
153
+ /** One identity read and, only for a readable Darwin identity, one bounded top child. */
154
+ export async function probeLaunchServicesPorts(options = {}) {
155
+ const s = activeSeam();
156
+ if (!s || (options.requireTop && !s.spawnTop))
157
+ return { ports: { status: "skipped" } };
158
+ const observation = await observeLaunchServices(s);
159
+ if (observation.status === "non-darwin")
160
+ return { observation, ports: { status: "non-darwin" } };
161
+ if (observation.status === "unreadable")
162
+ return { observation, ports: { status: "unknown" } };
163
+ return { observation, ports: await readPortCount(s, observation.identity.pid) };
164
+ }
81
165
  // doctor.json is never schema-validated: a malformed held record is no identity at all.
82
166
  const knownPrior = (prior) => typeof prior?.pid === "number" && typeof prior.start === "string";
83
167
  export function launchServicesAdvisory(prior, now) {
@@ -106,10 +190,11 @@ export function classifyLaunchServices(prior, observed) {
106
190
  }
107
191
  /** Observe, classify against the first record of `prior`, and hold the result on the first record of `health`.
108
192
  * Returns `health` itself when nothing changed, else a copy — the argument is never mutated. */
109
- export async function recordLaunchServices(prior, health) {
193
+ export async function recordLaunchServices(prior, health, observation) {
110
194
  const held = prior ? Object.values(prior)[0]?.launchServices : undefined;
111
195
  const s = activeSeam();
112
- const r = s ? classifyLaunchServices(held, await observeLaunchServices(s)) : { status: "skipped", next: knownPrior(held) ? held : undefined };
196
+ const r = observation ? classifyLaunchServices(held, observation)
197
+ : s ? classifyLaunchServices(held, await observeLaunchServices(s)) : { status: "skipped", next: knownPrior(held) ? held : undefined };
113
198
  const holder = Object.keys(health)[0];
114
199
  if (!holder || !r.next || JSON.stringify(health[holder].launchServices) === JSON.stringify(r.next))
115
200
  return { ...r, health };
@@ -1,5 +1,6 @@
1
1
  import type { TickmarkrConfig } from "../config/config.js";
2
2
  import { type Baseline, type BaselineProvenance, type GateEvidenceOptions, type FailureClassification } from "../gates/baseline.js";
3
+ import { type VerificationIdentity } from "../gates/cache.js";
3
4
  import type { GateEvidenceReceipt } from "./protocol.js";
4
5
  export interface TipVerifyResult {
5
6
  evidenceReceipt?: GateEvidenceReceipt;
@@ -30,18 +31,124 @@ export interface TipVerifyResult {
30
31
  * for a kill, the shared runner classifier for everything else. `infra` means nothing was verified.
31
32
  */
32
33
  cause?: FailureClassification;
34
+ /** 2.8.1 T1: with `deferVerdicts`, the verdict-store write this result would have made, left to the caller. */
35
+ deferredVerdict?: {
36
+ id: VerificationIdentity;
37
+ verdict: TipVerifyResult & {
38
+ meta: Record<string, unknown>;
39
+ };
40
+ };
33
41
  }
34
42
  /** Carry execution evidence without minting an invocation or rebasing its artifact paths. */
35
43
  export declare function reusedTipEvidence(source: Partial<TipVerifyResult>): Partial<TipVerifyResult>;
36
44
  export declare function integrationBranch(cfg: TickmarkrConfig, runId: string): string;
45
+ /**
46
+ * 2.8.1 T2 (ANL-023 §7.2): creates the integration ref create-only at `baseRef` (the empty old value means "must not
47
+ * exist") and returns the published tip E. The run keeps no checkout of the branch: every later publication is a
48
+ * compare-and-swap of the ref from `repo` (mergeTask). An existing or uncreatable ref throws — the fatal path's text.
49
+ */
37
50
  export declare function ensureIntegration(repo: string, branch: string, baseRef: string): Promise<string>;
38
- export declare function integrationHead(intWt: string): Promise<string>;
39
- export declare function mergeTask(intWt: string, taskBranch: string, message: string, gatedCommit: string): Promise<{
40
- ok: boolean;
41
- conflict?: string;
42
- tipMoved?: {
51
+ /** 2.8.1 T1 (ANL-023 §7.1): the closed states of the integration ref, as R(ref) reads them. */
52
+ export type IntegrationRefState = {
53
+ kind: "regular";
54
+ commit: string;
55
+ } | {
56
+ kind: "missing";
57
+ } | {
58
+ kind: "symbolic";
59
+ target: string;
60
+ commit?: string;
61
+ } | {
62
+ kind: "unreadable";
63
+ read: string;
64
+ };
65
+ export type IntegrationLeg = "symbolic" | "missing" | "value" | "unreadable";
66
+ /**
67
+ * R(ref): the integration ref read from `repo` through the own-git runner, never its reflog. The exact ref only —
68
+ * a descendant ref (`refs/heads/<branch>/x`) is not this ref, and a ref that exists but names no commit is unreadable.
69
+ */
70
+ export declare function readIntegrationRef(repo: string, branch: string): Promise<IntegrationRefState>;
71
+ /** I(x): holds iff the ref is regular at x; otherwise the leg, with the state the repair text is chosen by. */
72
+ export declare function checkIntegrationIdentity(repo: string, branch: string, expected: string): Promise<{
73
+ ok: true;
74
+ state: IntegrationRefState;
75
+ } | {
76
+ ok: false;
77
+ leg: IntegrationLeg;
78
+ state: IntegrationRefState;
79
+ }>;
80
+ /**
81
+ * The repair text (shared by run end's failure row, the merge park and the resume refusal): backticked commands run
82
+ * from the repository root in order, stopping at the first that fails. A found commit is preserved create-only under
83
+ * refs/tickmarkr/found/<runId>/<F> before a no-deref compare-and-swap restores the ref; a symbolic found ref or one
84
+ * already holding another commit stops the sequence before the restore. Dangling and unreadable print no command.
85
+ */
86
+ export declare function integrationRepairText(state: IntegrationRefState, branch: string, runId: string, expected: string): string;
87
+ type GitResult = {
88
+ code: number;
89
+ stdout: string;
90
+ stderr: string;
91
+ };
92
+ /** The merge's git steps after the identity check, in order; `publish` is the compare-and-swap. */
93
+ export type MergeStep = "ancestry" | "merge-tree" | "commit-tree" | "publish";
94
+ /** Test-only: runs before each step; a returned result replaces that step's git result (never `publish`'s). */
95
+ export type MergeSeam = (step: MergeStep) => Promise<GitResult | void> | GitResult | void;
96
+ export interface IntegrationMove {
97
+ branch: string;
98
+ expected: string;
99
+ found: string | null;
100
+ leg: IntegrationLeg;
101
+ target?: string;
102
+ stage: "check" | "publish";
103
+ }
104
+ export type MergeOutcome = {
105
+ ok: true;
106
+ commit: string;
107
+ from: string;
108
+ } | {
109
+ ok: true;
110
+ alreadyMerged: string;
111
+ } | {
112
+ ok: false;
113
+ tipMoved: {
43
114
  gatedCommit: string;
44
115
  branchTip: string;
45
116
  };
46
- }>;
47
- export declare function verifyIntegrationTip(intWt: string, commands: Record<string, string>, runDir: string, baseline?: Baseline, evidenceOptions?: GateEvidenceOptions): Promise<TipVerifyResult[]>;
117
+ } | {
118
+ ok: false;
119
+ conflict: string;
120
+ } | {
121
+ ok: false;
122
+ integrationMoved: IntegrationMove;
123
+ state: IntegrationRefState;
124
+ } | {
125
+ ok: false;
126
+ alreadyIntegrated: {
127
+ gated: string;
128
+ tip: string;
129
+ };
130
+ } | {
131
+ ok: false;
132
+ infra: {
133
+ step: MergeStep;
134
+ message: string;
135
+ };
136
+ };
137
+ /**
138
+ * 2.8.1 T2 (ANL-023 §7.2): publishes `gated` onto the integration ref by compare-and-swap from the stored tip
139
+ * `expected`, all git from `repo` — no checkout. (1) the task-tip check; (2) I(expected); (3) ancestry; (4) merge-tree;
140
+ * (5) commit-tree; (6) `update-ref --no-deref` from `expected`; (7) `onPublished(M)` in the same synchronous
141
+ * continuation as (6)'s result, so the caller's rows land in publication order. `merged` is the task's recorded merge
142
+ * commits, newest first: a gated commit already in the history is done only through its own recorded merge.
143
+ * `ancestorOnly` (a replay that ran no gates): a gated commit not in E is refused `infra`, never published.
144
+ */
145
+ export declare function mergeTask(repo: string, { branch, expected, merged, ancestorOnly }: {
146
+ branch: string;
147
+ expected: string;
148
+ merged?: readonly string[];
149
+ ancestorOnly?: boolean;
150
+ }, taskBranch: string, message: string, gated: string, onPublished: (commit: string) => void, seam?: MergeSeam): Promise<MergeOutcome>;
151
+ export declare function verifyIntegrationTip(intWt: string, commands: Record<string, string>, runDir: string, baseline?: Baseline, evidenceOptions?: GateEvidenceOptions, { deferVerdicts }?: {
152
+ deferVerdicts?: boolean;
153
+ }): Promise<TipVerifyResult[]>;
154
+ export {};