Commit Graph

1 Commits

Author SHA1 Message Date
drew b519ebd98f feat(controller): Phase 1d-2 — outcome mapper + master tick handler
The master-side state machine driver. When a worker writes a complete
(or failed) attempt, the tick handler picks it up next pass, maps the
outcome to a state machine event, and applies the transition.

tools/controller/master/:

- outcomes.py: map_outcome_to_event() — pure function. Inputs:
  role, current_state, output_payload, status, head_sha_advanced,
  attempts_remaining_at_tier, conflict_count_at_current_tier.
  Returns EventMapResult(event_name|None, reason).
  - Implementer: resolved+pushed → implementer_pushed;
    resolved+NO push → implementer_competence_failure (worker lied);
    rebase-failed / competence-failure / blocked / noop all routed.
  - Reviewer: approve → reviewer_approve; request-changes → retry vs
    escalate based on attempts_remaining_at_tier; abstain →
    reviewer_abstain; comment → no transition (operator review needed).
  - Estimator: is_metadata_only → estimator_metadata_only; else
    estimator_done.
  - Conflict resolver: counts at current tier — 1st → first; 2nd →
    second_same_tier (escalate); 3rd+ → three_plus (STUCK).
  - Summarizer: doesn't drive state transitions.
  - status='failed' policy: contract-violation → pickup_exhausted;
    other failed outcomes (worker-internal-error / stale-input /
    ttl-insufficient-for-retry / git-clone-failed / lost-lock / etc.)
    → no event (master re-enqueues per the v9 table).
  - status='reaped' → no event (pickup guard handled).

- tick.py: run_tick() — one master tick. Queries
  workflow_attempts WHERE status IN ('complete', 'failed') AND
  finished_at > workflow.last_transition_at (heuristic for "not yet
  processed"). Per row:
  1. Validate current_state ∈ KNOWN_STATES (per v6 unknown-state
     guard); if not, transition workflow → STUCK with reason='unknown-state'.
  2. Map outcome → event via outcomes.map_outcome_to_event.
  3. If no event, bump workflow.last_transition_at so we don't
     re-process forever.
  4. Apply event via state_machine.apply_event; IllegalTransition →
     STUCK with reason='illegal-event'.
  5. Commit transition (workflows.current_state + entered_state_at +
     last_transition_at) AND insert controller_events row.

  Returns TickReport with attempts_processed + transitions_applied +
  transitions_log.

42 new tests:
- outcomes (22): status='failed' policy (3 paths) + each role's
  happy paths + edge cases (unknown outcome / missing field /
  None payload / unknown role).
- tick (20): implementer transitions (resolved+push → AWAITING_CI,
  resolved-no-push → ESCALATING, rebase → CONFLICT_RESOLVING,
  blocked → STUCK), reviewer transitions (approve → MERGING,
  request-changes → IMPLEMENTING), event row content, no
  reprocessing on second tick, unmapped attempts bump
  last_transition_at, terminal workflows skipped (4 parametrized),
  unknown current_state → STUCK.

Total: 271 controller tests; full auto_agents suite 2633 pass.
2026-05-18 13:38:28 -04:00