qfai 1.9.2 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +67 -6
  2. package/assets/init/.qfai/assistant/README.md +27 -0
  3. package/assets/init/.qfai/assistant/agents/acceptance-test-engineer.md +1 -0
  4. package/assets/init/.qfai/assistant/agents/backend-engineer.md +1 -0
  5. package/assets/init/.qfai/assistant/agents/completion-reviewer.md +29 -2
  6. package/assets/init/.qfai/assistant/agents/delivery-planner.md +17 -0
  7. package/assets/init/.qfai/assistant/agents/frontend-engineer.md +1 -0
  8. package/assets/init/.qfai/assistant/agents/implementation-reviewer.md +17 -0
  9. package/assets/init/.qfai/assistant/agents/orchestrator.md +2 -2
  10. package/assets/init/.qfai/assistant/agents/qa-gatekeeper.md +183 -5
  11. package/assets/init/.qfai/assistant/agents/test-design-analyst.md +22 -3
  12. package/assets/init/.qfai/assistant/catalog/cli-ux-guidelines.md +2 -2
  13. package/assets/init/.qfai/assistant/catalog/spec_required_files.json +2 -1
  14. package/assets/init/.qfai/assistant/catalog/test-layers-ci-lanes.md +60 -0
  15. package/assets/init/.qfai/assistant/catalog/test-layers.md +430 -13
  16. package/assets/init/.qfai/assistant/catalog/worklog-entry.schema.md +165 -0
  17. package/assets/init/.qfai/assistant/constitution/communication.md +1 -1
  18. package/assets/init/.qfai/assistant/constitution/constitution.md +1 -1
  19. package/assets/init/.qfai/assistant/constitution/drift-protocol.md +359 -10
  20. package/assets/init/.qfai/assistant/constitution/quality.md +35 -5
  21. package/assets/init/.qfai/assistant/constitution/requirements-decomposition.md +37 -0
  22. package/assets/init/.qfai/assistant/constitution/shared-skill-delegation-baseline.md +419 -8
  23. package/assets/init/.qfai/assistant/constitution/shared-skill-operating-baseline.md +122 -5
  24. package/assets/init/.qfai/assistant/constitution/workflow.md +53 -7
  25. package/assets/init/.qfai/assistant/manifest/agent-catalog.yml +439 -946
  26. package/assets/init/.qfai/assistant/manifest/agent-routing.yml +116 -4
  27. package/assets/init/.qfai/assistant/manifest/review-profiles.yml +9 -0
  28. package/assets/init/.qfai/assistant/process/migrations/v1.4.27-atdd-alignment.md +1 -1
  29. package/assets/init/.qfai/assistant/skills/qfai-atdd/SKILL.md +150 -53
  30. package/assets/init/.qfai/assistant/skills/qfai-atdd/references/credential-reuse.md +146 -0
  31. package/assets/init/.qfai/assistant/skills/qfai-atdd/references/red-provenance.md +456 -0
  32. package/assets/init/.qfai/assistant/skills/qfai-atdd/references/review-fix-rounds.md +128 -0
  33. package/assets/init/.qfai/assistant/skills/qfai-atdd/references/scaffolding.md +29 -0
  34. package/assets/init/.qfai/assistant/skills/qfai-atdd/references/shared-test-artifacts.md +96 -0
  35. package/assets/init/.qfai/assistant/skills/qfai-atdd/references/stale-manifest.md +34 -0
  36. package/assets/init/.qfai/assistant/skills/qfai-atdd/references/test-case-depth-checklist.md +52 -5
  37. package/assets/init/.qfai/assistant/skills/qfai-configure/SKILL.md +16 -8
  38. package/assets/init/.qfai/assistant/skills/qfai-discussion/SKILL.md +8 -4
  39. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/design-md-brand-catalog.md +2 -2
  40. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/discussion-completion-matrix.md +17 -7
  41. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/rcp_footer.md +10 -4
  42. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/review-cycle-playbook.md +5 -1
  43. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/ui-bearing-playbook.md +4 -4
  44. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/ui_ux/review_audit_playbook.md +1 -1
  45. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/ui_ux/trend_scan_playbook.md +1 -1
  46. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/ui_ux_best_practices.md +17 -7
  47. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/01_Context.md +1 -1
  48. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/02_Inception-Deck.md +1 -1
  49. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/03_Story-Workshop.md +11 -3
  50. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/05_Scope.md +5 -2
  51. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/07_NFR.md +1 -1
  52. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/09_Constraints.md +7 -4
  53. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/10_Policy.md +1 -1
  54. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/11_OQ-Register.md +1 -1
  55. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/12_OQ-Resolution-Log.md +1 -1
  56. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/14_Review-Request.md +14 -7
  57. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/99_delta.md +1 -1
  58. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/review/Rxx_reviewer.md +16 -7
  59. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/review/review_request.md +9 -6
  60. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/review/summary.json +2 -0
  61. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/40_screen_contracts.md +3 -2
  62. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/50_review_input_bundle.md +4 -2
  63. package/assets/init/.qfai/assistant/skills/qfai-implement/SKILL.md +252 -130
  64. package/assets/init/.qfai/assistant/skills/qfai-implement/references/change-request-reset.md +93 -0
  65. package/assets/init/.qfai/assistant/skills/qfai-implement/references/checkpoint-verification.md +210 -0
  66. package/assets/init/.qfai/assistant/skills/qfai-implement/references/cross-spec-ownership.md +73 -0
  67. package/assets/init/.qfai/assistant/skills/qfai-implement/references/evidence-revision.md +291 -0
  68. package/assets/init/.qfai/assistant/skills/qfai-implement/references/execution-ledger.md +389 -0
  69. package/assets/init/.qfai/assistant/skills/qfai-implement/references/final-checklist.md +28 -0
  70. package/assets/init/.qfai/assistant/skills/qfai-implement/references/finding-classification.md +49 -0
  71. package/assets/init/.qfai/assistant/skills/qfai-implement/references/ledger-preconditions.md +55 -0
  72. package/assets/init/.qfai/assistant/skills/qfai-implement/references/oracle-strength.md +81 -0
  73. package/assets/init/.qfai/assistant/skills/qfai-implement/references/parallelization-policy.md +235 -0
  74. package/assets/init/.qfai/assistant/skills/qfai-implement/references/red-admissibility.md +91 -0
  75. package/assets/init/.qfai/assistant/skills/qfai-implement/references/red-not-observable.md +112 -0
  76. package/assets/init/.qfai/assistant/skills/qfai-implement/references/relevant-test-suite.md +87 -0
  77. package/assets/init/.qfai/assistant/skills/qfai-implement/references/review-artifact-layout.md +44 -0
  78. package/assets/init/.qfai/assistant/skills/qfai-implement/references/round-evidence.md +118 -0
  79. package/assets/init/.qfai/assistant/skills/qfai-implement/references/selector-granularity.md +24 -0
  80. package/assets/init/.qfai/assistant/skills/qfai-implement/references/upstream-artifact-ordering.md +33 -0
  81. package/assets/init/.qfai/assistant/skills/qfai-implement/references/volume-policy.md +149 -0
  82. package/assets/init/.qfai/assistant/skills/qfai-prototyping/SKILL.md +35 -18
  83. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/evidence-requirements.md +1 -1
  84. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/generator-prompt.md +106 -7
  85. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/handoff.md +40 -11
  86. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/iteration-loop.md +48 -1
  87. package/assets/init/.qfai/assistant/skills/qfai-prototyping/templates/DESIGN.md.sample +6 -0
  88. package/assets/init/.qfai/assistant/skills/qfai-sdd/SKILL.md +87 -22
  89. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/contract-artifact-rules.md +148 -0
  90. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/rcp_footer.md +10 -4
  91. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/review-cycle-playbook.md +5 -1
  92. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-execution-playbook.md +4 -2
  93. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-phase-checklists.md +18 -0
  94. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-quality-gate.md +44 -3
  95. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-triage.md +60 -7
  96. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/spec-traceability-rules.md +171 -6
  97. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/change-request.md +125 -0
  98. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/db-contract.sample.sql +6 -0
  99. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/evidence/sdd-spec.md +92 -0
  100. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/01_Objective.md +27 -0
  101. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/02_Initiative.md +30 -0
  102. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/05_Contracts.md +16 -6
  103. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/06_Glossary.md +19 -0
  104. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/07_Constraints.md +25 -0
  105. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/08_Decisions.md +22 -2
  106. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/11_Slice-Policy.md +37 -10
  107. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/02_User-stories.md +20 -0
  108. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/03_Acceptance-Criteria.md +19 -0
  109. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/04_Business-Rules.md +32 -3
  110. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/06_Test-Cases.md +56 -0
  111. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/07_Decisions.md +29 -2
  112. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/10_Plan.md +41 -0
  113. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/16_Traceability-ledger.md +58 -0
  114. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/tdd/test-list.md +56 -0
  115. package/assets/init/.qfai/assistant/skills/qfai-verify/SKILL.md +57 -102
  116. package/assets/init/.qfai/assistant/skills/qfai-verify/references/articles.md +24 -0
  117. package/assets/init/.qfai/assistant/skills/qfai-verify/references/context-load.md +22 -0
  118. package/assets/init/.qfai/assistant/skills/qfai-verify/references/verify-output-contract.md +48 -0
  119. package/assets/init/.qfai/assistant/skills/qfai-verify/templates/verify-evidence.md +48 -0
  120. package/assets/init/.qfai/assistant/skills/web-research/SKILL.md +25 -7
  121. package/assets/init/.qfai/waivers.yml +11 -5
  122. package/assets/init/root/.github/workflows/qfai-tests.yml +318 -0
  123. package/assets/init/root/.github/workflows/qfai-validate.yml +327 -24
  124. package/assets/init/root/DESIGN.md +6 -0
  125. package/assets/init/root/qfai.config.yaml +15 -12
  126. package/dist/cli/index.cjs +20646 -13620
  127. package/dist/cli/index.cjs.map +1 -1
  128. package/dist/cli/index.mjs +22101 -15061
  129. package/dist/cli/index.mjs.map +1 -1
  130. package/dist/index.cjs +13954 -8829
  131. package/dist/index.cjs.map +1 -1
  132. package/dist/index.d.cts +423 -14
  133. package/dist/index.d.ts +423 -14
  134. package/dist/index.mjs +9330 -4224
  135. package/dist/index.mjs.map +1 -1
  136. package/package.json +22 -19
@@ -0,0 +1,165 @@
1
+ # Work-log Entry Schema Contract
2
+
3
+ - Contract scope: frontmatter and body schema for `.qfai/steering/*.md` entries
4
+ - Owners: the validate stage (schema enforcement), the implement stage (primary writer) and the Reviewer-Gate stage (consumer)
5
+ - Used-by: All implementation/review-phase skills, `npx qfai validate`, Reviewer subagents
6
+ - SSOT modules:
7
+ - `packages/qfai/src/core/worklog/parseEntry.ts` (pure parser: `string → Result<Entry, SchemaError>`)
8
+ - `packages/qfai/src/core/worklog/validateLinks.ts` (link-integrity check)
9
+
10
+ ## Storage model
11
+
12
+ - Per-project, project-root location: `.qfai/steering/`.
13
+ - The **surface** lives at `.qfai/steering/`, not under `.qfai/assistant/`. This
14
+ **schema** ships with the package and `npx qfai init` seeds it at
15
+ `.qfai/assistant/catalog/worklog-entry.schema.md` — the seeded README and entry
16
+ template used to point at an unpublished path, so the contract was
17
+ unresolvable on every consuming project.
18
+ - By default `.gitignore` excludes the directory; projects MAY opt in via override.
19
+ - Filename: `.qfai/steering/<id>.md` where `<id>` is kebab-case ASCII; the frontmatter `id` MUST match the filename stem.
20
+ - Templates live at `.qfai/steering/_templates/`; templates MUST NOT contain entry-shaped frontmatter (validator ignores `_templates/`).
21
+
22
+ ## Frontmatter schema
23
+
24
+ ```yaml
25
+ ---
26
+ id: 2026-05-22-recut-design-call # required; string; kebab-case; matches filename stem
27
+ status: active # required; enum: active | handoff | archived
28
+ kind: decision # required; enum: see below
29
+ created: 2026-05-22 # required; ISO-8601 date (YYYY-MM-DD)
30
+ updated: 2026-05-22 # required; ISO-8601 date; >= created
31
+ scope: spec-0003 # required; "global" or "spec-NNNN"
32
+ blocking: false # required; boolean
33
+ promote-to: spec-0003/07_Decisions.md # required; string (path) OR null
34
+ links: # required; array (may be empty)
35
+ - spec-0003
36
+ - discussion-20260522081618995
37
+ closure-rationale: null # required when status=archived AND no promote-to satisfied; else null/omitted
38
+ promoted-to: null # required when status=archived AND promote-to was satisfied; value is the DR-ID (Decision Row identifier, e.g. `DR-3`) of the appended row in the target `07_Decisions.md`
39
+ ---
40
+ ```
41
+
42
+ ### `kind` enum (REQ-0004)
43
+
44
+ The `kind` field MUST be exactly one of:
45
+
46
+ | `kind` | Write trigger |
47
+ | --------------------- | ---------------------------------------------------------------------------- |
48
+ | `milestone` | Task milestone reached |
49
+ | `decision` | A decision was made during work that needs durable capture |
50
+ | `risk` | A risk was identified |
51
+ | `consultation-needed` | The skill needs user input to proceed |
52
+ | `unexpected` | An unexpected event occurred during work |
53
+ | `unscoped-discovery` | Out-of-scope concern discovered; current task continues unblocked (REQ-0016) |
54
+ | `handoff` | Work needs to pause; another session/operator will resume |
55
+ | `blocker` | The skill is stuck (e.g. root-cause hunt stalled) |
56
+ | `scope-up` | Work volume larger than expected |
57
+ | `scope-down` | Planned work is no longer required |
58
+ | `spike` | Exploratory investigation logged |
59
+
60
+ ### `status` enum
61
+
62
+ | `status` | Meaning |
63
+ | ---------- | --------------------------------------------------------------------------------------------- |
64
+ | `active` | Open; participates in drift/promote/stale checks |
65
+ | `handoff` | Open and awaiting resumption; body MUST satisfy the handoff-brief schema (REQ-0017) |
66
+ | `archived` | Closed; either promoted (`promoted-to` set) or closed-without-promotion (`closure-rationale`) |
67
+
68
+ ### `scope` semantics
69
+
70
+ - `global` — applies project-wide; visible to every skill invocation.
71
+ - `spec-NNNN` — applies only to the named spec. Implementation-phase skills filter on `scope ∈ {global, current-spec}` before reading.
72
+
73
+ ### `promote-to` semantics
74
+
75
+ - `null` — entry will not promote.
76
+ - non-`null` — string of the form `spec-NNNN/07_Decisions.md` (target Decisions row to append). The promote-gate surfaces `W-PENDING-PROMOTION` until satisfied (REQ-0007).
77
+
78
+ ### `links` array
79
+
80
+ Each element MUST resolve to one of:
81
+
82
+ - `spec-NNNN` — an existing spec directory under `.qfai/specs/`
83
+ - `discussion-*` — an existing discussion pack under `.qfai/discussion/`
84
+ - `<entry-id>` — another `.qfai/steering/<id>.md` entry
85
+
86
+ Broken links surface `W-WORKLOG-BROKEN-LINK` (REQ-0015).
87
+
88
+ ## Body schema
89
+
90
+ The body (everything after the closing `---`) is free-form Markdown except for two `kind`-specific schemas below.
91
+
92
+ ### `kind: handoff` body — required sections (REQ-0017)
93
+
94
+ ```markdown
95
+ ## State of the task
96
+
97
+ <one paragraph: where am I, what is done, what remains>
98
+
99
+ ## Next single action
100
+
101
+ <one bullet: the very next thing to do on resume>
102
+
103
+ ## Constraints to preserve
104
+
105
+ - <bulleted list of invariants that the next operator MUST preserve>
106
+
107
+ ## Open questions
108
+
109
+ - <bulleted list; may be empty>
110
+
111
+ ## References to consult first
112
+
113
+ - <bulleted list of entry IDs / spec IDs / discussion IDs>
114
+ ```
115
+
116
+ Reviewer Gate emits `R-HANDOFF-INCOMPLETE` if any of the five sections is missing or empty.
117
+
118
+ ### `kind: decision` body — recommended sections
119
+
120
+ ```markdown
121
+ ## Context
122
+
123
+ <what triggered the decision>
124
+
125
+ ## Decision
126
+
127
+ <what was decided>
128
+
129
+ ## Alternatives considered
130
+
131
+ <bulleted; mark each as accepted | rejected | deferred>
132
+
133
+ ## Rationale
134
+
135
+ <why this option>
136
+
137
+ ## Consequences
138
+
139
+ <what changes downstream>
140
+ ```
141
+
142
+ The body is consulted by Reviewer Gate when emitting `R-WORKLOG-DRIFT`; the structured shape improves the false-positive rate.
143
+
144
+ ## Unit-test obligations (NFR-0010)
145
+
146
+ `parseEntry.ts` MUST have ≥ 12 unit tests covering:
147
+
148
+ 1. Well-formed entry
149
+ 2. Missing required field (e.g. omitted `kind`)
150
+ 3. Invalid enum value
151
+ 4. Broken YAML (parse error)
152
+ 5. UTF-8 BOM tolerated
153
+ 6. CRLF line endings tolerated
154
+ 7. `scope: spec-NNNN`
155
+ 8. `scope: global`
156
+ 9. `promote-to: null`
157
+ 10. `promote-to: spec-NNNN/07_Decisions.md`
158
+ 11. `links: []`
159
+ 12. `links` array with broken reference (parser returns ok; validator emits `W-WORKLOG-BROKEN-LINK`)
160
+
161
+ ≥ 90 % line coverage of `parseEntry.ts` in CI.
162
+
163
+ ## Distributed-surface obligations
164
+
165
+ The seeded `_templates/entry.md`, and any sample entry shipped via `assets/init/`, MUST carry no internal spec ids, version markers or trace ids. The authoritative list of forbidden shapes is the scanner itself; entries use placeholder ids and dates only.
@@ -36,7 +36,7 @@ When an agent needs to ask the user a question, the following rules apply (see a
36
36
  All SKILL.md files MUST include a
37
37
  `## User Questions (AskUserQuestion Protocol)` section with MUST-level wording.
38
38
  SSOT: `packages/qfai/assets/init/.qfai/assistant/skills/*/SKILL.md`.
39
- Deployed copy (updated by `qfai init`): `.qfai/assistant/skills/*/SKILL.md`.
39
+ Deployed copy (updated by `npx qfai init`): `.qfai/assistant/skills/*/SKILL.md`.
40
40
 
41
41
  ## Error handling
42
42
 
@@ -1,7 +1,7 @@
1
1
  # QFAI Constitution (Non‑Negotiable)
2
2
 
3
3
  This document defines **non‑negotiable operating rules** for QFAI agents and subagents.
4
- It is inspired by proven “constitution / articles / guardrails” patterns in existing SDD toolchains, but tailored to QFAI’s minimal workflow.
4
+ It is inspired by proven “constitution / articles / guardrails” patterns in existing SDD toolchains, but adapted to QFAI’s minimal workflow.
5
5
 
6
6
  ---
7
7
 
@@ -14,29 +14,378 @@ Upstream artifacts include, at minimum:
14
14
  - Legacy spec-pack SSOT files when present: `spec.md`, `delta.md`, `plan.md`, `traceability-matrix.md`, `scenario.feature`, `case-catalogue.md`, and numbered pack files (for example `01_Spec.md`..`18_delta.md`)
15
15
  - contracts and schema decisions owned by earlier phases
16
16
  - outputs of discussion/sdd/review stages
17
+ - **test or production artifacts another spec's completed implement run
18
+ certifies** — a file named in another `tdd/test-list.md`'s `Test file` column
19
+ on a `done` row. Changing one is not forbidden (the codebase is not
20
+ partitioned and duplication removal is mandated), but it must be recorded and
21
+ re-reviewed per
22
+ `skills/qfai-implement/references/cross-spec-ownership.md`. It becomes drift
23
+ in the full sense — STOP, Change Request, owner rerun — when the other spec's
24
+ obligation no longer holds rather than merely moving.
25
+
26
+ One file inside `.qfai/specs/**` is carved out of that last line:
27
+ `<spec-id>/tdd/test-list.md`, and only its `Status` / `DR-ID` / `Evidence`
28
+ cells unconditionally, plus its `Test file` and `Selector` cells under the two
29
+ machine-checkable conditions in `#allowed-exceptions-minimal-whitelist`. Its
30
+ **rows** — which obligations exist and what each covers — remain upstream.
31
+
32
+ **Every artifact in this list requires an owner rerun by definition.** There is
33
+ no downstream test for "is an owner rerun required here?" — being on this list
34
+ is the answer, and the rerun is a _consequence_ of the artifact being upstream
35
+ SSOT, never a precondition for the prohibition. A downstream phase that finds
36
+ itself weighing whether the owner needs to be involved has already left its
37
+ lane: it cannot see who owns the artifact, and working that out in the observed
38
+ case required reading the agent roster and reasoning backwards from it.
17
39
 
18
40
  ## Allowed exceptions (minimal whitelist)
19
41
 
20
42
  - `.qfai/evidence/**` append/update
21
- - progress status updates only when the project workflow explicitly allows downstream updates
43
+ - `.qfai/specs/<spec-id>/tdd/test-list.md` — the `Status`, `DR-ID` and
44
+ `Evidence` cells unconditionally, append/update by `/qfai-implement`, plus two
45
+ cells that are writable **only** while a stated condition holds:
46
+ - the `Test file` cell, only while the seeded value is empty or a dash
47
+ placeholder;
48
+ - the `Selector` cell, only while the seeded value does not resolve against
49
+ the row's named test file — the validator's own `selectorResolves`
50
+ predicate is false.
51
+
52
+ Both conditions are machine-checkable, so a reviewer verifies the precondition
53
+ instead of taking the writing stage's word for it, and both are one-way: once
54
+ the condition that authorised the write has ceased to hold — a `Test file` that
55
+ names a path, a `Selector` that resolves — rewriting it is no longer covered.
56
+ Every other
57
+ column of that file — `TC-Refs`, `Layer`, `US-Refs`, `CON-API-Refs` — and
58
+ every other file under `.qfai/specs/**`, stays upstream SSOT: adding, removing
59
+ or re-scoping a row is an upstream change and takes the
60
+ `#when-drift-is-detected` path.
61
+
62
+ - **creating** a governance record under `.qfai/decisions/` — a Change Request
63
+ (`CR-YYYYMMDD-NNNN-<slug>.md`, per `#when-drift-is-detected` step 2) or an
64
+ anomaly Decision Record (`DR-<id>-<slug>.md`, where `<id>` follows the
65
+ Decision Record ID scheme in the spec's `07_Decisions.md`)
22
66
 
23
67
  Any exception beyond this list requires explicit user approval.
24
68
 
69
+ ### Why the execution ledger is named here
70
+
71
+ `/qfai-implement` must write `tdd/test-list.md` after every phase transition,
72
+ and the file lives inside `.qfai/specs/**`. The protocol never classified it in
73
+ either direction, but `#core-rule`'s list is explicitly open-ended ("at minimum")
74
+ and sweeps in "outputs of discussion/sdd/review stages" — and the ledger's schema
75
+ is documented in `skills/qfai-sdd/references/spec-traceability-rules.md`, an
76
+ SDD-stage reference. On the natural reading the ledger _is_ an sdd-stage output,
77
+ so "Downstream skills must not patch upstream SSOT directly" applied to it.
78
+
79
+ The bullet that used to sit here — "progress status updates only when the project
80
+ workflow explicitly allows downstream updates" — could not rescue that, for two
81
+ reasons:
82
+
83
+ - **The condition had no referent.** `progress status`, `project workflow` and
84
+ `downstream update` each occurred exactly once in the whole shipped tree: that
85
+ line itself. Nothing defined what the project workflow is, where such a
86
+ permission is recorded, or what the default is, so in a freshly initialized project the
87
+ condition could never be satisfied.
88
+ - **It was too narrow even if it had.** It covered "progress status", while
89
+ `qfai-implement`'s completion gate item 10 additionally requires the `Evidence`
90
+ column, and the skill's own hard rules forbid the substitute
91
+ ("status-only evidence … MUST be rejected"). The content declared mandatory and
92
+ non-substitutable was precisely the content no rule authorised anyone to
93
+ persist.
94
+
95
+ So an agent obeying the protocol could not satisfy gate item 10, and an agent
96
+ satisfying it was in drift. The entry above names the file and the three cells
97
+ unconditionally, which is what removes the choice.
98
+
99
+ ### Why `Test file` and `Selector` are conditional
100
+
101
+ The deadlock that put the ledger on this list recurs one and two columns over,
102
+ and two shipped validator rules are what create it:
103
+
104
+ - `TDDLIST_TEST_FILE_MISSING` fires at **error** severity for a row whose
105
+ `Test file` cell is empty or a dash placeholder, once its `Status` is `green`,
106
+ `refactor`, `review-fix` or `done`.
107
+ - `TDDLIST_SELECTOR_UNRESOLVED` fires when the `Selector` cell's text is not
108
+ found in the named test file, and its own remediation text says to update the
109
+ selector. The per-row checkpoint command is `<runner> <Test file> -t
110
+ '<Selector>'`, so an unresolved selector also produces a run that selects
111
+ nothing while exiting 0.
112
+
113
+ A row is seeded with a descriptive selector and, commonly, no test file: the
114
+ path is a downstream decision, and the test's title does not exist until the
115
+ micro-cycle authors it. So the writing stage cannot hold the status the first
116
+ three cells **do** authorise without also writing a cell they do **not** — and
117
+ the earlier status it would otherwise have to keep is false, because the row has
118
+ already passed that phase. Seeding both cells upstream does not resolve it
119
+ either: it would require inventing titles that implementers then match exactly,
120
+ inverting the direction of authority between the spec and the test.
121
+
122
+ The conditions are what keep this narrow. They authorise **filling a placeholder
123
+ and repairing an unresolvable selector**, never rewriting a cell that already
124
+ resolves, and never touching the columns that carry the row's obligation
125
+ identity. Decomposing an existing obligation across rows stays in remit for the
126
+ executing stage; minting a new obligation id does not, and that line is
127
+ unchanged.
128
+
129
+ One limit is worth stating, because the `Selector` condition is narrower than it
130
+ reads. `selectorResolves` is deliberately lenient: it accepts verbatim
131
+ containment, then containment of the selector's last identifier-shaped token. So
132
+ a `Selector` that is materially wrong — one that misdescribes which obligations
133
+ its row covers — but happens to share a trailing token with its test file still
134
+ **resolves**, the condition is false, and this carve-out does **not** authorise
135
+ repairing it. That is the conservative direction and it is intended: only a
136
+ selector the runner's own file could not match is repairable here, and a merely
137
+ misdescribing one stays an upstream change.
138
+
139
+ ### Why the Decision Record is on this list
140
+
141
+ A downstream stage cannot always avoid needing one. `qfai-implement` Phase Red
142
+ orders an anomalous row to `exception` as an inline step, and that status is
143
+ invalid without a `DR-*` in the `DR-ID` column — enforced at `error` by
144
+ `TDDLIST_EXCEPTION_MISSING_DR`. Every upstream home for a Decision Record
145
+ (`07_Decisions.md`, `09_delta.md`) is on the `#core-rule` list above, and neither
146
+ of the first two whitelist entries covers minting one: a Decision Record is not
147
+ an `.qfai/evidence/**` write and not a ledger-cell update.
148
+
149
+ Without this entry the only compliant route to executing an inline Phase Red
150
+ step was STOP -> Change Request -> user approval -> owner-skill rerun. That made
151
+ the framework's single escape hatch for a blocked item reachable only through
152
+ the approval loop the block is waiting on, so the first anomaly in any project
153
+ either halted the stage or produced a rule-violating ledger row.
154
+
155
+ The carve-out is exactly as narrow as that need:
156
+
157
+ - **create only.** `.qfai/decisions/` is not upstream SSOT and no owner phase
158
+ writes it, so creating a file there patches nothing. Editing an already-
159
+ approved record is not covered.
160
+ - **the record only, never the reference.** The `07_Decisions.md` /
161
+ `09_delta.md` entry that cites the DR stays an owner-skill write, exactly as
162
+ step 2 already says for a Change Request. A compliant `exception` row needs
163
+ the record and the `DR-ID` cell, not the upstream cross-reference.
164
+ - **not an approval.** Creating the record does not decide the anomaly. A parked
165
+ row still carries `TDDLIST_EXCEPTION_PARKED` until the risk is accepted
166
+ through the `TDDLIST-001` waiver, which is a separate, user-owned artifact.
167
+
168
+ ## Drift classes
169
+
170
+ Drift is one of two things, and the class decides what the Change Request must
171
+ carry. It does **not** decide whether a Change Request is needed: both classes
172
+ STOP, both raise a CR, both wait for approval, both are applied by the owner
173
+ skill. The ownership boundary in `#core-rule` is identical for both.
174
+
175
+ - **Intent drift** — the upstream artifact states something downstream
176
+ disagrees with. There is a real decision to make, the upstream artifact is
177
+ internally consistent, and reasonable alternatives exist.
178
+ - **Defect drift** — the upstream artifact is internally inconsistent,
179
+ unreachable, or contradicts its own declared behaviour, **demonstrated by a
180
+ reproduction**. A `.sql` contract that raises `AmbiguousColumnError` on its
181
+ own declared code path conflicts with nothing: it contradicts only itself.
182
+
183
+ Defect drift is claimed by evidence, not by assertion. A CR that declares
184
+ `Class: defect` without a reproduction — a command plus its verbatim output, or
185
+ the two artifact excerpts that contradict each other — is an intent-drift CR
186
+ that skipped its options, and must be treated as incomplete. "This is obviously
187
+ wrong" is not a reproduction; neither is "the fix is trivial". Cost is not a
188
+ classifier: a large intent change stays intent drift, and a one-token defect
189
+ stays defect drift.
190
+
191
+ Where exactly one correct fix exists, inventing a second and a third option to
192
+ satisfy a template produces a worse record, not a safer one — the operator then
193
+ ratifies a comparison the author knew was fabricated.
194
+
25
195
  ## When drift is detected
26
196
 
27
- 1. STOP downstream editing immediately.
28
- 2. Create a Change Request that includes:
29
- - context (what conflicts)
197
+ 1. STOP downstream editing **of the affected upstream artifact and of every
198
+ downstream item that depends on it**. Unaffected items continue. A dependent
199
+ item is one whose `TC-Refs` / `US-Refs` / `CON-API-Refs` names an obligation
200
+ the CR would change, or whose implementation reads the artifact under
201
+ dispute; when the dependency is arguable, it is dependent. The halt is not
202
+ repository-wide: one defective contract does not stop specs that never
203
+ reference it. What it does stop is `done` — a dependent item may not be
204
+ completed against an obligation known to be under revision.
205
+ 2. Create a Change Request as a file at
206
+ `.qfai/decisions/CR-YYYYMMDD-NNNN-<slug>.md`, from
207
+ `.qfai/assistant/skills/qfai-sdd/templates/change-request.md`. The ID
208
+ pattern is `CR-\d{8}-\d{4}` and the file carries `ID`, `Status`
209
+ (`open` / `approved` / `rejected` / `superseded`), `Approved by`,
210
+ `Approved at` and `Approved option` so the approval is a record, not a
211
+ memory. Creating this file is the only write this step makes: `09_delta.md`
212
+ and `07_Decisions.md` are upstream SSOT, so the reference to this CR is
213
+ written there by the owner skill in step 4, never before approval.
214
+ Contents:
215
+ - class (`intent` / `defect`) — see `#drift-classes`
216
+ - context — for intent drift, what conflicts; for defect drift, what the
217
+ artifact declares and how it breaks that declaration
218
+ - reproduction (command + verbatim output, or the two contradicting
219
+ excerpts) — **required for defect drift**, omit for intent drift
30
220
  - proposed change
31
- - options (at least 3) and recommendation
221
+ - options (at least 3) and recommendation — **intent drift only**; for
222
+ defect drift record the single correct fix instead. Do not manufacture
223
+ alternatives for a change that has one correct answer
224
+ - blocked downstream items — the enumerated set the halt in step 1 covers
225
+ (spec IDs, `TDD-ID` ledger rows, contract paths). This is what makes the
226
+ halt checkable: a reviewer can ask whether an item that kept moving is on
227
+ the list, and an item not on the list is not blocked by this CR
32
228
  - impact scope (spec/plan/tests/contracts/schema)
33
229
  - decision needed from user
34
230
  - approved actions (owner skill rerun plan)
35
- 3. Wait for explicit user approval.
36
- 4. Rerun the owner skill for the upstream artifact.
37
- 5. Resume downstream work only after upstream artifacts are updated.
231
+ 3. Wait for explicit user approval, then set `Status` and the approval fields.
232
+ A defect-drift CR has no option set, so `Approved option` stays `-`; what is
233
+ approved is the single correct fix under `## Proposed change`. The wait
234
+ itself is not waived — the operator is ratifying the classification as much
235
+ as the fix.
236
+ 4. Rerun the owner skill for the upstream artifact, **naming the invocation and
237
+ the mode** the CR approved. That rerun is what records the CR reference in
238
+ `09_delta.md` / `07_Decisions.md`.
239
+
240
+ Invocation by artifact class:
241
+
242
+ | Upstream artifact | Invocation |
243
+ | -------------------- | ------------------------------- |
244
+ | `spec-*/**` files | `/qfai-sdd <spec-id>` |
245
+ | `_policies/**` | `/qfai-sdd` (no argument) |
246
+ | `.qfai/contracts/**` | `/qfai-sdd --contract <CON-ID>` |
247
+
248
+ Mode — the CR's "approved actions" field MUST name one:
249
+ - **`confirm-only`** — re-read the artifact and confirm it already satisfies
250
+ the approved change. Writes nothing but the CR reference. Use when the
251
+ change was already applied by hand under approval, or when the CR only
252
+ re-scopes something the artifact already says.
253
+ - **`re-derive`** — regenerate the artifact from its inputs. May rewrite any
254
+ part of it, and sweeps the downstream ledgers in step 5.
255
+
256
+ Without a named mode neither the author nor the approver can state what the
257
+ rerun executes or what it costs, and "rerun the owner skill" is the whole
258
+ plan.
259
+
260
+ 5. **Sweep the downstream ledgers.** Identify every `tdd/test-list.md` row the
261
+ rerun invalidated — its `TC-Refs` / `US-Refs` / `CON-API-Refs` obligation
262
+ changed or disappeared — and apply the upstream reset transition
263
+ (any status -> `todo`), recording the approved CR/DR ID in `DR-ID` — that
264
+ column carries both `DR-*` and `CR-*` references. The
265
+ sweep covers in-flight rows too: a `red` row whose obligation changed, and
266
+ an `exception` row whose anomaly the rerun resolved or superseded, reset the
267
+ same way. A row whose obligation was deleted outright is removed, not reset.
268
+ 6. Resume the **blocked set of this CR** only after upstream artifacts are
269
+ updated **and** the sweep has run. Resuming with a stale `done` row is
270
+ resuming on a ledger that asserts something known to be false. Resume is
271
+ per-CR: an item on two blocked sets resumes when both release, and an item
272
+ on neither never stopped.
273
+ 7. Record the outcome in the CR: fill `Resolution` and set `Applied at`.
274
+ Approval alone does not release the downstream gate — `qfai-implement`
275
+ treats an `approved` CR without `Applied at` as unresolved.
276
+
277
+ ### Multiple open Change Requests
278
+
279
+ More than one Change Request may be open at once. They are **independent**
280
+ unless they name the same upstream artifact.
281
+
282
+ - A defect found while a CR is open is raised as **its own CR**, not folded
283
+ into the open one. Folding it in would silently widen an approval the
284
+ operator already gave, and the blocked set the operator approved would no
285
+ longer be the blocked set in force.
286
+ - Two CRs naming the same upstream artifact are **ordered**: the second states
287
+ which one it assumes has landed, because the owner-skill rerun for the first
288
+ changes the text the second is written against. If the first is rejected, the
289
+ second is restated or superseded, never applied as written.
290
+ - The effective halt is the **union** of the open CRs' blocked sets. Nothing
291
+ else is halted, however many CRs are open.
292
+ - Open CRs accumulating is itself a project risk: report the count and their
293
+ ages alongside the blockers, rather than letting a queue of unanswered
294
+ decisions read as normal.
295
+
296
+ ## Reviewer-originated obligations
297
+
298
+ The rules above govern a downstream phase **editing** upstream SSOT. This section governs the
299
+ mirror case: a downstream reviewer **originating** a requirement that upstream SSOT does not
300
+ contain. Both are drift.
301
+
302
+ ### Defect or new scope: decide this first
303
+
304
+ Reviewer-originated scope means a **new obligation on the product** — behaviour, policy, or a
305
+ quality bar that upstream never asked for. It does **not** mean "a problem with no `AC-*` beside
306
+ it".
307
+
308
+ A finding is a **defect in the deliverable under review** — not new scope — when it is
309
+ demonstrable from the changed artifacts themselves: the reviewer can point at the code or evidence
310
+ and show it is wrong on its own terms. Typical shapes:
311
+
312
+ - **correctness** — the code does not do what the artifact it implements says it does: an
313
+ unhandled rejection, an unreachable or inverted branch, a contract the code itself declares and
314
+ then breaks;
315
+ - **security / data integrity** — missing validation on an input the code already treats as
316
+ trusted, credential or personal-data exposure, an injection or traversal path opened by the
317
+ change;
318
+ - **code quality** — a regression against a gate the repository already runs (lint, types, tests)
319
+ or against a named constitution / catalog rule.
320
+
321
+ These findings are **blocking**. Their provenance is the deliverable plus the defect class, never
322
+ an `AC-*`: requiring an acceptance criterion for them would oblige a reviewer who has just
323
+ demonstrated a bug to pass it.
324
+
325
+ A finding is **reviewer-originated scope** only when satisfying it would add product behaviour or
326
+ a quality bar that upstream SSOT does not contain and the changed artifacts do not already imply.
327
+ "It would be better if the feature also did X" is scope. "The feature does not do what it says"
328
+ is a defect.
329
+
330
+ ### Provenance and routing
331
+
332
+ - Every reviewer finding declares a `Traces to:` value. See
333
+ `shared-skill-delegation-baseline.md#finding-provenance-must` for the response schema. Legal
334
+ values:
335
+ - an upstream obligation (`AC-*`, `BR-*`, `TC-*`, `CON-*`) or a named constitution/catalog rule;
336
+ - `defect:correctness`, `defect:security`, or `defect:code-quality` — the deliverable-defect
337
+ classes above, each of which MUST carry the concrete evidence in the changed artifacts that
338
+ demonstrates it;
339
+ - `none` — reviewer-originated scope.
340
+ - The first two are **blocking** and gate `done`.
341
+ - `Traces to: none` is reviewer-originated scope. It is **drift**, and it is **not satisfiable
342
+ downstream**: encoding it as production code plus a hard test assertion is the same violation as
343
+ patching upstream SSOT, inverted. It MUST be recorded as `advisory`, MUST NOT be `blocking`, and
344
+ is routed to the Change Request / Open Question path — never to the implementer.
345
+ - Routing an advisory finding:
346
+ 1. The reviewer records it in its response under `Advisory / Change Request proposals`, with
347
+ enough context for the owner phase to adjudicate. The reviewer does **not** write it into
348
+ `08_Open-questions.md`: that file is upstream SSOT (see `#core-rule`) and is owned by
349
+ `/qfai-sdd`.
350
+ 2. If it changes an already-approved obligation, raise a Change Request per
351
+ `#when-drift-is-detected`.
352
+ 3. The owner phase (`/qfai-sdd`) adjudicates and is the phase that records the question in
353
+ `08_Open-questions.md`: **promoted** into `AC-*`/`BR-*`/`TC-*`, **deferred**, or
354
+ **rejected-with-rationale**.
355
+ 4. Only after promotion and an owner rerun may the obligation become a blocking gate — at which
356
+ point it has an upstream ID and is no longer reviewer-originated.
357
+ - A **new** advisory — one that adds a question without changing an already-approved obligation —
358
+ does not block downstream work: the item may reach `done` against its existing upstream
359
+ obligations, with the advisory recorded.
360
+ - An advisory that **changes an already-approved obligation** takes the Change Request path
361
+ instead, and `#when-drift-is-detected` governs from step 1: STOP, no `done` for items that
362
+ depend on the obligation under dispute, resume only after approval and the owner rerun.
363
+ Completing against an obligation that is known to be under revision would ship a knowingly
364
+ inconsistent SSOT.
365
+
366
+ ### Which evidence is committed
367
+
368
+ - **Regenerable** — stage evidence (`.qfai/evidence/<stage>-<spec-id>.md`),
369
+ run logs, reports. Reproducible by rerunning the owner skill; not committed.
370
+ - **Governance record** — Change Requests (`.qfai/decisions/CR-*.md`) and
371
+ durable decision records (`.qfai/evidence/decisions/*.json`). They carry
372
+ user approval and cannot be regenerated, so they are committed. The managed
373
+ `.gitignore` block written by `npx qfai init` negates them after the ignore
374
+ lines for exactly this reason.
38
375
 
39
376
  ## Non-negotiable constraints
40
377
 
41
- - Downstream skills must not patch upstream SSOT directly.
42
- - If approval is not available, stay in STOP state and report blockers.
378
+ - Downstream skills must not patch upstream SSOT directly. **This is detected.**
379
+ `npx qfai validate --profile tdd` — the completion gate `qfai-implement` names
380
+ — diffs the branch against `baseBranch` and emits `QFAI-DRIFT-001` (`error`)
381
+ for every changed file under `paths.contractsDir`, under `_policies/`, or
382
+ matching a protected spec-pack filename. A Change Request at `Status:
383
+ approved` that **names the changed path** silences it; an `open` CR does not,
384
+ because an open CR authorises nothing. The check does not run in the `sdd`
385
+ profile: `/qfai-sdd` owns these files.
386
+ - Downstream reviewers must not originate binding obligations that upstream SSOT does not contain.
387
+ - If approval is not available, stay in STOP state **for that CR's blocked set**
388
+ and report blockers. Work outside every open CR's blocked set proceeds; an
389
+ unanswered decision is not a reason to stop what it does not touch. Report
390
+ each open CR with its age and its blocked set, so an unanswered CR surfaces as
391
+ a standing blocker rather than aging out of view.
@@ -8,13 +8,43 @@ update_frequency: occasional
8
8
 
9
9
  ## Quality gates (baseline)
10
10
 
11
+ **Gate commands are project-defined. Always discover them from the repo.** This
12
+ file names the capabilities a gate set must cover; it never names the commands,
13
+ because the commands belong to the stack. `constitution.md` Article VIII and
14
+ `workflow.md` say the same thing — this file used to disagree with both by
15
+ stating five `pnpm` commands as fact, which on a non-Node repository is five
16
+ commands that do not exist.
17
+
11
18
  When code changes are requested, the expected minimum gates are:
12
19
 
13
- - `pnpm format:check`
14
- - `pnpm lint`
15
- - `pnpm check-types`
16
- - `pnpm test`
17
- - `pnpm verify:pack` (when publishing/distribution matters)
20
+ - format check
21
+ - lint
22
+ - typecheck
23
+ - tests
24
+ - pack / distribution verification (when publishing or distribution matters)
25
+
26
+ Discover the actual commands from the repository, in this order: the project's
27
+ task-runner manifest (`package.json` `scripts`, `Makefile`, `justfile`,
28
+ `pyproject.toml`, `Cargo.toml`, …), then the CI workflow, then the project's own
29
+ contributing docs. `/qfai-configure` records what it detected in
30
+ `.qfai/assistant/catalog/tech.md`; read that before guessing.
31
+
32
+ A capability with no discoverable command is **UNRUN**, not passed. Report it as
33
+ a blocker rather than substituting a command from another stack — a gate that
34
+ cannot run is a gate that silently passes.
35
+
36
+ <!--
37
+ Worked examples, for reading only. Neither list is a default; the capability
38
+ list above is the rule.
39
+ Node (this toolkit's own): `pnpm format:check` / `pnpm lint` /
40
+ `pnpm check-types` / `pnpm test` / `pnpm verify:pack`.
41
+ Python: `uv run ruff format --check` / `uv run ruff check` / `uv run mypy` /
42
+ `uv run pytest` / `uv build`.
43
+ -->
44
+
45
+ This file stays stack-neutral, which is what `category: universal` in its front
46
+ matter claims. `/qfai-configure` reads it and reconciles it with the detected
47
+ toolchain; it does not rewrite the capability list.
18
48
 
19
49
  ## Do not weaken safety nets
20
50
 
@@ -64,3 +64,40 @@ This document is the decision rule SSOT for AI and humans when answering:
64
64
  - Managing release status flags in specs.
65
65
  - Keeping full requirement prose in `.qfai/discussion/`.
66
66
  - Treating diagrams as mandatory at require stage.
67
+
68
+ ## Item granularity (AC/BR/EX/TC)
69
+
70
+ Directory-level slicing answers "which spec does this belong to". It does not
71
+ answer "how big is one item". Referential integrity is trivially satisfied by a
72
+ single oversized node — one BR can carry nine independent rule families and
73
+ still pass every hop of `US -> AC -> BR -> EX -> TC` — so item granularity
74
+ needs its own rule.
75
+
76
+ - **AC** — one acceptance criterion is one observable outcome a reviewer can
77
+ agree or disagree with in isolation.
78
+ - **BR** — one business rule is one independently falsifiable rule. Deletion
79
+ test: if removing half the `Rule` text leaves a complete rule behind, split.
80
+ - **EX** — one example is one concrete input/expected pair for one BR.
81
+ - **TC** — one test case is one verification of one AC or EX. `06_Test-Cases.md`
82
+ already requires at least two TCs per AC; the reciprocal signal is a BR whose
83
+ fan-out is 1 while its `Rule` cell is a size outlier against its siblings.
84
+
85
+ ### Worked split
86
+
87
+ Too coarse:
88
+
89
+ | BR-ID | Rule |
90
+ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91
+ | BR-0001 | An order is accepted when the customer is verified, the stock is reserved, the payment authorisation succeeds, and the delivery address is inside the service area; otherwise it is rejected with the first failing reason. |
92
+
93
+ Each clause is independently falsifiable, so it is four rules:
94
+
95
+ | BR-ID | Rule |
96
+ | ------- | -------------------------------------------------------- |
97
+ | BR-0001 | An unverified customer's order is rejected. |
98
+ | BR-0002 | An order whose stock cannot be reserved is rejected. |
99
+ | BR-0003 | An order whose payment authorisation fails is rejected. |
100
+ | BR-0004 | An order addressed outside the service area is rejected. |
101
+
102
+ Rejection _ordering_ is a fifth rule if the order is observable, and belongs in
103
+ its own BR rather than as a trailing clause on the others.