Skip to content

Commit 8d13cb4

Browse files
mchwangclaude
andcommitted
Pass the runner owner token into execution deps
RunnerDeps now carries runnerOwner (#74), which D requires on every invocation and checks against the task storage owner. executionDeps takes the database's token and the workspace allocates storage under it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 47e6f2e commit 8d13cb4

2 files changed

Lines changed: 13 additions & 7 deletions

File tree

‎runner/execution.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ export const SAFETY_VIOLATION = 'Safety violation:';
3636
export interface ExecutionResult { head: string; unchanged: boolean; inScope: string[]; outOfScope: string[] }
3737
interface Private { workspace: WorkspaceRef; prompt: string; baseHead: string; linkSnapshot: unknown }
3838

39-
/** RunnerDeps for execute attempts: fresh workspace, prompt, agent, then audit and the runner's own commit. */
40-
export function executionDeps(store: Store, workspace: TaskWorkspace, launch: AgentLauncher, sources: ExecutionSources): RunnerDeps {
39+
/**
40+
* RunnerDeps for execute attempts: fresh workspace, prompt, agent, then audit and the runner's own commit.
41+
* `runnerOwner` is the database's runner token (`Store.runnerOwnerToken`); `workspace` must allocate task storage under it.
42+
*/
43+
export function executionDeps(store: Store, workspace: TaskWorkspace, launch: AgentLauncher, sources: ExecutionSources, runnerOwner: string): RunnerDeps {
4144
const identityOf = (attempt: AttemptRecord): PlanIdentity => findIdentity(store, attempt);
4245
return {
46+
runnerOwner,
4347
async prepare(attempt, signal) {
4448
if (attempt.kind !== 'execute' || !attempt.item) throw new Error('Execution deps run execute attempts for one plan item.');
4549
const identity = identityOf(attempt), plan = store.getPlan(identity, attempt.context.planRevision);

‎test/runner-execution.test.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { InvocationResult } from '../agents/contract.ts';
1010
import type { Plan, PlanContext } from '../core/plan.ts';
1111

1212
const oid = (n: number) => n.toString(16).padStart(40, '0');
13+
const RUNNER_OWNER = '0123456789abcdef0123456789abcdef';
1314
const identity = { repositoryId: 'repo', taskId: 'task', planId: 'plan' };
1415
const plan: Plan = { schema_version: 1, issue: 1, revision: 1, summary: 'Two items', questions: [], items: [
1516
{ id: 'P1', title: 'First', intent: 'Change a', files: [{ path: 'a.ts', kind: 'edit', renamed_from: null, change: 'x' }], acceptance: [{ type: 'cmd', text: 'npm test' }], depends_on: [] },
@@ -47,19 +48,19 @@ function setup(options: { manifests?: Record<string, ChangeManifest & { digest:
4748
},
4849
};
4950
const sources: ExecutionSources = { planContext: () => context, issue: () => ({ number: 1, title: 'Issue', body: 'Please fix', comments: [] }), lessons: () => [], vendor: () => 'claude' };
50-
const prompts: string[] = [], argv: (readonly (readonly string[])[])[] = [];
51+
const prompts: string[] = [], argv: (readonly (readonly string[])[])[] = [], owners: string[] = [];
5152
const deps = executionDeps(store, workspace, (input, prompt, ws) => {
52-
log.push(`start ${itemOf(ws)}`); prompts.push(prompt); argv.push(input.approvedArgv);
53+
log.push(`start ${itemOf(ws)}`); prompts.push(prompt); argv.push(input.approvedArgv); owners.push(input.runnerOwner);
5354
return { attemptId: input.attemptId, settled: Promise.resolve({ attemptId: input.attemptId, context: input.context, exitCode: 0, signal: null, stdout: 'done', stderr: '', ...options.exit?.[itemOf(ws)] }), cancel: () => undefined };
54-
}, sources);
55+
}, sources, RUNNER_OWNER);
5556
const runner = new RunnerCoordinator(store, deps);
5657
cleanups.push(async () => { await runner.close(); store.close(); });
57-
return { store, runner, executor: new ItemExecutor(store, runner, sources), log, commits, prompts, argv };
58+
return { store, runner, executor: new ItemExecutor(store, runner, sources), log, commits, prompts, argv, owners };
5859
}
5960

6061
describe('item execution', () => {
6162
it('runs items in order, commits each with trailers, and records owned ledger entries', async () => {
62-
const { store, executor, log, commits, prompts, argv } = setup();
63+
const { store, executor, log, commits, prompts, argv, owners } = setup();
6364
expect(await executor.runTask(identity)).toEqual({ kind: 'executed', items: ['P1', 'P2'], unchanged: [] });
6465
expect(commits.map(c => [c.item, c.baseHead.slice(-3), c.trailers, c.paths, c.message])).toEqual([
6566
['P1', '002', { 'Plan-Item': 'P1', 'Plan-Revision': 'r1' }, ['a.ts'], 'P1: First'],
@@ -72,6 +73,7 @@ describe('item execution', () => {
7273
'materialize P2 @064', 'snapshot P2 []', 'start P2', 'inspect P2 @064', 'commit P2 -> 065', 'release P2 after completed']);
7374
expect(prompts[0]).toContain('<plan_item_data>');
7475
expect(argv[0]).toEqual([['npm', 'test']]);
76+
expect(owners[0]).toBe(RUNNER_OWNER);
7577
expect(argv[1]).toEqual([]);
7678
});
7779
it('reports a planned-but-unchanged item without committing', async () => {

0 commit comments

Comments
 (0)