Security-review follow-up (LOGIC-01/02/05, all confirmed correctness). - New transport-neutral `decisions.normalize_decision(raw, *, allow_abandon)` is the single source of truth: approve-allowlist→approve; abandon-allowlist→abandon ONLY when allow_abandon; everything else (prose, empty, abandon-verbs when disallowed) → request_changes with the full reply as notes; idempotent on an already-formed decision dict. slack_adapter.map_plan_decision is now a thin wrapper (default allow_abandon=True, no caller churn). - LOGIC-01/02: graph._parse_decision now delegates to normalize_decision (was: any unrecognized verb → abandon → FAILED). The graph is now the universal safe backstop, so EVERY writer that bypassed the listener mapping — operator CLI answer_on_behalf (raw), Coordinator.submit_answer (raw), the recovery sweep — loops back on prose instead of silently FAILing the task. Explicit abandon still abandons (preserves the confirmed-button path). - LOGIC-05: the Slack FREE-TEXT reply path maps with allow_abandon=False, so a bare "cancel"/"stop"/"abandon" typed in-thread → request_changes (never terminal abandon); abandon stays reachable only via the confirm-guarded button. Tests: graph unrecognized→loops-back (not FAILED), operator raw-prose→request_ changes, free-text destructive verbs→request_changes vs button→abandon, normalizer idempotency. 1484 passed. |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| base.py | ||
| checker_intake.py | ||
| claude_code_adapter.py | ||
| claude_code_live.py | ||
| github_adapter.py | ||
| github_intake.py | ||
| github_live.py | ||
| slack_adapter.py | ||
| slack_listener.py | ||
| slack_live.py | ||