CRUMB a card from devarno-cloud

Classification Layers & Status Lifecycle

traceo beginner 4 min read

ELI5

The lifecycle is a recommended path: drafted, approved, built, verified, and eventually deprecated when something replaces it. Treat it as a convention the team follows. The server does not stop you skipping a step. The classification, L1 through L4, says which layer of the design a requirement belongs to, and you can edit it.

Technical Deep Dive

Source: traceo-cat/migrations/008_reconcile_schema.sql:99-113, traceo_mcp_server/traceo_mcp_server/models.py:50-59, server.py:663-715 (update_requirement), services/core.py:823-832 (analyze_impact status weights); traceo-cat origin/main 7d647ab, read 2026-10-25

Classification (5 layers, 6 values)

LayerValue(s)
L1, StrategicL1-Strategic
L2, Functional / PerformanceL2-Functional, L2-Performance
L3, SystemL3-System
L4, Implementation / VerificationL4-Implementation, L4-Verification

L2 and L4 each have two flavours, so a non-functional spec (L2-Performance) and a test-shaped requirement (L4-Verification) can be told apart without a second column. The column is nullable in 008. update_requirement accepts classification among its editable fields (server.py:690-701), so it is not immutable after creation.

Status lifecycle: intended order, not enforced

stateDiagram-v2
[*] --> draft
draft --> candidate
candidate --> approved
approved --> active
active --> implemented
implemented --> verified
draft --> deprecated
candidate --> deprecated
approved --> deprecated
active --> deprecated
implemented --> deprecated
verified --> deprecated
deprecated --> [*]

The diagram shows the convention. Neither layer enforces it:

  • Database: requirement_status is a plain enum with no transition CHECK and no trigger guarding order.
  • Application: update_requirement validates that status is a member of the enum and nothing more. A positive search on 2026-10-25 for transition / forward / can_transition across server.py, services/ and models.py found nothing. The v1.0 claim that core.py walks transitions in order was false.

The one place status order carries meaning in code is analyze_impact. It weights the current and proposed status (status_weights, services/core.py:823-832) to score how disruptive a change is.

flowchart LR
U["update_requirement {status: implemented}"] --> V{"value in RequirementStatus?"}
V -- no --> E["ValidationError"]
V -- yes --> W["written as-is, from any prior status"]
W --> T["version trigger snapshots the row"]

Deletion is soft

requirements.deleted_at exists, the uniqueness of external_id only counts rows where deleted_at IS NULL, and repository reads filter on deleted_at IS NULL. There is no rejected status. A requirement you never want is deleted. One you used to want is marked deprecated, so historical traceability still resolves.

Priority is orthogonal

requirement_priority (P0-critical … P3-low, default P2-medium) is independent of status.

Key Terms

  • Classification → which design layer the requirement belongs to (L1–L4). It can be edited.
  • Status → where the requirement is in the review, build and verify lifecycle. The order is a convention.
  • Deprecated → the “do not use” state. The row stays so audits and traces still resolve.

Q&A

Q: Can a requirement transition directly from draft to implemented? A: Yes. Nothing stops it: the enum has no transition constraint, and update_requirement only checks that the value is a valid status. The forward order is team practice, not server policy.

Q: What is the terminal status for an obsolete requirement? A: deprecated. The row and its relationships remain, so traceability for past reviews still resolves. Removing a row outright is a soft delete via deleted_at.

Q: Which classifications live at L2? A: L2-Functional and L2-Performance. They share a layer in the matrix and separate functional from non-functional concerns at query time.

Examples

A reviewer moves REQ-42 straight from draft to verified by mistake. The server accepts it, and the version trigger records the change in requirement_versions. To catch the slip, read that history (get_version_history) or review in the UI. Don’t expect the API to refuse it.

neighbors on the map