# Externally driven FSM (requestTransition) — not an auto-running pipeline.
#
# Reliability (aligned with task-pipeline / ADR-043 — lifecycle-only contract):
#   - Hard shell guards at active→verifying and verifying→done with --as <status>
#     (0418 one-active-goal evaluates the post-transition state)
#   - requestTransition fails closed when guard shell is non-zero
#   - No agent.run nodes (by design — pure status graph)
#   - cancelled is terminal; verifying→active rework is always-allowed
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
kind: state-machine
name: feature-lifecycle
version: "1"
description: >
  Feature lifecycle FSM (design §2.3, §5.1, DD-13). States are the canonical
  FeatureStatus vocabulary; transitions encode the §2.3 graph including the
  `verifying` status (DD-13). Guards invoke `${vars.spurBin} feature check`
  at the active→verifying and verifying→done placements, passing the edge's
  target via `--as <status>` (0418) so the one-active-goal rule evaluates the
  post-transition state and never denies the exit it would relieve.
  `cancelled` is terminal. Guard commands reference the check verb (0057) —
  structural validation passes today, behavioral wiring activates as the verb
  ships. Unconditional transitions use the `always` guard (externally-driven
  via `requestTransition`, not auto-advance).
initialState: backlog
terminalStates:
  - cancelled
vars:
  spurBin: 'spur'
  featureId: X

states:
  - id: backlog
    description: Initial state — feature exists but not yet started.
  - id: active
    description: Actively being worked on. P0 in active/verifying is the project goal (B09).
  - id: verifying
    description: >
      Feature verification work (DD-13). Makes verification derivable —
      listable, event-triggerable, assignable.
    onEnter:
      # Caller wiring (task 0880, ADR-119): entering verification RUNS the
      # feature-scoped pass instead of only passively reading its status file.
      - kind: shell
        options:
          command: '$spurBin workflow run feature-verification.yaml --vars "{\"featureId\":\"$featureId\"}"'
  - id: blocked
    description: Impediment; work suspended.
  - id: done
    description: Completed and verified.
  - id: cancelled
    description: Terminal — abandoned.

transitions:
  # Forward path: backlog → active → verifying → done
  - from: backlog
    to: active
    description: Start feature work
    guard:
      kind: always
  - from: active
    to: verifying
    description: >
      Enter feature verification. Warns unless all linked tasks are done/cancelled.
    guard:
      kind: shell
      options:
        command: '$spurBin feature check $featureId --as verifying'
  - from: verifying
    to: done
    description: >
      Feature verified — complete. Requires the feature-scoped verification pass
      (ADR-119) to have recorded PASS, then strict check (AC validated,
      traceability clean).
    guard:
      kind: shell
      options:
        command: 'test "$(cat .spur/run/$featureId-feature-verification.status 2>/dev/null)" = PASS && $spurBin feature check $featureId --strict --as done'

  # Rework: verifying → active (mandatory History entry)
  - from: verifying
    to: active
    description: Rework — feature sent back to active (mandatory History entry)
    guard:
      kind: always

  # Reopen: done → active (design §7.5 — done is re-enterable; cancelled is not)
  - from: done
    to: active
    description: >
      Reopen completed feature for follow-up work (mandatory History entry).
      Mirrors task-lifecycle done→wip. Prefer spur feature sync --force when
      non-terminal tasks are already linked.
    guard:
      kind: always

  # Blocked ↔ active — bidirectional
  - from: active
    to: blocked
    description: Blocked by an impediment
    guard:
      kind: always
  - from: blocked
    to: active
    description: Impediment resolved
    guard:
      kind: always

  # Cancel: any non-terminal → cancelled (terminal)
  - from: backlog
    to: cancelled
    description: Cancel feature
    guard:
      kind: always
  - from: active
    to: cancelled
    description: Cancel feature
    guard:
      kind: always
  - from: verifying
    to: cancelled
    description: Cancel feature
    guard:
      kind: always
  - from: blocked
    to: cancelled
    description: Cancel feature
    guard:
      kind: always
