Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eleven-words-feel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/brunch": patch
---

Record controller takeovers before retaining immutable comparison evidence.
11 changes: 10 additions & 1 deletion .pi/prompts/compare-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ Send only the approved shared framing. Do not substitute a raw binary, another m

After every lane terminates, first capture its final process status and all target-visible interaction evidence. Kill any still-running executor process, query the shell to a final status, dismiss the completed shell record, and verify that no executor process or interactive session remains.

Record every controller takeover immediately in `<attempt-staging>/intervention-ledger.json`, including a
mechanical selection of an already-proposed action. The ledger has schema `{ "schemaVersion": 1,
"interventions": [...] }`; each intervention uses the `ExecutionAttempt` index, kind, description, and
timestamp fields. The visible-interaction summary must be derived from this ledger rather than independently
authored. No unledgered controller takeover is permitted.

Then run the unchanged controller-owned oracle identity returned by `inspect` against the retained output:

```sh
Expand Down Expand Up @@ -163,10 +169,13 @@ Stage evidence under the fresh attempt path, construct an `ExecutionAttempt` JSO
```sh
npx tsx src/dev/execution-comparison-operator.ts retain-attempt \
--attempt-file <attempt-staging>/attempt.json \
--intervention-ledger <attempt-staging>/intervention-ledger.json \
--attempts-root .fixtures/scratch/execution-comparisons/<run-id>/attempt-records
```

The immutable writer rejects an existing attempt id. Never overwrite, delete, replace, or silently retry a poor-output attempt. A replacement is permitted only for provider, adapter, or mechanical invalidity under the unchanged packet, and both attempts remain retained.
The immutable writer rejects an existing attempt id and rejects a ledger that does not exactly match the attempt's
intervention list. Never overwrite, delete, replace, or silently retry a poor-output attempt. A replacement is
permitted only for provider, adapter, or mechanical invalidity under the unchanged packet, and both attempts remain retained.

## Report

Expand Down
9 changes: 7 additions & 2 deletions src/dev/execution-comparison-operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import process from 'node:process';
import { fileURLToPath } from 'node:url';

import {
assertExecutionAttemptInterventionLedger,
parseExecutionAttempt,
writeExecutionAttemptImmutable,
} from './execution-comparison/artifact-contract.js';
Expand Down Expand Up @@ -275,9 +276,13 @@ export async function runExecutionComparisonOperatorCli(args: readonly string[])
return;
}
case 'retain-attempt': {
assertOnlyOptions(options, ['attempt-file', 'attempts-root']);
const value = JSON.parse(await readFile(resolve(required(options, 'attempt-file')), 'utf8')) as unknown;
assertOnlyOptions(options, ['attempt-file', 'attempts-root', 'intervention-ledger']);
const attemptPath = resolve(required(options, 'attempt-file'));
const interventionLedgerPath = resolve(required(options, 'intervention-ledger'));
const value = JSON.parse(await readFile(attemptPath, 'utf8')) as unknown;
const attempt = parseExecutionAttempt(value);
const interventionLedger = JSON.parse(await readFile(interventionLedgerPath, 'utf8')) as unknown;
assertExecutionAttemptInterventionLedger(attempt, interventionLedger);
const attemptsRoot = resolve(required(options, 'attempts-root'));
await mkdir(attemptsRoot, { recursive: true });
const stored = await writeExecutionAttemptImmutable(attemptsRoot, attempt);
Expand Down
26 changes: 26 additions & 0 deletions src/dev/execution-comparison/__tests__/artifact-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { join } from 'node:path';
import { describe, expect, it } from 'vitest';

import {
assertExecutionAttemptInterventionLedger,
parseExecutionAttempt,
writeExecutionAttemptImmutable,
type ExecutionAttempt,
Expand Down Expand Up @@ -119,4 +120,29 @@ describe('execution comparison attempt artifact contract', () => {
}),
).toThrow('invalid execution attempt');
});

it('rejects an intervention ledger that does not exactly match the immutable attempt', () => {
const selected = attempt('success');
const ledger = {
schemaVersion: 1,
interventions: [
{
index: 1,
kind: 'mechanical',
description: 'controller selected the confirmed action',
at: '2026-07-20T12:05:00.000Z',
},
],
} as const;

expect(() => assertExecutionAttemptInterventionLedger(selected, ledger)).toThrow(
'execution intervention ledger does not match attempt',
);
expect(
assertExecutionAttemptInterventionLedger(selected, { schemaVersion: 1, interventions: [] }),
).toEqual({
schemaVersion: 1,
interventions: [],
});
});
});
14 changes: 14 additions & 0 deletions src/dev/execution-comparison/__tests__/operator-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,17 @@ describe('execution comparison oracle CLI', () => {
}
});
});

describe('execution comparison attempt retention CLI', () => {
it('requires an intervention ledger before reading or retaining an attempt', async () => {
await expect(
runExecutionComparisonOperatorCli([
'retain-attempt',
'--attempt-file',
'missing-attempt.json',
'--attempts-root',
'attempt-records',
]),
).rejects.toThrow('missing required option --intervention-ledger');
});
});
46 changes: 46 additions & 0 deletions src/dev/execution-comparison/artifact-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ export interface ExecutionAttempt {
};
}

export interface ExecutionInterventionLedger {
readonly schemaVersion: 1;
readonly interventions: readonly {
readonly index: number;
readonly kind: 'mechanical';
readonly description: string;
readonly at: string;
}[];
}

export function parseExecutionAttempt(value: unknown): ExecutionAttempt {
if (!record(value)) invalid();
const budget = child(value, 'budget');
Expand Down Expand Up @@ -144,6 +154,29 @@ export function parseExecutionAttempt(value: unknown): ExecutionAttempt {
return value as unknown as ExecutionAttempt;
}

export function assertExecutionAttemptInterventionLedger(
attempt: ExecutionAttempt,
value: unknown,
): ExecutionInterventionLedger {
if (
!record(value) ||
value['schemaVersion'] !== 1 ||
!interventionRecords(value['interventions'], Infinity)
) {
throw new Error('invalid execution intervention ledger');
}
const ledger = value as unknown as ExecutionInterventionLedger;
if (
ledger.interventions.length !== attempt.interventions.length ||
!ledger.interventions.every((intervention, index) =>
sameIntervention(intervention, attempt.interventions[index]),
)
) {
throw new Error('execution intervention ledger does not match attempt');
}
return ledger;
}

export async function writeExecutionAttemptImmutable(
attemptsRoot: string,
value: ExecutionAttempt,
Expand Down Expand Up @@ -211,6 +244,19 @@ function interventionRecords(value: unknown, budget: number): boolean {
);
}

function sameIntervention(
left: ExecutionInterventionLedger['interventions'][number],
right: ExecutionAttempt['interventions'][number] | undefined,
): boolean {
return (
right !== undefined &&
left.index === right.index &&
left.kind === right.kind &&
left.description === right.description &&
left.at === right.at
);
}

function gitRange(range: unknown, baseSha: unknown, reviewSha: unknown): boolean {
if (range === 'not_assessable') return reviewSha === 'not_assessable';
return (
Expand Down
Loading