mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 10:26:26 +01:00
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:
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, {
|
||||
|
||||
Reference in new issue
Block a user