Goal Lifecycle
This page documents the canonical goal state machine as it exists in scripts/goal-runner/state_machine.py and the orchestrator's transition logic.
Canonical states
The normal Goal Runner state machine:
DRAFT → READY_FOR_PLAN → PLANNED → READY_FOR_BUILD → READY_FOR_AUDIT → READY_FOR_RELEASE → FROZEN_PASS
DRAFT is the initial state of a freshly committed goal. After materialization, the goal moves to READY_FOR_PLAN and the orchestrator dispatches a PLANNER task.
Legal transitions
| From | To | Trigger |
|---|---|---|
DRAFT | READY_FOR_PLAN | First materialization. |
READY_FOR_PLAN | PLANNED | PLANNER role emits plan + receipts. |
PLANNED | READY_FOR_BUILD | Auto-advance; no agent dispatch. |
READY_FOR_BUILD | READY_FOR_AUDIT | BUILDER PASS + evidence. |
READY_FOR_AUDIT | READY_FOR_RELEASE | INDEPENDENT_AUDIT_HIGH PASS at exact head. |
READY_FOR_RELEASE | FROZEN_PASS | Governed merge + production acceptance. |
READY_FOR_* | REPAIR_REQUIRED | Adapter FAIL or canonical repair trigger. |
REPAIR_REQUIRED | READY_FOR_PLAN | Bounded repair goal PASS. |
READY_FOR_* | PAUSED | Owner goal-pause.ps1. |
PAUSED | READY_FOR_* | Owner goal-resume.ps1 with PHASE_00_RESUME_APPROVAL (if applicable). |
Illegal transitions
| From | To | Why rejected |
|---|---|---|
DRAFT | READY_FOR_BUILD | Must pass through planning. |
READY_FOR_PLAN | FROZEN_PASS | Skips build and audit. |
FROZEN_PASS | any non-terminal | Terminal state is immutable; revision needs a repair goal. |
READY_FOR_RELEASE | READY_FOR_AUDIT | Audit cannot follow release. |
CANCELED | any | Cancelled goals cannot be revived. |
Program Goal states
The Program Goal has its own state set:
DRAFT → READY_FOR_PLAN → PLANNED → READY_FOR_BUILD → READY_FOR_AUDIT → READY_FOR_RELEASE → FROZEN_PASS → PROGRAM_PRODUCTION_CERTIFIED
PROGRAM_PRODUCTION_CERTIFIED is the program-level terminal state, distinct from FROZEN_PASS (which is the per-IMP terminal state).
Materialization
GoalRegistry.materialize(goal_id) is the entry point:
from goal_registry import GoalRegistry
from runtime_store import RuntimeStore
from pathlib import Path
store = RuntimeStore(Path("scripts/goal-runner/local/runtime"))
registry = GoalRegistry(store, Path("."))
result = registry.materialize("ASF50_DOCS_PORTAL_001")
materialize is idempotent. Running it twice does not duplicate state.
Transitions in receipts
Every transition is recorded in state_transitions with created_at, the previous state, and the new state. The orchestrator refuses to perform a transition that is not in the legal set.