Skip to content

feat(kanban): post-#951 — worker cards inherit parent home; list home-first + --all; pings carry home; task_events actor provenance - #987

Merged
Kyzcreig merged 4 commits into
mainfrom
kanban/t_f59538cf-home-inherit
Sep 25, 2026
Merged

Kyzcreig merged 4 commits into
mainfrom
kanban/t_f59538cf-home-inherit

Conversation

@Kyzcreig

@Kyzcreig Kyzcreig commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

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_TASK set), gets session_id from the first parent that has one. If no parent has one, it uses the dispatched card's session_id. It never uses the worker run's own session id; if nothing in the lineage has a home, session_id is NULL.
  • The parent's origin: line goes at the top of the body, unless the body already starts with one.
  • decompose_triage_task children get the root card's home and its origin line.
  • The CLI, the kanban_create tool, swarm and dispatcher children all go through create_task, so they are all covered.
  • Decision for review: on a create with parents, the parent's home wins over an explicit --session. The spec says session_id := parent's.

2. hermes kanban list default view (hermes_cli/kanban.py)

  • When the caller has a session and passes none of --all, --home or --session: this session's cards print in full, then one line: N cards from other sessions (--all to show).
  • New --all flag gives the previous flat listing. --home and --session behave as before.
  • --json is unchanged and always returns the full filtered list, so scripts see no change.

3. Pings carry home (gateway/kanban_watchers.py)

  • Every text ping (done/blocked/gave up/crashed/timed out/review/triage/…) and the wake turn now end with a line home: <platform> #<channel> · session <id>.
  • Platform and channel come from sessions.origin_json in state.db, falling back to source and display_name. If there is no row, the line is home: session <id>. If the card has no home, no line is added.
  • The state.db lookup runs in the notifier's _collect worker thread, never on the event loop.

4. Event provenance (hermes_cli/kanban_db.py)

  • Two new columns on task_events: actor_profile and actor_session_id. They are in the schema and added by an idempotent migration. Old rows stay NULL.
  • _append_event fills 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 by mutation_actor (CLI and tool surfaces), then resolve_current_session_id(), then the environment (not the environment when running inside the gateway). They are NULL when there is no identity.
  • The bound actor is kept in a separate provenance ContextVar that the guard wrapper never clears. So events written inside a guarded mutator still record who requested the change.
  • Journal replay, board transfer and dashboard inserts are not touched. They copy or synthesize historical rows and have no live actor.

Tests


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Known limitations (accepted in review, not fixed in this PR)

  • _resolve_home_line (gateway/kanban_watchers.py) builds a SessionDB() for each subscription that has events, on every notifier tick. It runs in the _collect worker thread, not on the event loop, but against a large state.db a read-only sqlite connection (as home_ids uses) would be cheaper and would skip schema init.
  • A card whose home session lives in another profile's state.db cannot be resolved to a platform/channel, so its ping degrades to home: session <id>.

Worker fan-out guard boundary (Argus r3)

_worker_owns_card walks task_links UP from the target to the worker's card only, and only a created event by this run counts as run ownership. Pinned by tests in tests/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_session in create_task, extended with the worker-run lineage (HERMES_KANBAN_TASK) and the inherited parent origin: line. The separate _apply_inherited_home hook is gone. A worker run with no homed lineage gets unhomed, never its own per-run session id.
Item 2 (the session-first list view) is now #998's THIS SESSION / OTHER SESSIONS grouping. 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 in check_home_session.
Pre-rebase head kept at kanban/t_f59538cf-pre-998-backup (03f96e6).

Kyzcreig pushed a commit that referenced this pull request Sep 24, 2026
…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.
@Kyzcreig
Kyzcreig force-pushed the kanban/t_f59538cf-home-inherit branch from 1259ffc to f77508a Compare September 24, 2026 18:11
Kyzcreig and others added 2 commits September 24, 2026 12:33
…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.
Kyzcreig pushed a commit that referenced this pull request Sep 24, 2026
…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.
@Kyzcreig
Kyzcreig force-pushed the kanban/t_f59538cf-home-inherit branch from f77508a to 959e784 Compare September 24, 2026 20:13
…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).
@Kyzcreig
Kyzcreig force-pushed the kanban/t_f59538cf-home-inherit branch from 03f96e6 to 77b235e Compare September 24, 2026 21:16
… 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.
@Kyzcreig

Copy link
Copy Markdown
Collaborator Author

🤖 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

@Kyzcreig
Kyzcreig added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit ec50416 Sep 25, 2026
57 checks passed
@Kyzcreig
Kyzcreig deleted the kanban/t_f59538cf-home-inherit branch September 25, 2026 00:58
@Kyzcreig Kyzcreig added the fleetreview:post-merge Ask FleetReview to review this MERGED pull (merge commit vs first parent) label Sep 25, 2026
@ang-prism

ang-prism Bot commented Sep 27, 2026

Copy link
Copy Markdown

FleetReview

Post-merge review (fleetreview:post-merge override): this reviewed the merge commit against its first parent — the bytes that already shipped. It is not a pre-merge gate pass.

Confidence: 3/5

Findings

  • P1 gateway/kanban_watchers.py:328 — Dropped ping
  • P1 tools/kanban_tools.py:694 — Hidden Home Cards
  • P1 hermes_cli/kanban_db.py:12323 — Unstamped Children
  • P1 hermes_cli/kanban_db.py:4993 — Lost Audit
  • P1 hermes_cli/kanban_db.py:12406 — Unhomed children
  • P1 hermes_cli/kanban_db.py:5234 — Foreign bypass
  • P1 hermes_cli/kanban_db.py:4909 — Missing origin
  • P1 gateway/kanban_watchers.py:339 — Notifier opens a new writable SessionDB, with full schema init, for every subscription that has events on every tick
  • P1 hermes_cli/kanban_db.py:5362 — Takeover event records the new home instead of the displaced home
  • P1 hermes_cli/kanban_db.py:5306 — Worker fan-out exemption survives a child's transfer to another home
  • P1 hermes_cli/kanban_db.py:5575 — Resolve the parent's home within the task-creation transaction

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

ang-fleet-workers Bot added a commit that referenced this pull request Sep 27, 2026
…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)
ang-fleet-workers Bot added a commit that referenced this pull request Sep 27, 2026
…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)
ang-fleet-workers Bot added a commit that referenced this pull request Sep 27, 2026
…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)
ang-fleet-workers Bot added a commit that referenced this pull request Sep 27, 2026
…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)
Kyzcreig pushed a commit that referenced this pull request Sep 28, 2026
…_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.
Kyzcreig pushed a commit that referenced this pull request Sep 28, 2026
…_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>
ang-fleet-workers Bot added a commit that referenced this pull request Sep 28, 2026
…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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fleetreview:post-merge Ask FleetReview to review this MERGED pull (merge commit vs first parent)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant