Add deterministic runner conformance core (#12354)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The runner needs repeatable tests at the provider and control-plane
boundaries
> - Network services and live providers cannot produce deterministic
conformance results
> - Small in-memory adapters can exercise the same public contracts
without production side effects
> - This pull request adds a deterministic harness driver, control-plane
adapter, and fixture runner
> - It exposes these helpers only from the testing entry point
> - The benefit is stable cross-boundary verification for later runtime
changes

## Linked Issues or Issue Description

**Subsystem affected**

packages/paperclip-runner testing and conformance support

**Problem or motivation**

The public runner contracts have unit tests, but later provider and
server slices need a deterministic way to exercise session, event,
replay, checkpoint, and result behavior together.

**Proposed solution**

Add in-memory implementations of the harness-driver and control-plane
ports. Add a canonical conformance fixture and runner. Export these
utilities only from `@paperclipai/paperclip-runner/testing`.

**Alternatives considered**

The tests could start a real provider or server. That would make the
conformance gate slower, less portable, and dependent on credentials or
network state.

**Roadmap alignment**

This supports the existing experimental Paperclip Runner rollout. It
does not enable a production adapter or change current app execution.

## What Changed

- Add a deterministic harness-driver implementation.
- Add an in-memory control-plane adapter with replay and checkpoint
support.
- Add a canonical provider-neutral conformance fixture.
- Add a reusable conformance runner and contract tests.
- Export the helpers from the package testing entry point only.

## Verification

- `pnpm --filter @paperclipai/paperclip-runner test:typescript`
- `pnpm --filter @paperclipai/paperclip-runner typecheck:typescript`
- `pnpm -r typecheck`
- `pnpm build`
- The branch changes 10 files relative to its declared base.

## Risks

Low risk. This pull request adds test-only adapters and conformance
helpers. It does not select a runtime or change production behavior.
Contract tests verify event identity, replay, checkpoints, and
deterministic results.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex, `gpt-5`, with agentic reasoning, tool use, and code
execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either linked an existing public issue or described the
issue in-PR
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
This commit is contained in:
Dotta 2026-08-29 22:04:18 -05:00 committed by GitHub
parent e18632ebcb
commit 773d357d8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1425 additions and 5 deletions

View File

@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import type { HarnessDriver, HarnessSession, PersistedHarnessSession } from "../contracts/harness-driver.js";
import type { PrpEvent, PrpStructuredRunResult } from "../protocol/replay-contract.js";
import type { PrpEvent, PrpStructuredRunResult, PrpTerminalState } from "../protocol/replay-contract.js";
import { HarnessDriverBackend } from "./harness-driver-backend.js";
const result: PrpStructuredRunResult = {
@ -271,6 +271,105 @@ describe("HarnessDriverBackend", () => {
expect(receivedSignal).toBe(controller.signal);
});
it("does not resurrect a settled turn across repeated backend recovery", async () => {
const terminal: PrpTerminalState = {
schema: "paperclip.prp.terminal.v1",
turnTerminalState: "completed",
runTerminalState: "succeeded",
reportedWorkDisposition: "done",
};
const recoveredSnapshots: PersistedHarnessSession[] = [];
class SettledRecoveredSession extends FakeHarnessSession {
readonly #snapshot: PersistedHarnessSession;
constructor(snapshot: PersistedHarnessSession) {
super();
this.#snapshot = structuredClone(snapshot);
}
override async *events() {
// Model the deterministic driver's recovery rule: only an active turn
// produces a reconstructed result/terminal sequence.
if (this.#snapshot.activeTurnId === null || this.#snapshot.activeTurnId === undefined) return;
yield prpEvent(3, "run.result.proposed", result);
yield prpEvent(4, "turn.completed", { status: "completed" });
yield prpEvent(5, "run.terminal", terminal);
}
override async snapshot(): Promise<PersistedHarnessSession> {
return structuredClone(this.#snapshot);
}
}
const recoveryDriver: HarnessDriver = {
...driver,
async recoverSession(snapshot) {
recoveredSnapshots.push(structuredClone(snapshot));
return { recovered: true, session: new SettledRecoveredSession(snapshot) };
},
};
const backend = new HarnessDriverBackend(recoveryDriver);
const settledSnapshot = {
backendKind: "runner" as const,
driverKind: "fake",
sessionId: "driver-1",
providerSessionId: "provider-1",
identity: {
runId: "run-1",
sessionId: "session-1",
companyId: "company-1",
issueId: "issue-1",
agentId: "agent-1",
},
cursor: "2",
semanticResult: result,
terminal,
activeTurnId: null,
terminalTurns: [{ turnId: "turn-1", fingerprint: "terminal-1" }],
};
const recoveryOptions = { signal: new AbortController().signal };
const firstRecovery = await backend.recoverSession(
settledSnapshot,
recoveryOptions,
);
expect(firstRecovery.recovered).toBe(true);
const firstSession = firstRecovery.session!;
const firstRecoveredSnapshot = await firstSession.snapshot();
expect(firstRecoveredSnapshot).toMatchObject({
activeTurnId: null,
semanticResult: result,
terminal,
});
await expect(firstSession.result()).resolves.toEqual({
result,
terminal,
turnId: "turn-1",
});
const secondRecovery = await backend.recoverSession(
firstRecoveredSnapshot,
recoveryOptions,
);
expect(secondRecovery.recovered).toBe(true);
const secondSession = secondRecovery.session!;
await expect(secondSession.snapshot()).resolves.toMatchObject({
activeTurnId: null,
semanticResult: result,
terminal,
});
const replayedEvents: PrpEvent[] = [];
for await (const event of secondSession.events()) replayedEvents.push(event);
expect(replayedEvents.filter((event) =>
event.eventType === "run.result.proposed" || event.eventType === "run.terminal"
)).toEqual([]);
expect(recoveredSnapshots.map((snapshot) => snapshot.activeTurnId)).toEqual([null, null]);
await secondSession.close({ reason: "double recovery complete" });
await firstSession.close({ reason: "first recovery complete" });
});
it("allows only the run id to change when a harness session is attached", async () => {
const attachedRunIds: string[] = [];
class AttachableHarnessSession extends FakeHarnessSession {

View File

@ -70,6 +70,12 @@ export class HarnessDriverBackend implements NativeSessionBackend {
if (this.#driver.recoverSession === undefined) {
return { recovered: false, reason: "driver does not support recovery" };
}
// `null` is a durable "no active turn" checkpoint. Only an omitted legacy
// field may use the compatibility fallback; nullish coalescing here would
// otherwise turn the most recent terminal turn back into an active one.
const activeTurnId = snapshot.activeTurnId === undefined
? snapshot.terminalTurns?.at(-1)?.turnId ?? null
: snapshot.activeTurnId;
const persisted: PersistedHarnessSession = {
driverKind: snapshot.driverKind ?? snapshot.backendKind,
driverSessionId: snapshot.sessionId,
@ -82,7 +88,7 @@ export class HarnessDriverBackend implements NativeSessionBackend {
: { providerRecoveryPolicy: snapshot.providerRecoveryPolicy }),
runId: snapshot.identity.runId,
normalizedSessionId: snapshot.identity.sessionId,
activeTurnId: snapshot.activeTurnId ?? snapshot.terminalTurns?.at(-1)?.turnId ?? null,
activeTurnId,
lastSourceSequence: parseCursor(snapshot.cursor),
...(snapshot.semanticResult === undefined || snapshot.semanticResult === null
? {}
@ -90,7 +96,7 @@ export class HarnessDriverBackend implements NativeSessionBackend {
semanticResult: {
result: snapshot.semanticResult,
fingerprint: canonicalJson(snapshot.semanticResult),
turnId: snapshot.activeTurnId ?? snapshot.terminalTurns?.at(-1)?.turnId ?? "recovered",
turnId: activeTurnId ?? snapshot.terminalTurns?.at(-1)?.turnId ?? "recovered",
},
}),
terminalTurns: snapshot.terminalTurns ?? [],
@ -125,6 +131,7 @@ export class HarnessDriverBackend implements NativeSessionBackend {
session: new HarnessNativeSession(
{ identity: snapshot.identity },
recovered.session,
snapshot.terminal,
),
};
}
@ -154,9 +161,14 @@ class HarnessNativeSession implements NativeSession {
#terminal: PrpTerminalState | null = null;
#explicitlyCancelled = false;
constructor(input: OpenNativeSessionInput, session: HarnessSession) {
constructor(
input: OpenNativeSessionInput,
session: HarnessSession,
terminal?: PrpTerminalState | null,
) {
this.#input = structuredClone(input);
this.#session = session;
this.#terminal = terminal === undefined ? null : structuredClone(terminal);
}
identity() {
@ -410,7 +422,12 @@ class HarnessNativeSession implements NativeSession {
: String(snapshot.lastSourceSequence),
semanticResult: snapshot.semanticResult?.result ?? null,
terminal: this.#terminal,
activeTurnId: snapshot.activeTurnId ?? snapshot.semanticResult?.turnId ?? null,
// Harness snapshots use an explicit null to record a settled turn. Keep
// the semantic-result fallback solely for legacy snapshots that omitted
// activeTurnId, or a later recovery can replay the terminal turn.
activeTurnId: snapshot.activeTurnId === undefined
? snapshot.semanticResult?.turnId ?? null
: snapshot.activeTurnId,
terminalTurns: snapshot.terminalTurns ?? [],
pendingRuntimeRequests: snapshot.pendingRuntimeRequests ?? [],
lineage: snapshot.lineage ?? [],

View File

@ -0,0 +1,40 @@
import { describe, expect, it } from "vitest";
import { MockControlPlaneAdapter } from "../mock-core/mock-control-plane-adapter.js";
import {
CONTROL_PLANE_CONFORMANCE_EVENTS,
CONTROL_PLANE_CONFORMANCE_OPEN,
runControlPlanePortConformance,
} from "./control-plane-port.js";
describe("ControlPlanePort conformance", () => {
it("runs unchanged against the mock adapter", async () => {
const adapter = new MockControlPlaneAdapter();
await expect(runControlPlanePortConformance({
port: adapter,
start: () => adapter.start(),
stop: () => adapter.stop(),
})).resolves.toEqual({
eventCount: 3,
highestContiguousSourceSeq: 3,
duplicateDisposition: "duplicate",
terminalReplayIdempotent: true,
openBindingRejected: true,
eventIdMutationRejected: true,
eventSequenceMutationRejected: true,
replayBindingRejected: true,
resultMutationRejected: true,
});
});
it("fails closed when a source identity is replayed with different bytes", async () => {
const adapter = new MockControlPlaneAdapter();
await adapter.start();
await adapter.openRun(CONTROL_PLANE_CONFORMANCE_OPEN);
await adapter.appendEvent(CONTROL_PLANE_CONFORMANCE_EVENTS[0]);
await expect(adapter.appendEvent({
...CONTROL_PLANE_CONFORMANCE_EVENTS[0],
payload: { backend: "forged" },
})).rejects.toThrow("native_event_replay_conflict");
});
});

View File

@ -0,0 +1,286 @@
import { describe, expect, it } from "vitest";
import { DeterministicHarnessDriver } from "../mock-core/deterministic-harness-driver.js";
import {
loadHarnessDriverConformanceFixture,
runHarnessDriverConformance,
} from "./harness-driver.js";
describe("harness-driver conformance V1", () => {
it("ships a deterministic fixture covering the complete Evals hook set", async () => {
const fixture = await loadHarnessDriverConformanceFixture();
expect(fixture.requiredCapabilities).toContain("dynamicTools");
expect(fixture.unsupportedFeatures).toContain("steering");
const report = await runHarnessDriverConformance({
driver: new DeterministicHarnessDriver(),
fixture,
});
expect(report).toMatchObject({
schema: "paperclip-runner/harness-driver-conformance-report/v1",
contractVersion: 1,
eventCount: 7,
semanticToolCallCount: 1,
checks: {
capabilityDescription: true,
configValidation: true,
sessionLifecycle: true,
sessionRecovery: true,
semanticTools: true,
eventValidation: true,
interruptAndCancel: true,
usage: true,
transcriptCompleteness: true,
unsupportedFeatures: true,
},
});
});
it("settles an actively recovered turn when event consumption resumes", async () => {
const driver = new DeterministicHarnessDriver();
const session = await driver.openSession({
runId: "run_active_recovery",
normalizedSessionId: "session_active_recovery",
workingDirectory: "/deterministic/conformance",
});
const { turnId } = await session.startTurn({
message: { role: "user", text: "[conformance:interrupt]" },
});
const snapshot = await session.snapshot();
expect(snapshot).toMatchObject({ activeTurnId: turnId, lastSourceSequence: 2 });
const firstRecovery = await driver.recoverSession(snapshot);
expect(firstRecovery.recovered).toBe(true);
expect(firstRecovery.session).toBeDefined();
const repeatedSnapshot = await firstRecovery.session!.snapshot();
expect(repeatedSnapshot).toMatchObject({
activeTurnId: turnId,
lastSourceSequence: 2,
});
const recovery = await driver.recoverSession(repeatedSnapshot);
expect(recovery.recovered).toBe(true);
expect(recovery.session).toBeDefined();
const recovered = recovery.session!;
const recoveredEventsPromise = (async () => {
const events = [];
for await (const event of recovered.events()) events.push(event);
return events;
})();
const events = await recoveredEventsPromise;
expect(events.map((event) => [event.sourceSeq, event.eventType])).toEqual([
[3, "run.result.proposed"],
[4, "turn.interrupted"],
[5, "run.terminal"],
]);
await expect(recovered.snapshot()).resolves.toMatchObject({
activeTurnId: null,
semanticResult: {
result: { reportedWorkDisposition: "yielded" },
turnId,
},
lastSourceSequence: 5,
});
expect(events[1]?.payload).toEqual({
reason: "deterministic_active_turn_recovered_without_live_producer",
});
await expect(recovered.transcript?.()).resolves.toMatchObject({
schema: "paperclip-runner/harness-transcript/v1",
complete: false,
eventCount: 3,
events: events.map((event) => expect.objectContaining({
sourceSeq: event.sourceSeq,
eventType: event.eventType,
})),
omissionReason: "pre_recovery_events_not_reconstructed",
});
await recovered.close({ reason: "recovered_complete" });
await firstRecovery.session!.close({ reason: "first_recovery_complete" });
await session.close({ reason: "original_complete", force: true });
});
it("interrupts a recovered turn deterministically before event consumption", async () => {
const driver = new DeterministicHarnessDriver();
const session = await driver.openSession({
runId: "run_recovery_interrupt",
normalizedSessionId: "session_recovery_interrupt",
workingDirectory: "/deterministic/conformance",
});
const { turnId } = await session.startTurn({
message: { role: "user", text: "[conformance:interrupt]" },
});
const recovery = await driver.recoverSession(await session.snapshot());
const recovered = recovery.session!;
await new Promise<void>((resolve) => setImmediate(resolve));
await expect(recovered.interrupt?.({
turnId,
reason: "cancel_before_consumption",
})).resolves.toBeUndefined();
const events = [];
for await (const event of recovered.events()) events.push(event);
expect(events.map((event) => [event.sourceSeq, event.eventType])).toEqual([
[3, "run.result.proposed"],
[4, "turn.interrupted"],
[5, "run.terminal"],
]);
await expect(recovered.snapshot()).resolves.toMatchObject({
activeTurnId: null,
semanticResult: {
result: { reportedWorkDisposition: "yielded" },
turnId,
},
lastSourceSequence: 5,
});
await recovered.close({ reason: "recovered_complete" });
await session.close({ reason: "original_complete", force: true });
});
it("acknowledges interruption after recovered event settlement starts", async () => {
const driver = new DeterministicHarnessDriver();
const session = await driver.openSession({
runId: "run_recovery_interrupt_race",
normalizedSessionId: "session_recovery_interrupt_race",
workingDirectory: "/deterministic/conformance",
});
const { turnId } = await session.startTurn({
message: { role: "user", text: "[conformance:interrupt]" },
});
const recovery = await driver.recoverSession(await session.snapshot());
const recovered = recovery.session!;
const recoveredEvents = recovered.events()[Symbol.asyncIterator]();
const first = await recoveredEvents.next();
expect(first.value?.eventType).toBe("run.result.proposed");
await expect(recovered.interrupt?.({
turnId,
reason: "cancel_after_event_settlement_started",
})).resolves.toBeUndefined();
const events = first.done || first.value === undefined ? [] : [first.value];
for await (const event of { [Symbol.asyncIterator]: () => recoveredEvents }) {
events.push(event);
}
expect(events.map((event) => event.eventType)).toEqual([
"run.result.proposed",
"turn.interrupted",
"run.terminal",
]);
expect(events.filter((event) => event.eventType === "run.terminal")).toHaveLength(1);
await recovered.close({ reason: "recovered_complete" });
await session.close({ reason: "original_complete", force: true });
});
it("preserves a completed result when its terminal checkpoint lagged", async () => {
const driver = new DeterministicHarnessDriver();
const session = await driver.openSession({
runId: "run_result_first_recovery",
normalizedSessionId: "session_result_first_recovery",
workingDirectory: "/deterministic/conformance",
});
const { turnId } = await session.startTurn({
message: { role: "user", text: "Complete deterministically." },
});
const completedSnapshot = await session.snapshot();
const recovery = await driver.recoverSession({
...completedSnapshot,
activeTurnId: turnId,
terminalTurns: [],
});
const recovered = recovery.session!;
const events = [];
for await (const event of recovered.events()) events.push(event);
expect(events.map((event) => event.eventType)).toEqual([
"run.result.proposed",
"turn.completed",
"run.terminal",
]);
expect(events[0]?.payload).toMatchObject({ reportedWorkDisposition: "done" });
expect(events[2]?.payload).toMatchObject({ runTerminalState: "succeeded" });
await expect(recovered.snapshot()).resolves.toMatchObject({
activeTurnId: null,
semanticResult: completedSnapshot.semanticResult,
});
await recovered.close({ reason: "recovered_complete" });
await session.close({ reason: "original_complete" });
});
it("does not let an interrupt overwrite a completed result-first recovery", async () => {
const driver = new DeterministicHarnessDriver();
const session = await driver.openSession({
runId: "run_result_first_interrupt",
normalizedSessionId: "session_result_first_interrupt",
workingDirectory: "/deterministic/conformance",
});
const { turnId } = await session.startTurn({
message: { role: "user", text: "Complete deterministically." },
});
const completedSnapshot = await session.snapshot();
const recovery = await driver.recoverSession({
...completedSnapshot,
activeTurnId: turnId,
terminalTurns: [],
});
const recovered = recovery.session!;
await expect(recovered.interrupt?.({
turnId,
reason: "late cancellation",
})).resolves.toBeUndefined();
const events = [];
for await (const event of recovered.events()) events.push(event);
expect(events.map((event) => event.eventType)).toEqual([
"run.result.proposed",
"turn.completed",
"run.terminal",
]);
await expect(recovered.snapshot()).resolves.toMatchObject({
activeTurnId: null,
semanticResult: completedSnapshot.semanticResult,
});
await recovered.close({ reason: "recovered_complete" });
await session.close({ reason: "original_complete" });
});
it("preserves an interrupted result when its terminal checkpoint lagged", async () => {
const driver = new DeterministicHarnessDriver();
const session = await driver.openSession({
runId: "run_yielded_result_first_recovery",
normalizedSessionId: "session_yielded_result_first_recovery",
workingDirectory: "/deterministic/conformance",
});
const { turnId } = await session.startTurn({
message: { role: "user", text: "[conformance:interrupt]" },
});
await session.interrupt?.({ turnId, reason: "checkpoint_after_result" });
const interruptedSnapshot = await session.snapshot();
const recovery = await driver.recoverSession({
...interruptedSnapshot,
activeTurnId: turnId,
terminalTurns: [],
});
const recovered = recovery.session!;
const events = [];
for await (const event of recovered.events()) events.push(event);
expect(events.map((event) => event.eventType)).toEqual([
"run.result.proposed",
"turn.interrupted",
"run.terminal",
]);
expect(events[0]?.payload).toMatchObject({
reportedWorkDisposition: "yielded",
continuation: { kind: "same_agent" },
});
expect(events[2]?.payload).toMatchObject({
turnTerminalState: "interrupted",
runTerminalState: "cancelled",
reportedWorkDisposition: "yielded",
});
await recovered.close({ reason: "recovered_complete" });
await session.close({ reason: "original_complete" });
});
});

View File

@ -0,0 +1,483 @@
import {
type HarnessDriver,
type HarnessDriverConfigValidation,
type HarnessDriverDescriptor,
type HarnessSession,
type HarnessSessionRecoveryResult,
type HarnessTranscriptSnapshot,
type OpenHarnessSessionInput,
type PersistedHarnessSession,
} from "../contracts/harness-driver.js";
import {
createPrpSemanticToolInputEnvelope,
createPrpSemanticToolResultEnvelope,
} from "../protocol/semantic-tool-receipts.js";
import type {
PrpEvent,
PrpStructuredRunResult,
PrpTerminalState,
} from "../protocol/replay-contract.js";
export const DETERMINISTIC_HARNESS_DRIVER_CONFIG_SCHEMA =
"paperclip-runner/deterministic-harness-driver-config/v1" as const;
export interface DeterministicHarnessDriverConfig {
schema: typeof DETERMINISTIC_HARNESS_DRIVER_CONFIG_SCHEMA;
scenario: "semantic-tool-success";
}
const DEFAULT_CONFIG: DeterministicHarnessDriverConfig = {
schema: DETERMINISTIC_HARNESS_DRIVER_CONFIG_SCHEMA,
scenario: "semantic-tool-success",
};
function parseConfig(value: unknown): HarnessDriverConfigValidation {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return {
ok: false,
config: null,
issues: [{ path: "/", code: "type", message: "config must be an object" }],
};
}
const config = value as Record<string, unknown>;
if (config.schema !== DETERMINISTIC_HARNESS_DRIVER_CONFIG_SCHEMA) {
return {
ok: false,
config: null,
issues: [{
path: "/schema",
code: "unsupported_schema",
message: `schema must be ${DETERMINISTIC_HARNESS_DRIVER_CONFIG_SCHEMA}`,
}],
};
}
if (config.scenario !== "semantic-tool-success") {
return {
ok: false,
config: null,
issues: [{
path: "/scenario",
code: "unsupported_scenario",
message: "scenario must be semantic-tool-success",
}],
};
}
return { ok: true, config: structuredClone(config), issues: [] };
}
/** Provider-free fake used by packed consumers and App/Evals conformance. */
export class DeterministicHarnessDriver implements HarnessDriver {
readonly #config: DeterministicHarnessDriverConfig;
constructor(config: DeterministicHarnessDriverConfig = DEFAULT_CONFIG) {
const validation = parseConfig(config);
if (!validation.ok) throw new Error(validation.issues[0]?.message ?? "invalid config");
this.#config = structuredClone(config);
}
async descriptor(): Promise<HarnessDriverDescriptor> {
return {
kind: "paperclip-deterministic",
displayName: "Paperclip deterministic harness driver",
version: "1.0.0",
protocolVersion: "prp.v1",
capabilities: {
resume: true,
typedEvents: true,
steering: false,
interruption: true,
structuredResult: true,
read: true,
reconciliation: true,
usage: true,
dynamicTools: true,
runtimeRequestResolution: false,
goals: false,
threadLineage: false,
unsupported: ["steering", "runtimeRequestResolution", "goals", "threadLineage"],
},
};
}
async validateConfig(config: unknown): Promise<HarnessDriverConfigValidation> {
return parseConfig(config);
}
async openSession(input: OpenHarnessSessionInput): Promise<HarnessSession> {
return new DeterministicHarnessSession(input, this.#config);
}
async recoverSession(
snapshot: PersistedHarnessSession,
): Promise<HarnessSessionRecoveryResult> {
if (
snapshot.driverKind !== "paperclip-deterministic"
|| snapshot.runId === undefined
|| snapshot.normalizedSessionId === undefined
) {
return { recovered: false, reason: "snapshot does not belong to the deterministic driver" };
}
return {
recovered: true,
session: new DeterministicHarnessSession({
runId: snapshot.runId,
normalizedSessionId: snapshot.normalizedSessionId,
workingDirectory: "/deterministic/recovered",
}, this.#config, snapshot),
};
}
}
class DeterministicHarnessSession implements HarnessSession {
readonly #input: OpenHarnessSessionInput;
readonly #config: DeterministicHarnessDriverConfig;
#events: PrpEvent[] = [];
#semanticResult: PrpStructuredRunResult | null = null;
#turnId: string | null = null;
#closed = false;
#active = false;
#recoveredActiveTurn = false;
#recoveredTurnSettled = false;
#nextSourceSeq = 1;
readonly #transcriptOmissionReason: string | null;
readonly #eventWaiters = new Set<() => void>();
constructor(
input: OpenHarnessSessionInput,
config: DeterministicHarnessDriverConfig,
snapshot?: PersistedHarnessSession,
) {
this.#input = structuredClone(input);
this.#config = structuredClone(config);
this.#turnId = snapshot?.activeTurnId ?? snapshot?.semanticResult?.turnId ?? null;
this.#semanticResult = snapshot?.semanticResult?.result ?? null;
this.#active = snapshot?.activeTurnId !== null && snapshot?.activeTurnId !== undefined;
this.#recoveredActiveTurn = this.#active;
this.#nextSourceSeq = (snapshot?.lastSourceSequence ?? 0) + 1;
this.#transcriptOmissionReason = snapshot === undefined
? null
: "pre_recovery_events_not_reconstructed";
}
ids() {
return {
driverSessionId: `driver_${this.#input.normalizedSessionId}`,
providerSessionId: null,
displayId: this.#input.normalizedSessionId,
};
}
async *events(): AsyncIterable<PrpEvent> {
if (this.#recoveredActiveTurn && this.#active && this.#turnId !== null) {
// The deterministic fake has no provider process that can continue an
// in-flight turn after reconstruction. Produce one conservative terminal
// outcome when consumption resumes so the native loop cannot wait until
// its global timeout. An explicit interrupt issued before consumption
// still wins and supplies its own reason.
if (!this.#settleRecoveredSemanticResult()) {
this.#recoveredActiveTurn = false;
this.#semanticResult = interruptedResult(
this.#input.runId,
"The deterministic provider was unavailable after active-turn recovery.",
);
this.#appendEvents(
this.#event("run.result.proposed", this.#semanticResult),
this.#event("turn.interrupted", {
reason: "deterministic_active_turn_recovered_without_live_producer",
}),
this.#event("run.terminal", cancelledTerminal(), { turn: false }),
);
this.#recoveredTurnSettled = true;
}
this.#active = false;
}
let index = 0;
while (true) {
while (index < this.#events.length) {
const event = this.#events[index++]!;
yield structuredClone(event);
if (event.eventType === "run.terminal") return;
}
if (this.#closed) return;
await new Promise<void>((resolve) => this.#eventWaiters.add(resolve));
}
}
async startTurn(input: { message: { role: "user"; text: string } }): Promise<{ turnId: string }> {
this.#assertOpen();
if (this.#turnId !== null) throw new Error("deterministic session accepts one turn");
this.#turnId = `turn_${this.#input.runId}`;
this.#active = true;
this.#appendEvents(
this.#event("session.started", {}, { turn: false }),
this.#event("turn.started", { message: input.message.text }),
);
if (input.message.text.includes("[conformance:interrupt]")) {
return { turnId: this.#turnId };
}
const itemId = `item_${this.#input.runId}`;
const callId = `call_${this.#input.runId}`;
const correlation = {
runId: this.#input.runId,
normalizedSessionId: this.#input.normalizedSessionId,
turnId: this.#turnId,
itemId,
};
const inputEnvelope = createPrpSemanticToolInputEnvelope({
operationId: "finish_task",
callId,
correlation,
idempotencyKey: `finish_${this.#input.runId}`,
content: { summary: "Deterministic conformance complete." },
});
const resultEnvelope = createPrpSemanticToolResultEnvelope({
operationId: "finish_task",
callId,
correlation,
idempotencyKey: `finish_${this.#input.runId}`,
content: { disposition: "applied", status: "done" },
outcome: "succeeded",
code: "applied",
retryable: false,
authorizationBoundary: "active_task",
auditReceiptId: `audit_${this.#input.runId}`,
});
this.#appendEvents(
this.#event("mcp_app.tool_input", { semantic_tool: inputEnvelope }, { itemId }),
this.#event("mcp_app.tool_result", { semantic_tool: resultEnvelope }, { itemId }),
);
this.#semanticResult = completedResult();
const terminal = completedTerminal();
this.#appendEvents(
this.#event("run.result.proposed", this.#semanticResult),
this.#event("turn.completed", {}),
this.#event("run.terminal", terminal, { turn: false }),
);
this.#active = false;
return { turnId: this.#turnId };
}
async interrupt(input: { turnId?: string; reason?: string }): Promise<void> {
this.#assertOpen();
if (this.#turnId === null) throw new Error("no active turn to interrupt");
if (input.turnId !== undefined && input.turnId !== this.#turnId) {
throw new Error(`turn ${input.turnId} is not active`);
}
// Event consumption can conservatively settle a reconstructed turn before
// its first event is yielded. Treat a concurrent interrupt as an
// idempotent acknowledgement once that recovered terminal is committed,
// rather than making cancellation depend on iterator scheduling.
if (!this.#active) {
if (this.#recoveredTurnSettled) return;
throw new Error("no active turn to interrupt");
}
if (this.#settleRecoveredSemanticResult()) return;
this.#semanticResult = interruptedResult(
this.#input.runId,
input.reason ?? "The deterministic turn was interrupted.",
);
this.#appendEvents(
this.#event("run.result.proposed", this.#semanticResult),
this.#event("turn.interrupted", { reason: input.reason ?? "interrupted" }),
this.#event("run.terminal", cancelledTerminal(), { turn: false }),
);
this.#active = false;
}
#settleRecoveredSemanticResult(): boolean {
if (
!this.#recoveredActiveTurn
|| !this.#active
|| this.#turnId === null
|| this.#semanticResult === null
) return false;
this.#recoveredActiveTurn = false;
// A result-first checkpoint can still carry activeTurnId when the
// terminal checkpoint lagged behind the provider work. Preserve the
// authoritative result even if cancellation races event consumption.
if (this.#semanticResult.reportedWorkDisposition === "yielded") {
this.#appendEvents(
this.#event("run.result.proposed", this.#semanticResult),
this.#event("turn.interrupted", {
reason: "deterministic_active_turn_recovered_with_yielded_result",
}),
this.#event("run.terminal", cancelledTerminal(), { turn: false }),
);
} else {
this.#appendEvents(
this.#event("run.result.proposed", this.#semanticResult),
this.#event("turn.completed", {}),
this.#event(
"run.terminal",
completedTerminal(this.#semanticResult.reportedWorkDisposition),
{ turn: false },
),
);
}
this.#recoveredTurnSettled = true;
this.#active = false;
return true;
}
async read(): Promise<Record<string, unknown>> {
return { scenario: this.#config.scenario, eventCount: this.#events.length };
}
async reconcile(): Promise<Record<string, unknown>> {
return { active: this.#active, closed: this.#closed, eventCount: this.#events.length };
}
async usage(): Promise<Record<string, unknown>> {
return {
inputTokens: this.#turnId === null ? 0 : 12,
outputTokens: this.#semanticResult === null ? 0 : 8,
totalTokens: this.#turnId === null ? 0 : this.#semanticResult === null ? 12 : 20,
requestCount: this.#turnId === null ? 0 : 1,
cost: { currency: "USD", amountMicros: 0 },
};
}
async transcript(): Promise<HarnessTranscriptSnapshot> {
return {
schema: "paperclip-runner/harness-transcript/v1",
complete: this.#transcriptOmissionReason === null,
eventCount: this.#events.length,
events: structuredClone(this.#events),
omissionReason: this.#transcriptOmissionReason,
};
}
async snapshot(): Promise<PersistedHarnessSession> {
return {
driverKind: "paperclip-deterministic",
driverSessionId: `driver_${this.#input.normalizedSessionId}`,
providerSessionId: null,
runId: this.#input.runId,
normalizedSessionId: this.#input.normalizedSessionId,
activeTurnId: this.#active ? this.#turnId : null,
lastSourceSequence: this.#nextSourceSeq - 1,
semanticResult: this.#semanticResult === null || this.#turnId === null
? null
: {
result: structuredClone(this.#semanticResult),
fingerprint: JSON.stringify(this.#semanticResult),
turnId: this.#turnId,
},
terminalTurns: this.#events.some((event) => event.eventType === "run.terminal") && this.#turnId !== null
? [{ turnId: this.#turnId, fingerprint: `terminal_${this.#input.runId}` }]
: [],
};
}
async close(input: { reason: string; force?: boolean }): Promise<void> {
if (this.#closed) return;
if (this.#active) {
await this.interrupt({ reason: input.reason });
}
this.#closed = true;
this.#notifyEventWaiters();
}
#appendEvents(...events: PrpEvent[]): void {
this.#events.push(...events);
this.#notifyEventWaiters();
}
#notifyEventWaiters(): void {
for (const resolve of this.#eventWaiters) resolve();
this.#eventWaiters.clear();
}
#event(
eventType: PrpEvent["eventType"],
payload: Record<string, unknown> | PrpStructuredRunResult | PrpTerminalState,
options: { itemId?: string; turn?: boolean } = {},
): PrpEvent {
const sourceSeq = this.#nextSourceSeq++;
return {
schema: "paperclip.prp.event.v1",
sourceEventId: `event_${this.#input.runId}_${sourceSeq}`,
sourceSeq,
sourceInstanceId: `runner_${this.#input.runId}`,
sourceKind: "runner",
runId: this.#input.runId,
normalizedSessionId: this.#input.normalizedSessionId,
...(options.turn === false || this.#turnId === null ? {} : { turnId: this.#turnId }),
...(options.itemId === undefined ? {} : { itemId: options.itemId }),
eventType,
schemaVersion: 1,
priority: eventType === "run.terminal" || eventType === "run.result.proposed" ? 0 : 1,
emittedAt: new Date(Date.UTC(2026, 7, 11, 12, 0, sourceSeq)).toISOString(),
payload: structuredClone(payload),
} as PrpEvent;
}
#assertOpen(): void {
if (this.#closed) throw new Error("deterministic session is closed");
}
}
function completedResult(): PrpStructuredRunResult {
return {
schema: "paperclip.run_result.v1",
reportedWorkDisposition: "done",
summary: "Deterministic harness-driver conformance completed.",
completionClaim: {
contractRevision: "harness-driver-conformance-v1",
objectiveSatisfied: true,
criteria: [],
remainingWork: [],
},
evidence: [],
verification: [{ commandOrCheck: "deterministic harness flow", status: "passed" }],
attentionRequests: [],
artifacts: [],
};
}
function completedTerminal(
disposition: PrpTerminalState["reportedWorkDisposition"] = "done",
): PrpTerminalState {
return {
schema: "paperclip.prp.terminal.v1",
turnTerminalState: "completed",
runTerminalState: "succeeded",
reportedWorkDisposition: disposition,
};
}
function interruptedResult(runId: string, summary: string): PrpStructuredRunResult {
return {
schema: "paperclip.run_result.v1",
reportedWorkDisposition: "yielded",
summary,
completionClaim: {
contractRevision: "harness-driver-conformance-v1",
objectiveSatisfied: false,
criteria: [],
remainingWork: [{
description: "Resume the interrupted work in a live provider session.",
blocksCompletion: true,
}],
},
evidence: [],
verification: [],
attentionRequests: [],
artifacts: [],
continuation: {
kind: "same_agent",
summary: "Resume the interrupted deterministic turn.",
idempotencyKey: `resume_deterministic_${runId}`,
},
};
}
function cancelledTerminal(): PrpTerminalState {
return {
schema: "paperclip.prp.terminal.v1",
turnTerminalState: "interrupted",
runTerminalState: "cancelled",
reportedWorkDisposition: "yielded",
};
}

View File

@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
import { loadConformanceFixture } from "../protocol/conformance-fixture.js";
import { replayConformanceFixture } from "../tracer/conformance-runner.js";
import { MockControlPlaneAdapter } from "./mock-control-plane-adapter.js";
describe("MockControlPlaneAdapter", () => {
it("exercises the complete control-plane contract path", async () => {
const fixture = await loadConformanceFixture();
const mockCore = new MockControlPlaneAdapter();
await mockCore.start();
await replayConformanceFixture(mockCore, fixture);
expect(mockCore.snapshot()).toMatchObject({
lifecycle: "running",
openedRun: { identity: fixture.run, backendKind: "mock" },
events: fixture.events,
result: fixture.result,
});
await mockCore.stop();
});
it("rejects events before startup", async () => {
const fixture = await loadConformanceFixture();
const mockCore = new MockControlPlaneAdapter();
await expect(mockCore.appendEvent(fixture.events[0]!)).rejects.toThrow(
"mock control plane must be started first",
);
});
});

View File

@ -0,0 +1,199 @@
import type {
AppendedEventReceipt,
CompleteControlPlaneRunInput,
ControlPlanePort,
OpenControlPlaneRunInput,
ReplayControlPlaneEventsInput,
ReplayedControlPlaneEvents,
} from "../contracts/control-plane-port.js";
import type { NativeRunEvent, NativeRunResult } from "../contracts/types.js";
import {
validatePrpEvent,
validatePrpStructuredRunResult,
type PrpEvent,
} from "../protocol/replay-contract.js";
export interface MockControlPlaneSnapshot {
lifecycle: "stopped" | "running";
openedRun: OpenControlPlaneRunInput | null;
events: Array<NativeRunEvent | PrpEvent>;
result: NativeRunResult | CompleteControlPlaneRunInput | null;
}
/** In-memory control-plane shell used by standalone tests and tutorials. */
export class MockControlPlaneAdapter implements ControlPlanePort {
#lifecycle: "stopped" | "running" = "stopped";
#openedRun: OpenControlPlaneRunInput | null = null;
#events: Array<NativeRunEvent | PrpEvent> = [];
#result: NativeRunResult | CompleteControlPlaneRunInput | null = null;
async start(): Promise<void> {
if (this.#lifecycle === "running") {
throw new Error("mock control plane is already running");
}
this.#lifecycle = "running";
}
async stop(): Promise<void> {
this.#lifecycle = "stopped";
}
async openRun(input: OpenControlPlaneRunInput): Promise<void> {
this.#assertRunning();
if (this.#openedRun !== null) {
throw new Error("mock control plane accepts one Conformance run");
}
this.#openedRun = structuredClone(input);
}
async appendEvent(event: NativeRunEvent | PrpEvent): Promise<AppendedEventReceipt> {
const runId = this.#requireRunId();
if (event.runId !== runId) {
throw new Error("event runId does not match the opened run");
}
if ("sourceEventId" in event) {
const validation = validatePrpEvent(event);
if (!validation.ok) {
throw new Error(`event failed validation: ${validation.issues[0]?.message ?? "invalid event"}`);
}
const sameId = this.#events.find(
(candidate): candidate is PrpEvent =>
"sourceEventId" in candidate && candidate.sourceEventId === event.sourceEventId,
);
const sameSequence = this.#events.find(
(candidate): candidate is PrpEvent =>
"sourceSeq" in candidate &&
candidate.sourceInstanceId === event.sourceInstanceId &&
candidate.sourceSeq === event.sourceSeq,
);
for (const candidate of [sameId, sameSequence]) {
if (candidate !== undefined) {
if (canonicalJson(candidate) !== canonicalJson(event)) {
throw new Error("native_event_replay_conflict");
}
return {
cursor: event.sourceSeq,
highestContiguousSourceSeq: this.#highestContiguous(event.sourceInstanceId),
disposition: "duplicate",
};
}
}
this.#events.push(structuredClone(event));
return {
cursor: event.sourceSeq,
highestContiguousSourceSeq: this.#highestContiguous(event.sourceInstanceId),
disposition: "committed",
};
}
const expectedSequence = this.#events.length + 1;
if (event.sequence !== expectedSequence) {
throw new Error(`event sequence must be ${expectedSequence}`);
}
this.#events.push(structuredClone(event));
return {
cursor: event.sequence,
highestContiguousSourceSeq: event.sequence,
disposition: "committed",
};
}
async replayEvents(
input: ReplayControlPlaneEventsInput,
): Promise<ReplayedControlPlaneEvents> {
const runId = this.#requireRunId();
if (input.runId !== runId) {
throw new Error("replay runId does not match the opened run");
}
const events = this.#events
.filter(
(event): event is PrpEvent =>
"sourceSeq" in event &&
event.sourceInstanceId === input.sourceInstanceId &&
event.sourceSeq > input.afterSourceSeq,
)
.sort((left, right) => left.sourceSeq - right.sourceSeq)
.slice(0, input.limit)
.map((event) => structuredClone(event));
return {
events,
highestContiguousSourceSeq: this.#highestContiguous(input.sourceInstanceId),
};
}
async completeRun(result: NativeRunResult | CompleteControlPlaneRunInput): Promise<void> {
const runId = this.#requireRunId();
const resultRunId = "runId" in result ? result.runId : runId;
if (resultRunId !== runId) {
throw new Error("result runId does not match the opened run");
}
if ("terminal" in result) {
const validation = validatePrpStructuredRunResult(result.result);
if (!validation.ok) {
throw new Error("native_result_invalid");
}
const terminalEvent = this.#events.find(
(event) => "eventType" in event && event.eventType === "run.terminal",
);
if (terminalEvent === undefined) {
throw new Error("run.terminal must be ingested before the result");
}
} else if (this.#events.at(-1)?.type !== "run.completed") {
throw new Error("run.completed must be ingested before the result");
}
if (this.#result !== null) {
if (canonicalJson(this.#result) === canonicalJson(result)) return;
throw new Error("native_result_replay_conflict");
}
this.#result = structuredClone(result);
}
snapshot(): MockControlPlaneSnapshot {
return {
lifecycle: this.#lifecycle,
openedRun: this.#openedRun === null ? null : structuredClone(this.#openedRun),
events: structuredClone(this.#events),
result: this.#result === null ? null : structuredClone(this.#result),
};
}
#assertRunning(): void {
if (this.#lifecycle !== "running") {
throw new Error("mock control plane must be started first");
}
}
#requireRunId(): string {
this.#assertRunning();
if (this.#openedRun === null) {
throw new Error("a run must be opened first");
}
return this.#openedRun.identity.runId;
}
#highestContiguous(sourceInstanceId: string): number {
const sequences = new Set(
this.#events
.filter(
(event): event is PrpEvent =>
"sourceSeq" in event && event.sourceInstanceId === sourceInstanceId,
)
.map((event) => event.sourceSeq),
);
let cursor = 0;
while (sequences.has(cursor + 1)) cursor += 1;
return cursor;
}
}
function canonicalJson(value: unknown): string {
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
if (typeof value === "object" && value !== null) {
const record = value as Record<string, unknown>;
return `{${Object.keys(record)
.sort()
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
.join(",")}}`;
}
return JSON.stringify(value) ?? "undefined";
}

View File

@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import {
loadConformanceFixture,
validateConformanceFixture,
} from "./conformance-fixture.js";
describe("Conformance fixture validation", () => {
it("accepts the package-owned fixture", async () => {
const fixture = await loadConformanceFixture();
expect(fixture.run.runId).toBe("run_conformance_0001");
expect(fixture.events.map((event) => event.type)).toEqual([
"run.started",
"run.completed",
]);
expect(fixture.result.status).toBe("succeeded");
});
it("rejects a non-contiguous event sequence", () => {
expect(() =>
validateConformanceFixture({
schemaVersion: "paperclip.runner.conformance.fixture.v1",
run: {
runId: "run_conformance_0001",
sessionId: "session_conformance_0001",
companyId: "company_conformance",
issueId: "issue_conformance",
agentId: "agent_conformance",
},
events: [
{
eventId: "event_conformance_0001",
runId: "run_conformance_0001",
sequence: 2,
type: "run.started",
},
{
eventId: "event_conformance_0002",
runId: "run_conformance_0001",
sequence: 3,
type: "run.completed",
},
],
result: {
runId: "run_conformance_0001",
status: "succeeded",
summary: "Standalone Conformance fixture accepted.",
},
}),
).toThrow("events[0].sequence must be 1");
});
});

View File

@ -0,0 +1,127 @@
import { readFile } from "node:fs/promises";
import type {
NativeRunEvent,
NativeRunEventType,
NativeRunIdentity,
NativeRunResult,
NativeRunStatus,
} from "../contracts/types.js";
export const CONFORMANCE_FIXTURE_SCHEMA = "paperclip.runner.conformance.fixture.v1";
export interface ConformanceFixture {
schemaVersion: typeof CONFORMANCE_FIXTURE_SCHEMA;
run: NativeRunIdentity;
events: NativeRunEvent[];
result: NativeRunResult;
}
export const conformanceFixtureUrl = new URL(
"../../protocol/fixtures/conformance-minimal-run.json",
import.meta.url,
);
function asRecord(value: unknown, path: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new Error(`${path} must be an object`);
}
return value as Record<string, unknown>;
}
function asIdentifier(value: unknown, path: string): string {
if (typeof value !== "string" || !/^[a-z][a-z0-9_]*$/.test(value)) {
throw new Error(`${path} must be a stable lowercase identifier`);
}
return value;
}
function asString(value: unknown, path: string): string {
if (typeof value !== "string" || value.trim().length === 0) {
throw new Error(`${path} must be a non-empty string`);
}
return value;
}
function parseRun(value: unknown): NativeRunIdentity {
const run = asRecord(value, "run");
return {
runId: asIdentifier(run.runId, "run.runId"),
sessionId: asIdentifier(run.sessionId, "run.sessionId"),
companyId: asIdentifier(run.companyId, "run.companyId"),
issueId: asIdentifier(run.issueId, "run.issueId"),
agentId: asIdentifier(run.agentId, "run.agentId"),
};
}
function parseEvents(value: unknown, runId: string): NativeRunEvent[] {
if (!Array.isArray(value) || value.length < 2) {
throw new Error("events must contain at least run.started and run.completed");
}
const allowedTypes = new Set<NativeRunEventType>(["run.started", "run.completed"]);
const events = value.map((entry, index) => {
const event = asRecord(entry, `events[${index}]`);
const type = asString(event.type, `events[${index}].type`) as NativeRunEventType;
if (!allowedTypes.has(type)) {
throw new Error(`events[${index}].type is unsupported`);
}
if (event.runId !== runId) {
throw new Error(`events[${index}].runId must match run.runId`);
}
if (event.sequence !== index + 1) {
throw new Error(`events[${index}].sequence must be ${index + 1}`);
}
return {
eventId: asIdentifier(event.eventId, `events[${index}].eventId`),
runId,
sequence: index + 1,
type,
};
});
if (events[0]?.type !== "run.started") {
throw new Error("the first event must be run.started");
}
if (events.at(-1)?.type !== "run.completed") {
throw new Error("the final event must be run.completed");
}
return events;
}
function parseResult(value: unknown, runId: string): NativeRunResult {
const result = asRecord(value, "result");
if (result.runId !== runId) {
throw new Error("result.runId must match run.runId");
}
const status = asString(result.status, "result.status") as NativeRunStatus;
if (status !== "succeeded") {
throw new Error("the Conformance fixture result must be succeeded");
}
return {
runId,
status,
summary: asString(result.summary, "result.summary"),
};
}
export function validateConformanceFixture(value: unknown): ConformanceFixture {
const fixture = asRecord(value, "fixture");
if (fixture.schemaVersion !== CONFORMANCE_FIXTURE_SCHEMA) {
throw new Error(`schemaVersion must be ${CONFORMANCE_FIXTURE_SCHEMA}`);
}
const run = parseRun(fixture.run);
return {
schemaVersion: CONFORMANCE_FIXTURE_SCHEMA,
run,
events: parseEvents(fixture.events, run.runId),
result: parseResult(fixture.result, run.runId),
};
}
export async function loadConformanceFixture(
url: URL = conformanceFixtureUrl,
): Promise<ConformanceFixture> {
const contents = await readFile(url, "utf8");
return validateConformanceFixture(JSON.parse(contents) as unknown);
}

View File

@ -9,4 +9,8 @@ export * from "./index.js";
export * from "./conformance/control-plane-port.js";
export * from "./conformance/harness-driver.js";
export * from "./conformance/semantic-conformance.js";
export * from "./mock-core/deterministic-harness-driver.js";
export * from "./mock-core/mock-control-plane-adapter.js";
export * from "./protocol/conformance-fixture.js";
export * from "./protocol/replay-loader.js";
export * from "./tracer/conformance-runner.js";

View File

@ -0,0 +1,21 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
import { MockControlPlaneAdapter } from "../mock-core/mock-control-plane-adapter.js";
import { runConformanceTracer } from "./conformance-runner.js";
const expectedOutputUrl = new URL(
"../../protocol/fixtures/conformance-expected-output.json",
import.meta.url,
);
describe("Conformance tracer", () => {
it("prints a stable identity and result and stops the mock core", async () => {
const mockCore = new MockControlPlaneAdapter();
const expectedOutput = (await readFile(expectedOutputUrl, "utf8")).trimEnd();
await expect(runConformanceTracer(mockCore)).resolves.toBe(expectedOutput);
expect(mockCore.snapshot().lifecycle).toBe("stopped");
});
});

View File

@ -0,0 +1,59 @@
import type { ControlPlanePort } from "../contracts/control-plane-port.js";
import { MockControlPlaneAdapter } from "../mock-core/mock-control-plane-adapter.js";
import {
loadConformanceFixture,
type ConformanceFixture,
} from "../protocol/conformance-fixture.js";
export const CONFORMANCE_OUTPUT_SCHEMA = "paperclip.runner.conformance.output.v1";
export interface ConformanceTracerOutput {
schemaVersion: typeof CONFORMANCE_OUTPUT_SCHEMA;
runIdentity: {
runId: string;
sessionId: string;
};
result: {
status: "succeeded";
summary: string;
};
}
export async function replayConformanceFixture(
controlPlane: ControlPlanePort,
fixture: ConformanceFixture,
): Promise<void> {
await controlPlane.openRun({ identity: fixture.run, backendKind: "mock" });
for (const event of fixture.events) {
await controlPlane.appendEvent(event);
}
await controlPlane.completeRun(fixture.result);
}
export function formatConformanceOutput(fixture: ConformanceFixture): string {
const output: ConformanceTracerOutput = {
schemaVersion: CONFORMANCE_OUTPUT_SCHEMA,
runIdentity: {
runId: fixture.run.runId,
sessionId: fixture.run.sessionId,
},
result: {
status: "succeeded",
summary: fixture.result.summary,
},
};
return JSON.stringify(output);
}
export async function runConformanceTracer(
mockCore = new MockControlPlaneAdapter(),
): Promise<string> {
const fixture = await loadConformanceFixture();
await mockCore.start();
try {
await replayConformanceFixture(mockCore, fixture);
return formatConformanceOutput(fixture);
} finally {
await mockCore.stop();
}
}