test(runner-e2e): bound remote Stop proof to process lifetime

Distinguish live snapshots from the automatic owned-process retirement seal. Preserve local four-phase observation and separately revalidate remote UI/API cancellation state without claiming later filesystem reads. Version the suite and calibrate stale/replayed/foreign/lost-descendant evidence.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-30 15:45:22 -05:00
1 parent 2467e24614
commit a31fc90b17
6 files changed
+147 -25

No files matched your search

+12 -3
View File
@@ -415,9 +415,18 @@ interrupted or normal terminal evidence fails. Only after this proof does the
fixture attempt a stale public answer, requiring HTTP409 and an unanswerable
browser card. It retains one cancelled run, issue `in_progress`, exact target
absence through continuous observation, and all observed owned descendants
retired. Remote proof is sealed before environment teardown; unproven cleanup
fails independently. Only fully attested bootstrap reads may precede the one
tested operation; alternate operations or attempts are rejected.
retired. Local files require four fresh observations through cleanup. Daytona
instead retains two live snapshots (baseline and pending) plus one automatic
owned-process-retirement seal, with a continuous zero-mutation watcher and the
same complete root/descendant journal. Suite version 2 retains this as
`paperclip.e2e.native-active-stop-remote-retirement.v1`; it explicitly records
`filesystemAfterRetirementObserved:false`. The per-turn observer seals itself
when the owned tree retires, before the sandbox is released. Reading that receipt
later is not a fresh post-UI or post-cleanup filesystem observation. Relabeled,
reused, missing or out-of-order samples fail. The stale-answer and rendered UI
checks remain separate, followed by fresh API cancellation/no-extra-run checks
both after UI and in cleanup. Unproven cleanup fails independently. Only fully
attested bootstrap reads may precede the one tested operation; alternate operations or attempts are rejected.
The existing `copilot-protection` denial remains distinct: rejecting a permission
before Stop does not exercise this pending-callback boundary. This new suite has
+6 -1
View File
@@ -1447,7 +1447,12 @@ not qualify this case. Native permission cards use runtime requests; ordinary
issue-interaction rows are not substituted for their authority. Pending and final
screenshots, `native-active-stop-pending.json`, `native-active-stop-settlement.json`
and cleanup evidence retain the boundaries. Daytona also requires the exact
owned lease and sealed remote observer proof before sandbox deletion.
owned lease and sealed remote observer proof before sandbox deletion. Suite
version 2 records remote filesystem coverage only through verified retirement
of the owned runner/provider tree, with no target or workspace mutations; it
does not call a later read of the seal a fresh observation. Local cases retain
four filesystem phases through cleanup. Remote UI/stale-answer checks and final
API checks are separate from this lifetime-bound filesystem proof.
This suite adds an active-work cancellation oracle; it does not reinterpret
older denial or cancellation results. Current live qualification is pending.
+1 -1
View File
@@ -1052,7 +1052,7 @@ export const runnerSuites: readonly RunnerSuiteFixture[] = [
description: "Stop while one exact Cursor or Copilot native permission remains unanswered; require cancelled provider settlement, caller-owned acknowledgement, stale-answer refusal and independent retirement/no effects.",
groups: ["native"], profiles: extendedHarnessProfiles.filter(profile => ["cursor", "copilot"].includes(profile.qualificationCandidate ?? "")),
environments: runnerEnvironments, tasks: nativeActiveStopTasks, expectedMatrixSize: 4,
definitionMetadata: { version: 1, qualification: "pending", scheduling: "explicit-only", evidence: "paperclip.e2e.native-active-stop-settlement.v1", pendingObservation: "retained-api-before-caller-uuid-stop", normalCompletionAccepted: false, permissionPolicy: "approve-reads", lifecycle: "per_turn", remoteEvidence: "owned-lease-sealed-observer", providerDeath: "not-covered" },
definitionMetadata: { version: 2, qualification: "pending", scheduling: "explicit-only", evidence: "paperclip.e2e.native-active-stop-settlement.v1", pendingObservation: "retained-api-before-caller-uuid-stop", normalCompletionAccepted: false, permissionPolicy: "approve-reads", lifecycle: "per_turn", remoteEvidence: "paperclip.e2e.native-active-stop-remote-retirement.v1", remoteObservationCoverage: "continuous-through-owned-process-retirement", filesystemAfterRemoteRetirementObserved: false, localObservationCoverage: "four-phases-through-cleanup", providerDeath: "not-covered" },
},
{
id: "copilot-protection", label: "Copilot native protection", manualOnly: true,
@@ -1,6 +1,7 @@
import { createHash } from "node:crypto";
import { canonicalJson } from "../../packages/shared/src/portability-hash.js";
import { hasAcpxNativeOrigin } from "./acpx-native-origin.js";
import { assertCopilotRemoteRetirement, copilotRemoteDeniedSample, type CopilotRemoteSnapshot } from "./copilot-protection-evidence.js";
import { readCopilotToolEvidence } from "./copilot-evidence.js";
import { readCursorToolEvidence } from "./cursor-native-evidence.js";
import { bootstrapReadExecutionId, withoutProvenBootstrapReads, type BootstrapReadProof } from "./native-bootstrap-read-proof.js";
@@ -158,18 +159,59 @@ export function readActiveStopSettlement(input: { events: readonly unknown[]; ru
taskStillInProgress: true, normalCompletionAccepted: false, replayAllowed: false };
}
/** Read-only watcher/process receipts are mandatory through teardown, even if
* canonical cancellation already passed. A missing phase is not no-effect proof. */
export interface ActiveStopRemoteObservation {
phase: "before-request" | "pending" | "owned-process-retirement";
source: "live-snapshot" | "retirement-seal";
snapshot: CopilotRemoteSnapshot;
}
/** A per-turn Daytona observer seals itself when the owned tree retires. The
* host may retrieve that receipt later; retrieval is not a fresh observation. */
export function readActiveStopRemoteRetirement(input: {
scope: Pick<ActiveStopScope, "companyId" | "runId" | "target">; observations: readonly ActiveStopRemoteObservation[];
}) {
const observations = input.observations;
fail(observations.length === 3
&& observations.map(o => `${o.phase}:${o.source}`).join(",") === "before-request:live-snapshot,pending:live-snapshot,owned-process-retirement:retirement-seal",
"remote seal cannot stand in for a fresh causal sample");
const [baseline, pending, terminal] = observations.map(o => o.snapshot) as [CopilotRemoteSnapshot, CopilotRemoteSnapshot, CopilotRemoteSnapshot];
fail(baseline.binding.companyId === input.scope.companyId && baseline.binding.runId === input.scope.runId, "remote observation belongs to another run");
for (const snapshot of [baseline, pending, terminal]) {
fail(!copilotRemoteDeniedSample(snapshot, baseline, input.scope.target, "pending").exists, "remote target changed");
}
fail(BigInt(baseline.observedMonotonicNs) < BigInt(pending.observedMonotonicNs)
&& BigInt(pending.observedMonotonicNs) < BigInt(terminal.observedMonotonicNs), "remote observation reused or out of order");
fail(!baseline.setup.published && pending.setup.published && pending.setup.sha256 === terminal.setup.sha256
&& baseline.processes.captured && pending.processes.captured
&& baseline.processes.live.includes(baseline.processes.root?.pid ?? -1)
&& pending.processes.live.includes(pending.processes.root?.pid ?? -1), "remote pending lifetime unproven");
assertCopilotRemoteRetirement(terminal, baseline);
assertCopilotRemoteRetirement(terminal, pending);
for (const snapshot of [baseline, pending]) {
fail(snapshot.processes.journal.every(p => terminal.processes.journal.some(q =>
p.pid === q.pid && p.startTicks === q.startTicks && p.bootId === q.bootId)), "remote descendant journal lost");
}
return { schema: "paperclip.e2e.native-active-stop-remote-retirement.v1" as const,
coverage: "continuous-through-owned-process-retirement" as const, filesystemAfterRetirementObserved: false as const,
binding: terminal.binding, target: input.scope.target,
observations: observations.map(o => ({ phase: o.phase, source: o.source,
observedMonotonicNs: o.snapshot.observedMonotonicNs, snapshotSha256: hash(o.snapshot) })) };
}
/** Local files remain accessible through teardown. Remote per-turn files do
* not: require the complete lifetime seal, never relabel it as a later sample. */
export function assertActiveStopRetirement(input: {
completed: boolean; identityChanged: boolean;
processes: { captured: boolean; live: readonly number[] };
watcher: { complete: boolean; targetMutationCount: number };
samples: ReadonlyArray<{ phase: string; absent: boolean }>;
}) {
} & ({ environment: "local"; samples: ReadonlyArray<{ phase: string; absent: boolean }> }
| { environment: "daytona"; remote: Parameters<typeof readActiveStopRemoteRetirement>[0] })) {
fail(input.completed && !input.identityChanged && input.processes.captured
&& input.processes.live.length === 0 && input.watcher.complete && input.watcher.targetMutationCount === 0,
"retirement or continuous no-effect proof is incomplete");
fail(input.samples.length === 4 && input.samples.every(s => s.absent === true)
if (input.environment === "daytona") return readActiveStopRemoteRetirement(input.remote);
fail(input.environment === "local" && input.samples.length === 4 && input.samples.every(s => s.absent === true)
&& input.samples.map(s => s.phase).join(",") === "before-request,pending,after-stop,after-cleanup",
"causal target observation missing or changed");
return { coverage: "local-through-cleanup" as const };
}
+25 -12
View File
@@ -7,7 +7,7 @@ import { createTaskThroughUi } from "./user-actions.js";
import { createDeniedTargetFixture, exists, observeRunProcesses } from "./copilot-local-fixtures.js";
import { assertCopilotRemoteRetirement, copilotRemoteDeniedSample, prepareCopilotRemoteAction, type CopilotRemoteBootstrap, type CopilotRemoteFixture, type CopilotRemoteSnapshot } from "./copilot-protection-evidence.js";
import { cursorDeniedCommand } from "./cursor-native-evidence.js";
import { assertActiveStopRetirement, readActiveStopCaller, observeActiveStopPending, readActiveStopSettlement, type ActiveStopCaller, type ActiveStopPending, type ActiveStopScope } from "./native-active-stop-evidence.js";
import { assertActiveStopRetirement, readActiveStopCaller, observeActiveStopPending, readActiveStopSettlement, type ActiveStopRemoteObservation, type ActiveStopCaller, type ActiveStopPending, type ActiveStopScope } from "./native-active-stop-evidence.js";
import type { BootstrapReadProof } from "./native-bootstrap-read-proof.js";
import type { LiveFixtureValues } from "./live-fixtures.js";
import type { MatrixExecution } from "./types.js";
@@ -60,6 +60,8 @@ export async function runNativeActiveStopFlow(input: {
const observer = remote ? undefined : observeRunProcesses();
let processes: { captured: boolean; live: number[] } = { captured: false, live: [] };
let processIdentity: string | undefined, processError = false;
const remoteObservations: ActiveStopRemoteObservation[] = [];
let retirement: ReturnType<typeof assertActiveStopRetirement> | undefined;
const samples: Array<{ phase: string; absent: boolean }> = [];
let fixture: CopilotRemoteFixture | undefined, baseline: CopilotRemoteSnapshot | undefined, sealed: CopilotRemoteSnapshot | undefined;
let completed: Awaited<ReturnType<typeof stopAtPendingPermission>> | undefined;
@@ -80,7 +82,9 @@ export async function runNativeActiveStopFlow(input: {
let value: boolean;
if (remote) {
if (!fixture || !baseline) throw new Error("Missing remote active Stop observer");
const snap = sealed ?? await fixture.snapshot(phase);
if (phase !== "pending") throw new Error("Remote filesystem phase requires a fresh live snapshot");
const snap = await fixture.snapshot(phase);
remoteObservations.push({ phase, source: "live-snapshot", snapshot: snap });
value = !copilotRemoteDeniedSample(snap, baseline, target, "pending").exists;
await input.evidence(`active-stop-${phase}-remote.json`, snap);
} else value = !await exists(local!.targetPath);
@@ -98,17 +102,18 @@ export async function runNativeActiveStopFlow(input: {
} else if (processes.captured && processes.live.length) {
await pollUntil({ label: "active Stop provider retirement", deadlineAt: Date.now() + 5000, intervalMs: 100, load: async () => { observeProcesses(); return processes; }, accept: p => p.live.length === 0 });
}
await sample("after-cleanup");
if (!remote) await sample("after-cleanup");
const watch = remote ? sealed!.watcher : local!.watcher.finish();
assertActiveStopRetirement({ completed: Boolean(completed), identityChanged: processError, processes, watcher: watch, samples });
retirement = assertActiveStopRetirement({ completed: Boolean(completed), identityChanged: processError, processes, watcher: watch,
...(remote ? { environment: "daytona" as const, remote: { scope: scope(), observations: remoteObservations } } : { environment: "local" as const, samples }) });
if (!completed) throw new Error("Missing active Stop settlement");
readActiveStopSettlement({ ...await load(), ...completed, bootstrap: bootstrap() });
receipt.push({ id: "active-stop-retired-no-effects", passed: true, detail: "Exact run root/descendants retired; unanswered target remained absent through continuous observation" });
receipt.push({ id: "active-stop-retired-no-effects", passed: true, detail: remote ? "Exact run root/descendants retired with continuous no-effects proof through retirement; later UI/API checks do not claim fresh filesystem observation" : "Exact run root/descendants retired; unanswered target remained absent through cleanup" });
return receipt;
} finally {
if (timer) clearInterval(timer);
try {
await input.evidence("native-active-stop-cleanup.json", { processes, processError, samples, sealed, watcher: local?.watcher.finish(), checks: receipt });
await input.evidence("native-active-stop-cleanup.json", { processes, processError, samples, remoteObservations, retirement, sealed, watcher: local?.watcher.finish(), checks: receipt });
} finally {
if (remote) await fixture?.close();
}
@@ -133,7 +138,8 @@ export async function runNativeActiveStopFlow(input: {
const bound = await input.remoteBootstrap!.bindAndRelease({ issueId: issue.id, runId: runs[0]!.id, targets: [target], actionPrompt: async value => {
fixture = value; if (provider === "cursor") command = cursorDeniedCommand(join(value.remoteCwd, target));
const prepared = await prepareCopilotRemoteAction({ fixture: value, companyId: fixtures.company.id, environmentId: fixtures.environment.id, runId: runs[0]!.id, target, prompt: prompt() });
baseline = prepared.baseline; samples.push({ phase: "before-request", absent: true });
baseline = prepared.baseline;
remoteObservations.push({ phase: "before-request", source: "live-snapshot", snapshot: baseline });
await input.evidence("active-stop-before-request-remote.json", baseline); return prepared.prompt;
} });
if (bound !== fixture) throw new Error("Remote active Stop publication identity changed");
@@ -154,8 +160,13 @@ export async function runNativeActiveStopFlow(input: {
const response = await api.request.post(`/api/heartbeat-runs/${runs[0]!.id}/runtime-requests/${encodeURIComponent(completed.pending.requestId)}/resolve`, { data: { turnId: completed.pending.turnId, requestKind: "permission_approval", resolution: { action: "accept" } } });
check("stale-answer-refused", response.status() === 409, "Stopped permission rejects a later answer rather than replaying work");
await load(); readActiveStopSettlement({ ...await load(), ...completed, bootstrap: bootstrap() });
if (remote) { sealed = await fixture!.finish(); assertCopilotRemoteRetirement(sealed, baseline!); processes = sealed.processes; }
await sample("after-stop");
if (remote) {
// finish drains the observer's automatic retirement seal. It does not
// extend the remote watch through subsequent host UI/cleanup assertions.
sealed = await fixture!.finish(); assertCopilotRemoteRetirement(sealed, baseline!); processes = sealed.processes;
remoteObservations.push({ phase: "owned-process-retirement", source: "retirement-seal", snapshot: sealed });
await input.evidence("active-stop-owned-process-retirement-remote.json", sealed);
} else await sample("after-stop");
await page.reload();
const finalUiTimeout = () => {
const remaining = input.deadlineAt - Date.now();
@@ -174,12 +185,14 @@ export async function runNativeActiveStopFlow(input: {
.toHaveCount(0, { timeout: finalUiTimeout() });
await expect(card.getByRole("button", { name: "Deny", exact: true }))
.toHaveCount(0, { timeout: finalUiTimeout() });
await load(); readActiveStopSettlement({ ...await load(), ...completed, bootstrap: bootstrap() });
check("one-unfinished-cancelled-run", runs.length === 1 && issue.status === "in_progress" && runs[0]!.status === "cancelled", "No automatic follow-up run or false task completion");
await input.capture("final-state", "Stopped native permission is no longer answerable", "final-state.png");
await input.evidence("api-state.json", { issue, run: runs[0], runs, checks, samples, runEvents: events, runEventsByRun: [{ runId: runs[0]!.id, events }], activeStop: completed });
await input.evidence("api-state.json", { issue, run: runs[0], runs, checks, samples, remoteObservations, runEvents: events, runEventsByRun: [{ runId: runs[0]!.id, events }], activeStop: completed });
return { issue, runs, checks };
} finally {
// Keep watcher/process observation live through the registered cleanup assertion.
await input.evidence("native-active-stop-checks.json", { issue, runs, checks, samples, completed });
// Local observation continues through cleanup. Remote cleanup revalidates
// the lifetime seal and fresh API state without claiming later file reads.
await input.evidence("native-active-stop-checks.json", { issue, runs, checks, samples, remoteObservations, completed });
}
}
+56 -3
View File
@@ -1,6 +1,6 @@
import { createHash } from "node:crypto";
import { describe, expect, it, vi } from "vitest";
import { assertActiveStopRetirement, readActiveStopCaller, observeActiveStopPending, readActiveStopSettlement, type ActiveStopProvider } from "./native-active-stop-evidence.js";
import { assertActiveStopRetirement, readActiveStopRemoteRetirement, type ActiveStopRemoteObservation, readActiveStopCaller, observeActiveStopPending, readActiveStopSettlement, type ActiveStopProvider } from "./native-active-stop-evidence.js";
import { stopAtPendingPermission } from "./native-active-stop-flow.js";
import { runnerMatrix, runnerSuites, suiteDefinitionHash } from "./catalog.js";
import { selectRunnerExecutions, parseRunnerSelectors } from "./selectors.js";
@@ -166,7 +166,7 @@ describe("explicit active Stop discovery", () => {
expect(cells).toHaveLength(4); expect(suite.manualOnly).toBe(true);
expect(cells.map(e => `${e.profile.qualificationCandidate}/${e.environment.id}`).sort()).toEqual(["copilot/daytona", "copilot/local", "cursor/daytona", "cursor/local"]);
expect(cells.every(e => e.task.expectedRunCount === 1 && e.task.flow === "native_active_stop" && e.task.expectedTerminalState?.run === "cancelled")).toBe(true);
expect(suite.definitionMetadata).toMatchObject({ version: 1, normalCompletionAccepted: false, providerDeath: "not-covered" });
expect(suite.definitionMetadata).toMatchObject({ version: 2, normalCompletionAccepted: false, providerDeath: "not-covered" });
expect(suiteDefinitionHash(suite)).not.toBe(suiteDefinitionHash({ ...suite, definitionMetadata: { ...suite.definitionMetadata, normalCompletionAccepted: true } }));
expect(selectRunnerExecutions(parseRunnerSelectors(["--all"])).some(e => e.suite.id === suite.id)).toBe(false);
});
@@ -174,7 +174,7 @@ describe("explicit active Stop discovery", () => {
describe("active Stop cleanup remains mandatory", () => {
const proof = () => ({ completed: true, identityChanged: false, processes: { captured: true, live: [] as number[] },
const proof = () => ({ environment: "local" as const, completed: true, identityChanged: false, processes: { captured: true, live: [] as number[] },
watcher: { complete: true, targetMutationCount: 0 }, samples: ["before-request", "pending", "after-stop", "after-cleanup"].map(phase => ({ phase, absent: true })) });
it("requires all causal samples plus the continuous watcher and retired owned identities", () => expect(() => assertActiveStopRetirement(proof())).not.toThrow());
it.each([
@@ -188,3 +188,56 @@ describe("active Stop cleanup remains mandatory", () => {
["missing settlement", (p: ReturnType<typeof proof>) => { p.completed = false; }],
] as const)("rejects %s even after an apparent cancelled run", (_label, mutate) => { const p = proof(); mutate(p); expect(() => assertActiveStopRetirement(p)).toThrow(); });
});
function remoteRetirementProof() {
const root = { pid: 50, ppid: 1, startTicks: "200", bootId: "12345678-1234-1234-1234-123456789abc" };
const child = { ...root, pid: 51, ppid: 50, startTicks: "201" };
const snapshot = (sequence: number, live: number[]) => ({
binding: { companyId: "company", environmentId: "env", runId: "run", leaseId: "lease", sandboxId: "sandbox", image: `image@sha256:${"a".repeat(64)}`, remoteCwd: "/home/daytona/workspace" },
observedAtMs: sequence, receivedAtMs: sequence + 1, observedMonotonicNs: String(sequence), complete: true, workspace: {},
targets: { "target.txt": { absent: true, sha256: null, complete: true, mutationCount: 0, parent: { dev: "1", ino: "2" } } },
watcher: { complete: true, targetMutationCount: 0, workspaceMutationCount: 0 },
processes: { captured: true, root, journal: [root, child], live },
setup: { path: ".action.txt", sha256: sequence === 1 ? null : `sha256:${"b".repeat(64)}`, published: sequence !== 1 }, attached: null,
});
const observations: ActiveStopRemoteObservation[] = [
{ phase: "before-request", source: "live-snapshot", snapshot: snapshot(1, [50, 51]) },
{ phase: "pending", source: "live-snapshot", snapshot: snapshot(2, [50, 51]) },
{ phase: "owned-process-retirement", source: "retirement-seal", snapshot: snapshot(3, []) },
];
return { scope: { companyId: "company", runId: "run", target: "target.txt" }, observations };
}
describe("Daytona active Stop observation lifetime", () => {
it("binds one continuous owned-tree retirement seal without claiming a later filesystem observation", () => {
const remote = remoteRetirementProof(), terminal = remote.observations[2]!.snapshot;
expect(assertActiveStopRetirement({ environment: "daytona", completed: true, identityChanged: false,
processes: terminal.processes, watcher: terminal.watcher, remote })).toMatchObject({
schema: "paperclip.e2e.native-active-stop-remote-retirement.v1", coverage: "continuous-through-owned-process-retirement",
filesystemAfterRetirementObserved: false, observations: [
{ phase: "before-request", source: "live-snapshot" }, { phase: "pending", source: "live-snapshot" },
{ phase: "owned-process-retirement", source: "retirement-seal" },
],
});
});
it.each([
["seal relabeled fresh", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.source = "live-snapshot"; }],
["seal replayed as pending", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[1]!.snapshot = p.observations[2]!.snapshot; }],
["after-UI seal replay", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations.push({ ...p.observations[2]!, phase: "after-cleanup" as any }); }],
["missing pending", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations.splice(1, 1); }],
["all foreign run snapshots", (p: ReturnType<typeof remoteRetirementProof>) => { for (const o of p.observations) o.snapshot.binding.runId = "other"; }],
["all foreign company snapshots", (p: ReturnType<typeof remoteRetirementProof>) => { for (const o of p.observations) o.snapshot.binding.companyId = "other"; }],
["foreign lease", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.binding.leaseId = "other"; }],
["foreign run", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[1]!.snapshot.binding.runId = "other"; }],
["replacement root", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.processes.root = { ...p.observations[2]!.snapshot.processes.root!, startTicks: "300" }; }],
["live child", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.processes.live = [51]; }],
["lost child journal", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.processes.journal.pop(); }],
["unobserved pending root", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[1]!.snapshot.processes.live = []; }],
["transient target mutation", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.watcher.targetMutationCount = 1; }],
["workspace mutation", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.watcher.workspaceMutationCount = 1; }],
["late target present", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.targets["target.txt"]!.absent = false; }],
["incomplete watcher", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.watcher.complete = false; }],
["changed action", (p: ReturnType<typeof remoteRetirementProof>) => { p.observations[2]!.snapshot.setup.sha256 = `sha256:${"c".repeat(64)}`; }],
] as const)("rejects %s", (_label, mutate) => {
const proof = remoteRetirementProof(); mutate(proof); expect(() => readActiveStopRemoteRetirement(proof)).toThrow();
});
});