test(runner): finish phase 6 conformance

Exercise all atomic-liveness failpoints, make native cancellation retry-safe, and align the allowlist and verification evidence with the final database-backed matrix.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-09 03:49:43 +00:00
1 parent 0ca23dd535
commit f285bbbab8
8 files changed
+60 -12

No files matched your search

@@ -703,14 +703,18 @@ from the provisional list above. They are added without expanding authority:
```text
packages/paperclip-runner/src/native-session-runtime.ts
packages/paperclip-runner/src/native-session-runtime.test.ts
packages/paperclip-runner/spec/fixtures/status-authority-phase5.json
server/src/services/native-runtime/native-session-executor.ts
server/src/services/native-runtime/native-session-executor.test.ts
server/src/services/native-runtime/evidence-classifier.test.ts
server/src/services/native-runtime/paperclip-control-plane-port.test.ts
server/src/services/native-runtime/status-arbiter.test.ts
packages/paperclip-runner/docs/design/phase-6-thin-paperclip-adapter.md
```
The package runtime files own persisted provider-session recovery. The server
The package runtime files own persisted provider-session recovery. The corpus
edit moves two existing coverage labels between fixtures without changing any
fixture input or expected outcome, eliminating an unjoinable fixture. The server
executor files own only the run-scoped cancellation handle and coordinator
lease around that package runtime. The colocated port and arbiter tests exercise
the same approved database and pure-policy seams exposed through the named
@@ -57,7 +57,7 @@ pnpm --filter @paperclipai/server exec vitest run \
```
This runs the unchanged package conformance suite against the real Paperclip
port. Twenty-five targeted tests check company binding, duplicate/gap replay,
port. Twenty-eight targeted tests check company binding, duplicate/gap replay,
checkpoint recovery, immutable result ingestion, durable-evidence authority,
workspace-gated finalization, atomic liveness rollback, status-version CAS,
migration repair, legacy byte equivalence, and audit output. All database tests
@@ -39,7 +39,7 @@ existing accepted result event, and appended only the missing terminal fact.
# Real Paperclip adapter and public-session task
The complete acceptance gate below passed 25 tests in ten files against
The complete acceptance gate below passed 28 tests in ten files against
embedded PostgreSQL with zero skipped tests. Database-backed tests start their
own database instead of skipping when a developer `DATABASE_URL` is absent.
The same package conformance suite passed unchanged against
@@ -68,10 +68,11 @@ The focused corpus also proved:
- a failed workspace barrier preserves the accepted semantic result, leaves the
issue status unchanged, and records a leased `retryable_failure` plus recovery
owner/action;
- four injected failures prove interactions, wakes, blocker bindings, status,
- six injected failures prove governance binding, interactions, wakes, blocker
bindings, recovery, status,
decisions, and effects roll back together before retry ownership is recorded.
The acceptance-matrix entry points also passed as one 10-file, 25-test gate:
The acceptance-matrix entry points also passed as one 10-file, 28-test gate:
```sh
pnpm --filter @paperclipai/server exec vitest run \
@@ -47,8 +47,8 @@ existing server authority and keep legacy execution as the default.
- [Phase 6 reference](../../docs/phase-06-thin-paperclip-adapter.md)
- [Runnable tutorial](../../docs/tutorials/phase-06-thin-paperclip-adapter.md)
- Package conformance and recovery: four files, six tests passed.
- Phase 6 acceptance-matrix entry points: ten files, twenty-five tests passed
with zero skips, including the database-backed selected-task canary, four
- Phase 6 acceptance-matrix entry points: ten files, twenty-eight tests passed
with zero skips, including the database-backed selected-task canary, six
atomic-liveness failpoints, migration, sequencing, bounded recovery, and
legacy compatibility.
- Focused database rehearsal: five files, eleven tests passed with zero skips;
@@ -1,3 +1,4 @@
// Acceptance-matrix entry point for evidence, finalization, CAS, and atomic
// status/liveness effects exercised by the real database-backed port suite.
import "../services/native-runtime/paperclip-control-plane-port.test.js";
import "../services/native-runtime/evidence-classifier.test.js";
@@ -96,4 +96,23 @@ describe("native session cancellation", () => {
await running;
await expect(cancelNativeSession(execution.binding.runId, "late cancel")).resolves.toBe(false);
});
it("allows cancellation to be retried when the session dispatch fails", async () => {
state.cancel.mockRejectedValueOnce(new Error("transport unavailable"));
const running = executePaperclipNativeSession({
db: leaseDb(),
execution,
runnerInstanceId: "runner",
});
await vi.waitFor(() => expect(state.release).toBeTypeOf("function"));
await expect(cancelNativeSession(execution.binding.runId, "budget hard stop"))
.rejects.toThrow("transport unavailable");
await expect(cancelNativeSession(execution.binding.runId, "retry budget stop"))
.resolves.toBe(true);
expect(state.cancel).toHaveBeenNthCalledWith(2, { reason: "retry budget stop" });
state.release?.();
await running;
});
});
@@ -23,8 +23,13 @@ export async function cancelNativeSession(runId: string, reason: string): Promis
if (!active) return false;
if (active.cancelRequested) return true;
active.cancelRequested = true;
if (active.session.cancel) await active.session.cancel({ reason });
else if (active.session.interrupt) await active.session.interrupt({ reason });
try {
if (active.session.cancel) await active.session.cancel({ reason });
else if (active.session.interrupt) await active.session.interrupt({ reason });
} catch (error) {
active.cancelRequested = false;
throw error;
}
return true;
}
@@ -43,6 +43,7 @@ describe("PaperclipControlPlanePort conformance", () => {
const taskIssueId = "00000000-0000-4000-8000-000000000008";
const taskContractId = "00000000-0000-4000-8000-000000000009";
const taskRunId = "00000000-0000-4000-8000-000000000010";
const taskSessionId = "00000000-0000-4000-8000-000000000018";
const taskWorkProductId = "00000000-0000-4000-8000-000000000017";
const workspaceFailureIssueId = "00000000-0000-4000-8000-000000000011";
const workspaceFailureContractId = "00000000-0000-4000-8000-000000000012";
@@ -135,6 +136,7 @@ describe("PaperclipControlPlanePort conformance", () => {
runtimeMode: "native",
runtimeModeResolverVersion: "phase6-v1",
runtimeModeReason: "eligible_opt_in",
nativeSessionId: taskSessionId,
completionContractId: taskContractId,
completionContractSha256: "phase6-task-contract",
contextSnapshot: { issueId: taskIssueId },
@@ -297,7 +299,7 @@ describe("PaperclipControlPlanePort conformance", () => {
it("completes one selected Paperclip task through the public package session contract", async () => {
const identity = CONTROL_PLANE_CONFORMANCE_OPEN.identity;
const sessionId = "session-phase6-paperclip-task";
const sessionId = taskSessionId;
const evidenceRef = `work_product:${taskWorkProductId}`;
const taskResult = structuredClone(CONTROL_PLANE_CONFORMANCE_RESULT);
taskResult.completionClaim.criteria[0]!.evidenceRefs = [evidenceRef];
@@ -558,12 +560,14 @@ describe("PaperclipControlPlanePort conformance", () => {
await expect(db.select().from(nativeRunResults).where(eq(nativeRunResults.runId, workspaceFailureRunId))).resolves.toHaveLength(1);
});
it("LIVE-01..04 rolls back status, decision, and liveness rows at every materialization failpoint", async () => {
it("LIVE-01..06 rolls back status, decision, and liveness rows at every materialization failpoint", async () => {
const identity = CONTROL_PLANE_CONFORMANCE_OPEN.identity;
const cases: Array<{
suffix: number;
failpoint: "interaction_materialization" | "continuation_materialization" | "blocker_materialization" | "status_projection";
failpoint: "governance_materialization" | "interaction_materialization" | "continuation_materialization" | "blocker_materialization" | "recovery_materialization" | "status_projection";
result: PrpStructuredRunResult;
terminalState?: "succeeded" | "failed" | "cancelled";
executionState?: Record<string, unknown>;
}> = [
{
suffix: 20,
@@ -593,6 +597,18 @@ describe("PaperclipControlPlanePort conformance", () => {
failpoint: "status_projection",
result: { ...structuredClone(CONTROL_PLANE_CONFORMANCE_RESULT), reportedWorkDisposition: "needs_review" },
},
{
suffix: 25,
failpoint: "recovery_materialization",
terminalState: "cancelled",
result: structuredClone(CONTROL_PLANE_CONFORMANCE_RESULT),
},
{
suffix: 26,
failpoint: "governance_materialization",
executionState: { status: "pending", stage: "approval" },
result: structuredClone(CONTROL_PLANE_CONFORMANCE_RESULT),
},
];
for (const entry of cases) {
const value = String(entry.suffix).padStart(12, "0");
@@ -606,6 +622,7 @@ describe("PaperclipControlPlanePort conformance", () => {
status: "in_progress",
assigneeAgentId: identity.agentId,
workMode: "standard",
executionState: entry.executionState,
});
await db.insert(completionContracts).values({
id: contractId,
@@ -634,6 +651,7 @@ describe("PaperclipControlPlanePort conformance", () => {
});
const terminal: PrpTerminalState = {
...CONTROL_PLANE_CONFORMANCE_TERMINAL,
runTerminalState: entry.terminalState ?? "succeeded",
reportedWorkDisposition: entry.result.reportedWorkDisposition,
};
const port = new PaperclipControlPlanePort(db, {