feat(kanban): post-#951 — worker cards inherit parent home; list home-first + --all; pings carry home; task_events actor provenance - #987
Conversation
…riven home-line test; actor_profile for session callers Argus r1 on #987: - B1: default `list` split used an exact session-id match; now uses kb.home_ids(caller) (same definition as --home/show/guard). Test with a real state.db rotation lineage. - C1: replace the inspect.getsource test with one real notifier tick (stub push adapter, notify+wake) asserting the home: line in the sent ping and the wake event text. - C2: _event_actor falls back to the running profile when a session id resolved but no profile env is set; identity-less callers stay NULL. Mutants (each fix reverted) fail their test: B1, C1-ping, C1-wake, C2.
1259ffc to
f77508a
Compare
…ession-first list, --takeover, home-lint - create_task (single choke point: CLI, tool, swarm, dashboard) stamps session_id = explicit > first homed parent > 'unhomed' (never NULL) and prepends an 'origin: <platform> <chat> (<id>) · session <id> · <date>' line unless present. created_by falls back to HERMES_SESSION_PROFILE. - 'unhomed' loads as Task.session_id=None + unhomed=True: notification/wake consumers unchanged. Guard refuses chat sessions on unhomed cards. - --takeover REASON (alias of --foreign-ok) records a 'takeover' event + 'takeover:' audit comment. - kanban list: THIS SESSION / OTHER SESSIONS grouping when a caller session exists; --this-session (= --home), --all flat. - kanban home-lint: silent/exit 0 when green, ids + exit 1 otherwise; --backfill [--dry-run] stamps homeless open cards 'unhomed' + comment. - transfer scrub stamps 'unhomed' instead of NULL. Verified locally: test_kanban_home_session + home_lineage 88 passed, tools/test_kanban_tools 63 passed, test_kanban_transfer 20 passed.
…rst kanban_list tool - backfill_notify_sub_user_ids normalizes the 'unhomed' sentinel to None at the raw-SQL reader, so notify-repair never adopts the lone human in a user-less card's chat (r1 BEHAVIOUR finding; born-sessionless and backfilled-legacy arms were adopting user_id on 4905838). - kanban_list tool: with a caller session, this session's cards in full under tasks, others collapsed to {id,status,title[,unhomed]} under other_sessions; all=true (new schema bool) or no session = flat. - docs: raw-reader rule, tool grouping, re-scope surfaces (specify guarded; dashboard PATCH is actor-less like every dashboard mutation). Verified: new negative control over create_task/legacy NULL/backfilled with a real 5-tuple index + raw-id positive control (red on revert of the fix); tool grouping test (red on revert); Argus battery v1 3/4 with POSCTRL_raw_id red by design; 99 + 67 passed on the touched suites.
…riven home-line test; actor_profile for session callers Argus r1 on #987: - B1: default `list` split used an exact session-id match; now uses kb.home_ids(caller) (same definition as --home/show/guard). Test with a real state.db rotation lineage. - C1: replace the inspect.getsource test with one real notifier tick (stub push adapter, notify+wake) asserting the home: line in the sent ping and the wake event text. - C2: _event_actor falls back to the running profile when a session id resolved but no profile env is set; identity-less callers stay NULL. Mutants (each fix reverted) fail their test: B1, C1-ping, C1-wake, C2.
f77508a to
959e784
Compare
…carry home; task_events actor provenance; worker fan-out guard Rebased onto #998 (born-homed). ONE birth-home implementation: #998's _resolve_birth_session in create_task, extended with the worker-run lineage (HERMES_KANBAN_TASK) and the inherited origin line; the separate _apply_inherited_home hook is removed. Worker run with no homed lineage -> 'unhomed', never its per-run session id. Parent home wins over an explicit session_id (spec ':='). Session-first list view is #998's; this PR's item-2 list code and its tests are dropped, lineage test adapted to #998's grouped format. Kept: ping/wake home line, task_events actor_profile/actor_session_id, check_home_session worker fan-out exemption (up-walk only; created event only) + boundary tests (Argus r3 C3/C4).
03f96e6 to
77b235e
Compare
… a worker run The worker_env fixture sets HERMES_KANBAN_TASK to an unhomed card; under item 1 a worker run with no homed lineage is born 'unhomed', never its per-run session id. The test asserts the CHAT-session stamp, so it now unsets HERMES_KANBAN_TASK. CI slice 12 failure on 77b235e.
|
🤖 merged-by: apollo · lane: discord · gate: BYPASS: FR paused by Ace ruling 09-22; slice card under review_policy=milestone_only · why: t_f59538cf: worker-created cards inherit parent home + origin line (one birth-home impl shared with #998), kanban list home-first, pings carry home:, task_events actor provenance. Stacked on #998 (queued ahead). Ace 14:55: slice card, CI is the gate. Gate: CI 56/56; local PR tests green |
FleetReviewPost-merge review ( Confidence: 3/5 Findings
FleetReview provenance · models: B=gpt-6-sol, C=claude-code-opus-5-5, D=grok-4.6, F=gpt-6-sol · cost: $7.06 · duration: 17m 44s · rounds: 1 · files examined: 15 |
…fill Class fix for backfill section C5 (race-atomicity), 20 confirmed instances: - kanban_db: home-session ownership re-checked inside the mutator's first write txn (TOCTOU, #951); backfill_unhomed stamp+comment in one txn (#987) - turn_handoff: consume claims via atomic rename; prune/expired drop only deletes unchanged content (#813 prune race, lost handoff) - lcm lifecycle_state: prune DELETE conditioned on the judged session ids (#966) - checkout_admission: reject negative per-entry work counts (#1035) - provider_seam: materialize publish inputs before the retryable build; models: SeamCollision no longer latches discovery (#1072) - gateway/session: turn-marker publish gated on revision (#1043 x2), clear override keeps a newer concurrent set + its pin, off-lock prune detects an in-place heal; run.py stuck-loop suspend under the store lock; Telegram HWM tracker serialized across worker threads (#1043) Verified: 13 new tests red on base, green on head; neighbouring gateway and kanban suites green (1 pre-existing env-dependent failure, same on base). (cherry picked from commit 0ccb18e)
…fill Class fix for backfill section C5 (race-atomicity), 20 confirmed instances: - kanban_db: home-session ownership re-checked inside the mutator's first write txn (TOCTOU, #951); backfill_unhomed stamp+comment in one txn (#987) - turn_handoff: consume claims via atomic rename; prune/expired drop only deletes unchanged content (#813 prune race, lost handoff) - lcm lifecycle_state: prune DELETE conditioned on the judged session ids (#966) - checkout_admission: reject negative per-entry work counts (#1035) - provider_seam: materialize publish inputs before the retryable build; models: SeamCollision no longer latches discovery (#1072) - gateway/session: turn-marker publish gated on revision (#1043 x2), clear override keeps a newer concurrent set + its pin, off-lock prune detects an in-place heal; run.py stuck-loop suspend under the store lock; Telegram HWM tracker serialized across worker threads (#1043) Verified: 13 new tests red on base, green on head; neighbouring gateway and kanban suites green (1 pre-existing env-dependent failure, same on base). (cherry picked from commit 0ccb18e)
…fill Class fix for backfill section C5 (race-atomicity), 20 confirmed instances: - kanban_db: home-session ownership re-checked inside the mutator's first write txn (TOCTOU, #951); backfill_unhomed stamp+comment in one txn (#987) - turn_handoff: consume claims via atomic rename; prune/expired drop only deletes unchanged content (#813 prune race, lost handoff) - lcm lifecycle_state: prune DELETE conditioned on the judged session ids (#966) - checkout_admission: reject negative per-entry work counts (#1035) - provider_seam: materialize publish inputs before the retryable build; models: SeamCollision no longer latches discovery (#1072) - gateway/session: turn-marker publish gated on revision (#1043 x2), clear override keeps a newer concurrent set + its pin, off-lock prune detects an in-place heal; run.py stuck-loop suspend under the store lock; Telegram HWM tracker serialized across worker threads (#1043) Verified: 13 new tests red on base, green on head; neighbouring gateway and kanban suites green (1 pre-existing env-dependent failure, same on base). (cherry picked from commit 0ccb18e)
…fill Class fix for backfill section C5 (race-atomicity), 20 confirmed instances: - kanban_db: home-session ownership re-checked inside the mutator's first write txn (TOCTOU, #951); backfill_unhomed stamp+comment in one txn (#987) - turn_handoff: consume claims via atomic rename; prune/expired drop only deletes unchanged content (#813 prune race, lost handoff) - lcm lifecycle_state: prune DELETE conditioned on the judged session ids (#966) - checkout_admission: reject negative per-entry work counts (#1035) - provider_seam: materialize publish inputs before the retryable build; models: SeamCollision no longer latches discovery (#1072) - gateway/session: turn-marker publish gated on revision (#1043 x2), clear override keeps a newer concurrent set + its pin, off-lock prune detects an in-place heal; run.py stuck-loop suspend under the store lock; Telegram HWM tracker serialized across worker threads (#1043) Verified: 13 new tests red on base, green on head; neighbouring gateway and kanban suites green (1 pre-existing env-dependent failure, same on base). (cherry picked from commit 0ccb18e)
…_60634825) C7 backfill (durable-state loss) slice A. Each fix has a regression test that fails on 7a81d46 and passes here. - k103 (#987) home-session guard: takeover/operator_override events now record the home read BEFORE the guarded mutation, so `update --session <new> --takeover` names the displaced home instead of <new>. - k105 (#953) rate-limit circuit notify: the episode latch is written only after notify.py exits 0; a failed page is retried on the next tick. kanban_budget._run_notify now returns whether the send succeeded. - k107 (#1081) request_changes: a coverage comment the gate refuses on an already-held review run is rolled back with the refusal. - k109 (#1050) worker identity: `spawned` records the Linux boot_id next to the start token; a token stamped under another boot is ignored and the causal window decides. - k110 (#994) end_orphaned_terminal_runs merges its payload into existing run metadata (keeps `pool`) instead of overwriting it. - k112 (#980) model switch: the unknown-provider refusal is exempted only by a declaration on the CURRENT provider, same match as the override block. - k114 (#1254) strip_overlay restores PATH components the overlay removed. - k115 (#1035) desktop cron ticker passes a serve admission gate as can_dispatch, so a checkout hold refuses scheduled dispatch. - k121 (#1014) dashboard create homes the card to the viewing ?session=, so it stays visible under the default "this" facet. - k138 (#1024) kanban_attach refuses a supplied but invalid expected_sha256 instead of storing the file unverified. Dropped (no code change): k108, k111, k113. Reasons are in the PR body. test_desktop_cron_ticker_profiles: two exact-kwargs asserts now ignore the new can_dispatch key.
…_60634825) (#1373) C7 backfill (durable-state loss) slice A. Each fix has a regression test that fails on 7a81d46 and passes here. - k103 (#987) home-session guard: takeover/operator_override events now record the home read BEFORE the guarded mutation, so `update --session <new> --takeover` names the displaced home instead of <new>. - k105 (#953) rate-limit circuit notify: the episode latch is written only after notify.py exits 0; a failed page is retried on the next tick. kanban_budget._run_notify now returns whether the send succeeded. - k107 (#1081) request_changes: a coverage comment the gate refuses on an already-held review run is rolled back with the refusal. - k109 (#1050) worker identity: `spawned` records the Linux boot_id next to the start token; a token stamped under another boot is ignored and the causal window decides. - k110 (#994) end_orphaned_terminal_runs merges its payload into existing run metadata (keeps `pool`) instead of overwriting it. - k112 (#980) model switch: the unknown-provider refusal is exempted only by a declaration on the CURRENT provider, same match as the override block. - k114 (#1254) strip_overlay restores PATH components the overlay removed. - k115 (#1035) desktop cron ticker passes a serve admission gate as can_dispatch, so a checkout hold refuses scheduled dispatch. - k121 (#1014) dashboard create homes the card to the viewing ?session=, so it stays visible under the default "this" facet. - k138 (#1024) kanban_attach refuses a supplied but invalid expected_sha256 instead of storing the file unverified. Dropped (no code change): k108, k111, k113. Reasons are in the PR body. test_desktop_cron_ticker_profiles: two exact-kwargs asserts now ignore the new can_dispatch key. Co-authored-by: ang-fleet-workers[bot] <333956806+ang-fleet-workers[bot]@users.noreply.github.com>
…fill Class fix for backfill section C5 (race-atomicity), 20 confirmed instances: - kanban_db: home-session ownership re-checked inside the mutator's first write txn (TOCTOU, #951); backfill_unhomed stamp+comment in one txn (#987) - turn_handoff: consume claims via atomic rename; prune/expired drop only deletes unchanged content (#813 prune race, lost handoff) - lcm lifecycle_state: prune DELETE conditioned on the judged session ids (#966) - checkout_admission: reject negative per-entry work counts (#1035) - provider_seam: materialize publish inputs before the retryable build; models: SeamCollision no longer latches discovery (#1072) - gateway/session: turn-marker publish gated on revision (#1043 x2), clear override keeps a newer concurrent set + its pin, off-lock prune detects an in-place heal; run.py stuck-loop suspend under the store lock; Telegram HWM tracker serialized across worker threads (#1043) Verified: 13 new tests red on base, green on head; neighbouring gateway and kanban suites green (1 pre-existing env-dependent failure, same on base). (cherry picked from commit 0ccb18e)
Kanban card t_f59538cf. Follow-ups to #951 (home-session card ownership). Base: main @ 813b750 (includes #951 and #982).
1. Worker-created cards inherit the parent's home (
hermes_cli/kanban_db.py)create_task: a card created with parents, or from inside a worker run (HERMES_KANBAN_TASKset), getssession_idfrom the first parent that has one. If no parent has one, it uses the dispatched card'ssession_id. It never uses the worker run's own session id; if nothing in the lineage has a home,session_idis NULL.origin:line goes at the top of the body, unless the body already starts with one.decompose_triage_taskchildren get the root card's home and its origin line.kanban_createtool, swarm and dispatcher children all go throughcreate_task, so they are all covered.--session. The spec sayssession_id := parent's.2.
hermes kanban listdefault view (hermes_cli/kanban.py)--all,--homeor--session: this session's cards print in full, then one line:N cards from other sessions (--all to show).--allflag gives the previous flat listing.--homeand--sessionbehave as before.--jsonis unchanged and always returns the full filtered list, so scripts see no change.3. Pings carry home (
gateway/kanban_watchers.py)home: <platform> #<channel> · session <id>.sessions.origin_jsonin state.db, falling back tosourceanddisplay_name. If there is no row, the line ishome: session <id>. If the card has no home, no line is added._collectworker thread, never on the event loop.4. Event provenance (
hermes_cli/kanban_db.py)task_events:actor_profileandactor_session_id. They are in the schema and added by an idempotent migration. Old rows stay NULL._append_eventfills them. The session is resolved in the same order as the feat(kanban): home-session card ownership — session stamping, --home, foreign-session mutation guard #951 guard: the actor bound bymutation_actor(CLI and tool surfaces), thenresolve_current_session_id(), then the environment (not the environment when running inside the gateway). They are NULL when there is no identity.Tests
tests/hermes_cli/test_kanban_home_inherit.py: 7 tests for item 1. With the hook removed, 4 of them fail.tests/hermes_cli/test_kanban_post951_list_ping_provenance.py: covers items 2–4, including the migration of a legacytask_eventstable.tests/hermes_cli/test_kanban_home_session.py(feat(kanban): home-session card ownership — session stamping, --home, foreign-session mutation guard #951, including the AST contract test) passes.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Known limitations (accepted in review, not fixed in this PR)
_resolve_home_line(gateway/kanban_watchers.py) builds aSessionDB()for each subscription that has events, on every notifier tick. It runs in the_collectworker thread, not on the event loop, but against a large state.db a read-only sqlite connection (ashome_idsuses) would be cheaper and would skip schema init.home: session <id>.Worker fan-out guard boundary (Argus r3)
_worker_owns_cardwalks task_links UP from the target to the worker's card only, and only acreatedevent by this run counts as run ownership. Pinned by tests intests/hermes_cli/test_kanban_worker_fanout_guard.py: the worker's ancestor and sibling stay refused (CLI and tool), and a worker comment does not adopt an unrelated same-home card.Rebased onto #998 (Apollo ruling, 2026-09-24)
This PR now stacks on #998 (born-homed) and keeps ONE birth-home implementation: #998's
_resolve_birth_sessionincreate_task, extended with the worker-run lineage (HERMES_KANBAN_TASK) and the inherited parentorigin:line. The separate_apply_inherited_homehook is gone. A worker run with no homed lineage getsunhomed, never its own per-run session id.Item 2 (the session-first
listview) is now #998'sTHIS SESSION / OTHER SESSIONSgrouping. This PR's--all/collapsed-line code and its tests were dropped; the lineage test now asserts against #998's format.Still in this PR: item 1 (worker/parented inheritance), item 3 (ping/wake
home:line), item 4 (task_events.actor_profile/actor_session_id), and the worker fan-out exemption incheck_home_session.Pre-rebase head kept at
kanban/t_f59538cf-pre-998-backup(03f96e6).