diff --git a/doc/architecture/runner-pi-capabilities.md b/doc/architecture/runner-pi-capabilities.md index 7876c5cba45..044fd4e195f 100644 --- a/doc/architecture/runner-pi-capabilities.md +++ b/doc/architecture/runner-pi-capabilities.md @@ -97,6 +97,76 @@ Pi's published CLI mode union is `text | json | rpc`; no native ACP mode is present in this release. The reviewed `pi-acp` wrapper remains necessary. +## Explicit Linux companion setup for a Mac controller + +A Mac controller needs independently verified Linux runner bytes for Daytona. +Its host Pi installation is not a Linux provider pack. The operator imports a +release companion once, before launching agents: + +```sh +paperclipai runtime import-remote /path/to/release/linux-x64 --sha256 MANIFEST_SHA256 +``` + +Obtain `MANIFEST_SHA256` from the trusted release channel, separately from the +copied directory. The directory contains `companion.json`, +`bin/paperclip-runnerd`, and the complete `provider-pack/`. Import verifies the +installed server's exact build commit, qualified Pi profile, Linux x64 ELF, +manifest digest, and every file, mode, directory and contained symbolic link. +External hardlinks, escaping links, extra or modified files, and unsafe modes +are rejected. This command performs no network requests or model calls and does +not run the Linux executable on the Mac. + +The destination is the private `remote-companions/linux-x64` directory beneath +the actual installed `@paperclipai/server` package. It must be writable by the +operator, outside the task workspace, and protected with the same host access +controls as the server installation. It is not an agent workspace or a remote +image's self-reported authority. An existing different or damaged installation +is never replaced automatically. Stop all runs before an operator removes or +upgrades that cache. Verification and copying use asynchronous 1 MiB chunks, +yield between chunks, and enforce the same full inventory and ten-minute bound. +Directory entries must retain owner read/write/search permissions so failed imports +can drain their owned cleanup. SIGINT/SIGTERM are deferred through owned filesystem +phases and drain cleanup; +an uncatchable kill or host failure leaves the import unadmitted until the +operator resolves its retained lock or incomplete directory. Do not move the +cache into a task workspace to work around permissions. + +Normal remote Pi resolves this authority without binary or pack environment +overrides. The controller re-verifies its complete inventory and uses the exact +Linux daemon identity. Existing remote verification compares image bytes against +that local authority; a mismatch takes the existing verified upload path. +Controller restart and warm-session recovery retain the original Runner artifact +identity checks. Cursor, Copilot and explicit operator overrides keep their +existing admission rules. + +For release maintainers, assemble the Linux daemon and default provider pack +for the **same final commit** as the public server/CLI packages. A reused daemon +requires complete native source/config/protocol input equality and retains its +original compiler/build provenance; the manifest does not claim recompilation. In a fresh +release directory place those outputs at `bin/paperclip-runnerd` and +`provider-pack/`, preserving the pack's relative links and modes. Then run the +source-owned manifest generator after building the matching server: + +```sh +node scripts/create-runner-remote-companion.mjs /path/to/release/linux-x64 FINAL_SOURCE_SHA +``` + +Publish the complete companion directory (or distribute it through the normal +trusted release channel) together with the printed manifest SHA256 and the +source/profile/platform provenance. The import command accepts an already +extracted directory, not an archive or URL. The release companion must remain +available with that release; a short-lived qualification artifact is not a +release distribution. No release publication is implied by the tests here. + +Build/publish the Daytona image from those same daemon/pack outputs and configure +its immutable OCI digest through the ordinary Daytona environment `image` field. +The image digest and companion manifest are output metadata, not new source +constants, so publication does not require another source commit. The final live +proof must use the installed public CLI, this import command, and ordinary image +configuration without E2E remote binary/provider-pack overrides. That installed +and paid proof remains pending. + + Historical v10 qualification checkpoint (2026-09-30): **Pi profile v10 remains unqualified.** The capped-key/login prerequisite remains blocked. Native USD is unknown. The optional private budget helper is not integrated; Cursor's account-cycle cap does not establish Pi's provider spending bound. V10 keeps the native wrapper and closures unchanged, binds the shared ACPX patch under a new digest, and rejects v9 sessions. Fresh exact-runtime admission and final controller verification remain pending. Historical v9 checkpoint (2026-09-30): **Pi profile v9 remains unqualified**. Current runtime source is `5b8e4454ef0bf12d0bb068c2e41d8c9df9356a1c`; controller/Product harness source is `40064d28522de25fea85c1297f35b41bb8a8897a`. Runtime builds for macOS ARM64/x64 and Linux x64 are complete. No paid profile-v9 pass is claimed. A credential with a verifiable spend limit is still needed for the remaining paid qualification. The optional transport-budget candidate is frozen on a separate branch and is not integrated or a live spending guarantee. See the [comparative capability report](runner-rich-acp-capabilities.md) for current qualification gates and the field audit. The dated observations below retain their original profile identities. @@ -209,8 +279,8 @@ addressed by v4. Version 2 hello completion and every failure remain retained. The wider local and Linux x64 Daytona matrix remains pending; this document does not promote the candidate to a qualified production runtime. -The runner pins `pi-acp@0.0.33` and -`@earendil-works/pi-coding-agent@0.84.2`. The candidate model is +At this historical pre-1.0 checkpoint, the runner pinned `pi-acp@0.0.33` and +`@earendil-works/pi-coding-agent@0.84.2`. The candidate model was `openrouter/deepseek/deepseek-v4-flash-0731`; only an explicitly bound OpenRouter credential may reach this profile. `patches/pi-acp@0.0.33.patch` repairs the ACP wrapper. `pi-runtime-extension.ts` supplies the runner-owned semantic bridge and @@ -985,3 +1055,37 @@ that ID unchanged into the shared MCP dedupe boundary. This is a further integration defect. The shared dedupe guard and canonical grader remain intact. Both runs lack terminal usage; measured billing is $0.002440082 and cleanup passes. Restart and refreshed hello are held until this defect is repaired. + +## Explicit host installation + +For a published local Paperclip installation, run `paperclipai runtime setup pi` +with the same installed CLI and account that owns the server package. This is an +explicit download and verification step; npm installation and agent launch never +perform it automatically. It installs only this host's supported platform +(macOS ARM64, macOS x64, or Linux x64), using the source-pinned Node archive, npm +lock, wrapper patch and complete Pi closure. Node 24, npm, git, tar and the normal +platform dependency inspector (`otool` or `ldd`) must be available. The server +package must be writable by the installing account. Explicit setup preserves +`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, their lowercase equivalents, +`SSL_CERT_FILE`, `SSL_CERT_DIR` and +`NODE_EXTRA_CA_CERTS` for public downloads, and enables Node's environment proxy +handling. The provisioner keeps the same closed allowlist. Instance settings, +provider credentials, user npm configuration, `HOME`, `NODE_OPTIONS` and +`NODE_PATH` are excluded; TLS certificate validation stays enabled. + +The public server carries a self-contained setup tool and small pinned inputs in +`dist/vendor/paperclip-runner/cli`; the installed host closure lives in that +server package's `provider-assets/pi/`. Setup validates an existing +closure again before accepting it. A corrupt existing installation is left +untouched and rejected; reinstall the same Paperclip release into a clean package +location and repeat setup. Concurrent setup is rejected. Cancellation drains the +current bounded download/build command before removing its private staging tree; +allow that cleanup to complete before trying again. + +Then select Pi with an explicit provider/model ID and bind its provider credential +through the normal agent environment configuration. Setup itself makes no model request. +A missing host closure produces explicit setup guidance. Daytona uses the +separately built and verified Linux provider pack in its runner image; running +local setup does not install or qualify a remote image. Published-tar local and +Daytona startup evidence must bind the final installation candidate, with no +candidate qualification flags, before a production-readiness claim. diff --git a/doc/architecture/runner-rich-acp-capabilities.md b/doc/architecture/runner-rich-acp-capabilities.md index c1334de585b..1232e16ceca 100644 --- a/doc/architecture/runner-rich-acp-capabilities.md +++ b/doc/architecture/runner-rich-acp-capabilities.md @@ -22,6 +22,67 @@ for the exact source/image evidence and merge status. All dated checkpoints below describe historical qualification attempts. Their failures and profile numbers are retained; they are not current release gates. +## Historical Pi 1.0 qualification checkpoint — October 2, 2026 + +Pi now uses `@earendil-works/pi-coding-agent@1.0.0`, `pi-acp@0.0.33`, +ACPX `0.13.1`, and profile **12**. Its exact model remains +`openrouter/deepseek/deepseek-v4-flash-0731`. Cursor v10 and Copilot v12 +remain gated while Pi qualification is completed. The held Pi admission branch +is preparation for testing normal installation; it is not production qualification. + +The first paid Pi 1.0 local hello on profile 11 failed: Pi's serialized RPC +message updates no longer carry the old SDK-shaped partial message. That failure +is retained, with $0.000142518 observed on the dedicated OpenRouter key and +complete owned-process cleanup. Profile 12 repairs the actual RPC boundary; +its local-only tests exercise the real Pi CLI, wrapper and owned extension. +They do not replace authenticated product tests. + +At runtime source `b9e5d6ecdb05ab7244c90976e07c950f8d09b15b`, the fresh +macOS ARM64 daemon and verified pack pass the original no-key startup test +(6.741 seconds to settlement) and all 48 native/ACP contract tests without skips. +The original evidence collector rejected Node 24's summary format after the +runtime checks had passed. Its failure is preserved; independently reviewed +offline finalization verifies those same logs and full asset inventories without +rerunning the tests. The fresh native Intel build also passed all 48 contract +tests, but its canonical startup failed at the unchanged 30-second deadline +(35.032 seconds to settlement). A later instrumented run passed at 22.959 seconds; +it used a read-verified, warmed filesystem state and does not erase the original +failure. A four-run I/O-pool comparison was inconclusive, so no production +thread-pool change was adopted. Intel startup and the final installed-package +local/Daytona tests remain separate gates. + +Testing the actual public package installation exposed a packaging defect: +ambient npm 10 pruned 84 files from the pinned Pi dependency closure. Setup now +uses npm 11.19.0 bundled in the independently verified Node archive. The repaired +provisioner reproduces the original profile-12 closure with ambient npm disabled; +fresh public-package installation proof remains required. + +Normal Daytona setup now has an explicit operator command to import a verified +Linux companion into the installed server. It binds the trusted manifest to the +installed source, Pi profile, daemon and complete provider-pack inventory, then +uses the ordinary remote execution path without test-only binary or pack +overrides. Its 31 focused tests cover asynchronous bounded verification, +cancellation, existing-cache validation, contained paths and owned cleanup. +These tests do not establish live Daytona qualification or release publication. + +Full repository typecheck, tests, token gates, Product E2E typecheck/unit checks, +and build passed on the earlier source `efe019a79f50440d7bd6c3bc6c75fb8f18953093`. +Its Runner verification also passed. These results are historical prerequisites; +profile 12 and the final installation changes still need their own verification. + +The dedicated Pi test key has a $5 lifetime OpenRouter-credit limit. Its BYOK +charges are not included in that provider-enforced limit. Qualification therefore +also requires the reviewed operational policy verifying no configured BYOK +credentials, fresh account/key evidence, one paid case at a time, and usage +monitoring. The key's $5 reservation is counted once within the combined $100 +campaign budget. Native price estimates are never substituted for provider bills. + +The [Pi capability inventory](runner-pi-capabilities.md#historical-pi-10-candidate-2026-10-02-profile-v12) +records Pi 1.0's native interfaces, wrapper changes, and unused capabilities. +The comparison below remains the supported-surface map; older paid observations +retain their named historical profiles. Current Pi 1.0 paid Product, Runner +protocol, native controls, and Daytona qualification are still pending. + Historical source checkpoint (2026-10-01): **Cursor v10, Copilot v12 and Pi v10 remain unqualified**. Copilot receipt v2 distinguishes original provider arguments from the validated outgoing completion input. The sidecar commits @@ -73,7 +134,7 @@ as `25303cf84953985b961b95f89aff0bdb864b2d65`, from head unchanged; this adds no paid proof. Those definitions remain the historical Cursor v8 checkpoint and require a separate reviewed Cursor v9 update. -Current checkpoint (2026-09-30): **Cursor v9, Copilot v8 and Pi v10 remain unqualified.** The frozen runtime is `5c69b69ef2aeab8d8a367b25a8d894cc5308befa`; controller candidate is `a8df2064d68f40fbf4dec670c4b8478c4b1b1b3f`. All profiles bind shared ACPX patch SHA-256 `bd5393058a218040d217fa85449d59a6f30507de54cd645bf0ef21422823f85e`. No profile is promoted and no prior grade is changed. +Historical checkpoint (2026-09-30): **Cursor v9, Copilot v8 and Pi v10 remain unqualified.** The frozen runtime is `5c69b69ef2aeab8d8a367b25a8d894cc5308befa`; controller candidate is `a8df2064d68f40fbf4dec670c4b8478c4b1b1b3f`. All profiles bind shared ACPX patch SHA-256 `bd5393058a218040d217fa85449d59a6f30507de54cd645bf0ef21422823f85e`. No profile is promoted and no prior grade is changed. Current-profile protocol eval definitions merged in [paperclip-evals #36](https://github.com/paperclipai/paperclip-evals/pull/36) as `d987357461933baca0d4c10cc081f40e7eae5c1b`: 136 deterministic tests and 21 validated cells. These definitions do not supply paid qualification evidence. @@ -808,7 +869,7 @@ qualification gates, not claims that a JavaScript path check confines a shell. | P1 | User attachments and image prompting | `AcpxRuntimeTurnInput` and the common runtime adapter currently forward text only, despite underlying image-input support. Implement validated attachment-to-ACP content conversion and model-specific capability admission, then qualify real local/Daytona image prompts. This is an implementation gap as well as a live-verification gap. | | P1 | Copilot native session event attribution | `_session_event` omits an originating turn. Preserve bounded event fields as session-scoped notices with `turnAttribution: unknown`; typed delegation, artifacts and compaction need an explicit native correlation contract before turn-owned projection. Standard correlated ACP events remain separate. | | P1 | Copilot session-store files and export/artifact URIs | Provider paths are not task-workspace paths. Add a separately authorized export flow with validated bytes and provenance; do not resolve arbitrary URLs or auto-register. | -| P1 | Pi native fork/history/export interfaces | Pinned Pi 0.84.2 native RPC exposes `fork(entryId)`, `clone`, `get_fork_messages` and `export_html`. The wrapper does not map them to runner controls. Add durable branch lineage for fork/clone and an authorized, contained artifact flow for HTML export before exposing them; do not label these native capabilities absent. | +| P1 | Pi native fork/history/export interfaces | Pi native RPC exposes `fork(entryId)`, `clone`, `get_fork_messages` and `export_html`; these remain available in the pinned 1.0.0 release. The wrapper does not map them to runner controls. Add durable branch lineage for fork/clone and an authorized, contained artifact flow for HTML export before exposing them; do not label these native capabilities absent. | | P1 | Complete usage/billing provenance | Missing cache fields remain unknown. Pi price estimates are displayed separately. Budget qualification requires actual spend coverage, not an estimate presented as a bill. | | P1 | Fork/history and richer configuration controls | Cursor session mode now has explicit admission-bound setup. Arbitrary session discovery/forking, mid-turn configuration changes and the parameterized model picker still need company-scoped controls and durable lineage. | | P1 | Exact pending-request restoration after process death | Session transcript restoration does not restore callbacks. Expire unresolved requests unless a provider proves exact restoration. | diff --git a/packages/db/src/backup-lib.test.ts b/packages/db/src/backup-lib.test.ts index de3166714cc..c625208eacc 100644 --- a/packages/db/src/backup-lib.test.ts +++ b/packages/db/src/backup-lib.test.ts @@ -7,6 +7,7 @@ import postgres from "postgres"; import { createBufferedTextFileWriter, runDatabaseBackup, runDatabaseRestore } from "./backup-lib.js"; import { ensurePostgresDatabase } from "./client.js"; import { + EMBEDDED_POSTGRES_TEST_TIMEOUT_MS, getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase, } from "./test-embedded-postgres.js"; @@ -105,7 +106,7 @@ describeEmbeddedPostgres("runDatabaseBackup", () => { if (previous === undefined) delete process.env.PAPERCLIP_PG_DUMP_PATH; else process.env.PAPERCLIP_PG_DUMP_PATH = previous; } - }); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); it("preserves identity generation, sequence options and progress in JavaScript backups", async () => { const source = await createTempDatabase(); diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts index 857109376d3..9f3a8b92cd3 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts @@ -6450,11 +6450,11 @@ it("rotates PRP authority in place for a warm cross-run attachment", async () => it("reopens Pi after a completed turn within its cold admission budget during warm attachment", async () => { const root = await mkdtemp(join(tmpdir(), "runnerd-pi-warm-admission-")); const cliRoot = join(root, "dist/cli"); - await mkdir(cliRoot, { recursive: true }); + await mkdir(cliRoot, { recursive: true, mode: 0o700 }); const sidecarPath = join(cliRoot, "acpx-runtime-sidecar.cjs"); const journal = join(root, "commands.ndjson"); const fixture = await readFile(fileURLToPath(new URL("./fixtures/fake-pi-warm-sidecar.cjs", import.meta.url)), "utf8"); - await writeFile(sidecarPath, fixture.replace("/* fixture-config */ null", JSON.stringify({ journal, resumeDelayMs: 32_000, commandDigest: QUALIFIED_ACPX_PROFILES.pi.commandDigest }))); + await writeFile(sidecarPath, fixture.replace("/* fixture-config */ null", JSON.stringify({ journal, resumeDelayMs: 32_000, commandDigest: QUALIFIED_ACPX_PROFILES.pi.commandDigest })), { mode: 0o600 }); const digest = (path: string) => `sha256:${createHash("sha256").update(readFileSync(path)).digest("hex")}`; const bundle = createCapabilityRunnerdCodexTransport({ provider: "acpx", acpxAgent: "pi", piThinkingLevel: "low", acpxPermissionMode: "deny-all",