Skip to content

Story workflow — status, ceremonies, validation, documentation

Purpose : map every Story going through the project to a small, predictable status machine in OpenProject, with explicit gates between states (so the operator never has to guess what's next), the rituals that produce each gate's verdict, and the documentation artefact every state must leave behind.

Authority : this document is the canonical operational reference for Story state management. CLAUDE.md §0 redirects here for the gory details. ADR-0081 is the invariant authority behind the 8-state contract ; this file is its implementation guide. ADR-0068 (Expert Committee as default review channel) and ADR-0069 (OpenProject as orchestrator) are the upstream ADRs.

Audience : the operator (and Claude when assisting). No team-wide rollout assumed — the project is single-operator today.


1. The 8 happy-path states (per ADR-0081)

[ New ]
  │  pick + plan dossier file created
[ In specification ]
  │  committee plan_review verdict=PASSED
[ Specified ]
  │  impl branch + first commit
[ In progress ]
  │  PR opened + QA + CI/CR/committee green
[ Developed ]
  │  squash merge to main
[ In testing ]
  │  Deploy K8s SUCCESS + smoke test passes
[ Tested ]
  │  ML/FTF gate verdict / Infra runbook / final closure comment
[ Closed ]

Plus 2 escape hatches : On hold (pause with ETA) and Rejected (committee blocker without resolution path) — accessible from any happy-path state.

These map to 8 of the 14 OP statuses available — always referenced by name, never by numeric id, since the IDs are instance-specific and the tooling (scripts/openproject_import_gh.py) resolves statuses by name :

OP status name (authoritative) Workflow role
New filed, not picked
In specification picked, plan dossier + plan_review in flight
Specified plan PASSED, impl-ready buffer
In progress active dev (single-WIP)
Developed PR open + CI/CR green, awaiting merge
In testing merged + deployed in K8s, awaiting smoke + ML gate
Tested system-validated, awaiting final closure verdict
Closed done
On hold escape — pause with ETA
Rejected escape — committee blocker, work item closed without merge

Note : OpenProject status IDs vary across instances. To resolve the current ID for a name, query GET /api/v3/statuses on the active instance. Do not hardcode IDs in tooling — always look them up by name.

The other 4 OP statuses (Confirmed, To be scheduled, Scheduled, Test failed) are not used — operator-side discipline replaces them. They're scaffolding for multi-team workflows that don't fit the single-operator model.

1.1 Sprint version assignment — Stories only

The OP version field (= sprint) follows a strict convention :

Work package type version Rationale
Story always set (Backlog OR active sprint) the unit of delivery — single-WIP applies, sprint = engagement window
Epic never set (always NONE) aggregates Stories spanning multiple sprints ; assigning a sprint creates a false promise of single-sprint closure
Need never set (always NONE) strategic objective on a multi-sprint / multi-month timeline

When a Story is created, it lands in Backlog by default ; the operator moves it to the active sprint version on pick (per §0 of CLAUDE.md). When a Story closes, the version stays — the closure record is the historical mapping of work to sprints.

Epics and Needs surface in the OP Roadmap implicitly via their child Stories' parent links (via the OP UI Hierarchy filter). They do not need their own version row to be visible.

Tooling rule : the canonical importer scripts/openproject_import_gh.py does not set version for any type. Operators (or ad-hoc creation scripts) MUST NOT set version on Epics or Needs. Ad-hoc Story creation scripts SHOULD default to Backlog if no explicit sprint is provided.


2. Per-state contract

Tooling rule — flipping OP status : every "Operator flips OP status to X" step below is the ADR-81 gate ritual (fetch lockVersion, PATCH the status by name, leave a mandatory verdict + evidence comment). Use scripts/op_story_transition.py to do it in one call — it resolves the status by name, validates the edge against the legal-edge graph in this section, and enforces the verdict comment :

python scripts/op_story_transition.py \
    --wp 218 --to "In progress" \
    --verdict "Plan PASSED; impl started" \
    --evidence "branch fix/...-S29; committee 2b0ef8c9; meeting 201" \
    --cvn-id CVN-N001-EE-S29

Non-canonical edges (rework back-edges, state skips) require --force and --force-reason "<why>". --dry-run previews the PATCH + comment with zero writes. (op_story_sync.py only covers Stories in the F1 plan §10 tracking table; this CLI covers any work package by id — EE/diagnostic/tooling Stories.)

2.1 New

  • Trigger to enter : Story created in OP under a version=open sprint (or in the Backlog version, with explicit justification when later pulled).
  • What happens : backlog grooming. Story body should already capture the problem, the user-facing goal, and the type (ML/FTF / Infra/Ops / ADR/Process / Docs).
  • Ceremonies : none.
  • Documentation produced : Story body in OP + linked GitHub issue (created via scripts/openproject_import_gh.py or manually).
  • Gate to exit (→ In specification) : operator picks the Story → creates the plan dossier file under documentation/reviews/YYYY-MM-DD-<slug>-plan.md (or, for Docs / ADR Stories, the ADR draft file itself ; for trivial bug Stories, the GH issue body serves as the plan with explicit operator note in OP comment).

2.2 In specification

  • Trigger to enter : plan dossier file exists in working tree (committed or uncommitted on the impl branch). OP status flipped to In specification by the operator.
  • What happens : operator drafts the plan dossier ; runs committee plan_review (or marks INCONCLUSIVE → addresses gap → re-runs).
  • Ceremonies :
  • Committee plan_review (ADR-68) — python scripts/expert_committee.py --artifact <dossier> --question "..." --session-type plan_review --issue "#NNN". Default-mandatory ; waivable per ADR-68 invariant on operator override with written justification in OP comment + explicit re-review deadline.
  • Plan dossier iteration if committee returns INCONCLUSIVE or REJECTED with addressable blockers.
  • Documentation produced :
  • documentation/reviews/YYYY-MM-DD-<slug>-plan.md — the plan dossier
  • committee/sessions/<session_id>_committee.json — the committee session output (auto-saved by scripts/expert_committee.py)
  • For ML Stories : MLOps readiness draft begins (per ADR-70)
  • Gate to exit (→ Specified) : committee plan_review verdict PASSED (or INCONCLUSIVE with written waiver per the rules above). Operator flips OP status to Specified.

2.3 Specified

  • Trigger to enter : plan_review PASSED + OP status flipped to Specified.
  • What happens : Story is "impl-ready", waiting in the queue. The operator may have several Specified Stories — single-WIP does NOT apply here.
  • Ceremonies : none (the gate already happened).
  • Documentation produced : (none beyond In specification's — ML Stories should have their MLOps readiness signed off by this point per ADR-70)
  • Gate to exit (→ In progress) : implementation branch created (feat/<cvn_id>-... or fix/<cvn_id>-...) with at least one commit pushed. Operator flips OP status to In progress.

2.4 In progress

  • Trigger to enter : impl branch created + first commit pushed.
  • What happens : code + tests + docs + (if applicable) MLOps readiness completion. Single-WIP rule applies — at most ONE Story In progress per operator at any time.
  • Ceremonies : local QA (make qa + mkdocs build --strict) before the PR opens. No formal external ceremony at this stage.
  • Documentation produced :
  • Code + unit tests + integration tests
  • For Infra / Ops Stories : documentation/design/<cvn_id>-<slug>-design.md (signed off by operator before code in non-trivial cases)
  • For ML / Infra-touching-ML Stories (per ADR-70) : documentation/stories/<cvn_id>/mlops_readiness.md
  • For new alerts : a new runbook under documentation/runbooks/ (drafted, published in Tested)
  • Gate to exit (→ Developed) : pull request opened on main ; local QA green ; CI 14 checks green ; CI workflow guardrails (G1-G4) green ; CodeRabbit cycle complete with no actionable comments in the latest review (4-5 passes typical) ; if applicable, committee pr_review (ADR-68) verdict PASSED.

2.5 Developed

  • Trigger to enter : PR open + CI/CR/committee all green per the gate above. Operator flips OP status to Developed.
  • What happens : Story is "merge-ready", awaiting operator's go-ahead to squash. The operator may have several Developed Stories — single-WIP does NOT apply here (only In progress does).
  • Ceremonies : none (the gates already happened in In progress). Optional final operator skim of the diff before clicking merge.
  • Documentation produced : (none beyond In progress's — the PR description is the audit artefact)
  • Gate to exit (→ In testing) : operator squash-merges to main ; squash commit message ends with Closes #NNN. The auto-syncer detects the merge + flips OP to In testing within 5 min (per ADR-0079 invariant 10 + ADR-0081 invariant 3).

2.6 In testing

  • Trigger to enter : squash merge to main recorded.
  • What happens : K8s deploy workflow runs ; operator validates a representative DAG / API endpoint / Console page ; for ML/FTF Stories, the FTF sweep is triggered + per-track gate decision is computed.
  • Ceremonies :
  • Deploy K8s — workflow .github/workflows/cvntrade-deploy-k8s.yml MUST show SUCCESS for the merge SHA.
  • Smoke test — operator triggers a representative DAG / hits an API endpoint / loads a Console page (depending on Story type). Pass criterion = no regression vs pre-merge baseline.
  • FTF sweep + per-track gate (ML/FTF only) — operator triggers dag_finetune__pte with the new factor, waits for ≥ 90 % cell completion, applies the 6 official gates of F1_BUY_BOOST_PLAN.md §6 (with f1_buy-primary derogation for the F1 mission).
  • Documentation produced :
  • PR description with links to plan dossier + committee session ID
  • Squash commit message ending with Closes #NNN (auto-closes the GH issue) — already created at gate entry
  • For ML/FTF : documentation/missions/<area>/YYYY-MM-DD-<slug>-results.md — the results dossier draft (gate-by-gate verdict + provisional decision)
  • Gate to exit (→ Tested) : Deploy K8s SUCCESS + smoke test passes + (for ML/FTF) per-track gate decision is computable from the sweep results.

2.7 Tested

  • Trigger to enter : system validation passed. Operator flips OP status to Tested.
  • What happens : final-closure prep. For ML : the per-track gate verdict (lock / keep available / abandon) is finalised in the results dossier ; for Infra : the runbook is published if alerting changed ; for ADR : adr/index.md + CLAUDE.md are updated if invariant introduced.
  • Ceremonies : final operator review of the closure artefacts.
  • Documentation produced :
  • For ML/FTF : results dossier complete with per-track gate verdict + sign-off checklist all checked
  • For Infra : runbook published under documentation/runbooks/ (if alerting changed)
  • For ADR : documentation/adr/index.md updated + CLAUDE.md updated if invariant introduced
  • Gate to exit (→ Closed) : operator posts final OP comment with PR # + commit SHA + verdict + artefact links.

2.8 Closed

  • Trigger to enter : final operator OP closure comment posted.
  • What happens : the Story is done. OP comment captures the verdict and the references.
  • Ceremonies : none for the Story itself. If this is the last open Story of the active version, triggers a separate version retrospective + gate review (per CLAUDE.md §14) before the next version opens.
  • Documentation produced :
  • OP Story comment with PR #NNN, squash commit SHA, verdict (lock/keep/abandon for ML), runbook references
  • For ML : F1 plan §10 row updated → auto-syncer mirrors
  • For incidents : OPERATIONS.md §17.X log entry with the standard template

2.9 Escape hatches

On hold

  • When : operator decides to pause (external priority, missing dependency, blocker outside the project scope). Accessible from any happy-path state.
  • Mechanism : OP comment with reason + ETA reprise + (optional) link to the blocker. Status → On hold. The Story is not released from single-WIP if it was In progress — operator must explicitly move it back to Specified (give up the slot) or stay in On hold (decision documented, slot consumed by a void).
  • Decay : On hold for > 30 days without ETA update should be Rejected or returned to Specified.

Rejected

  • When : committee plan_review verdict REJECTED and operator decides not to address the blockers (entered from In specification) ; OR pr_review verdict REJECTED blocking the merge with no scope-fitting correction path (entered from Developed).
  • Mechanism : OP comment with committee session ID + post-mortem dossier under documentation/reviews/. Status → Rejected. The work item is closed without a merge ; lessons learned land in OPERATIONS.md if applicable.

3. Ceremonies — quick reference

Ceremony Verdict shape Where in workflow Mandatory ?
Plan dossier draft deterministic (text) gate New → In specification yes
Committee plan_review (ADR-68) LLM consensus → PASSED / REJECTED / INCONCLUSIVE gate In specification → Specified yes — INCONCLUSIVE = document gap, agree with operator on next step, re-submit only when gap is closed
Local QA (make qa + mkdocs build --strict) green/red within In progress yes — pre-PR
CodeRabbit CR cycle itératif, 4-5 passes within In progress yes — wait full cycle, never auto-merge mid-cycle
Committee pr_review (ADR-68) LLM consensus within In progress, before flipping to Developed mandatory for Infra/Ops + ML/FTF + ADR PRs ; exemption for docs/dashboards/config-only
CI build + tests green/red gate In progress → Developed yes
CI workflow guardrails (.github/workflows/pr-workflow-guardrails.yml) green/red gate In progress → Developed yes — enforces G1 title format / G2 Story ref / G3 plan dossier / G4 MLOps readiness ; bypass via guardrails-waiver label or repo var GUARDRAILS_KILL_SWITCH=true
Squash merge to main merge SHA gate Developed → In testing yes
Deploy K8s + smoke success/fail within In testing, post-merge yes
FTF sweep + per-track gate lock / keep available / abandon within In testing, ML/FTF only yes for ML/FTF Stories
Final closure OP comment text gate Tested → Closed yes
Version retrospective text rapport when last Story of a version closes yes — CLAUDE.md §14

4. Documentation per Story type — quick map

Story type New → In specification → Specified In progress → Developed In testing → Tested → Closed
ML / FTF plan dossier + MLOps readiness code + tests results dossier (per-track gate verdict)
Infra / Ops plan dossier + design dossier + (if ML paths) MLOps readiness code + tests + new runbook draft runbook published (if alerting changed) + OPERATIONS update (if incident-related) + production validation evidence
ADR / Process ADR draft (replaces plan dossier) adr/index.md update + CLAUDE.md update if invariant introduced none beyond merge
Docs draft on docs/<slug> branch (no separate plan dossier — the docs ARE the deliverable) mkdocs strict build green visual check on docs.cvntrade.eu post-deploy

The exact phase template for each Story type lives under documentation/templates/TEMPLATE_story_phases_*.md (4 templates landed in PR #766) and is paste-friendly into the OP Story description. Templates may pre-date this ADR's 8-state contract and may need a refresh — track in CVN-N011-EA backlog.


5. Short rules (operator cheat sheet)

  1. Pick = New → In specification : not In progress. In specification is the new "I claimed it, plan_review is running" landing state.
  2. PASSED = In specification → Specified : never skip the committee verdict (or its written waiver per ADR-68).
  3. First commit = Specified → In progress : the impl branch existing without a commit doesn't promote.
  4. Single-WIP : at most ONE Story In progress per operator. Buffer of Specified Stories is unbounded ; same for Developed, In testing, Tested.
  5. Merge = Developed → In testing : the auto-syncer flips OP within 5 min of the merge to main. Operator does not need to manually flip.
  6. Smoke + ML gate = In testing → Tested : both required for ML Stories ; only smoke for non-ML.
  7. Closure comment = Tested → Closed : always with PR # + commit SHA + verdict + artefact links. For ML, wait for the gate decision after the FTF sweep before flipping to Closed.
  8. No code without Story : pulling from Backlog requires written justification in OP comment.
  9. Verdict triage : INCONCLUSIVE is not PASSED. Document the gap, agree on next step (gather evidence, time-boxed waiver, or escalation), re-submit only when gap is closed.
  10. Closes #NNN in the squash commit message → auto-closes the GH issue. Do not close manually.
  11. Last Story of version : closure triggers gate review + close version + retrospective (per CLAUDE.md §14).
  12. Stale In specification / Specified : a Story stuck for > 7 days without progressing should be re-evaluated (priority? blocker? on hold? rejected?).

6. Migration of existing Stories

Pas de migration agressive — les Stories en cours suivent leur cycle, le nouveau workflow s'applique aux picks futurs (post-ADR-0081 merge). Le tableau ci-dessous est indicatif pour les Stories actives au moment où ce workflow est introduit.

Story Status au moment de l'intro ADR-0081 Status cible avec ce workflow Action operator
CVN-N011-EA-S09 (ECE bug) OP In progress (PR #815 ouvert) Developed (PR #815 awaiting merge) ou In testing (post-merge) selon état du PR flip on next OP visit
CVN-N011-EA-S10 (gRPC fork) OP In progress re-évaluer selon état réel du PR inspect PR + flip
CVN-N011-EA-S11 (post-mortem) OP New reste New jusqu'au pick no action
CVN-N011-EA-S12 (CI guardrails) OP In progress re-évaluer inspect + flip
CVN-N011-EA-S13 (this ADR) OP In progress (the ADR PR) suit le 8-state flow dès l'introduction natural progression
CVN-N014-EA-S01 (PTE dedup wp#90) OP New first Story to follow the new 8-state flow end-to-end pick → flip to In specification
CVN-N014-EA-S03 (FTF empty-window wp#104) OP New reste New jusqu'au pick no action

Les Stories pré-ADR qui n'ont pas suivi le 8-state flow ne sont PAS re-classées rétroactivement. Leur historique reste dans le 5-state ancien — l'audit-trail OP préserve la séquence réelle des transitions.


7. Cross-references

  • CLAUDE.md §0 — entry point for the operator workflow (pick + dev process). Redirects to this file for the gory details.
  • ADR-0081 — the invariant authority behind this workflow (8 states, rituals per gate, escape hatches).
  • ADR-0068 — Expert Committee as the default review channel. Defines plan_review and pr_review session types.
  • ADR-0069 — OpenProject as project orchestrator (SSoT). Defines the link between OP Stories, GH issues, and commits. Amended by ADR-0081 for the state model.
  • ADR-0070 — MLOps readiness template mandatory before merge for ML-touching PRs.
  • ADR-0077 — MkDocs + Structurizr as documentation SSoT.
  • ADR-0079 — FTF sweep → Story closure 8-step workflow (the per-Story closure ritual nests inside the ADR-0081 Story state machine).
  • documentation/templates/TEMPLATE_story_phases_ml_ftf.md — phase template for ML/FTF Stories (5 phases).
  • documentation/templates/TEMPLATE_story_phases_infra.md — phase template for Infra / Ops Stories (5 phases).
  • documentation/templates/TEMPLATE_story_phases_adr.md — phase template for ADR / process Stories (3 phases).
  • documentation/templates/TEMPLATE_story_phases_docs.md — phase template for documentation Stories (3 phases).
  • documentation/OPERATIONS.md §15-16 — the operator runbook for committee sessions and OpenProject orchestration (deeper detail than this file, focused on "how do I do X" rather than "what is the state machine").