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. |
||
|---|---|---|
| .. | ||
| db | ||
| nodes | ||
| transport | ||
| __init__.py | ||
| api.py | ||
| billing.py | ||
| ci_fetcher.py | ||
| ci_gate.py | ||
| ci_watcher.py | ||
| coordinator.py | ||
| dashboard.py | ||
| deadline_timer.py | ||
| decisions.py | ||
| dispatcher.py | ||
| draft_pr_monitor.py | ||
| graph.py | ||
| invoker.py | ||
| invoker_multi.py | ||
| ledger.py | ||
| operator_cli.py | ||
| recovery.py | ||
| responder.py | ||
| resume_worker.py | ||
| state_store.py | ||
| status_page.py | ||
| task_model.py | ||
| topology.py | ||