chore(runner): apply contribution rules with immutable source attestations

Retain Pi profile 24 and bind the updated sandbox source in profile 25. Provider closures and qualification boundaries are unchanged.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 20:34:26 -05:00
1 parent e3a8171679
commit bb765e9009
16 files changed
+85 -47

No files matched your search

+1
View File
@@ -56,6 +56,7 @@ packages/paperclip-runner/test-fixtures/pi-acp/**
!packages/paperclip-runner/test-fixtures/pi-acp/profile-v22-identity.json
!packages/paperclip-runner/test-fixtures/pi-acp/profile-v23-identity.json
!packages/paperclip-runner/test-fixtures/pi-acp/profile-v24-identity.json
!packages/paperclip-runner/test-fixtures/pi-acp/profile-v25-identity.json
!packages/paperclip-runner/docs/capability-contract.md
# check:runner-workflow-traceability access()es every regression test the
# stress-traceability spec names — those are src/**/*.test.ts files, so
+1 -1
View File
@@ -2,7 +2,7 @@
## Current source integration — 2026-10-12
Pi profile 24 (`sha256:d25d674cda9a0853bdbf0521025099f1424c573d2f533630cb67d90e1bfcda3b`)
Pi profile 25 (`sha256:dd67387487dadb08da76534c057dc0aa059f7698d1779cbd5fc452813828167a`)
advances the shared sandbox source attestation after Claude settings initialization
moved behind provider lifetime ownership. Profile 23 remains an immutable
historical identity. Pi's provider executable closures, model/effort options and
@@ -2,7 +2,7 @@
## Current source integration — 2026-10-12
Pi profile 24 (`sha256:d25d674cda9a0853bdbf0521025099f1424c573d2f533630cb67d90e1bfcda3b`)
Pi profile 25 (`sha256:dd67387487dadb08da76534c057dc0aa059f7698d1779cbd5fc452813828167a`)
advances the shared sandbox source attestation after Claude settings initialization
moved behind provider lifetime ownership. Profile 23 remains an immutable
historical identity. Pi's provider executable closures, model/effort options and
+2 -2
View File
@@ -14,12 +14,12 @@
"requiresProviderPolicy": false
},
"pi": {
"agentProfileVersion": 24,
"agentProfileVersion": 25,
"agentServerPackage": "pi-acp",
"agentServerVersion": "0.0.33",
"agentRuntimePackage": "@earendil-works/pi-coding-agent",
"agentRuntimeVersion": "1.0.0",
"commandDigest": "sha256:d25d674cda9a0853bdbf0521025099f1424c573d2f533630cb67d90e1bfcda3b",
"commandDigest": "sha256:dd67387487dadb08da76534c057dc0aa059f7698d1779cbd5fc452813828167a",
"requiresProviderPolicy": true
},
"cursor": {
@@ -3200,7 +3200,7 @@ mod tests {
#[test]
fn pi_profile_matches_published_identity_and_rejects_prior_profiles() {
let published: Value = serde_json::from_str(include_str!(
"../../../../test-fixtures/pi-acp/profile-v24-identity.json"
"../../../../test-fixtures/pi-acp/profile-v25-identity.json"
))
.unwrap();
let mut value = descriptor("codex");
@@ -3217,6 +3217,7 @@ mod tests {
current.validate(&context()).unwrap();
for prior in [
include_str!("../../../../test-fixtures/pi-acp/profile-v24-identity.json"),
include_str!("../../../../test-fixtures/pi-acp/profile-v23-identity.json"),
include_str!("../../../../test-fixtures/pi-acp/profile-v15-identity.json"),
include_str!("../../../../test-fixtures/pi-acp/profile-v16-identity.json"),
@@ -29,7 +29,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option<AcpxReleaseProfile> {
agent_runtime_package: Some("@earendil-works/pi-coding-agent"),
agent_runtime_version: Some("1.0.0"),
command_digest:
"sha256:d25d674cda9a0853bdbf0521025099f1424c573d2f533630cb67d90e1bfcda3b",
"sha256:dd67387487dadb08da76534c057dc0aa059f7698d1779cbd5fc452813828167a",
requires_provider_policy: true,
},
"cursor" => AcpxReleaseProfile {
@@ -17,7 +17,7 @@ use sha2::{Digest, Sha256};
const CODEX_ACPX_DIGEST: &str =
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3";
const PI_ACPX_DIGEST: &str =
"sha256:d25d674cda9a0853bdbf0521025099f1424c573d2f533630cb67d90e1bfcda3b";
"sha256:dd67387487dadb08da76534c057dc0aa059f7698d1779cbd5fc452813828167a";
fn temporary_directory(label: &str) -> PathBuf {
let nonce = SystemTime::now()
@@ -8,8 +8,6 @@ export function resolveClaudeEffort(agent: string, value: unknown): ClaudeEffort
return value;
}
export function createClaudeEffortAdmission(expected: ClaudeEffort) {
// Every connection, including session/load, must select and acknowledge the
// configured effort. A restored preference alone never admits a prompt.
return createAcpxConfigAdmission(expected, { label: "Claude effort", configId: "effort",
supportedValues: ["default", "low", "medium", "high", "xhigh", "max"] });
}
@@ -1699,9 +1699,6 @@ async function silentPrimaryQuorumFixture(
if (failure?.status === "rejected") throw failure.reason;
},
};
// Only fixture preparation may retry. Release the unselected candidates
// immediately before the caller stages once. A foreign bind racing this
// handoff remains a visible production-call failure, never a hidden retry.
const released = await Promise.allSettled(
owned
.filter((_, index) => !occupiedIndices.includes(index))
@@ -2,8 +2,6 @@ function object(value: unknown): Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
}
/** Native mode is independent of permissions. Every connection must prove its
* current mode before a prompt; a persisted ACPX preference is not proof. */
export function createAcpxConfigAdmission(expected: string, options: { label: string; configId: string; supportedValues: readonly string[]; restoring?: boolean; sessionMode?: boolean }) {
type State = { sessionId: string | null; mode: string | null; error: Error | null };
let current: State | null = null;
@@ -17,8 +15,6 @@ export function createAcpxConfigAdmission(expected: string, options: { label: st
const configs = Array.isArray(result.configOptions) ? result.configOptions.filter(value => object(value).id === options.configId) : [];
if (configs.length !== 1) throw failure("native mode configuration is missing or ambiguous");
const mode = object(configs[0]).currentValue;
// Before exact model selection, Pi can legitimately report a different
// native model's level. Observation alone never admits the first prompt.
if (typeof mode !== "string" || !options.supportedValues.includes(mode)) throw failure("native mode is unsupported");
if (session && options.sessionMode && object(result.modes).currentModeId !== mode) throw failure("native mode acknowledgements conflict");
return mode;
@@ -56,16 +52,12 @@ export function createAcpxConfigAdmission(expected: string, options: { label: st
pending.set(message.id, { method, sessionId: params.sessionId, selectsMode: params.configId === options.configId });
} else if (options.sessionMode && method === "session/set_mode") {
if (!state.sessionId || params.sessionId !== state.sessionId || params.modeId !== expected) throw failure("mode control changes the selected mode");
// This method only returns {}; require a later config/session
// acknowledgement rather than treating that empty result as proof.
state.mode = null;
}
return;
}
if (message.method === "session/update" && object(params.update).sessionUpdate === "config_option_update") {
const update = object(params.update);
// Config updates may contain only model options. A mode entry is
// authoritative observation, but never an admission acknowledgement.
if (Array.isArray(update.configOptions) && update.configOptions.some(value => object(value).id === options.configId)) {
if (state.sessionId && params.sessionId !== state.sessionId) throw failure("mode update belongs to a different session");
const observed = readMode(update, false);
@@ -22,12 +22,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = {
"protocolVersion": 1,
"acpxVersion": "0.13.1",
"agent": "pi",
"agentProfileVersion": 24,
"agentProfileVersion": 25,
"agentServerPackage": "pi-acp",
"agentServerVersion": "0.0.33",
"agentRuntimePackage": "@earendil-works/pi-coding-agent",
"agentRuntimeVersion": "1.0.0",
"commandDigest": "sha256:d25d674cda9a0853bdbf0521025099f1424c573d2f533630cb67d90e1bfcda3b",
"commandDigest": "sha256:dd67387487dadb08da76534c057dc0aa059f7698d1779cbd5fc452813828167a",
"permissionPolicy": "interactive"
},
"cursor": {
@@ -104,11 +104,19 @@ describe("Pi installation factory", () => {
expect(current.commandDigest).not.toBe(prior.commandDigest);
});
it("retains profile 24 while binding the comment-free sandbox source in profile 25", async () => {
const read = async (version: number) => JSON.parse(await readFile(new URL(`../../../test-fixtures/pi-acp/profile-v${version}-identity.json`, import.meta.url), "utf8"));
const prior = await read(24), current = await read(25);
expect(Object.keys(current.declaration).filter(key => JSON.stringify(current.declaration[key]) !== JSON.stringify(prior.declaration[key])).sort())
.toEqual(["agentProfileVersion", "runtimeSandboxSourceSha256"]);
expect(current.commandDigest).not.toBe(prior.commandDigest);
});
it("binds the profile declaration to the reviewed patch and platform closure pins", async () => {
const hash = (bytes: string | Uint8Array) => createHash("sha256").update(bytes).digest("hex");
const canonical = (value: any): string => Array.isArray(value) ? `[${value.map(canonical).join(",")}]`
: value && typeof value === "object" ? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}` : JSON.stringify(value);
const identity = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v24-identity.json", import.meta.url), "utf8"));
const identity = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v25-identity.json", import.meta.url), "utf8"));
const declaration = identity.declaration;
expect(declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1");
expect(identity.commandDigest).toBe(`sha256:${hash(canonical(declaration))}`);
@@ -139,21 +147,21 @@ describe("Pi installation factory", () => {
it("rejects legacy profiles and tampered executable identities", () => {
expect(() => assertPiInstallationProfile(candidate())).not.toThrow();
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 13 })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 14 })).toThrow("version 24");
for (const priorVersion of [15, 16, 17, 18, 19, 20, 21, 22, 23]) expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: priorVersion })).toThrow("version 24");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 13 })).toThrow("version 25");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 14 })).toThrow("version 25");
for (const priorVersion of [15, 16, 17, 18, 19, 20, 21, 22, 23, 24]) expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: priorVersion })).toThrow("version 25");
for (const changed of [{ agentServerVersion: "latest" }, { commandDigest: `sha256:${"0".repeat(64)}` }, { agentRuntimePackage: "ambient-pi" }]) {
expect(() => assertPiInstallationProfile({ ...candidate(), ...changed })).toThrow("trusted declaration");
}
@@ -483,8 +483,6 @@ export class AcpxRuntimeHost {
});
}
options.signal?.throwIfAborted();
// Await this write under ownership even if cancellation arrives. Releasing
// the lease before it settles could let a late write affect the next owner.
await sandbox.initializeOwnedConfiguration?.();
options.signal?.throwIfAborted();
assertAcpxProfileEnvironment(options.agent, sandbox.launchEnvironment);
@@ -53,7 +53,6 @@ export interface AcpxRuntimeSandbox {
cacheDirectory: string;
agentHomeDirectory: string;
workspaceRecordPath: string;
/** Mutate provider settings only while the host holds its lifetime lease. */
initializeOwnedConfiguration?: () => Promise<void>;
launchEnvironment: Readonly<NodeJS.ProcessEnv>;
persistedEnvironment: Readonly<NodeJS.ProcessEnv>;
@@ -405,9 +404,6 @@ export async function prepareAcpxRuntimeSandbox(input: {
`${JSON.stringify({
model: input.binding.requestedModel,
availableModels: [input.binding.requestedModel],
// Claude reloads effort from settings on a new ACP connection. Keep
// the bound choice available during model verification and resume;
// the protocol guard still requires a provider acknowledgement.
...(input.binding.claudeEffort ? { effortLevel: input.binding.claudeEffort } : {}),
...((input.binding.permissionMode === "approve-reads" || input.binding.permissionMode === "approve-paperclip")
? { permissions: { allow: claudePaperclipPermissionRules(input.tools ?? [], input.binding.permissionMode) } }
@@ -1766,9 +1766,6 @@ describe("Capability live runnerd and Codex session", () => {
expect((await store.load(replacement.id))?.status).toBe("suspended");
});
// This case includes a deliberate 500 ms killed turn, a resumed turn with
// its own 5-second bound, and durable writes. The enclosing test must not
// expire at the resumed turn's deadline before those assertions can finish.
it("durably resumes a distinct attempt, preserves partial usage, and deduplicates the semantic effect", async () => {
const state = providerState();
const directory = await mkdtemp(join(tmpdir(), "capability-live-durable-"));
@@ -0,0 +1,50 @@
{
"commandDigest": "sha256:dd67387487dadb08da76534c057dc0aa059f7698d1779cbd5fc452813828167a",
"declaration": {
"schema": "paperclip.acpx_profile_declaration.v1",
"agent": "pi",
"agentProfileVersion": 25,
"acpxVersion": "0.13.1",
"agentServerVersion": "0.0.33",
"agentRuntimeVersion": "1.0.0",
"nodeVersion": "24.21.0",
"closure": {
"darwin-arm64": "282022db10150c6632b3444df421342e7d534bdf5d5fb1097a2e79d0625a2bcf",
"darwin-x64": "64e251e19009f755c0b04f73ce2138246faab71a961b0f13d75ebfcc34bef12e",
"linux-x64": "713b1fdff42fb56a1518bdc084f181d70bee8ebadc3e4b1d76321ed9108c8410"
},
"wrapperSha256": "9d129b3d38772e93e97080aa6c4e574ac5df4e47ce5bc331a484a9fbf8188b36",
"helperSha256": "41e0490b617da0d60e0c8ec58ef311236f945129d99f816cff6897f78da7f91d",
"extensionSha256": "f5818573a355702388072b28926897db546a5cf346b5171f646582ce5f9102a4",
"agentFilesBinding": "registered-runtime-context-v1",
"nativeQuestions": "select-confirm-input-editor-v4-root-field-types",
"pathPolicy": "sdk-normalization-v1",
"mcpToolNames": "provider-safe-exact-source-v1",
"acpxPatchSha256": "00d6af17216cb0cad902b48633eb19bf0ec6b05837a984cb24661fca593f09c1",
"nativeAssistantMessages": "pi1-rpc-deltas-v3-bound-message-and-tool-receipts",
"nativeNotices": "session-extension-v2-bounded-runtime-failure",
"messageProjectionSha256": "17d1e91b948dfa6a56e1905645add52b502041ad843c8d50a13b07956f22aadd",
"noticeProjectionSha256": "014485adc690998e395d334ea3ae67f72be6060a2fc78a1185df7b8d1da28fbf",
"sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1",
"nativeInputDisposition": "queued-only-v1",
"cacheWarming": "disabled-owned-decision-v1",
"thinkingLevel": {
"supported": [
"off",
"low",
"high",
"max"
],
"newConfigurationDefault": "low",
"admission": "explicit-native-effective-before-prompt-v1",
"recovery": "mode-bound-v1"
},
"modelSelection": "caller-selected-native-verified-v1",
"providerConfigurationSourceSha256": "d5b4c1a426afc85f040c9b55358d1d645299586dc8cec72d1be2509187ca3865",
"credentialEnvironmentSourceSha256": "f8dc40ea1aa894bd44754a35a8f65fbcaa2f4c7ffc88cc5fdaa965e0d378d9bc",
"runtimeSandboxSourceSha256": "144d62b3415cfedf272b5b052e14af1e41fe50a644af84247fc9c20c7ef7edde",
"recoveryIdentitySourceSha256": "8479e04da1ae2f7742e34f045b0270b99e9c3ae39a5b8fb9f164e5776d83224c",
"nativeCancellation": "acknowledged-cancelled-terminal-preserves-usage-v1",
"dependencySecurityPatchSha256": "5273a195b952f233336122842faaf650193c04f36b9359d20d4d63079b0ce814"
}
}