# 0.5.1 — Per-ticket roster filter + operator-question bridge

Two follow-ons to 0.5.0 — one substrate optimization, one bug fix
from the 0.4.0 detached-process work.

## Highlights

- **Per-ticket roster filter on M7.** Tickets marked
  `stack_span: frontend` only load Tweedledee for the
  implementation iteration; `backend` only loads Tweedledum;
  `full-stack` loads both. The non-roster Tweedle can still buzz
  in via §III selective engagement when a contract question
  surfaces — narrowing only gates priority windows, not
  emission. Cuts M7 call volume + clock time materially on the
  dominant mixed-stack case.
- **Operator questions reach the operator on background runs.**
  The detached `wonderland run-bg` subprocess no longer
  auto-sentinels every `question_to_operator` with "no operator
  available". Disk-mediated bridge: subprocess writes
  `pending_question.json`, App polls + pushes `AskUserModal`,
  operator answers, App writes `pending_answer.json`, subprocess
  unblocks.

## What changed

### Per-ticket roster filter

- New `RosterFilter` typed sub-model with a validator enforcing
  narrowed-roster ⊆ full-roster (catches YAML typos).
- `Meeting.per_item_roster_filter: RosterFilter | None` and
  `Meeting.apply_roster_filter(item_payload)` returning a copy
  with roster + phase team_groupings narrowed.
- `_run_one_meeting` accepts `item_payload` and applies the
  filter at convene time. `_run_lane` plumbs `outer_item`
  through. All five per-item call sites updated.
- New `TicketPayload.stack_span: TicketStackSpan` field
  (frontend / backend / full-stack; default full-stack for
  back-compat). `render_ticket` writes a `**Stack span:**`
  markdown line; `read_ticket_stack_span` reads it back.
  `_collect_per_item_items` attaches the value to each item
  payload alongside `blocked_by`.
- `tdd-implement.yaml` M7 declares the filter:
  ```yaml
  per_item_roster_filter:
    field: stack_span
    map:
      frontend: [tweedledee]
      backend: [tweedledum]
  ```
- M3 (decomposition) directive gains a `stack_span` bullet with
  "be explicit and stingy" guidance. Includes the anti-pattern
  callout: if a frontend ticket needs a backend change to land
  first, that's a `Blocked by:` dependency on a separate backend
  ticket, not a reason to mark this one full-stack.
- Rabbit's constitution output-schema example adds `stack_span`
  to the ticket shape so the model sees the field in its
  structured output. Without this the default fires uniformly
  and every ticket lands full-stack.

### Operator-question bridge

- `cli_run_bg.py` wires `_disk_mediated_question` as the
  LiveRunHandle's user-question handler. Per-call uuid
  `question_id` lets the subprocess distinguish its own answer
  from stale files. 10-minute timeout falls back to sentinel
  (operator may have left; run continues).
- `WonderlandApp.launch_background_run` spawns a sibling
  `question_poller_task` alongside the consumer task. The
  poller watches the run dir for `pending_question.json`, pushes
  `AskUserModal` via the existing global handler shape, writes
  `pending_answer.json` on dismissal.
- Discovery on TUI startup also re-spawns the question-poller
  for recovered runs — a question that fired while the TUI was
  closed is still visible when it relaunches.
- `ActiveRun.question_poller_task` field added for lifecycle
  tracking.

## Test plan

1. **Stack_span ends up on disk**. Run `tdd-design` on a simple
   directive that spans both backend and frontend. Open the
   tickets directory; confirm each ticket has a
   `**Stack span:**` line with a specific value (not blank, not
   defaulting to full-stack for everything).
2. **M7 narrows the roster**. Launch `tdd-implement` against a
   queued feature with mixed `stack_span` tickets. In the
   meetings pane / live call feed, confirm:
   - Frontend tickets: only Tweedledee in the M7 iteration's
     speaker stream.
   - Backend tickets: only Tweedledum.
   - Full-stack tickets: both.
3. **Background-run operator questions**. Launch the
   `smoke-ask-user` workflow as a background run. When Alice
   fires the operator question, confirm the AskUserModal
   pushes in the TUI; answer it; confirm the run continues
   with the operator's reply (instead of the sentinel "no
   operator available").
4. **Restart-recovery for pending questions**. Launch
   `smoke-ask-user`, wait for the question to fire, quit the
   TUI without answering. Re-launch the TUI; confirm the
   question modal pushes shortly after startup (discovery
   re-spawns the poller).

## Known follow-ups

- Auto-sentinel toggle (`T` keybind on LiveRunScreen) doesn't
  thread through the App-level question handler yet — operator
  who toggles auto-sentinel mid-watch sees no effect. Move the
  toggle to App level when someone hits this.
