Verify Pi native extension admission against pinned runtime

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-28 15:14:57 -05:00
1 parent 927efd0699
commit ea9eb3f85e
2 files changed
+89 -4

No files matched your search

+6 -4
View File
@@ -79,7 +79,8 @@ skill paths. Project trust is denied; user-bash RPC and terminal login are disab
Only the controlled `/compact`, `/session`, and `/autocompact` slash commands are
accepted. Startup package update checks are disabled.
Pi can continue after a broken extension. Therefore the wrapper probes
The wrapper must not infer complete extension registration from process startup.
It therefore probes
`get_commands` and requires the exact readiness sentinel
`paperclip-runtime-ready-v1` / `Paperclip runtime gate v1`. The extension registers
this command only after gates and every MCP catalog finish initialization. A
@@ -114,9 +115,10 @@ installed pinned package; an absent or unpatched dependency is a failure.
`PAPERCLIP_TEST_PI_ACP_PACKAGE` can select a separately installed pinned fixture.
These fixtures invoke no model or paid API.
A no-key probe against the real Pi 0.84.2 CLI loaded the compiled owned extension
and returned the exact readiness command. This checks the extension ABI and
explicit loading path. It does not prove authenticated inference, native tool
`test/pi-native-package-contract.test.mjs` runs the real Pi 0.84.2 CLI without
credentials. It loads the compiled owned extension, initializes a loopback MCP
catalog, and returns the exact readiness command. An invalid catalog exits Pi
before RPC admission. This checks the extension ABI and explicit loading path. It does not prove authenticated inference, native tool
execution, MCP model invocation, credential renewal, or remote sandbox behavior.
Those remain required local and Daytona qualification evidence under the shared
live-spend cap. No live inference spend was incurred by these deterministic tests.
@@ -0,0 +1,83 @@
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { once } from "node:events";
import { createServer } from "node:http";
import { mkdtemp, mkdir, readFile, realpath, rm, writeFile } from "node:fs/promises";
import { stripTypeScriptTypes } from "node:module";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { StringDecoder } from "node:string_decoder";
import { fileURLToPath } from "node:url";
import test from "node:test";
// This test runs real pinned Pi RPC without issuing a prompt or loading keys.
const packageRoot = process.env.PAPERCLIP_TEST_PI_RUNTIME_ROOT
?? dirname(dirname(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))));
const metadata = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
const extensionSource = await readFile(new URL("../src/drivers/acpx/pi-runtime-extension.ts", import.meta.url), "utf8");
for (const brokenCatalog of [false, true]) {
test(`real pinned Pi ${brokenCatalog ? "withholds" : "registers"} readiness after MCP initialization`, { timeout: 20_000 }, async (t) => {
assert.equal(metadata.version, "0.84.2");
const root = await realpath(await mkdtemp(join(tmpdir(), "paperclip-pi-native-contract-")));
await mkdir(join(root, "workspace")); await mkdir(join(root, "agent"));
const extension = join(root, "extension.mjs");
await writeFile(extension, stripTypeScriptTypes(extensionSource));
const calls = [];
const server = createServer(async (request, response) => {
assert.equal(request.headers.authorization, "Bearer 0123456789abcdef0123456789abcdef");
let body = ""; for await (const chunk of request) body += chunk;
const rpc = JSON.parse(body); calls.push(rpc.method);
response.setHeader("Content-Type", "application/json");
const result = rpc.method === "initialize"
? { protocolVersion: "2025-03-26", capabilities: { tools: {} }, serverInfo: { name: "fixture", version: "1" } }
: { tools: [{ name: brokenCatalog ? "invalid/name" : "report_progress", description: "Report progress", inputSchema: { type: "object", properties: { text: { type: "string" } }, required: ["text"] } }] };
response.end(JSON.stringify({ jsonrpc: "2.0", id: rpc.id, result }));
});
server.listen(0, "127.0.0.1"); await once(server, "listening");
const port = server.address().port;
const child = spawn(await realpath(process.execPath), [join(packageRoot, "dist/cli.js"), "--mode", "rpc", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-themes", "--no-approve", "--offline", "-e", extension], {
cwd: join(root, "workspace"), stdio: "pipe",
env: {
PATH: process.env.PATH, HOME: root, PI_CODING_AGENT_DIR: join(root, "agent"), PI_SKIP_VERSION_CHECK: "1", PI_TELEMETRY: "0",
PAPERCLIP_PI_RUNTIME_CONFIGURATION: JSON.stringify({
workspace: join(root, "workspace"), readOnly: true, readRoots: [], protectedRoots: [join(root, "agent")], instructions: "Use the assigned tools",
servers: [{ type: "http", name: "paperclip", url: `http://127.0.0.1:${port}/mcp`, headers: [{ name: "Authorization", value: "Bearer 0123456789abcdef0123456789abcdef" }] }],
}),
},
});
let stderr = ""; child.stderr.on("data", (chunk) => { stderr += chunk; });
t.after(async () => {
child.stdin.end();
if (child.exitCode === null) { const timer = setTimeout(() => child.kill("SIGKILL"), 2000); await once(child, "exit"); clearTimeout(timer); }
server.closeAllConnections(); await new Promise((resolve) => server.close(resolve));
await rm(root, { recursive: true, force: true });
});
const admission = new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`Pi RPC admission timed out: ${stderr}`)), 15_000);
const decoder = new StringDecoder("utf8"); let buffered = "";
child.stdout.on("data", (chunk) => {
buffered += decoder.write(chunk);
for (;;) {
const at = buffered.indexOf("\n"); if (at < 0) break;
const line = buffered.slice(0, at); buffered = buffered.slice(at + 1); if (!line.trim()) continue;
const value = JSON.parse(line);
if (value.type === "response" && value.id === "admission") { clearTimeout(timer); resolve(value); }
}
});
child.once("error", (error) => { clearTimeout(timer); reject(error); });
child.once("exit", () => { clearTimeout(timer); reject(new Error(`Pi RPC exited before admission: ${stderr}`)); });
child.stdin.write(JSON.stringify({ type: "get_commands", id: "admission" }) + "\n");
});
if (brokenCatalog) {
await assert.rejects(admission, /Pi RPC exited before admission: Error: Failed to load extension/);
assert.deepEqual(calls, ["initialize", "tools/list"]);
return;
}
const result = await admission;
assert.equal(result.success, true);
assert.deepEqual(calls, ["initialize", "tools/list"]);
const readiness = result.data.commands.find((command) => command.name === "paperclip-runtime-ready-v1");
assert.equal(readiness?.description, "Paperclip runtime gate v1");
});
}