Repository navigation
fix(seats): superviseRun passes the run's actor, task and pod to the trace store - #2736
Conversation
…trace store The supervisor started every run with only runId, adapter and label, so its own run.started entry, and any entry an adapter or the valet emitted without naming an actor, recorded actor: null, and no entry carried the board task or pod. superviseRun now takes taskId, podId and actor and hands them to store.startRun, which already validates them (a taskId without a podId is refused before anything spawns). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ENddpGzhE7S8qjDSYAMuu
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
lilyshen0722
left a comment
There was a problem hiding this comment.
#2736 security gate at ba85bf9 (base feat/jellymoon-record)
Scope: 2 files, packages/seats/src/supervisor.js (+12/-2) and packages/seats/test/supervisor.test.js (+39). No prior gate on this PR; read in full.
Verdict: PASS. No defect that changes the trust boundary. One non-blocking note below.
What changed
superviseRun now forwards taskId, podId and actor to store.startRun. Before, every run-owned entry (run.started, tool calls, permission requests, emits with no actor) was written with actor: null and no task or pod. The store already validates all three, so the change only moves values that were previously dropped into the record.
Checked and holds
startRunrefuses ataskIdwith nopodIdbefore the run file is created (trace/store.js:99), and the supervisor calls startRun before the adapter spawns, so no adapter starts. The new test "a taskId without a podId is refused before the adapter is spawned" assertsspawned === false.actorgoes throughvalidateActor(record/format.js:202 and the actorProblem checks above it): the type, id and owner shape,onBehalfOfrestricted to user ids and roles, and the result frozen. Anything malformed throws before the run file is created.- The run's actor is used only for entries that name no actor (defaultActor, 'run' case). It does not change which actor a given entry carries when one is set.
- No in-repo caller of superviseRun passes
actor,taskIdorpodIdyet (git grep over packages, backend and cli at this head: only the export and the definition). The values come from whichever seat wrapper calls it, the same party that already controls the run. - Redaction: entry
datais still scrubbed with the run's secrets (writeLine, store.js:119). This PR does not touch scrubbing.
Non-blocking (name it, do not hold on it)
taskIdandpodIdhave no shape check instartRun. They are written verbatim to the envelope of every line (store.js:119 scrubsdataonly). In practice they are board ids, so this is hygiene, not an exposure. A future caller that passes a secret astaskIdwould get it into the hash-chained record unscrubbed. Suggest a token-shape check on both at the boundary, in a follow-up.actor.ownerandactor.onBehalfOfare caller-asserted. The record now says whatever the caller says about who a run is for. That is the intended attribution (the caller is the seat), but it is a claim, not a verified fact, and nothing in this PR says so. Worth one sentence in record-v1.md when the first real caller lands.
Tests (run at ba85bf9 in a temp worktree, removed after)
node --test test/supervisor.test.js: 23/23 pass.npm testin packages/seats: 191/191 pass.- Reverted supervisor.js to ba85bf9^: the two new tests fail (21 pass, 2 fail), so they do guard the change.
Not checked here
- CI at this head (not read).
- Sable goes after this gate, per the ask.
Written by Rhea, a Commonly agent — https://commonly.me/v2/pods/6a8f6dc7a1dccf2e02f31015
GATE: PASS
lilyshen0722
left a comment
There was a problem hiding this comment.
Sable code-leg gate at ba85bf9. The PR is stacked on feat/jellymoon-record, so I read its own commit, which touches two files: packages/seats/src/supervisor.js (+13/-2) and packages/seats/test/supervisor.test.js (+37).
What it does: superviseRun now accepts taskId, podId and actor and passes them to store.startRun. The run's own run.started entry and any emit that names no actor now record the run's actor, and every entry carries the task and pod. startRun already validates these: a taskId without a podId throws before anything is written.
Checked:
- The
startRuncall fires before the adapter spawns. The new "a taskId without a podId is refused before the adapter is spawned" test confirms the refusal and thatspawnnever runs. - The actor fallback is in
appendEntrythrough{ runActor }, and the store'srunActoris null when no actor is passed. Existing callers that pass no new fields are unchanged, since the new parameters are optional. - Clean worktree at ba85bf9, Node 22:
supervisor.test.js23/23. Fullpackages/seatssuite, everytest/*.test.jsfile: 191 of 191, matching Lily's container count. - Mutant: I reverted the
startRuncall to{ runId, adapter, label }. Two tests fail: "the run's actor, task and pod reach the record…" and "a taskId without a podId is refused…". I restored the file and confirmed it withdiff -q.
Non-blocking: the new test that checks byType['agent.message_chunk'].actor relies on the emit-side fallback. It passes, so the fallback is exercised, but a future change to appendEntry would surface there rather than in a direct unit test.
Written by Sable, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf
GATE: PASS
The branch carried pre-merge copies of the record line (supervisor, record-v1, grants, valet, trace) that reached main through #2739, #2701, #2736, #2763 and #2766. Every conflict in a file touched on this side only by those commits takes main's reviewed version: valet/env.js, valet/grant.js, record/format.js, record-v1.md, supervisor.js and their tests, fake-provider, the seats README, the seats-package test and the cli lock file. Main already carries every record kind the ACP commits added (pod-member, session.opened, agent.retry, sandbox.applied, egress.*, onBehalfOf). Three files carry ACP additions main lacks and are resolved as unions: - failures.js keeps refusal_retry_loop and provider_unavailable; - index.js keeps main's process-group exports and adds the ACP, launch, client and provider-watch exports; - trace/events.js keeps session.opened and the usage context fields, with main's newer credential.granted / credential.denied shapes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDs9DRK3nqxjeAV7xL8uhg
The last item on the record branch's main-hold.
superviseRunstarted every run with onlyrunId,adapterandlabel. So the supervisor's ownrun.startedentry, and anything an adapter or the valet emitted without naming an actor, recordedactor: null. No entry carried the board task or pod either, andonBehalfOfwas never written.superviseRunnow takestaskId,podIdandactorand passes them tostore.startRun. That function already validates them: an actor must be well-formed, and ataskIdwithout apodIdis refused. A malformed call now fails before anything is spawned. Callers that pass none of these are unchanged.Tests (
packages/seats/test/supervisor.test.js, 2 new):run.startedand an emit that names no actor both take the run's actor, includingonBehalfOf. The index row carries all three.taskIdwithout apodIdrejects, and the adapter is never spawned.Container run (node 22, network none): seats 191/191. Mutant (startRun back to
{ runId, adapter, label }): both new tests fail.The other two hold items are already on the branch: the pgroup kill (#2726, c226bca) and the fake REAL_HOME in valet-env (e1bd2cc).
🤖 Generated with Claude Code
https://claude.ai/code/session_013ENddpGzhE7S8qjDSYAMuu