63 KiB
Standalone Thin Paperclip Adapter Boundary
Scope correction — 2026-08-09: The board superseded real Paperclip installation and live-instance execution for the current checkpoint. Active Standalone work is limited to the standalone demo runner and demo page under
packages/paperclip-runner/. Product/server/database sections below are retained as historical design context, not current execution instructions.
Status: design approved; remediation 8 documentation reconciliation complete; final CTO gate pending Date: 2026-08-09 Decision scope: the first feature-flagged Paperclip native run, including the normative native finalizer and server-owned Section 18 status arbitration
Decision
Paperclip will integrate the native runner through two public package ports:
NativeSessionBackendowns a normalized native session and hides the package's concreteHarnessDriverand Codex implementation.ControlPlanePortaccepts validated PRP events and terminal results from the package and exposes durable acknowledgement and replay cursors.
The two ports are complementary, not two core implementations. The runner
package supplies the NativeSessionBackend implementation; Paperclip supplies
the server-bound ControlPlanePort implementation. The core adapter only
composes those public contracts and translates the final result into existing
Paperclip types. It does not implement a native backend or contain runner
behavior.
The dependency direction is one way:
server heartbeat orchestration
-> PaperclipNativeRuntimeAdapter (core seam)
-> @paperclipai/paperclip-runner public contracts
-> NativeSessionBackend -> package-owned driver/runner logic
-> ControlPlanePort -> server-bound Paperclip implementation
packages/paperclip-runner/ never imports server/, packages/db/,
packages/shared/, or a Paperclip service. The server may import the public
package. The core seam may translate company-scoped Paperclip records into the
public input types, persist protocol records, and convert one terminal native
result into the existing AdapterExecutionResult. It must not contain driver,
provider, reducer, outbox, reconnect, or process-supervision logic.
This remains a narrow, default-off tracer: it does not enable native execution by default, migrate legacy runs, or add browser UI. “Thin” limits where runner behavior lives; it does not remove the normative completion contract. Standalone is complete only when the additive native finalizer applies the complete Section 18 arbitration contract required by the spike exit criteria and NR-006.
Why the current sketches need one package-local reconciliation
The original ControlPlanePort and NativeRunEvent sketch proves the Conformance
lifecycle, while the accepted Replay-5 path uses PrpEvent,
PrpStructuredRunResult, durable source sequences, and replay. Before the real
adapter is added, the package contract must be reconciled without importing
Paperclip types:
interface ControlPlanePort {
openRun(input: OpenControlPlaneRunInput): Promise<void>;
appendEvent(event: PrpEvent): Promise<{
highestContiguousSourceSeq: number;
disposition: "committed" | "duplicate";
}>;
replayEvents(input: {
runId: string;
sourceInstanceId: string;
afterSourceSeq: number;
limit: number;
}): Promise<{
events: PrpEvent[];
highestContiguousSourceSeq: number;
}>;
completeRun(input: {
result: PrpStructuredRunResult;
terminal: PrpTerminalState;
}): Promise<void>;
}
The exact exported names may stay source-compatible through overloads, but the observable contract above is required. The Conformance fixture may be adapted inside the package; no production adapter may invent a second event type.
NativeSessionBackend remains the control-plane-facing session contract. A
package-local backend adapts HarnessDriver/HarnessSession into it, including
the accepted semantic result stored in the harness snapshot. Paperclip core
must not construct CodexAppServerDriver or inspect provider notifications.
Core branch point
The sole execution branch belongs in server/src/services/heartbeat.ts after
the existing environment lease and workspace realization have succeeded and
immediately before adapter.execute(...) today:
const mode = resolveNativeRuntimeMode({ settings, agent, run, issue, target });
await persistResolvedNativeRunEnvelope({ run, issue, mode, contract, target });
const adapterResult = mode.kind === "native"
? await nativeSessionRuntime.execute(
buildNativeExecutionInput({ run, issue, mode, contract, target, session }),
)
: await adapter.execute(existingLegacyExecutionContext);
Everything before the branch remains Paperclip-owned preparation. Shared
cleanup after either branch still owns usage/cost accounting, session and agent
state, audit/live events, environment lease release, runtime service release,
and scratch cleanup. Native mode replaces only the legacy terminal heuristic:
after the existing workspace_finalize barrier, NativeRunFinalizer preserves
the typed result, classifies evidence, builds an immutable assessment, calls
the pure StatusArbiter, and commits the run/issue/liveness effects atomically.
Legacy mode retains the current finalizer byte-for-byte.
The native adapter returns through AdapterExecutionResult with an additive,
strictly validated nativeFinalization discriminator. Existing adapters omit
that field. Native run terminal state is taken only from the persisted run
profile plus that typed discriminator; exit code and model-authored fields in
resultJson are diagnostics and are never native fallback heuristics.
Durable native run envelope and recovery boundary
Standalone uses first-class columns and the Section 18 tables, not keys hidden in
context_snapshot or result_json.
heartbeat_runs gains these exact fields:
runtime_mode text not null default 'legacy'
runtime_mode_resolver_version text null
runtime_mode_reason text null
runtime_mode_resolved_at timestamptz null
runner_profile_json jsonb null -- redacted resolved snapshot
runner_instance_id uuid null
native_session_id uuid null
driver_kind text null
driver_version text null
completion_contract_id uuid null references completion_contracts
completion_contract_sha256 text null
next_event_seq bigint not null default 1
native_phase text null
native_phase_updated_at timestamptz null
The selection transaction locks the run row and writes runtime_mode, the
resolver version/reason/timestamp, redacted profile, realized target identity,
and the immutable completion-contract reference before any native provider, runner,
or harness invocation. runtime_mode='native' without a valid contract/profile
is a fail-closed native setup/finalization error; it never means legacy.
Terminal claims are stored in the normative append-only
native_run_results table (company_id, issue_id, run_id, turn_id,
completion_contract_id, caller IDs, server_fingerprint, schema_status,
rejection_code, the safe original result_json, and canonical_sha256). One
row-locked native_run_finalizations coordinator per run records phase,
attempt, lease, result_id, assessment_id, decision_id, and redacted
failure/retry fields. Receipt of a terminal result is acknowledged only after
the result row and coordinator result_id commit.
The rest of the finalization storage uses the exact Section 18.7 field sets:
| Durable record | Exact fields added/used |
|---|---|
completion_contracts |
id, company_id, issue_id, revision, schema_version, policy_version, risk, completion_authority, incomplete_criteria_policy, contract_json, canonical_sha256, created_by_actor_type, created_by_actor_id, created_at, supersedes_contract_id |
native_run_results |
id, company_id, issue_id, run_id, turn_id, completion_contract_id, caller_result_id, caller_dedupe_key, server_fingerprint, schema_status, rejection_code, result_json, canonical_sha256, created_at |
native_run_finalizations |
run_id, company_id, issue_id, phase, attempt, lease_owner, lease_expires_at, result_id, assessment_id, decision_id, failure_code, failure_detail, next_attempt_at, created_at, updated_at |
work_assessments |
id, company_id, issue_id, run_id, turn_id, contract_id, result_id, trigger_kind, trigger_ref, trigger_capability, trigger_actor_company_id, prior_issue_status, prior_status_version, prior_decision_id, policy_version, assessment_json, input_digest, supersedes_assessment_id, created_at |
status_decisions |
id, company_id, issue_id, assessment_id, decision_version, policy_version, from_status, to_status, reason_code, decision_json, decision_digest, application_state, supersedes_decision_id, applied_at, created_at |
status_decision_effects |
id, company_id, issue_id, decision_id, ordinal, effect_kind, target_type, target_id, idempotency_key, payload, delivery_state, attempt_count, next_attempt_at, last_error, delivered_at, created_at, updated_at |
issues |
new status_version bigint not null default 0 and last_status_decision_id uuid null; existing status/liveness/lock fields remain projections |
Their unique keys, company foreign keys, immutable/supersession rules, and
effect-ledger semantics are unchanged from Section 18.7. heartbeat_runs also
gets only a compact result/finalization read projection in result_json; that
JSON is never the source of truth.
Byte equivalence means equality after the Section 18.8 canonical serializer: UTF-8 JSON, recursively sorted object keys, protocol-order arrays, UTC RFC 3339 timestamps with millisecond precision, lowercase UUIDs, absent optionals omitted, and bounded numeric serialization. Prose is preserved byte-for-byte after schema normalization with no locale/Unicode compatibility folding. The hashed material includes the complete validated envelope and server-bound run/session/turn/contract identity, but excludes transport-only delivery metadata. The server stores SHA-256 of those bytes. Reusing a source event ID, source sequence, caller result ID, or caller dedupe key with another hash is a replay conflict with no acknowledgement or effects. Equivalent retries, including fresh caller keys with the same server fingerprint, return the already committed canonical row and cursor.
Recovery never re-runs the resolver. Startup and periodic reconciliation read
heartbeat_runs.runtime_mode; for native rows they follow
native_run_finalizations.result_id to native_run_results and resume the
first missing durable phase. This remains true when the instance flag or agent
profile has since been disabled, the server restarted, or the runner process
disconnected. A disabled flag affects only runs whose mode has not yet been
resolved (runtime_mode_resolved_at is null).
Feature flag, selection, and kill switch
The global gate is instance.experimental.enableNativeRunner, default false.
It is server/API configurable in the first slice; there is no UI control. The
per-agent opt-in is stored in the existing company-scoped
agents.runtime_config JSON:
{
"nativeRunner": {
"mode": "native",
"backend": "codex_app_server",
"protocolVersion": 1
}
}
The resolver is pure and versioned. Its inputs are the instance flag, the persisted agent profile, the authenticated run/agent/issue records, and the realized execution target. It does not read model output or PRP events.
| Global flag | Agent profile | Eligibility | New run mode |
|---|---|---|---|
| off | any | any | legacy |
| on | absent or legacy |
any | legacy |
| on | native |
eligible | native |
| on | native |
ineligible before selection | rejected before invocation |
Initial eligibility is intentionally narrow: an issue-bound standard run, a
same-company active codex_local agent, protocol version 1, and a local
execution target with an already-realized workspace. Remote environments,
unscoped timer wakes, skill tests, task bridges, low-trust review, and other
drivers remain legacy or are rejected when explicitly misconfigured as native.
The resolved mode, resolver version, and non-secret reason code are persisted on the heartbeat run before invocation. An active run never changes modes.
Turning enableNativeRunner off is the kill switch:
- queued and future runs resolve to legacy;
- an already-selected native run remains native until terminal, drained, or cancelled through the existing run-cancellation path;
- replays and recovery use the persisted mode even while the flag is off;
- no native error may retry the same run through
adapter.execute.
This gives rollback without dual execution or ambiguous event history.
Company, authentication, and threat boundary
The real ControlPlanePort is constructed inside the server with a bound
companyId, runId, issueId, agentId, and sourceInstanceId loaded from
authoritative rows. Every method compares incoming identity fields with that
binding and rejects generically before persistence on any mismatch.
The native branch does not spread or pass the legacy context, runtimeConfig,
AdapterInvocationMeta, or process.env. buildNativeExecutionInput is the
only constructor and returns this closed, strict schema (unknown keys are
rejected at the server/package boundary):
interface NativeExecutionInputV1 {
schema: "paperclip.native-execution-input.v1";
binding: {
companyId: string;
runId: string;
issueId: string;
agentId: string;
executionWorkspaceId: string;
};
task: {
identifier: string;
title: string;
description: string | null;
workMode: "standard";
};
workspace: {
cwd: string;
repoUrl: string | null;
repoRef: string | null;
branchName: string | null;
};
session: {
normalizedSessionId: string | null;
driverKind: "codex_app_server";
protocolVersion: 1;
};
completionContract: {
id: string;
sha256: string;
schemaVersion: string;
contract: StrictCompletionContractInput;
};
interactionResponses: NativeInteractionResponseEnvelope[];
credentialBindings: NativeCredentialBindingRef[];
}
credentialBindings contains only opaque binding IDs, service/destination
policy, expiry, and non-secret display metadata. A broker or trusted launch
boundary may resolve a binding outside the model process; neither gateway
tokens nor secret values are fields in this input. Likewise, interaction
responses are the typed, destination-bound response envelopes from the native
interaction bridge, not a wake payload.
The package then builds a still smaller NativeModelEnvelopeV1 from only
task, the safe workspace location, the completion schema, and applicable
typed interaction responses. Binding IDs, credential binding refs, runner
profile, and driver/session control data stay outside model-visible content.
There is no escape hatch such as extra, metadata, arbitrary context, or
arbitrary env in either schema.
The runner, package driver, and model/harness never receive:
- the local agent JWT,
PAPERCLIP_API_KEY, a board session, or a board API key; - managed MCP gateway credentials, runner-lease/bootstrap credentials, or credential-broker secret material;
PAPERCLIP_WAKE_PAYLOAD_JSON, rendered Paperclip wake text, Paperclip skill instructions, the Paperclip API manual, or run-scoped skill material;- raw
process.env, agent/project/routine env maps,runtimeConfig.env, or the legacy adapter's generic execution context; - authority to choose a company, issue, agent, policy, approval, or status;
- database access or a public issue-mutation endpoint;
- resolved secret values in events, snapshots, diagnostics, or digests.
The native constructor never reads the already-built legacy wake/context object, and the native branch occurs before local-agent JWT construction, managed-MCP gateway materialization, and legacy adapter invocation. Tests seed unique canaries into every forbidden source, recursively inspect the backend launch input and captured model request, and assert that no key, value, serialized canary, or object reference crosses the native boundary.
The server-side port is an in-process capability for the first tracer. A future runner WebSocket must authenticate a one-time runner lease and bind to the same server-owned values; that transport is not part of this issue.
Threats and required responses:
| Threat | Required response |
|---|---|
| Event names another company/run/session | Generic rejection, no lookup disclosure, no write |
| Same source ID or sequence with different bytes | native_event_replay_conflict; fail native run closed |
| Caller supplies status, approval, or policy outcome | Preserve as untrusted payload at most; never apply |
| Event/result contains a known credential | Redact before diagnostics and reject persistence of the unsafe payload |
| Legacy context or raw env reaches native constructor | Validation/test failure before provider invocation |
| Native port or event store is unavailable | Do not acknowledge; runner keeps its durable outbox; fail/recover without legacy fallback |
| Flag is disabled after a run started | Finish/cancel the persisted native mode; never splice legacy execution into it |
Governance and status authority
PRP events and reportedWorkDisposition are reports, not organizational
commands. The Standalone tracer does not add a runner-accessible issue status API.
- Existing board/user issue routes remain authorized organizational command
paths. For native finalization,
StatusDecisionCommitteris the only server-internal status writer and it applies only aStatusArbiterdecision. - Existing execution-policy participants remain the only execution-decision authority.
- Existing approval and interaction services remain the only materialization and decision paths. A native request cannot approve itself or translate an interaction into a formal approval.
- Existing budget checks decide whether dispatch is allowed and existing budget hard-stop cancellation remains authoritative.
- Existing activity logging records flag changes, runtime selection, run terminal state, cancellation, and any later governed mutation.
The native result and reported disposition are preserved as claims. The
additive native finalizer then applies the complete normative Section 18 flow:
immutable CompletionContractService revision, authenticated
NativeResultIngestor, exactly-once workspace finalization,
EvidenceClassifier, immutable WorkAssessmentService snapshot, pure
versioned StatusArbiter, serialized StatusDecisionCommitter, durable effect
ledger, and NativeFinalizationReconciler.
Shadow computation remains mandatory at MIG-04/MIG-05, but it is a rollout stage, not the Standalone scope or exit state. Standalone evidence must proceed through MIG-06 for one allowlisted internal company/agent and prove an applied decision. Rollout can then be disabled again; already-selected native rows still reconcile as native. If the complete corpus or application gate cannot pass, Standalone stays blocked rather than shipping a contradictory shadow-only substitute.
The arbiter may choose done, in_review, blocked, continued in_progress,
or preserve-on-finalization-failure. Non-terminal decisions atomically create
the named reviewer/approval/interaction/delegated-review/monitor, blocker owner
and action, continuation, or recovery action required by Section 18. A reported
done never directly closes an issue. Board/user status races increment
issues.status_version; the losing native finalizer reloads and appends a
superseding assessment instead of overwriting organizational authority.
Existing lifecycle mapping
| Paperclip concern | Standalone mapping | Owner |
|---|---|---|
| Checkout and issue execution lock | Unchanged; resolved before native selection | Existing issue/heartbeat services |
| Budget and pause gate | Unchanged pre-dispatch check | Existing budget/invokability services |
| Workspace preparation | Unchanged; native receives the realized cwd/target only | Environment/workspace orchestrators |
| Session execution | NativeSessionBackend through package-owned runtime loop |
Runner package |
| Event validation/reducer semantics | Accepted PRP schemas and reducer | Runner package |
| Event commit/ACK/replay | Bound ControlPlanePort implementation |
Thin server adapter |
| Cancellation | Existing cancel route invokes a registered native cancel handle, then existing terminal cleanup | Heartbeat + package session |
| Workspace finalization | Existing workspace_finalize barrier after native execute returns |
Heartbeat/workspace services |
| Run terminal state | Validated discriminator after workspace finalization, coordinated with native finalization | Thin adapter + native finalizer |
| Issue status | Section 18 assessment, arbitration, CAS, and atomic liveness effects | Native finalizer + existing issue/governance services |
| Approvals/interactions | Typed native bridge materializes through existing services; unsupported/forged requests fail closed and cannot self-approve | Native interaction bridge + existing governance services |
| Usage/cost/session state | Converted to existing AdapterExecutionResult fields |
Existing heartbeat finalizer |
| Audit/live events | Existing activity and heartbeat event publication | Existing services |
| Lease/runtime/scratch release | Existing finally path |
Existing heartbeat/environment services |
Cancellation seam
The heartbeat service already cancels process-backed adapters through
runningProcesses. Native sessions need one additional run-scoped registry of
idempotent cancel functions. cancelRunInternal, agent pause, budget pause, and
shutdown drain call the same registry before their current status/cleanup
writes. The package implementation performs session.cancel/interrupt and
close; provider process escalation remains package-owned. Registering a
cancel handle does not grant status authority.
Native event persistence and replay
The first slice extends heartbeat_run_events rather than creating a parallel
operator timeline. Nullable native-source columns keep legacy rows unchanged:
source_instance_id
source_event_id
source_seq
source_payload_sha256
protocol_schema_version
Required unique indexes (the source identities are partial):
(run_id, seq)
(run_id, source_event_id) where source_event_id is not null
(run_id, source_instance_id, source_seq)
where source_instance_id is not null and source_seq is not null
Every writer uses one server-owned appendHeartbeatRunEvent transaction.
nextRunEventSeq(max(seq)+1) is removed. The transaction:
- validates the event and bound company/run/agent identity;
- locks the owning
heartbeat_runsrowFOR UPDATE; - checks source-event/source-sequence deduplication before allocating a canonical sequence;
- canonicalizes and hashes the complete event payload;
- inserts with
seq = heartbeat_runs.next_event_seq, then incrementsnext_event_seqin the same transaction; - on a dedup conflict, loads the canonical row and accepts only an equivalent hash without consuming a sequence;
- computes the highest contiguous committed native source sequence; and
- publishes/acknowledges only after commit.
The same allocator is mandatory for lifecycle, stdout/stderr, cancellation,
workspace, adapter, recovery, and native PRP events. Publication happens from
the committed row/outbox, never between allocation and commit. Concurrent
cancel/lifecycle/native appends therefore serialize on the per-run row and the
database unique (run_id, seq) constraint is the final guard.
The expand migration changes event seq and run next_event_seq to bigint.
For each existing run it first preserves the earliest row for every existing
sequence; if historical duplicates exist, it deterministically moves only the
later duplicates (ordered by created_at, id) to fresh values above that run's
old maximum and records duplicate counts in migration verification output. It
then sets next_event_seq = max(seq) + 1 (or 1 for an empty run), installs
the unique constraint, and only then deploys the shared writer. Legacy event
payloads/source-null behavior are unchanged; no synthetic native source fields
or finalization history are backfilled.
Replay reads the same rows by the bound run/source instance, ordered by
source_seq, after an exclusive cursor. A gap is reported and never hidden by
canonical server sequence. Legacy rows have null native-source fields and are
unaffected. Existing /api/heartbeat-runs/:runId/events remains the operator
read path; no new public mutation route is required.
Failure behavior
- Failures before mode persistence use the existing setup-failure path.
- A native eligibility/configuration error never invokes a provider.
- After native mode is persisted, every error stays native and uses a stable
native_*error code; there is no legacy retry for that run. - Missing, inconsistent, or invalid
nativeFinalizationfails the run withnative_finalization_missingornative_finalization_invalid. - Workspace-finalization failure wins over a successful reported result. The report remains persisted for inspection, the run is failed, and issue status is preserved.
- Cancellation is idempotent. Late native completion cannot overwrite a cancelled heartbeat run because the existing conditional terminal write remains authoritative.
- Event replay conflict, company mismatch, auth mismatch, unsafe payload, or acknowledgement ambiguity fails closed and never mutates issue governance.
- A native runtime request that the tracer does not support is declined with
native_runtime_request_unsupported; it is not auto-approved.
Mock-versus-real conformance strategy
The package exports one table-driven ControlPlanePort conformance suite. It
runs unchanged against:
MockControlPlaneAdapter, with deterministic in-memory storage; andPaperclipControlPlanePort, with a real test database, heartbeat run, issue, agent, and company binding.
Both adapters produce a normalized snapshot containing canonical PRP events, source cursors, duplicate dispositions, terminal result, and failure code. The suite compares snapshots while excluding database IDs and wall-clock fields. The real suite adds authorization and transaction assertions that a mock cannot prove. A separate deterministic fake backend drives both ports. Real Codex is a smoke proof only and does not replace the deterministic matrix.
Required shared cases:
- open, append, terminal happy path;
- ordered replay after every cursor;
- identical duplicate event and terminal replay;
- conflicting duplicate ID and conflicting source sequence;
- source gap and recovery after missing event arrives;
- wrong run/session/company/agent identity;
- terminal before result and event after terminal;
- cancellation before turn, during turn, and after terminal;
- connection loss before and after commit acknowledgement.
- concurrent lifecycle, cancellation, native, and log appends with one gap-free canonical server sequence and stable replay after restart;
- terminal-result replay before/after acknowledgement with one canonical
native_run_resultsrow and one finalization coordinator; - process loss and flag disablement after result persistence, proving recovery uses only the persisted native run envelope.
Test matrix
| ID | Concern | Mock | Real Paperclip | Legacy assertion |
|---|---|---|---|---|
| P6-01 | Default flag off | — | yes | adapter.execute called exactly once; no native rows |
| P6-02 | Per-agent opt-in absent | — | yes | Same as current path |
| P6-03 | Eligible native selection | yes | yes | Legacy adapter not called |
| P6-04 | Persisted mode survives flag change | yes | yes | No mid-run mode switch |
| P6-05 | Kill switch before dispatch | — | yes | New run uses legacy once |
| P6-06 | Workspace cwd/branch/identity | yes | yes | Same realized workspace and finalize barrier |
| P6-07 | Workspace finalize failure | yes | yes | Failed run; result preserved; issue unchanged |
| P6-08 | Budget blocked before dispatch | — | yes | Neither native nor legacy provider starts |
| P6-09 | Budget hard stop during native run | yes | yes | One cancel, normal lease/resource cleanup |
| P6-10 | Manual/agent-pause cancellation | yes | yes | Idempotent native cancel and terminal race guard |
| P6-11 | PRP append/ACK/replay | yes | yes | Legacy event rows unchanged |
| P6-12 | Duplicate replay | yes | yes | One semantic event, stable cursor |
| P6-13 | Conflicting replay | yes | yes | Native fails closed; no legacy fallback |
| P6-14 | Cross-company/run/agent forgery | — | yes | Generic denial, zero rows, no disclosure |
| P6-15 | Typed input/credential isolation | yes | yes | JWT, API key, MCP credentials, wake/skills, raw env/context canaries absent from package and model |
| P6-16 | Durable mode/result recovery | yes | yes | Restart/flag-off/process-loss resumes persisted native result and coordinator |
| P6-17 | Canonical allocator concurrency | yes | yes | Lifecycle/cancel/native/log writers produce unique (run_id, seq) and stable replay |
| P6-18 | Migration/backfill | — | yes | Legacy duplicate repair, next_event_seq, bigint, uniqueness, and null native fields proven |
| P6-19 | Terminal result idempotency/conflict | yes | yes | One canonical result/finalization; changed bytes fail closed |
| P6-20 | Reported done with satisfied contract |
yes | yes | Claim stored; server assessment/arbiter/committer applies authoritative done once |
| P6-21 | Incomplete/rejected evidence | yes | yes | Arbiter preserves or creates a valid blocker/review/continuation path; never trusts prose |
| P6-22 | Governance/status forgery | yes | yes | Caller status/policy/approval fields rejected; zero authoritative effects |
| P6-23 | Native interaction bridge | yes | yes | Durable materialization/typed response, no invalid issue status or credential exposure |
| P6-24 | Board/cancel/dependency race | yes | yes | status_version CAS and superseding assessment; no reopen/duplicate effect |
| P6-25 | Missing/invalid native finalization | yes | yes | Native run fails with named recovery; no legacy heuristic |
| P6-26 | Workspace finalization ordering | yes | yes | Workspace success precedes assessment; failure prevents done and preserves claim |
| P6-27 | Non-terminal liveness effects | yes | yes | Reviewer/blocker/continuation/recovery entity commits atomically with status |
| P6-28 | Kill switch during arbitration | yes | yes | Existing native coordinator finishes/reconciles; new run selects legacy |
| P6-29 | Cost/usage/session projection | yes | yes | Existing field meanings unchanged |
| P6-30 | Activity/live/effect ledger | — | yes | Selection/result/decision/effects/cancel audited and published once |
| P6-31 | Section 18.13 corpus | yes | yes | Every SD/TC/ATT/LIVE/REC/COMP/MIG fixture emits a joinable consumer result |
| P6-32 | Full legacy targeted regression | — | yes | Byte-equivalent result/events/UI-read snapshot; zero native history rows |
Exact implementation sequence
- Reconcile
ControlPlanePortwith accepted PRP events/results and add the shared mock conformance suite. No core files change in this step. - Add the package-local
HarnessDrivertoNativeSessionBackendadapter and a deterministic fake-backend executor. Keep provider/process logic packaged. - Expand storage: add first-class run-mode/contract/sequence fields, native
result/finalization/assessment/decision/effect tables,
issues.status_version, and nullable native event-source columns. Run production-shaped migration and legacy snapshot tests before enabling any writer. - Replace every heartbeat event writer with the shared per-run transactional
allocator, backfill
next_event_seq, add unique(run_id, seq), and prove concurrent lifecycle/cancel/native/log appends. - Implement the server-bound
PaperclipControlPlanePortwith constructor binding, canonical-byte deduplication, commit-before-ACK replay, and terminal result idempotency. - Add the default-off instance flag, parse the per-agent profile, materialize immutable completion contracts, and persist the resolved native run envelope before invocation. Test resolver/kill-switch precedence and restart recovery.
- Add strict
NativeExecutionInputV1/NativeModelEnvelopeV1validators and the explicit constructor. Prove forbidden legacy context and credential canaries cannot reach package, driver, model, event, result, log, or digest. - Add
nativeFinalizationtoAdapterExecutionResult, the package executor, native cancel registry, and the single heartbeat branch. Keep legacy JWT, MCP, prompt/context, and adapter invocation construction entirely on the legacy branch. - Implement the Section 18 services: result ingestion, workspace-first native finalization, evidence classification, assessment, pure arbiter, serialized decision/effect commit, interaction bridge, and reconciler. Version every existing issue-status writer before enabling native application.
- Execute MIG-01 through MIG-05 with the unchanged Section 18.13 fixture corpus. Resolve every unexplained shadow divergence; do not waive cases.
- Enable MIG-06 only for one isolated company/agent, run mock and database conformance, governance/status, workspace, budget/cancel, disconnect, terminal replay, and legacy regression proofs, then disable new dispatch.
- Add the package-local tracer/tutorial/evidence and run one safe local Codex task only after deterministic tests pass. Stop for mandatory Security and CTO implementation review before QA or wider opt-in.
Exact files that may change
Package-owned contract, implementation, tests, and evidence
packages/paperclip-runner/package.json
packages/paperclip-runner/src/index.ts
packages/paperclip-runner/src/contracts/control-plane-port.ts
packages/paperclip-runner/src/contracts/native-session-backend.ts
packages/paperclip-runner/src/contracts/types.ts
packages/paperclip-runner/src/mock-core/mock-control-plane-adapter.ts
packages/paperclip-runner/src/mock-core/mock-control-plane-adapter.test.ts
packages/paperclip-runner/src/backends/harness-driver-backend.ts (new)
packages/paperclip-runner/src/backends/harness-driver-backend.test.ts (new)
packages/paperclip-runner/src/conformance/control-plane-port.ts (new)
packages/paperclip-runner/src/conformance/control-plane-port.test.ts (new)
packages/paperclip-runner/src/cli/paperclip-adapter.ts (new)
packages/paperclip-runner/protocol/fixtures/standalone/* (new)
packages/paperclip-runner/docs/standalone-thin-paperclip-adapter.md (new)
packages/paperclip-runner/docs/tutorials/standalone-thin-paperclip-adapter.md
packages/paperclip-runner/docs/tutorials/end-to-end.md
packages/paperclip-runner/docs/index.md
packages/paperclip-runner/docs/architecture.md
packages/paperclip-runner/.paperclip-local/log.md
Paperclip storage, adapter, finalizer, and read seam
server/package.json
packages/adapter-utils/src/types.ts
packages/shared/src/types/instance.ts
packages/shared/src/types/native-finalization.ts (new)
packages/shared/src/validators/instance.ts
packages/shared/src/validators/native-finalization.ts (new)
packages/shared/src/constants.ts
packages/shared/src/feature-catalog.ts
packages/db/src/schema/heartbeat_runs.ts
packages/db/src/schema/heartbeat_run_events.ts
packages/db/src/schema/issues.ts
packages/db/src/schema/completion_contracts.ts (new)
packages/db/src/schema/native_run_results.ts (new)
packages/db/src/schema/native_run_finalizations.ts (new)
packages/db/src/schema/work_assessments.ts (new)
packages/db/src/schema/status_decisions.ts (new)
packages/db/src/schema/status_decision_effects.ts (new)
packages/db/src/schema/index.ts
packages/db/src/migrations/<generated>.sql
packages/db/src/migrations/meta/* (generated only)
server/src/services/instance-settings.ts
server/src/services/heartbeat-run-events.ts (new)
server/src/services/native-runtime/runtime-mode.ts (new)
server/src/services/native-runtime/native-execution-input.ts (new)
server/src/services/native-runtime/paperclip-control-plane-port.ts (new)
server/src/services/native-runtime/native-session-runtime.ts (new)
server/src/services/native-runtime/completion-contracts.ts (new)
server/src/services/native-runtime/native-result-ingestion.ts (new)
server/src/services/native-runtime/native-run-finalizer.ts (new)
server/src/services/native-runtime/evidence-classifier.ts (new)
server/src/services/native-runtime/work-assessments.ts (new)
server/src/services/native-runtime/status-arbiter.ts (new)
server/src/services/native-runtime/status-decision-committer.ts (new)
server/src/services/native-runtime/native-interaction-bridge.ts (new)
server/src/services/native-runtime/native-finalization-reconciler.ts (new)
server/src/services/native-runtime/index.ts (new)
server/src/services/issue-thread-interactions.ts
server/src/services/issues.ts
server/src/services/heartbeat.ts
server/src/routes/issues.ts
server/src/routes/agents.ts
server/src/routes/openapi.ts
server/src/__tests__/native-runner-standalone.integration.test.ts (new)
server/src/__tests__/heartbeat-native-runner-selection.test.ts (new)
server/src/__tests__/heartbeat-native-runner-cancellation.test.ts (new)
server/src/__tests__/heartbeat-run-event-sequencing.test.ts (new)
server/src/__tests__/native-runner-input-boundary.test.ts (new)
server/src/__tests__/native-run-finalizer.test.ts (new)
server/src/__tests__/native-status-arbiter-corpus.test.ts (new)
server/src/__tests__/native-finalization-recovery.test.ts (new)
server/src/__tests__/native-finalization-migration.test.ts (new)
server/src/__tests__/legacy-finalization-regression.test.ts (new)
No other file is pre-approved. The migration owns the universal
status_version increment (a database trigger on an actual status change), so
Standalone does not scatter versioning edits across unrelated writers. The native
committer calls transaction-aware existing issue, blocker, review, interaction,
recovery, wake, activity, and live-event entry points; if one of those files
must change to expose such an entry point, implementation pauses for an
allowlist amendment and CTO review. In particular, Standalone must not change a
concrete legacy adapter, approval authority, browser UI, workspace policy,
budget policy, or activity semantics merely to make the tracer pass.
Remediation allowlist amendment (2026-08-09)
The implementation review found four seam-local paths whose names differed from the provisional list above. They are added without expanding authority:
packages/paperclip-runner/src/native-session-runtime.ts
packages/paperclip-runner/src/native-session-runtime.test.ts
packages/paperclip-runner/src/contracts/native-execution.ts
packages/paperclip-runner/src/contracts/native-execution.test.ts
packages/paperclip-runner/src/backends/codex-native-backend.ts
packages/paperclip-runner/spec/fixtures/status-authority-sdk.json
packages/adapter-utils/package.json
packages/shared/src/index.ts
packages/shared/src/types/index.ts
packages/shared/src/validators/index.ts
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
server/src/services/recovery/service.ts
packages/paperclip-runner/docs/design/standalone-thin-paperclip-adapter.md
The package runtime, closed native-input contract, and Codex backend files own
persisted provider-session recovery and construction. The adapter-utils
metadata declares the package dependency used by the approved native
finalization result type; the shared barrels only expose the already-reviewed
types and validators. The recovery service touch reuses the existing
source-scoped recovery action and wake path rather than creating new authority.
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
server/src/__tests__ matrix entry points. This amendment does not authorize
changes to a concrete legacy adapter, approval authority, UI, workspace policy,
budget policy, or runner/provider/session behavior outside the package.
Remediation 2 test-seam amendment (2026-08-09)
The following already-listed files receive one provider-boundary test seam for the persisted recovery proof:
server/src/services/heartbeat.ts
server/src/services/native-runtime/native-session-executor.ts
server/src/__tests__/native-session-resumption.test.ts
server/src/__tests__/native-status-arbiter-corpus.test.ts
heartbeatService may receive an optional native-backend factory, and the
native session executor may receive the resulting NativeSessionBackend.
Production supplies neither option and therefore still constructs the
package-owned Codex backend exactly as before. The embedded-PostgreSQL test
uses this seam only at the external provider boundary; it still executes the
production orphan reaper, database lease claim, executeRun, package session
recovery, control-plane port, workspace barrier, evidence classifier, status
committer, finalizer, and terminal heartbeat projection. No package contract,
provider/session behavior, legacy adapter, approval authority, workspace
policy, or UI surface changes.
Remediation 3 production-policy and corpus amendment (2026-08-09)
The following production files were already listed in the approved authority seams above. This amendment explicitly permits corpus-conformance vocabulary and return-shape corrections inside those seams; it grants no new authority:
server/src/services/native-runtime/status-arbiter.ts
server/src/services/native-runtime/status-decision-committer.ts
server/src/services/native-runtime/native-run-finalizer.ts
server/src/services/native-runtime/native-interaction-bridge.ts
server/src/services/native-runtime/native-session-executor.ts
server/src/services/native-runtime/native-finalization-reconciler.ts
server/src/services/native-runtime/runtime-mode.ts
The arbiter remains the versioned server-owned policy boundary. The finalizer, attention, cancellation, reconciliation, compatibility, and migration modules expose trigger-specific production consumers of that policy. The committer continues to materialize decisions and effects atomically through existing issue, interaction, wake, recovery, and activity services. This amendment does not authorize changes to package contracts, provider/session behavior, a concrete legacy adapter, approval authority, workspace or budget policy, or UI.
The corpus test remains in its pre-approved matrix path. It creates a fixture-specific database shape and dispatches the fixture to the responsible production consumer. All eleven expected fields are derived from consumer return values and persisted production rows: run status, status/preserve action, reason, required and forbidden effects, live-path kind, claim preservation, native-record behavior, decision count, maximum wake count, and maximum notification count. A separate mutation check changes every expected field for every fixture and must fail. Every one of the 70 matrix row IDs maps to a named finalizer, terminal projection, attention, cancellation, committer, reconciliation, compatibility, or migration consumer, and the row fails if that consumer did not execute or returned different semantics. No hand-written test policy table supplies observed outcomes and no row is satisfied by a coverage-label join to a generic observation.
Remediation 4 live-consumer and effect-materialization amendment (2026-08-09)
The fixture-keyed scenario arbiter is removed. Finalization now calls the same fact-based arbiter as the heartbeat finalizer, while attention, cancellation, reconciliation, compatibility, and migration decisions accept canonical facts and each has a non-test production caller. The corpus constructs those durable facts, invokes the production consumer, and uses that return plus persisted issue, run, decision, interaction, wake, recovery, workspace-operation, and contract rows as its observation.
StatusDecisionCommitter handles every NativeStatusEffect explicitly. Each
case creates or changes its named target before its delivered ledger row is
written. Reconciliation acknowledges a pending effect only after its existing
company- and issue-bound target is verified. An unknown effect or target type
throws inside the transaction, leaving the decision, effect ledger, issue
status/version, and finalization coordinator unchanged. Corpus replay calls the
committer twice and requires one decision identity and exactly one delivery
attempt per target; audit-only attention and replacement-turn paths likewise
assert their real target state.
The 52-fixture/70-row proof therefore depends on runtime-reachable consumer
returns and materialized state. Removing a production consumer invocation,
changing its facts or decision, suppressing its target mutation, duplicating a
delivery, or restoring a synthetic issue_checkout fallback fails the focused
database gate.
Remediation 5 live-entrypoint proof amendment (2026-08-09)
The remediation stays within the approved server authority seams and adds one owning workspace service:
server/src/services/heartbeat.ts
server/src/services/native-runtime/native-session-executor.ts
server/src/services/native-runtime/native-interaction-bridge.ts
server/src/services/native-runtime/native-finalization-reconciler.ts
server/src/services/native-runtime/native-workspace-finalizer.ts
server/src/services/native-runtime/status-decision-committer.ts
server/src/services/native-runtime/runtime-mode.ts
server/src/__tests__/native-status-arbiter-corpus.test.ts
Policy resolvers remain pure and may be tested directly, but their return value
is not operational proof. Cancellation fixtures enter through
cancelNativeSession, which commits the decision or an explicit audit-only
replacement-turn outcome. Attention fixtures enter through
the persisted accepted-result ingress, which calls routeNativeAttention
internally, resolves a same-company eligible delegate, and uses the
company-scoped issue service. REC-04/06/07/08 fixtures enter through
reconcileNativeFinalizations; REC-04 invokes the workspace operation recorder
and observes the actual finalizer result. REC-06/07/08 reclassify the persisted
result and contract into a new append-only assessment before an explicitly
authorized superseding commit; REC-07 cannot be selected by an unrelated work
product. MIG-08 uses the instance-global flag through the production heartbeat selector:
persisted native mode wins for an active run and a fresh unresolved run selects
legacy while the agent profile remains unchanged.
resolveNativeFinalizerStatus, resolveNativeAttentionStatus,
resolveNativeCancellationStatus, resolveNativeReconciliationStatus, the
compatibility/migration resolvers, and their read models are policy evidence
only when called directly. A fixture claiming an operational effect must also
contain the named live-entrypoint receipt and the owning service's durable
target. Pending-effect replay remains acknowledgement-only and is restricted
to the original company, issue, decision, and still-existing target. Negative
tests deliberately remove each live entrypoint/action and the replay target;
the mapped fixture fails even though the corresponding pure resolver still
returns its expected label.
Remediation 6 persisted-attention and global-kill-switch amendment (2026-08-09)
Accepted native attention now enters through the runtime finalization call
graph, not through a corpus-owned call to routeNativeAttention:
PaperclipControlPlanePort.completeRun
-> native_run_results (accepted immutable package result)
-> finalizeNativeRun
-> routePersistedNativeResultAttention
-> recordNativeAttentionAssessment
-> routeNativeAttention
-> StatusDecisionCommitter
-> issueService / issueThreadInteractionService / recovery + activity owners
The persisted-result ingress derives company, issue, run, agent, result, and
contract identity from database bindings. A same-company eligible delegate is
materialized by issueService; a human-authority request is materialized as a
typed issue-thread interaction; an explicit cross-company target is rejected
into an immutable decision, recovery action, failed native finalization, and
activity receipt. The corpus mutates the accepted result row and calls
finalizeNativeRun, which owns the persisted-result ingress. The database
integration test starts one layer earlier at
PaperclipControlPlanePort.completeRun and then calls the same production
finalizer. routePersistedNativeResultAttention and routeNativeAttention
remain internal helpers and are no longer accepted as the operational proof.
Audit-only duplicate and stale requests are a terminal success even though
they intentionally create no status decision. The finalizer accepts that shape
only when every request has an attention_duplicate_suppressed receipt bound
to the exact durable interaction target. It commits the coordinator with a null
decision, projects the successful heartbeat, and preserves issue status and
version. A committed zero-decision coordinator is replayable only while those
durable receipts remain present; replay does not update the named interaction a
second time. Missing or cross-company interaction bindings fail closed into
named finalization recovery.
MIG-08 now represents the actual instance-global transition. The production
heartbeat selector reads the persisted mode first: an already-selected native
run remains native and can finish or reconcile from its coordinator after
experimental.enableNativeRunner turns off. A fresh paperclip_runner start
is rejected with paperclip_runner_rollout_disabled; direct adapters remain on
their existing legacy paths and are never selected into native execution by an
old runtime profile. The agent configuration is not rewritten, and no second
rollback control is persisted. Re-enabling the instance flag therefore restores
normal fresh-run selection without an operator profile repair.
Sabotage changes the production inputs rather than the policy labels: removing
the persisted-result ingress breaks the attention fixtures, and changing the
global flag input from off to on breaks MIG-08 while
resolveNativeAttentionStatus and resolveNativeMigrationStatus continue to
return their pure labels. The full recovery test observes both durable
outcomes: the original native rows reach committed terminal state and the
fresh runner start is rejected before creating native result/finalization rows.
This amendment does not add an attention UI, a public native-attention route,
external-system execution, credential delegation, or auto-approval. The Codex
v1 structured-output surface still emits the compact kind/summary form;
the canonical PRP result can carry richer target metadata, which the server
validates as untrusted routing hints. Richer provider authoring UX and
additional resolver kinds remain deferred.
Remediation 7 authoritative exact-file reconciliation (2026-08-09)
The provisional list and seam amendments above remain the decision history.
For final diff auditing, their authoritative union is the following exact
89-file allowlist. It matches the Standalone branch diff from the parent of the
authorization commit through this remediation, including the package README,
the Section 18.13 checker, package/runtime tests, canonical.ts, and
runtime-mode.test.ts. No file outside this list is authorized as Standalone work.
packages/adapter-utils/package.json
packages/adapter-utils/src/types.ts
packages/db/src/migrations/0211_famous_guardsmen.sql
packages/db/src/migrations/meta/0211_snapshot.json
packages/db/src/migrations/meta/_journal.json
packages/db/src/schema/completion_contracts.ts
packages/db/src/schema/heartbeat_run_events.ts
packages/db/src/schema/heartbeat_runs.ts
packages/db/src/schema/index.ts
packages/db/src/schema/issues.ts
packages/db/src/schema/native_run_finalizations.ts
packages/db/src/schema/native_run_results.ts
packages/db/src/schema/status_decision_effects.ts
packages/db/src/schema/status_decisions.ts
packages/db/src/schema/work_assessments.ts
packages/paperclip-runner/README.md
packages/paperclip-runner/docs/design/standalone-thin-paperclip-adapter.md
packages/paperclip-runner/docs/index.md
packages/paperclip-runner/docs/standalone-thin-paperclip-adapter.md
packages/paperclip-runner/docs/tutorials/standalone-thin-paperclip-adapter.md
packages/paperclip-runner/.paperclip-local/log.md
packages/paperclip-runner/package.json
packages/paperclip-runner/spec/fixtures/status-authority-sdk.json
packages/paperclip-runner/src/backends/codex-native-backend.ts
packages/paperclip-runner/src/backends/harness-driver-backend.test.ts
packages/paperclip-runner/src/backends/harness-driver-backend.ts
packages/paperclip-runner/src/cli/standalone-paperclip.ts
packages/paperclip-runner/src/conformance/control-plane-port.test.ts
packages/paperclip-runner/src/conformance/control-plane-port.ts
packages/paperclip-runner/src/contracts/control-plane-port.ts
packages/paperclip-runner/src/contracts/native-execution.test.ts
packages/paperclip-runner/src/contracts/native-execution.ts
packages/paperclip-runner/src/contracts/native-session-backend.ts
packages/paperclip-runner/src/index.ts
packages/paperclip-runner/src/mock-core/mock-control-plane-adapter.ts
packages/paperclip-runner/src/native-session-runtime.test.ts
packages/paperclip-runner/src/native-session-runtime.ts
packages/shared/src/feature-catalog.ts
packages/shared/src/index.ts
packages/shared/src/types/index.ts
packages/shared/src/types/instance.ts
packages/shared/src/types/native-finalization.ts
packages/shared/src/validators/index.ts
packages/shared/src/validators/instance.ts
packages/shared/src/validators/native-finalization.ts
scripts/check-runner-sdk-spec.mjs
server/package.json
server/src/__tests__/heartbeat-native-runner-cancellation.test.ts
server/src/__tests__/heartbeat-native-runner-selection.test.ts
server/src/__tests__/heartbeat-run-event-sequencing.test.ts
server/src/__tests__/legacy-finalization-regression.test.ts
server/src/__tests__/native-finalization-migration.test.ts
server/src/__tests__/native-finalization-recovery.test.ts
server/src/__tests__/native-interaction-bridge.test.ts
server/src/__tests__/native-run-finalizer.test.ts
server/src/__tests__/native-runner-input-boundary.test.ts
server/src/__tests__/native-runner-standalone.integration.test.ts
server/src/__tests__/native-session-resumption.test.ts
server/src/__tests__/native-status-arbiter-corpus.test.ts
server/src/services/heartbeat-run-events.ts
server/src/services/heartbeat.ts
server/src/services/instance-settings.ts
server/src/services/issues.ts
server/src/services/native-runtime/canonical.ts
server/src/services/native-runtime/completion-contracts.ts
server/src/services/native-runtime/evidence-classifier.test.ts
server/src/services/native-runtime/evidence-classifier.ts
server/src/services/native-runtime/index.ts
server/src/services/native-runtime/native-execution-input.ts
server/src/services/native-runtime/native-finalization-reconciler.ts
server/src/services/native-runtime/native-interaction-bridge.ts
server/src/services/native-runtime/native-result-ingestion.ts
server/src/services/native-runtime/native-run-finalizer.ts
server/src/services/native-runtime/native-session-executor.test.ts
server/src/services/native-runtime/native-session-executor.ts
server/src/services/native-runtime/native-workspace-finalizer.ts
server/src/services/native-runtime/paperclip-control-plane-port.test.ts
server/src/services/native-runtime/paperclip-control-plane-port.ts
server/src/services/native-runtime/runtime-mode.test.ts
server/src/services/native-runtime/runtime-mode.ts
server/src/services/native-runtime/status-arbiter.test.ts
server/src/services/native-runtime/status-arbiter.ts
server/src/services/native-runtime/status-decision-committer.ts
server/src/services/native-runtime/work-assessments.ts
server/src/services/recovery/service.ts
Commands the implementation must make runnable
These commands are the acceptance contract for the implementation issue. They do not exist yet in this design-only task.
Deterministic package/mock proof:
pnpm check:runner-sdk-spec
pnpm --filter @paperclipai/paperclip-runner exec vitest run \
src/conformance/control-plane-port.test.ts \
src/backends/harness-driver-backend.test.ts
pnpm --filter @paperclipai/paperclip-runner trace:standalone -- \
--target mock --scenario happy-path
First real Paperclip tracer and inspection (against an isolated local dev
instance with the five PAPERCLIP_* identifiers/auth variables already set):
pnpm --filter @paperclipai/paperclip-runner trace:standalone -- \
--target paperclip --scenario happy-path
PAPERCLIP_API_BASE="${PAPERCLIP_API_URL%/}"
PAPERCLIP_API_BASE="${PAPERCLIP_API_BASE%/api}"
curl -fsS \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
"$PAPERCLIP_API_BASE/api/heartbeat-runs/$PAPERCLIP_RUN_ID/events?after=0&limit=200" \
| jq '[.[] | select(.sourceEventId != null)] | {count: length, events: map({sourceSeq, sourceEventId, eventType})}'
Targeted real integration proof:
pnpm --filter @paperclipai/server exec vitest run \
src/__tests__/native-runner-standalone.integration.test.ts \
src/__tests__/heartbeat-native-runner-selection.test.ts \
src/__tests__/heartbeat-native-runner-cancellation.test.ts \
src/__tests__/heartbeat-run-event-sequencing.test.ts \
src/__tests__/native-runner-input-boundary.test.ts \
src/__tests__/native-run-finalizer.test.ts \
src/__tests__/native-status-arbiter-corpus.test.ts \
src/__tests__/native-finalization-recovery.test.ts \
src/__tests__/native-finalization-migration.test.ts \
src/__tests__/legacy-finalization-regression.test.ts
pnpm --filter @paperclipai/server exec vitest run \
src/__tests__/heartbeat-run-event-sequencing.test.ts \
-t "serializes concurrent lifecycle cancel native and log writers"
pnpm --filter @paperclipai/server exec vitest run \
src/__tests__/native-status-arbiter-corpus.test.ts \
-t "executes all 52 fixtures in their production consumers"
Legacy fallback proof after disabling the flag:
pnpm --filter @paperclipai/paperclip-runner trace:standalone -- \
--target paperclip --scenario legacy-fallback
pnpm --filter @paperclipai/server exec vitest run \
src/__tests__/native-runner-standalone.integration.test.ts \
-t "uses the unchanged legacy path when the kill switch is off"
The tracer must print a stable JSON summary containing resolvedMode,
runtimeModeResolverVersion, runStatus, reportedWorkDisposition,
nativeResultId, nativeResultSha256, finalizationPhase, assessmentId,
decisionId, authoritativeDecision, issueStatusBefore, issueStatusAfter,
statusVersionBefore, statusVersionAfter, nativeEventCount,
highestContiguousSourceSeq, nextEventSeq, workspaceFinalizeStatus, and
legacyAdapterInvocationCount. It must never print a bearer token, credential
binding value, wake payload, skill instruction, raw environment, or private
host path.
Tutorial outline
The implementation fills in the package-local Standalone tutorial with:
- prerequisites and an isolated local instance;
- mock conformance first;
- flag and one-agent opt-in;
- one safe local native task;
- canonical event replay, persisted mode/result, and coordinator inspection;
- Section 18 assessment/decision/effect and
status_versioninspection; - cancellation, concurrent append, disconnect recovery, and workspace-first finalization checks;
- explicit credential-boundary canary proof;
- kill-switch disablement while persisted native recovery still succeeds;
- a new legacy task proving fallback and zero native rows;
- cleanup and expected stable summaries.
No production credentials or destructive cleanup command belongs in the tutorial.
Acceptance evidence
The package verification run records the git revision, migration revision, exact commands and exit codes, flag/profile scope, and these redacted machine artifacts:
- one Section 18.13 result per
{corpusRevision, gitRevision, fixtureId, consumer}with observed digests and semantic row/effect counts; - migration before/after counts, repaired duplicate event IDs, per-run
next_event_seq, and unique-constraint verification on production-shaped legacy data; - concurrent-writer attempt order plus final canonical event order and the byte-equivalent replay snapshot after a simulated restart;
- typed-input canary inventory and negative package/model/event/result/log/ digest observations (names of forbidden categories, never secret values);
- terminal replay/recovery snapshots before ACK, after ACK, after disconnect, with the flag disabled, and after reconciliation;
- one safe real Codex trace showing workspace finalize, preserved claim, assessment, applied server decision/effects, and zero legacy invocation;
- one new post-kill-switch legacy trace with byte-equivalent legacy projection and zero native contract/result/finalization/decision rows.
The evidence index must call out any expected shadow divergence by fixture ID. An unexplained divergence, missing consumer result, duplicate semantic effect, credential-canary hit, or unjoinable aggregate “suite passed” result blocks the implementation handoff.
Browser UI decision
No browser component changes are required for the Standalone tracer. The Section 18 company-authorized read models/routes for completion contracts, native finalization, assessments/decisions, and compact run/issue summaries plus the package tracer provide inspectable evidence. The flag is server/API-only and default off. Rendering those read models in the board UI remains bound to the separately approved Section 18.12 operator UX gate.
Approval questions
The CTO can approve or reject this record by deciding these five points:
- Is the one-way package dependency and single heartbeat branch narrow enough?
- Is the default-off instance flag plus per-agent opt-in an acceptable first rollout and kill-switch contract?
- Is extending
heartbeat_run_eventswith the shared per-run allocator and unique canonical sequence preferable to a second event store? - Does the restored Section 18 native finalizer/arbitration scope, including the MIG-04/MIG-05 shadow stages and MIG-06 internal application proof, satisfy the normative Standalone gate?
- Are the allowed files, conformance matrix, and exact tracer/fallback commands sufficient to begin implementation?