You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#1708 (fixed by PR #1967) let sub-session workers upsert their own task boards over the lead session's tasks rows for as long as deck sub-sessions have shipped. PR #1967 stops new corruption; it cannot repair what existing databases already hold, and those rows are user-visible.
Two kinds of residue sit in every .stella/private/store.db whose sessions used task_assign or mid-turn dispatch:
Overwritten lead rows. A worker's task "1" replaced the lead's task "1" — subject, description, status and owner all came from the worker. For a session that is still live these heal on the driver's next board mirror (it upserts the lead's true state back). For a session that has ended they are permanently wrong, because nothing will ever write that key again.
Stray rows above the lead's ordinal range. A worker board longer than the lead's left rows at ordinals the lead never had. Nothing overwrites those and nothing deletes them short of /clear on that session — the same "stale-row half" already documented in crates/stella-store/src/task_board.rs's module docs as the reason /clear deletes rather than supersedes.
Why this is not simply a cleanup migration
The rows are indistinguishable by construction — that is precisely #1708's complaint. tasks records no lane and no provenance, so after the fact there is no predicate separating "the lead's row" from "a worker's row that landed on the lead's key". A migration cannot repair (1) at all, and can only guess at (2) by comparing against a board length the table does not store.
Store::list_session_tasks (crates/stella-store/src/task_board.rs) — same rows, but it has no production caller today.
Repro
On any store predating PR #1967 that ran a delegated worker with its own task_create calls:
SELECT session_id, task_id, subject, owner FROM tasks ORDER BY session_id, CAST(task_id ASINTEGER);
Rows whose owner names a sub:/req: lane while the subject describes work the lead never planned are the overwritten kind; rows at ordinals beyond what that session's board ever reached are the stray kind. stella observe → Sessions → an affected session renders both.
Caveat in the UI — the Observatory marks a session's task list as untrustworthy when the session has any deck-sub execution and predates the fix (executions.started_at vs. the release, joined on session_id). More honest, more code, and needs a version marker the DB does not currently carry.
Offer a manual escape hatch — document that /clear on an affected live session deletes its mirror (Store::clear_session_tasks), which is already the only correct repair available. Does nothing for ended sessions.
Whichever is chosen, the outcome belongs in crates/stella-store/src/task_board.rs's module docs, beside the existing /clear rationale, so the next reader of that table learns it from the table.
Problem
#1708 (fixed by PR #1967) let sub-session workers upsert their own task boards over the lead session's
tasksrows for as long as deck sub-sessions have shipped. PR #1967 stops new corruption; it cannot repair what existing databases already hold, and those rows are user-visible.Two kinds of residue sit in every
.stella/private/store.dbwhose sessions usedtask_assignor mid-turn dispatch:/clearon that session — the same "stale-row half" already documented incrates/stella-store/src/task_board.rs's module docs as the reason/cleardeletes rather than supersedes.Why this is not simply a cleanup migration
The rows are indistinguishable by construction — that is precisely #1708's complaint.
tasksrecords no lane and no provenance, so after the fact there is no predicate separating "the lead's row" from "a worker's row that landed on the lead's key". A migration cannot repair (1) at all, and can only guess at (2) by comparing against a board length the table does not store.So this is a decision, not a patch.
Where it is visible
crates/stella-observatory/src/sessions.rs— the Sessions tab's task list (SELECT task_id, subject, status, owner, updated_at FROM tasks WHERE session_id = ?1). A user inspecting an old session sees a mixture of lead and worker tasks presented as one board, with no indication anything is wrong. This is the reader that makes the residue matter; it shipped in PR feat(stella-observatory): Sessions tab — session replay & inspection, prompt diff (#1511), tendencies, self-improvement residue #1876.Store::list_session_tasks(crates/stella-store/src/task_board.rs) — same rows, but it has no production caller today.Repro
On any store predating PR #1967 that ran a delegated worker with its own
task_createcalls:Rows whose
ownernames asub:/req:lane while the subject describes work the lead never planned are the overwritten kind; rows at ordinals beyond what that session's board ever reached are the stray kind.stella observe→ Sessions → an affected session renders both.Definition of done
A maintainer's call between:
crates/stella-store/src/task_board.rs's module docs recording that rows written before PR fix(stella-cli): stop a worker mirroring its private board over the lead's tasks rows (#1708) #1967 may mix lead and worker boards and cannot be told apart, so the Observatory's pre-fix session views are approximate. Cheapest, and arguably right for a local telemetry table.deck-subexecution and predates the fix (executions.started_atvs. the release, joined onsession_id). More honest, more code, and needs a version marker the DB does not currently carry./clearon an affected live session deletes its mirror (Store::clear_session_tasks), which is already the only correct repair available. Does nothing for ended sessions.Whichever is chosen, the outcome belongs in
crates/stella-store/src/task_board.rs's module docs, beside the existing/clearrationale, so the next reader of that table learns it from the table.Refs #1708, #1692, #1876. Follows PR #1967.