fix(runner): preserve ACPX command attestation
This commit is contained in:
parent
d2c9977065
commit
5835c98675
|
|
@ -283,7 +283,10 @@ async function dispatch(
|
|||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
return {
|
||||
identity: acpxProviderSessionIdentity(openedHost.identity()),
|
||||
identity: acpxProviderSessionIdentity(
|
||||
openedHost.identity(),
|
||||
openedHost.binding(),
|
||||
),
|
||||
sidecarPid: process.pid,
|
||||
status: opened.status,
|
||||
};
|
||||
|
|
@ -398,7 +401,10 @@ async function dispatch(
|
|||
if (request.command === "session.read") {
|
||||
const activeHost = requireHost();
|
||||
return {
|
||||
identity: acpxProviderSessionIdentity(activeHost.identity()),
|
||||
identity: acpxProviderSessionIdentity(
|
||||
activeHost.identity(),
|
||||
activeHost.binding(),
|
||||
),
|
||||
status: sanitizeRuntimeStatus(
|
||||
await readSidecarHostStatusWithin(activeHost),
|
||||
),
|
||||
|
|
@ -407,7 +413,10 @@ async function dispatch(
|
|||
if (request.command === "session.snapshot") {
|
||||
const activeHost = requireHost();
|
||||
return {
|
||||
identity: acpxProviderSessionIdentity(activeHost.identity()),
|
||||
identity: acpxProviderSessionIdentity(
|
||||
activeHost.identity(),
|
||||
activeHost.binding(),
|
||||
),
|
||||
status: sanitizeRuntimeStatus(
|
||||
await readSidecarHostStatusWithin(activeHost),
|
||||
),
|
||||
|
|
@ -426,7 +435,10 @@ async function dispatch(
|
|||
// still serialized, and retainActiveHostCleanup keeps admission closed
|
||||
// until one sequential close proves ownership was released.
|
||||
const activeHost = requireHost({ allowCleanupRetry: true });
|
||||
const identity = acpxProviderSessionIdentity(activeHost.identity());
|
||||
const identity = acpxProviderSessionIdentity(
|
||||
activeHost.identity(),
|
||||
activeHost.binding(),
|
||||
);
|
||||
await closeSidecarHostForCommand(
|
||||
activeHost,
|
||||
boundedOptionalText(request.params.reason, "Paperclip suspension", 4_000),
|
||||
|
|
|
|||
|
|
@ -42,13 +42,13 @@ describe("ACPX recovery identity", () => {
|
|||
normalizedSessionId: "session-1",
|
||||
permissionMode: "approve-reads",
|
||||
});
|
||||
expect(acpxProviderSessionIdentity(record)).toEqual({
|
||||
expect(acpxProviderSessionIdentity(record, fixture.binding)).toEqual({
|
||||
kind: "acpx",
|
||||
normalizedSessionId: "session-1",
|
||||
acpxRecordId: fixture.expected.acpxRecordId,
|
||||
backendSessionId: fixture.expected.backendSessionId,
|
||||
agentSessionId: fixture.expected.agentSessionId,
|
||||
profileDigest: fixture.binding.profileDigest,
|
||||
profileDigest: fixture.binding.commandDigest,
|
||||
workspaceDigest: fixture.binding.workspaceDigest,
|
||||
requestedModel: fixture.binding.requestedModel,
|
||||
effectiveModel: fixture.binding.effectiveModel,
|
||||
|
|
@ -140,7 +140,7 @@ describe("ACPX recovery identity", () => {
|
|||
{
|
||||
...fixture.expected,
|
||||
normalizedSessionId: otherBinding.normalizedSessionId,
|
||||
profileDigest: otherBinding.profileDigest,
|
||||
profileDigest: otherBinding.commandDigest,
|
||||
workspaceDigest: otherBinding.workspaceDigest,
|
||||
},
|
||||
otherBinding,
|
||||
|
|
@ -176,17 +176,6 @@ describe("ACPX recovery identity", () => {
|
|||
expect(() =>
|
||||
verifyExpectedAcpxIdentity(fixture.expected, fixture.binding, earlyV1),
|
||||
).toThrow(/persisted runtime record/);
|
||||
expect(() =>
|
||||
verifyExpectedAcpxIdentity(
|
||||
{
|
||||
...fixture.expected,
|
||||
profileDigest: fixture.input.profile.commandDigest,
|
||||
},
|
||||
fixture.binding,
|
||||
earlyV1,
|
||||
),
|
||||
).toThrow(/immutable session configuration/);
|
||||
|
||||
const changedBinding = await createAcpxRecoveryBinding({
|
||||
...fixture.input,
|
||||
profile: {
|
||||
|
|
@ -197,11 +186,12 @@ describe("ACPX recovery identity", () => {
|
|||
expect(changedBinding.profileDigest).not.toBe(
|
||||
fixture.binding.profileDigest,
|
||||
);
|
||||
expect(changedBinding.commandDigest).toBe(fixture.binding.commandDigest);
|
||||
expect(() =>
|
||||
verifyExpectedAcpxIdentity(
|
||||
{
|
||||
...fixture.expected,
|
||||
profileDigest: changedBinding.profileDigest,
|
||||
profileDigest: changedBinding.commandDigest,
|
||||
},
|
||||
changedBinding,
|
||||
earlyV1,
|
||||
|
|
@ -275,7 +265,7 @@ async function recoveryFixture() {
|
|||
acpxRecordId: "record-1",
|
||||
backendSessionId: "backend-1",
|
||||
agentSessionId: "agent-1",
|
||||
profileDigest: binding.profileDigest,
|
||||
profileDigest: binding.commandDigest,
|
||||
workspaceDigest: binding.workspaceDigest,
|
||||
requestedModel: binding.requestedModel,
|
||||
effectiveModel: binding.effectiveModel,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export interface AcpxRecoveryBinding {
|
|||
workspacePath: string;
|
||||
workspaceDigest: string;
|
||||
runtimeRoot: string;
|
||||
commandDigest: string;
|
||||
profileDigest: string;
|
||||
requestedModel: string;
|
||||
effectiveModel: string;
|
||||
|
|
@ -87,6 +88,7 @@ export async function createAcpxRecoveryBinding(input: {
|
|||
workspacePath,
|
||||
workspaceDigest,
|
||||
runtimeRoot,
|
||||
commandDigest: input.profile.commandDigest,
|
||||
profileDigest,
|
||||
requestedModel: input.requestedModel,
|
||||
effectiveModel: input.requestedModel,
|
||||
|
|
@ -117,19 +119,25 @@ export function createAcpxIdentityRecord(
|
|||
/** Project the private persisted record into the PRP sidecar wire identity. */
|
||||
export function acpxProviderSessionIdentity(
|
||||
record: AcpxIdentityRecord,
|
||||
binding: AcpxRecoveryBinding,
|
||||
): AcpxExpectedSessionIdentity {
|
||||
return {
|
||||
const identity: AcpxExpectedSessionIdentity = {
|
||||
kind: "acpx",
|
||||
normalizedSessionId: record.normalizedSessionId,
|
||||
acpxRecordId: record.acpxRecordId,
|
||||
backendSessionId: record.backendSessionId,
|
||||
agentSessionId: record.agentSessionId,
|
||||
profileDigest: record.profileDigest,
|
||||
// The PRP provider contract historically names this field
|
||||
// `profileDigest`, but it attests the qualified executable digest. Keep
|
||||
// the broader immutable-profile digest private in the persisted record.
|
||||
profileDigest: binding.commandDigest,
|
||||
workspaceDigest: record.workspaceDigest,
|
||||
requestedModel: record.requestedModel,
|
||||
effectiveModel: record.effectiveModel,
|
||||
permissionMode: record.permissionMode,
|
||||
};
|
||||
verifyExpectedAcpxIdentity(identity, binding, record);
|
||||
return identity;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -146,7 +154,7 @@ export function verifyExpectedAcpxIdentity(
|
|||
validateExpected(expected);
|
||||
if (
|
||||
expected.normalizedSessionId !== binding.normalizedSessionId ||
|
||||
expected.profileDigest !== binding.profileDigest ||
|
||||
expected.profileDigest !== binding.commandDigest ||
|
||||
expected.workspaceDigest !== binding.workspaceDigest ||
|
||||
expected.requestedModel !== binding.requestedModel ||
|
||||
expected.effectiveModel !== binding.effectiveModel ||
|
||||
|
|
|
|||
|
|
@ -448,7 +448,7 @@ export class AcpxRuntimeHost {
|
|||
kind: "acpx",
|
||||
normalizedSessionId: binding.normalizedSessionId,
|
||||
...runtimeIdentity,
|
||||
profileDigest: binding.profileDigest,
|
||||
profileDigest: binding.commandDigest,
|
||||
workspaceDigest: binding.workspaceDigest,
|
||||
requestedModel: binding.requestedModel,
|
||||
effectiveModel: binding.effectiveModel,
|
||||
|
|
|
|||
Loading…
Reference in New Issue