tickmarkr 2.7.8 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/codex.d.ts +1 -1
- package/dist/adapters/codex.js +18 -6
- package/dist/adapters/fake.js +42 -4
- package/dist/cli/commands/doctor.js +11 -2
- package/dist/cli/commands/fleet.js +206 -18
- package/dist/cli/commands/verify.js +9 -1
- package/dist/gates/acceptance.d.ts +1 -1
- package/dist/gates/acceptance.js +75 -68
- package/dist/gates/baseline.d.ts +42 -9
- package/dist/gates/baseline.js +261 -47
- package/dist/gates/operation-facts.d.ts +47 -0
- package/dist/gates/operation-facts.js +84 -0
- package/dist/gates/run-gates.js +20 -11
- package/dist/gates/test-manifest.js +2 -2
- package/dist/run/git.d.ts +5 -1
- package/dist/run/git.js +7 -3
- package/dist/run/journal.d.ts +1 -1
- package/dist/run/launchservices-check.d.ts +23 -1
- package/dist/run/launchservices-check.js +88 -3
- package/dist/run/merge.js +26 -12
- package/package.json +1 -1
package/dist/gates/run-gates.js
CHANGED
|
@@ -7,7 +7,7 @@ import { TIER_RANK } from "../config/config.js";
|
|
|
7
7
|
import { getAdapter } from "../adapters/registry.js";
|
|
8
8
|
import { GATE_NAMES } from "../graph/schema.js";
|
|
9
9
|
import { acceptanceGate } from "./acceptance.js";
|
|
10
|
-
import { classifiedFileDurations, compareToBaseline, effectiveCeilingMs, waitForCalmWindow, calmWindowReady } from "./baseline.js";
|
|
10
|
+
import { classifiedFileDurations, compareToBaseline, effectiveCeilingMs, waitForCalmWindow, calmWindowReady, SIGNAL_EXIT_RE } from "./baseline.js";
|
|
11
11
|
import { evidenceGate } from "./evidence.js";
|
|
12
12
|
import { captureLlmOutput, SeatLaunchError } from "./llm.js";
|
|
13
13
|
import { withLaunchGuard } from "../drivers/orca.js";
|
|
@@ -364,30 +364,36 @@ export async function reobserveTestFiles(worktree, testCmd, baseline, files, art
|
|
|
364
364
|
return r.meta?.recovery === undefined ? r
|
|
365
365
|
: { ...r, pass: false, meta: { ...r.meta, classification: "infra", infra: true, retryable: false, recoveryRefused: true } };
|
|
366
366
|
}
|
|
367
|
-
const SIGNAL_EXIT_RE = /\b(?:SIGTERM|SIGKILL|signal\s+(?:9|15)|exit(?:s|ed|\s+code)?\s+(?:137|143))\b/i;
|
|
368
367
|
const FAILURE_IDENTITY_RE = /\b(?:AssertionError|FAIL\s+\S|Tests?\s+\d+\s+failed|expected\s+.+\s+to\s+)\b/i;
|
|
369
368
|
/** D1: apply the daemon's signal-only rider before either battery cache read or write. Its onGate
|
|
370
369
|
* classification happens after persistence, too late to keep a scripted runner's non-verdict out.
|
|
371
370
|
* Keep named failures as work verdicts and preserve details for failure-policy fingerprinting.
|
|
372
371
|
* A baseline-forgiven "green" whose runner was signal-killed is the same non-verdict: a killed job
|
|
373
|
-
* completed no suite
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
372
|
+
* completed no suite. The termination receipt decides first: a signal-terminated job is killed whatever its
|
|
373
|
+
* details say, and a green stands only on a receipt showing a normal exit — a receiptless CACHED one (an
|
|
374
|
+
* older or planted entry) is unavailable proof, so a fresh job measures instead. A normal exit 1 can still
|
|
375
|
+
* be a kill (a wrapper reports its killed worker and exits 1), and a forgiven green's details never carry
|
|
376
|
+
* the runner's output, so this rider cannot see it: S19m4 — the shared forgiveness decision
|
|
377
|
+
* (baseline.ts freshFailures) reads kill text beside a forgiven pass itself, turns one into infra that is
|
|
378
|
+
* never cached, and marks the green it does publish `killTextRead`. A cached forgiven green without that
|
|
379
|
+
* marker was written by an engine that never read kill text, so it is refused here — a normal exit 1
|
|
380
|
+
* receipt included — and the job runs again. A fresh row is this invocation's own: every production
|
|
381
|
+
* runner attaches its receipt, and the manifest path's verdict already requires a clean exit
|
|
378
382
|
* (test-manifest.ts verifyManifestReport). A no-command skip ran nothing and claims none. */
|
|
379
383
|
function classifySignalOnlyTest(g, cached = false) {
|
|
380
384
|
const termination = g.evidenceReceipt?.termination.kind;
|
|
381
385
|
const exitCode = g.evidenceReceipt?.termination.exitCode;
|
|
382
386
|
const killed = termination === "signal" || exitCode === 137 || exitCode === 143 || (!g.pass && SIGNAL_EXIT_RE.test(g.details));
|
|
383
|
-
const
|
|
387
|
+
const killTextUnread = cached && Array.isArray(g.meta?.forgivenFingerprints) && g.meta.killTextRead !== true;
|
|
388
|
+
const unproven = g.pass && g.meta?.skipped !== true && (killTextUnread || termination !== "exit" && (cached || termination !== undefined));
|
|
384
389
|
if (g.gate !== "test" || g.meta?.infra === true || !(killed || unproven))
|
|
385
390
|
return;
|
|
386
391
|
const named = Array.isArray(g.meta?.failingTests) && g.meta.failingTests.length > 0;
|
|
387
392
|
if (!g.pass && (named || FAILURE_IDENTITY_RE.test(g.details)))
|
|
388
393
|
return;
|
|
389
394
|
g.pass = false;
|
|
390
|
-
g.meta = { ...g.meta, classification: "infra", infra: true, retryable: false,
|
|
395
|
+
g.meta = { ...g.meta, classification: "infra", infra: true, retryable: false,
|
|
396
|
+
kind: killed || (termination && termination !== "exit") ? "signal-exit" : termination ? "kill-text-unread" : "no-exit-receipt" };
|
|
391
397
|
}
|
|
392
398
|
/** OBS-1151: a criterion's comparable subject — its canonical text, the task's declared bounds and the
|
|
393
399
|
* operator context. The cited files' blobs are compared separately, over the union of both citations. */
|
|
@@ -1193,7 +1199,7 @@ export async function runGates(task, ctx) {
|
|
|
1193
1199
|
// details. The v1.1 badReviewers precedent's TIMING can't transfer: its failover lands on the NEXT
|
|
1194
1200
|
// worker attempt — exactly what this fix forbids; only its meta-carries-channel pattern is mirrored.
|
|
1195
1201
|
// Straight-line single `if` — NO loop/counter/knob: exactly-once by construction (a knob is a
|
|
1196
|
-
// fail-closed weakening vector); a second garbage verdict fails the gate closed
|
|
1202
|
+
// fail-closed weakening vector); a second garbage verdict fails the gate closed as terminal infra (S23-F11).
|
|
1197
1203
|
// T6: exclusion mirrors consult reroute semantics — the flaked channel's whole adapter is banned, not
|
|
1198
1204
|
// just its exact channel key, so an outage window cannot re-select the vendor being routed around.
|
|
1199
1205
|
// If no other adapter is live, the exclusion degrades to a channel-level reroute within the same
|
|
@@ -1226,8 +1232,11 @@ export async function runGates(task, ctx) {
|
|
|
1226
1232
|
}
|
|
1227
1233
|
// OBS-1168(b): the re-routed seat could not launch either — no seat produced a verdict, so this is
|
|
1228
1234
|
// an infra park over whatever the deterministic gates proved, never a charge against the worker.
|
|
1229
|
-
|
|
1235
|
+
// S23-F11: the same holds for a retry whose verdict is still unparseable: two seats delivered no readable
|
|
1236
|
+
// verdict, which judges nothing about the worker's diff, so the row is terminal infra, never a charged red.
|
|
1237
|
+
if (a.meta?.cause === "seat-launch-failed" || (a.meta?.judgeRetry !== undefined && a.meta.unparseable === true)) {
|
|
1230
1238
|
a = { ...a, meta: { ...a.meta, classification: "infra", infra: true, retryable: false } };
|
|
1239
|
+
}
|
|
1231
1240
|
else if (!cancelled && Array.isArray(a.meta?.judgment))
|
|
1232
1241
|
a = await adjudicate(a);
|
|
1233
1242
|
// No dispatch, no key: a deterministic-oracle round writes no `invocations` field rather than an
|
|
@@ -719,8 +719,8 @@ export async function evaluateManifestedTest(cmd, cwd, opts) {
|
|
|
719
719
|
const evidenceRoot = opts.evidence?.artifactDir ?? dir;
|
|
720
720
|
const stdoutPath = join(evidenceRoot, evidenceReceipt.stdout.path);
|
|
721
721
|
const stderrPath = join(evidenceRoot, evidenceReceipt.stderr.path);
|
|
722
|
-
const stdoutTail = Buffer.from(redactGateOutput(invoked.stdout, env)).subarray(-16 * 1024);
|
|
723
|
-
const stderrTail = Buffer.from(redactGateOutput(invoked.stderr, env)).subarray(-16 * 1024);
|
|
722
|
+
const stdoutTail = Buffer.from(redactGateOutput(invoked.stdout, env, spawnedCommand)).subarray(-16 * 1024);
|
|
723
|
+
const stderrTail = Buffer.from(redactGateOutput(invoked.stderr, env, spawnedCommand)).subarray(-16 * 1024);
|
|
724
724
|
const report = invoked.report?.nonce === nonce ? invoked.report : undefined;
|
|
725
725
|
const neverStarted = report ? files.filter(file => !(file in report.started)).length : "unknown";
|
|
726
726
|
const errors = report?.certificate?.errors;
|
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
|
/**
|
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);
|
package/dist/run/journal.d.ts
CHANGED
|
@@ -455,9 +455,9 @@ export declare const TelemetryRowSchema: z.ZodObject<{
|
|
|
455
455
|
signalBasis: z.ZodOptional<z.ZodEnum<{
|
|
456
456
|
"judge-only": "judge-only";
|
|
457
457
|
skipped: "skipped";
|
|
458
|
+
legacy: "legacy";
|
|
458
459
|
proved: "proved";
|
|
459
460
|
"review-agree": "review-agree";
|
|
460
|
-
legacy: "legacy";
|
|
461
461
|
vacuous: "vacuous";
|
|
462
462
|
}>>;
|
|
463
463
|
kind: z.ZodOptional<z.ZodLiteral<"judge">>;
|
|
@@ -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.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { shq } from "../adapters/types.js";
|
|
4
|
-
import { beginGateEvidence, ceilingKillResult, classifyFreshRunnerOutput, classifyRunnerOutput, fileCountDeficit, waitForCalmWindow, effectiveCeilingMs, classifiedFileDurations, fingerprint, freshFailures, } from "../gates/baseline.js";
|
|
4
|
+
import { baselineEntryRefusal, beginGateEvidence, ceilingKillResult, classifyFreshRunnerOutput, classifyRunnerOutput, fileCountDeficit, waitForCalmWindow, effectiveCeilingMs, classifiedFileDurations, fingerprint, freshFailures, } from "../gates/baseline.js";
|
|
5
5
|
import { evaluateManifestedTest, isVitestTestCommand } from "../gates/test-manifest.js";
|
|
6
6
|
import { computeVerificationIdentity, formatReusedDetails, getVerdictStore, getWorktreeTree, resolveStateDir, isInfraResult, } from "../gates/cache.js";
|
|
7
7
|
import { tickmarkrDir } from "../graph/graph.js";
|
|
@@ -216,7 +216,9 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
216
216
|
// Persistent reuse requires the run's durable journal. Without it there is no
|
|
217
217
|
// recorded cycle provenance to reconcile with the caller's capacity checks.
|
|
218
218
|
const hit = journalFound ? store.get(id) : undefined;
|
|
219
|
-
|
|
219
|
+
// S19m4: a forgiven test green written before the tip read kill text never proves the suite completed.
|
|
220
|
+
const killTextUnread = gate === "test" && hit?.meta?.forgiven === true && hit.meta.killTextRead !== true;
|
|
221
|
+
if (hit && id && hit.pass && !isInfraResult(hit) && !killTextUnread) {
|
|
220
222
|
results.push({
|
|
221
223
|
gate,
|
|
222
224
|
cmd,
|
|
@@ -339,7 +341,10 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
339
341
|
});
|
|
340
342
|
continue;
|
|
341
343
|
}
|
|
342
|
-
const
|
|
344
|
+
const verdict = freshFailures(entry, stripped, gate === "test");
|
|
345
|
+
const { failing, unreadable } = verdict;
|
|
346
|
+
// S22: a missing or unreadable entry forgives nothing; the red names it rather than a bare exit code.
|
|
347
|
+
const refusal = baselineEntryRefusal(gate, verdict);
|
|
343
348
|
const deficit = fileCountDeficit(entry, stripped);
|
|
344
349
|
const cause = deficit ? "infra" : classifyFreshRunnerOutput(entry, stripped, r.code);
|
|
345
350
|
const greenTeardown = classifyRunnerOutput(stripped, r.code) === "green-teardown";
|
|
@@ -354,7 +359,7 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
354
359
|
// fingerprintable yet OBS-42-exempt from rejecting, so `failing` comes back empty, `unreadable`
|
|
355
360
|
// false and the cause reads "regression" — every other guard satisfied, and a real red forgiven
|
|
356
361
|
// against a capture that never finished asking the question.
|
|
357
|
-
const baselineRed =
|
|
362
|
+
const baselineRed = verdict.baselineRed;
|
|
358
363
|
// Battery parity on the infra rule too (T9): infrastructure-only output means the runner never
|
|
359
364
|
// completed a suite — nothing was verified, so nothing is forgivable, however familiar its
|
|
360
365
|
// fingerprints. Stricter-than-battery edge kept: unreadable output never forgives.
|
|
@@ -363,8 +368,12 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
363
368
|
// often the previous session's — so a capture taken under a different resolved capacity forgives
|
|
364
369
|
// nothing at the tip either. An absent capacity is a pre-T7 baseline and keeps today's verdict.
|
|
365
370
|
const comparable = sameCapacity(entry?.capacity, r.capacity);
|
|
366
|
-
const
|
|
371
|
+
const forgivable = r.code !== 0 && baselineRed && failing.length === 0 && !unreadable && cause !== "infra"
|
|
367
372
|
&& comparable;
|
|
373
|
+
// S19m4 (battery parity): kill text beside forgivable failures means the suite never completed — red, infra.
|
|
374
|
+
const killLine = forgivable && !greenTeardown && !deficit ? verdict.killLine : undefined;
|
|
375
|
+
const forgiven = forgivable && killLine === undefined;
|
|
376
|
+
const rowCause = killLine !== undefined ? "infra" : cause;
|
|
368
377
|
const pass = !deficit && (r.code === 0 || greenTeardown || forgiven);
|
|
369
378
|
if (!pass)
|
|
370
379
|
writeTipLog(artifact, raw);
|
|
@@ -375,16 +384,20 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
375
384
|
evidenceReceipt, evidenceReceipts,
|
|
376
385
|
exitCode: r.code,
|
|
377
386
|
fingerprints: r.code !== 0 && !greenTeardown ? fingerprint(stripped) : [],
|
|
378
|
-
details: `${
|
|
387
|
+
details: `${rowCause === "infra" ? "infra; " : ""}${rerun ? `${rerun}: ` : ""}` + (deficit?.replace(/^infra; /, "") ?? (r.code === 0 ? "exit 0"
|
|
379
388
|
: greenTeardown ? `exit ${r.code} after a green suite summary; only the runner's teardown fingerprint followed it`
|
|
380
389
|
: forgiven ? `exit ${r.code} but only baseline-recorded failures (forgiven vs baseline)`
|
|
381
|
-
:
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
390
|
+
: killLine !== undefined ? `exit ${r.code}; every failure is baseline-recorded, but the runner printed kill text — `
|
|
391
|
+
+ `it never completed a suite, so nothing is forgiven: ${killLine}`
|
|
392
|
+
: !comparable && baselineRed && failing.length === 0
|
|
393
|
+
? `exit ${r.code}; every failure is baseline-recorded, but that capture ran under `
|
|
394
|
+
+ `${describeCapacity(entry?.capacity)} and this verification ran under ${describeCapacity(r.capacity)} `
|
|
395
|
+
+ `— forgiveness across a changed capacity is not evidence`
|
|
396
|
+
: `exit ${r.code}`))
|
|
397
|
+
// S22: every red here names a missing or unreadable entry beside its own cause, deficit included
|
|
398
|
+
+ (!pass && refusal ? `; ${refusal}` : ""),
|
|
386
399
|
...(forgiven && !deficit ? { forgiven: true, ...(baseline?.provenance ? { baselineProvenance: baseline.provenance } : {}) } : {}),
|
|
387
|
-
...(
|
|
400
|
+
...(rowCause ? { cause: rowCause } : {}),
|
|
388
401
|
...(pass ? {} : { artifact }),
|
|
389
402
|
};
|
|
390
403
|
results.push(tipResult);
|
|
@@ -403,6 +416,7 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline, ev
|
|
|
403
416
|
if (id && id.tree === completedTree && !isInfraResult(result)) {
|
|
404
417
|
store.set(id, { ...result, meta: {
|
|
405
418
|
fingerprints: result.fingerprints, forgiven: result.forgiven, baselineProvenance: result.baselineProvenance,
|
|
419
|
+
...(result.forgiven && result.gate === "test" ? { killTextRead: true } : {}),
|
|
406
420
|
reportPath: result.reportPath, spawnedCommand: result.spawnedCommand,
|
|
407
421
|
artifact: result.artifact,
|
|
408
422
|
evidenceReceipt: result.evidenceReceipt, evidenceReceipts: result.evidenceReceipts,
|