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/adapters/codex.d.ts +1 -1
- package/dist/adapters/codex.js +18 -6
- package/dist/adapters/fake.js +42 -4
- package/dist/cli/commands/approve.js +33 -8
- package/dist/cli/commands/doctor.js +11 -2
- package/dist/cli/commands/verify.js +32 -12
- package/dist/gates/acceptance.d.ts +1 -1
- package/dist/gates/acceptance.js +75 -68
- package/dist/gates/baseline.d.ts +5 -1
- package/dist/gates/baseline.js +31 -6
- package/dist/gates/operation-facts.d.ts +47 -0
- package/dist/gates/operation-facts.js +84 -0
- package/dist/gates/run-gates.js +81 -12
- package/dist/run/daemon.d.ts +77 -13
- package/dist/run/daemon.js +348 -94
- package/dist/run/git.d.ts +21 -4
- package/dist/run/git.js +26 -9
- package/dist/run/launchservices-check.d.ts +23 -1
- package/dist/run/launchservices-check.js +88 -3
- package/dist/run/merge.d.ts +114 -7
- package/dist/run/merge.js +146 -44
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +8 -0
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
|
|
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
|
-
/**
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
-
/**
|
|
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
|
|
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 =
|
|
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 };
|
package/dist/run/merge.d.ts
CHANGED
|
@@ -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
|
-
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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 {};
|