qfai 1.9.2 → 1.10.0

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 (123) hide show
  1. package/README.md +48 -2
  2. package/assets/init/.qfai/assistant/agents/acceptance-test-engineer.md +1 -0
  3. package/assets/init/.qfai/assistant/agents/backend-engineer.md +1 -0
  4. package/assets/init/.qfai/assistant/agents/completion-reviewer.md +13 -2
  5. package/assets/init/.qfai/assistant/agents/delivery-planner.md +9 -0
  6. package/assets/init/.qfai/assistant/agents/frontend-engineer.md +1 -0
  7. package/assets/init/.qfai/assistant/agents/implementation-reviewer.md +6 -0
  8. package/assets/init/.qfai/assistant/agents/orchestrator.md +2 -2
  9. package/assets/init/.qfai/assistant/agents/qa-gatekeeper.md +96 -3
  10. package/assets/init/.qfai/assistant/agents/test-design-analyst.md +20 -3
  11. package/assets/init/.qfai/assistant/catalog/cli-ux-guidelines.md +2 -2
  12. package/assets/init/.qfai/assistant/catalog/spec_required_files.json +2 -1
  13. package/assets/init/.qfai/assistant/catalog/test-layers.md +355 -14
  14. package/assets/init/.qfai/assistant/catalog/worklog-entry.schema.md +165 -0
  15. package/assets/init/.qfai/assistant/constitution/communication.md +1 -1
  16. package/assets/init/.qfai/assistant/constitution/drift-protocol.md +304 -10
  17. package/assets/init/.qfai/assistant/constitution/quality.md +35 -5
  18. package/assets/init/.qfai/assistant/constitution/requirements-decomposition.md +37 -0
  19. package/assets/init/.qfai/assistant/constitution/shared-skill-delegation-baseline.md +244 -8
  20. package/assets/init/.qfai/assistant/constitution/shared-skill-operating-baseline.md +122 -5
  21. package/assets/init/.qfai/assistant/constitution/workflow.md +53 -7
  22. package/assets/init/.qfai/assistant/manifest/agent-catalog.yml +316 -945
  23. package/assets/init/.qfai/assistant/manifest/agent-routing.yml +50 -4
  24. package/assets/init/.qfai/assistant/manifest/review-profiles.yml +9 -0
  25. package/assets/init/.qfai/assistant/process/migrations/v1.4.27-atdd-alignment.md +1 -1
  26. package/assets/init/.qfai/assistant/skills/qfai-atdd/SKILL.md +61 -21
  27. package/assets/init/.qfai/assistant/skills/qfai-atdd/references/test-case-depth-checklist.md +23 -4
  28. package/assets/init/.qfai/assistant/skills/qfai-configure/SKILL.md +15 -7
  29. package/assets/init/.qfai/assistant/skills/qfai-discussion/SKILL.md +8 -4
  30. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/design-md-brand-catalog.md +2 -2
  31. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/discussion-completion-matrix.md +17 -7
  32. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/rcp_footer.md +10 -4
  33. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/review-cycle-playbook.md +1 -1
  34. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/ui-bearing-playbook.md +4 -4
  35. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/ui_ux/review_audit_playbook.md +1 -1
  36. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/ui_ux/trend_scan_playbook.md +1 -1
  37. package/assets/init/.qfai/assistant/skills/qfai-discussion/references/ui_ux_best_practices.md +17 -7
  38. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/01_Context.md +1 -1
  39. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/02_Inception-Deck.md +1 -1
  40. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/03_Story-Workshop.md +11 -3
  41. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/05_Scope.md +5 -2
  42. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/07_NFR.md +1 -1
  43. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/09_Constraints.md +7 -4
  44. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/10_Policy.md +1 -1
  45. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/11_OQ-Register.md +1 -1
  46. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/12_OQ-Resolution-Log.md +1 -1
  47. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/14_Review-Request.md +14 -7
  48. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/99_delta.md +1 -1
  49. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/review/Rxx_reviewer.md +16 -7
  50. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/review/review_request.md +9 -6
  51. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/40_screen_contracts.md +3 -2
  52. package/assets/init/.qfai/assistant/skills/qfai-discussion/templates/uiux/50_review_input_bundle.md +4 -2
  53. package/assets/init/.qfai/assistant/skills/qfai-implement/SKILL.md +250 -128
  54. package/assets/init/.qfai/assistant/skills/qfai-implement/references/change-request-reset.md +93 -0
  55. package/assets/init/.qfai/assistant/skills/qfai-implement/references/checkpoint-verification.md +106 -0
  56. package/assets/init/.qfai/assistant/skills/qfai-implement/references/cross-spec-ownership.md +73 -0
  57. package/assets/init/.qfai/assistant/skills/qfai-implement/references/evidence-revision.md +77 -0
  58. package/assets/init/.qfai/assistant/skills/qfai-implement/references/execution-ledger.md +303 -0
  59. package/assets/init/.qfai/assistant/skills/qfai-implement/references/final-checklist.md +19 -0
  60. package/assets/init/.qfai/assistant/skills/qfai-implement/references/finding-classification.md +49 -0
  61. package/assets/init/.qfai/assistant/skills/qfai-implement/references/ledger-preconditions.md +55 -0
  62. package/assets/init/.qfai/assistant/skills/qfai-implement/references/oracle-strength.md +81 -0
  63. package/assets/init/.qfai/assistant/skills/qfai-implement/references/parallelization-policy.md +235 -0
  64. package/assets/init/.qfai/assistant/skills/qfai-implement/references/red-admissibility.md +91 -0
  65. package/assets/init/.qfai/assistant/skills/qfai-implement/references/red-not-observable.md +70 -0
  66. package/assets/init/.qfai/assistant/skills/qfai-implement/references/relevant-test-suite.md +87 -0
  67. package/assets/init/.qfai/assistant/skills/qfai-implement/references/review-artifact-layout.md +31 -0
  68. package/assets/init/.qfai/assistant/skills/qfai-implement/references/round-evidence.md +102 -0
  69. package/assets/init/.qfai/assistant/skills/qfai-implement/references/selector-granularity.md +24 -0
  70. package/assets/init/.qfai/assistant/skills/qfai-implement/references/volume-policy.md +149 -0
  71. package/assets/init/.qfai/assistant/skills/qfai-prototyping/SKILL.md +35 -18
  72. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/evidence-requirements.md +1 -1
  73. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/generator-prompt.md +106 -7
  74. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/handoff.md +40 -11
  75. package/assets/init/.qfai/assistant/skills/qfai-prototyping/references/iteration-loop.md +48 -1
  76. package/assets/init/.qfai/assistant/skills/qfai-prototyping/templates/DESIGN.md.sample +6 -0
  77. package/assets/init/.qfai/assistant/skills/qfai-sdd/SKILL.md +82 -22
  78. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/contract-artifact-rules.md +148 -0
  79. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/rcp_footer.md +10 -4
  80. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/review-cycle-playbook.md +1 -1
  81. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-execution-playbook.md +4 -2
  82. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-phase-checklists.md +18 -0
  83. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-quality-gate.md +44 -3
  84. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/sdd-triage.md +60 -7
  85. package/assets/init/.qfai/assistant/skills/qfai-sdd/references/spec-traceability-rules.md +157 -5
  86. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/change-request.md +125 -0
  87. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/contracts/db-contract.sample.sql +6 -0
  88. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/evidence/sdd-spec.md +92 -0
  89. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/01_Objective.md +27 -0
  90. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/02_Initiative.md +30 -0
  91. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/05_Contracts.md +16 -6
  92. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/06_Glossary.md +19 -0
  93. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/07_Constraints.md +25 -0
  94. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/08_Decisions.md +22 -2
  95. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/_policies/11_Slice-Policy.md +37 -10
  96. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/02_User-stories.md +20 -0
  97. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/03_Acceptance-Criteria.md +19 -0
  98. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/04_Business-Rules.md +32 -3
  99. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/06_Test-Cases.md +56 -0
  100. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/07_Decisions.md +29 -2
  101. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/10_Plan.md +41 -0
  102. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/16_Traceability-ledger.md +58 -0
  103. package/assets/init/.qfai/assistant/skills/qfai-sdd/templates/specs/spec/tdd/test-list.md +56 -0
  104. package/assets/init/.qfai/assistant/skills/qfai-verify/SKILL.md +57 -102
  105. package/assets/init/.qfai/assistant/skills/qfai-verify/references/articles.md +24 -0
  106. package/assets/init/.qfai/assistant/skills/qfai-verify/references/context-load.md +22 -0
  107. package/assets/init/.qfai/assistant/skills/qfai-verify/references/verify-output-contract.md +48 -0
  108. package/assets/init/.qfai/assistant/skills/qfai-verify/templates/verify-evidence.md +48 -0
  109. package/assets/init/.qfai/assistant/skills/web-research/SKILL.md +25 -7
  110. package/assets/init/.qfai/waivers.yml +11 -5
  111. package/assets/init/root/DESIGN.md +6 -0
  112. package/assets/init/root/qfai.config.yaml +15 -12
  113. package/dist/cli/index.cjs +11023 -7139
  114. package/dist/cli/index.cjs.map +1 -1
  115. package/dist/cli/index.mjs +10963 -7080
  116. package/dist/cli/index.mjs.map +1 -1
  117. package/dist/index.cjs +8782 -5257
  118. package/dist/index.cjs.map +1 -1
  119. package/dist/index.d.cts +280 -8
  120. package/dist/index.d.ts +280 -8
  121. package/dist/index.mjs +11776 -8264
  122. package/dist/index.mjs.map +1 -1
  123. package/package.json +18 -19
@@ -0,0 +1,93 @@
1
+ # Approved Change Request reset (the only sanctioned backward transition)
2
+
3
+ `tdd/test-list.md` has a forward-only lifecycle
4
+ (`todo -> red -> green -> refactor -> done`) and skips `done` rows on
5
+ re-execution. An approved upstream change can invalidate rows that already
6
+ reached `green`, `refactor` or `done`, and the Change Request template
7
+ (`.qfai/assistant/skills/qfai-sdd/templates/change-request.md`) instructs the
8
+ approved actions to "reset the `tdd/test-list.md` rows this change
9
+ invalidates". Without a stated exception that instruction is unperformable:
10
+ the skill would either violate the forward-only rule or leave a `done` row
11
+ holding an implementation result the approved change already invalidated.
12
+
13
+ ## The mandatory preflight
14
+
15
+ The reset cannot be reached from the normal loop: `Required Process` starts by
16
+ selecting the first `todo` row, and the skill exits with "nothing to do" when
17
+ every row is `done` — exactly the state an approved reset is meant to reopen.
18
+ So the sweep is a **preflight**, not an opportunistic step:
19
+
20
+ 1. On every run, before the ledger is read for any other purpose, enumerate
21
+ `.qfai/decisions/CR-*.md` and keep those in scope for the target spec.
22
+ 2. Apply every reset the approved ones authorise (conditions below).
23
+ 3. Only then judge the ledger. A run whose preflight reset rows must not report
24
+ "nothing to do", because the all-`done` state it would report is stale.
25
+
26
+ A run with no in-scope approved CR does nothing here and proceeds unchanged.
27
+
28
+ ## The exception
29
+
30
+ A `.qfai/decisions/CR-*.md` whose "Approved actions" name a downstream ledger
31
+ sweep may reset the rows that change invalidates back to `todo`. Those rows are
32
+ then re-executed rather than skipped.
33
+
34
+ This is an operator-approved act, not a skill decision. It is legal only when
35
+ all of the following hold:
36
+
37
+ - the CR's `Status` is `approved`, with `Approved by` and `Approved at`
38
+ populated;
39
+ - the CR's "Approved actions" section **enumerates the rows** — the `TDD-ID`s,
40
+ or a verifiable selection rule — so the reset scope is what the operator
41
+ approved and not what the skill later decided was invalidated. A row outside
42
+ that enumeration is not approved; widening the scope needs a new CR;
43
+ - each reset row records that CR's ID in its `DR-ID` column — that column
44
+ carries both `DR-*` and `CR-*` references as a comma-separated list, and the
45
+ ID is **retained** as the row re-runs through `red` / `green` / `refactor` /
46
+ `done`, since clearing it on the next transition erases the only record of the
47
+ approved reopen; and
48
+ - the reset is recorded in the CR's `Resolution` section (which rows, by
49
+ `TDD-ID`).
50
+
51
+ ## A retained `CR-*` is not an exception's `DR-*`
52
+
53
+ A reset row that later hits an anomaly still owes a Decision Record. The
54
+ retained `CR-*` records the approved reopen, not the anomaly, so it does not
55
+ satisfy the `exception` requirement on its own: add the `DR-*` alongside it
56
+ (`DR-NNNN, CR-YYYYMMDD-NNNN`). `TDDLIST_EXCEPTION_MISSING_DR` fires on an
57
+ `exception` row whose `DR-ID` cell is empty **or holds `CR-*` references only**.
58
+
59
+ ## What stays prohibited
60
+
61
+ - Any backward transition not covered by an approved CR, including the named
62
+ `green -> red` case. The error text is unchanged:
63
+ `"Backward transition prohibited: green -> red"`.
64
+ - Resetting rows before approval. A CR at `Status: open` authorises nothing.
65
+ - Resetting rows the CR's "Approved actions" did not name.
66
+
67
+ ## When an in-scope CR counts as resolved
68
+
69
+ The spec completion gate covers only the `.qfai/decisions/CR-*.md` **in scope for
70
+ this spec**: `Impact scope` names this spec or a shared policy it depends on, or
71
+ this spec's artifacts reference it. A CR confined to another spec never blocks
72
+ this one.
73
+
74
+ An in-scope CR is **resolved** only when every one of these holds. A CR failing
75
+ any single one — a half-filled record included — is **unresolved** and blocks
76
+ completion:
77
+
78
+ - `Status` is `approved`, `rejected` or `superseded` (never `open`);
79
+ - `Approved by` / `Approved at` are populated, plus `Approved option` when
80
+ `Status` is `approved` and `Superseded by` when it is `superseded`;
81
+ - `Resolution` records what was actually done; and
82
+ - when `Status` is `approved`, `Applied at` is populated — approval alone does
83
+ not release the gate. It is set only after the owner-skill rerun in "Approved
84
+ actions" completed and upstream artifacts are updated, which is when
85
+ `constitution/drift-protocol.md` resumes downstream work.
86
+
87
+ ## Ordering against the drift protocol
88
+
89
+ `.qfai/assistant/constitution/drift-protocol.md` resumes downstream work only
90
+ after the owner skill has been rerun and the upstream artifacts are updated.
91
+ The ledger sweep is downstream work, so it follows the owner-skill rerun; the
92
+ CR's `Applied at` is set once both are done. An `approved` CR without
93
+ `Applied at` is still unresolved and blocks spec completion.
@@ -0,0 +1,106 @@
1
+ # Checkpoint Verification
2
+
3
+ "Checkpoint verification" is the whole-repository regression check run at a checkpoint boundary. It
4
+ is what item 12 of the 12-point gate refers to and the only thing it refers to.
5
+
6
+ ## Checkpoint boundary
7
+
8
+ A checkpoint boundary is reached in exactly two places:
9
+
10
+ - **Per item** — after all routed blocking reviewers have returned PASS for the item and before it
11
+ transitions `refactor` -> `done`. Every item has exactly one.
12
+ - **Per spec** — after the last item in `test-list.md` reaches `done` or a valid `exception`, before
13
+ declaring spec-level completion.
14
+
15
+ No other point is a checkpoint boundary. There is no "every N items" rule.
16
+
17
+ ## Verification command set (per item)
18
+
19
+ Run all of the following from the repository root, in order. Substitute the project's own runner for
20
+ the placeholders; record the literal commands actually executed in evidence.
21
+
22
+ 1. The item's own test — the ledger's `Test file` **plus** the runner's test-name option applied to
23
+ the ledger's `Selector`. Two runner-specific decisions, and both must be made from the ledger row:
24
+
25
+ **a. The name option.** The `Selector` is a test NAME, and every common runner takes a name
26
+ through a flag, not a positional argument: a positional is a file filter. Vitest and Jest use
27
+ `-t`, pytest uses `-k`, `go test` uses `-run`. Passing the selector positionally
28
+ (`<test runner> '<Selector>'`) exits 1 with "No test files found", so the checkpoint would fail
29
+ on every item and none could leave `refactor`.
30
+
31
+ **b. The unit of selection.** What goes in front of the name option is whatever the runner
32
+ selects by, which is not always a file:
33
+ - **File-selecting runners** (vitest, jest, pytest) take the `Test file` itself:
34
+
35
+ ```bash
36
+ <test runner> <Test file> -t '<Selector>'
37
+ ```
38
+
39
+ - **Package-selecting runners** (`go test`) take a package. `go help test` documents the usage
40
+ as `go test [build/test flags] [packages] [build/test flags & test binary flags]`, and it
41
+ compiles the package's sources together with the matching `*_test.go` files. Handing it a lone
42
+ file switches it into file mode and drops the rest of the package from the build, so the item's
43
+ test normally fails on undefined symbols. Derive the package from the `Test file`'s directory:
44
+
45
+ ```bash
46
+ go test ./<dir of Test file> -run '<Selector>'
47
+ ```
48
+
49
+ Record the literal command actually run either way. If a project's runner selects by something
50
+ else again, derive that unit from `Test file` in the same manner and say so in the evidence.
51
+
52
+ 2. The full test suite — `<test runner>` with no file filter and no test-name option.
53
+ 3. The project's static gates, when the repository defines them — formatter check, linter, and type
54
+ check.
55
+ 4. `npx qfai validate --profile tdd --fail-on error` — qfai is a project dependency, not a global
56
+ command; a bare `qfai …` is `command not found` (exit 127) on a normal local install, which would
57
+ fail every checkpoint.
58
+
59
+ ## Verification command set (per spec)
60
+
61
+ The spec-level boundary has no "item just completed" — a re-run in a later session has none, and
62
+ under parallel slices the ledger order does not identify one either. So step 1 is dropped: the
63
+ spec-level set is steps 2, 3 and 4 only. Everything step 1 would have proved is already covered by
64
+ the full suite.
65
+
66
+ ## Pass criteria
67
+
68
+ Checkpoint verification PASSES only when **every** command in the applicable set exits 0, and step 4
69
+ reports zero `QFAI-TEST-001` findings. Any non-zero exit is a FAIL: for a per-item checkpoint the
70
+ item stays at `refactor`, the failure is fixed, and the whole set is re-run. A partial run is not a
71
+ pass.
72
+
73
+ **A fix invalidates the reviewer PASS that preceded it.** The per-item boundary sits after
74
+ `completion-reviewer` and `implementation-reviewer` returned PASS (Phase: Refactor, steps 4-5), so
75
+ those verdicts were given against the pre-fix test and production code. Whenever the fix changes
76
+ either, re-submit to every routed blocking reviewer the change could affect — always the ones whose
77
+ scope it touches — and obtain a fresh PASS **before** re-running the command set. Re-running the
78
+ commands alone would let an item reach `done` carrying code no reviewer ever saw, which the
79
+ skill's own stale-evidence rule forbids. A fix that changes nothing a reviewer judged — re-running
80
+ after a flaky external service, say — needs no re-review; record which case applied.
81
+
82
+ ## Spec-level boundary on an already-complete ledger
83
+
84
+ A ledger is **terminal** when every row is `done` or a valid `exception` — the same condition the
85
+ spec completion conditions use. The last row reaching `exception` ends the loop exactly as `done`
86
+ does, so both must reach this boundary.
87
+
88
+ The per-spec boundary is owed once per terminal ledger state, not once per session. Two cases leave
89
+ it unrecorded:
90
+
91
+ - the run was interrupted between the last row reaching its terminal status and the spec-level
92
+ verification;
93
+ - `/qfai-implement` is re-run against a ledger that is already terminal.
94
+
95
+ In both, an unconditional "nothing to do" exit would skip the boundary permanently, and no later
96
+ re-run could repair it — a re-run finds no `todo` rows to process either. So before that exit: read
97
+ `.qfai/evidence/implement-<spec-id>.md` for spec-level `Checkpoint verification command` /
98
+ `Checkpoint verification result` entries covering the current ledger state. Run the **per spec**
99
+ command set above and record them when they are absent, or when they predate the last ledger
100
+ change. Only then report "nothing to do".
101
+
102
+ ## Evidence
103
+
104
+ Record the result in `.qfai/evidence/implement-<spec-id>.md` using the two per-item evidence fields
105
+ `Checkpoint verification command` and `Checkpoint verification result`. As with RED/GREEN evidence,
106
+ a status without its command is invalid, and evidence from a previous checkpoint MUST NOT be reused.
@@ -0,0 +1,73 @@
1
+ # Cross-spec Code Ownership
2
+
3
+ What to do when implementing one spec correctly requires changing something
4
+ another spec's `done` rows certify.
5
+
6
+ ## Why this needs a rule at all
7
+
8
+ `/qfai-implement` constrains execution to **one spec** and scopes every artifact
9
+ it writes — the ledger, the evidence file — to that spec. The codebase is not
10
+ partitioned at all, qfai defines no module → owning-spec index, and the Refactor
11
+ phase **mandates** duplication removal one line before this rule applies.
12
+
13
+ So the collision is the normal case, not an edge one. And both available
14
+ outcomes used to violate a shipped rule:
15
+
16
+ - change the file, and spec A's `done` rows now certify behaviour that was
17
+ re-tested by nobody — `done` has no outbound edge, so those rows cannot be
18
+ reopened;
19
+ - do not change it, and the Refactor phase's own instruction is unmet, and
20
+ spec B ships a duplicate.
21
+
22
+ Neither left any trace under `.qfai/**`. `drift-protocol.md`'s upstream list
23
+ contains no code or test artifacts, so the STOP → Change Request → owner-rerun
24
+ route never fired for them.
25
+
26
+ ## The rule
27
+
28
+ **A file another spec's ledger names in `Test file` is that spec's to certify.**
29
+ Editing it does not require permission — it requires a record and a re-review.
30
+
31
+ 1. **Detect.** Before editing a production file in Refactor, search every
32
+ `tdd/test-list.md` for it. A hit means the edit is cross-spec.
33
+ 2. **Record.** Add a `## Cross-spec obligations` entry to this spec's evidence
34
+ file (fields below).
35
+ 3. **Re-review.** Run `completion-reviewer` against the affected specs'
36
+ obligations as well as this one's. The reviewer is the party that can say
37
+ whether the other spec's assertions still hold.
38
+ 4. **Do not close over it.** An open entry is a completion prohibition
39
+ (`qfai-implement/SKILL.md#completion-prohibition-conditions`).
40
+
41
+ ## The evidence entry
42
+
43
+ Per affected spec, in `.qfai/evidence/implement-<spec-id>.md`:
44
+
45
+ | Field | Meaning |
46
+ | -------------------- | ------------------------------------------------------------- |
47
+ | `TDD-ID` | the row in **this** spec whose work forced the change |
48
+ | `Blocked spec` | the spec whose `done` rows the file belongs to |
49
+ | `Blocked TDD-IDs` | the rows in that spec that name the file |
50
+ | `File` | the exact path changed |
51
+ | `Change required` | what had to change, in one sentence |
52
+ | `Obligation at risk` | what that spec asserted about the file that is now unverified |
53
+ | `Resolution` | `re-reviewed` (the reviewer confirmed it holds) or `CR-*` |
54
+
55
+ `Obligation at risk` is the load-bearing field. "Changed a shared helper" is not
56
+ a record; "spec-0004 TDD-0012 asserts this helper rejects an empty batch, and
57
+ that path is now routed through the new guard" is.
58
+
59
+ ## When re-review is not enough
60
+
61
+ If the other spec's obligation genuinely no longer holds — the behaviour it
62
+ asserted has changed, not merely moved — that is upstream drift, and
63
+ `constitution/drift-protocol.md#when-drift-is-detected` governs from step 1.
64
+ Record the CR id in `Resolution`. Re-review cannot ratify a behaviour change the
65
+ other spec never agreed to; only its owner can.
66
+
67
+ ## What this does not do
68
+
69
+ It does not partition the codebase, and it does not give `done` an outbound
70
+ edge. Both are larger changes. What it does is make the collision **visible and
71
+ blocking** instead of silent — the two outcomes above are still the two
72
+ outcomes, but neither can now be taken without a record that names the
73
+ obligation it puts at risk.
@@ -0,0 +1,77 @@
1
+ # Evidence revision (what state the observation describes)
2
+
3
+ Four of the twelve gate items — 3, 5, 7 and 8 — are sub-agent observations, and
4
+ `#evidence-hard-rules` says stale evidence MUST NOT be reused. That rule needs
5
+ something to compare against. This file defines it.
6
+
7
+ ## The field
8
+
9
+ Every observation records the revision it was made against.
10
+
11
+ ```
12
+ Revision: <git rev> | working-tree+<porcelain digest>
13
+ ```
14
+
15
+ - **`<git rev>`** — the output of `git rev-parse HEAD` at the moment of the
16
+ observation. Preferred: it is exact and someone else can reproduce from it.
17
+ - **`working-tree+<porcelain digest>`** — for an uncommitted tree, the `HEAD`
18
+ rev plus a short digest of `git status --porcelain`. Not as good, and honest
19
+ about not being as good: it says "this observation was made against a state
20
+ that was never committed".
21
+
22
+ It appears in three places, all carrying the same address:
23
+
24
+ 1. **Reviewer responses** — as `Reviewed revision`, per
25
+ `constitution/shared-skill-delegation-baseline.md#reviewer-response-template`.
26
+ 2. **The per-item evidence contract** — one `Revision` per round block, beside
27
+ the RED / GREEN commands and results, and one for the refactor-verify pair.
28
+ 3. **The review pack** — `summary.json`'s `revision` field, which is what makes
29
+ the fact machine-checkable (`QFAI-REVIEW-009`).
30
+
31
+ ## Why an address, not a timestamp
32
+
33
+ A timestamp orders observations; it does not identify what was observed. Two
34
+ runs a second apart against different trees have different meanings and nearly
35
+ identical timestamps. The revision is the only thing that survives the question
36
+ "which code did this verdict actually rule on?".
37
+
38
+ Reviewers and `qa-gatekeeper` are dispatched against the **integrated** tree by
39
+ design — `constitution/workflow.md`'s worktree-separation rule constrains
40
+ implementers, not reviewers — so the tree a reviewer reads is legitimately
41
+ allowed to move under it. A fully independent reviewer reading a tree that is
42
+ being edited mid-review produces a verdict that is honest, independent and
43
+ unattributable. That is the failure this field exists for, and it is not fixed
44
+ by choosing a better reviewer.
45
+
46
+ ## What makes evidence stale
47
+
48
+ Mechanically, so it can be checked rather than judged:
49
+
50
+ > Evidence is **stale** when the revision it names differs from the revision the
51
+ > item's work finally landed at.
52
+
53
+ Consequences:
54
+
55
+ - An item's four verdicts (gate items 3, 5, 7, 8) MUST all name the **same**
56
+ revision. Verdicts from different revisions do not compose into a ruling about
57
+ one state — the earlier ones ruled on code that no longer exists.
58
+ - A commit that changes any file the observation covered invalidates it. Re-run
59
+ the observation; do not carry the verdict forward because "the change was
60
+ unrelated". Whether it was unrelated is exactly the judgement the evidence
61
+ exists to remove.
62
+ - A reviewer that noticed the tree moving mid-review says so and pins its ruling
63
+ to a named revision. A pinned ruling is a valid verdict for **that** revision
64
+ and a stale one for any later revision.
65
+
66
+ ## When the tree moved under a reviewer
67
+
68
+ State it, in the response, with the revision. Two forms are acceptable:
69
+
70
+ - **Pinned** — the ruling stands for the named revision. The orchestrator then
71
+ decides whether that revision is still the item's final state; if it is not,
72
+ the review is re-run rather than reinterpreted.
73
+ - **Aborted** — the reviewer could not obtain a coherent read at all (for
74
+ example, a suite whose collected count changed between two runs of the same
75
+ command). That is `REVISE` with the reason, not `PASS` with a caveat: a
76
+ mandated back-to-back identical run that could not be produced is a gate that
77
+ did not pass.
@@ -0,0 +1,303 @@
1
+ # Execution Ledger: test-list.md
2
+
3
+ The execution ledger at `.qfai/specs/<spec-id>/tdd/test-list.md` is the single record of what
4
+ `/qfai-implement` has done and may still do. This file holds its schema and its status rules.
5
+
6
+ ## Required columns
7
+
8
+ | Column | Description |
9
+ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10
+ | TDD-ID | Unique identifier for the TDD item (e.g., TDD-0001) |
11
+ | TC-Refs | References to test cases from `06_Test-Cases.md`. Belongs on `Layer = Unit` / `Component` / `Integration` rows |
12
+ | Layer | Test layer. Legal values: `Unit`, `Component`, `Integration`, `API`, `E2E` |
13
+ | Test file | Path to the test file |
14
+ | Selector | Test selector(s) for targeted execution — one entry, a comma-separated list, or a glob pattern |
15
+ | Status | Current lifecycle status |
16
+ | DR-ID | Decision Record / Change Request IDs, comma-separated: a `DR-*` is required for `exception` rows, a `CR-*` for a row reset by an approved Change Request and is retained through that row's later statuses; blank otherwise |
17
+ | Evidence | The RED/GREEN outcome in one word each, plus an anchor into `.qfai/evidence/implement-<spec-id>.md` for this TDD-ID. **Not** the commands and output themselves — see "Evidence cell contract" below |
18
+
19
+ ## Declared seam column (optional, required for parallel dispatch)
20
+
21
+ | Column | Description |
22
+ | ------------- | ------------------------------------------------------------------------------------------ |
23
+ | Owning module | The production module this row will write, as a repo-relative path or a dotted module path |
24
+
25
+ `Owning module` is a **declaration, not an observation**, and that is the whole
26
+ point: it exists before the code does.
27
+
28
+ The parallel-dispatch gate asks whether two items write the same source module.
29
+ Under RED-first the production module does not exist when `delivery-planner`
30
+ has to answer, and the only path-valued required column is `Test file` — two
31
+ items trivially have independent test files and land on the same production
32
+ module. So the planner had nothing to evaluate against, and
33
+ `parallelization-policy.md`'s "cannot be explained with concrete file/module
34
+ evidence" asked for exactly what test-first withholds.
35
+
36
+ - Fill it at ledger-authoring time (`/qfai-sdd` Phase 2b) from the TC's parent
37
+ `BR`, which already names the behaviour's home.
38
+ - One module per row. A row that would honestly need two is a row that should
39
+ be split — that is the same signal `selector-granularity.md` describes.
40
+ - `-` is legal and means "not declared". A row carrying `-` is **not eligible
41
+ for parallel dispatch**; it may still be executed serially.
42
+ - It is a claim the row is later measured against, not a lock. See
43
+ `parallelization-policy.md#seam-reconciliation-after-a-parallel-run`.
44
+
45
+ ## Obligation columns (optional, required by layer)
46
+
47
+ A row's obligation lives in the column its `Layer` selects. `TC-Refs` is the one
48
+ every row has; the other two are optional columns that become required when the
49
+ row's layer cannot host a `TC-*`.
50
+
51
+ | Column | Description |
52
+ | ------------ | --------------------------------------------------------------------------------- |
53
+ | US-Refs | `US-*` obligations this row implements. Legal **only** on `Layer = E2E` rows |
54
+ | CON-API-Refs | `CON-API-*` obligations this row implements. Legal **only** on `Layer = API` rows |
55
+ | Blocked-By | What a `blocked` row is waiting on. Required on `blocked` rows, blank otherwise |
56
+
57
+ `Blocked-By` takes a Change Request ID (`CR-YYYYMMDD-NNNN`), a contract path
58
+ with line (`.qfai/contracts/db/CON-DB-0005.sql:2715`), or a cross-spec row
59
+ (`spec-0006:TDD-0034`). `DR-ID` is **not** widened to carry it: that column is
60
+ what distinguishes a parked `exception` from a row that never started, and
61
+ overloading it would merge the two states the `blocked` status exists to
62
+ separate.
63
+
64
+ `test-layers.md` forbids `TC-*` annotations in `tests/e2e/**` and `tests/api/**`,
65
+ so an E2E or API row has no legal `TC-Refs` value. Those rows carry `-` in
66
+ `TC-Refs` and record their obligation in `US-Refs` / `CON-API-Refs` instead.
67
+
68
+ The binding is enforced in both directions: a `TC-*` on an E2E/API row raises
69
+ `TDDLIST_OBLIGATION_LAYER_MISMATCH` and is **not** counted towards TC coverage,
70
+ so a forbidden placement cannot close a coverage-target TC.
71
+ `TDDLIST_OBLIGATION_LAYER_MISMATCH` likewise rejects a `US-Refs` /
72
+ `CON-API-Refs` value on a layer that does not own it.
73
+
74
+ A `Layer` outside the legal values raises `TDDLIST_UNKNOWN_LAYER` (warning) —
75
+ without a legal `Layer` the row has no obligation column. Coverage counting
76
+ excludes `API` and `E2E` specifically rather than allowlisting the other three,
77
+ so a mistyped layer keeps counting and is reported by that warning, which names
78
+ the real cause; an allowlist would instead drop the row silently and resurface
79
+ as a coverage error about a TC the author did cover.
80
+
81
+ Coverage measurement is otherwise unaffected: it reads `TC-*` tokens only, so
82
+ non-TC obligation IDs are inert to it by design.
83
+
84
+ ## Evidence cell contract
85
+
86
+ The `Evidence` cell is a **pointer**, not the payload.
87
+
88
+ `.qfai/evidence/implement-<spec-id>.md` is the single home for the per-item
89
+ evidence contract — the RED/GREEN commands, their output, and the reviewer
90
+ verdicts. The ledger cell records the outcome and says where to read the proof:
91
+
92
+ ```
93
+ RED fail / GREEN pass — evidence at `.qfai/evidence/implement-spec-0001.md#tdd-0027`
94
+ ```
95
+
96
+ ### Why it cannot hold the payload
97
+
98
+ A GFM table row is **one physical line**, and `splitMarkdownRow` ends a cell at
99
+ every unescaped `|`. Pasting a command and its output into the cell has two
100
+ failure modes, both silent:
101
+
102
+ - a newline ends the row — `parseAllMarkdownTables` stops the table at the first
103
+ line that does not start with `|`, so every row below it disappears from the
104
+ ledger the validators read;
105
+ - a bare `|` — a shell pipe, a table in the output, a regex alternation — splits
106
+ the row into extra cells and misaligns every column after `Evidence`.
107
+
108
+ The column description used to read "RED/GREEN command+result pairs proving the
109
+ TDD cycle", so following it literally corrupted the evidence or destroyed the
110
+ ledger. None of the Evidence hard rules change: they now bind the evidence file,
111
+ which can hold what they ask for.
112
+
113
+ ### Evidence cell encoding
114
+
115
+ When a cell must contain either character, encode it with
116
+ `specPackParsers.ts#escapeTableCell` — the exported encoder the parser inverts.
117
+ The left column is the character **in the value you want the cell to hold**;
118
+ the right column is what is **written into the row**. Both are shown as HTML
119
+ entities, because a literal pipe in this table would split its own row.
120
+
121
+ Exactly two rules:
122
+
123
+ | Character in the value | Written into the cell |
124
+ | ---------------------- | --------------------- |
125
+ | a pipe (&#124;) | &#92;&#124; |
126
+ | CR / LF / CRLF | a single space |
127
+
128
+ A literal `\` is **not** escaped: the parser passes it through unchanged, so
129
+ doubling it would corrupt Windows paths and regex literals while keeping the
130
+ column count valid — a corruption no validator can see.
131
+
132
+ ## Evidence cell rules (enforced)
133
+
134
+ `Evidence` is checked as content, not only as a header name. On a row whose
135
+ `Status` is `green`, `refactor`, `review-fix` or `done` — the statuses that
136
+ assert a cycle has run:
137
+
138
+ | Finding | Fires when | Severity |
139
+ | ------------------------------ | ------------------------------------------------------------------- | -------- |
140
+ | `TDDLIST_EVIDENCE_EMPTY` | the cell is empty or holds only dash placeholders (`-`, `–`, `—`) | error |
141
+ | `TDDLIST_EVIDENCE_STATUS_ONLY` | the cell claims a verdict (`PASS`, `looks good`, …) with no command | warning |
142
+
143
+ A command is recognised by shape, not from a list of known runners, so the rule
144
+ holds on any stack: a program name followed by an argument carrying a flag, a
145
+ path, a selector or an assignment. Backticked commands and the common runners
146
+ are accepted directly.
147
+
148
+ `TDDLIST_EVIDENCE_STATUS_ONLY` is a warning, waivable under `TDDLIST-004`: a
149
+ ledger written before the check exists carries prose verdicts, and failing a
150
+ build on them is a migration rather than a gate. An empty cell is unambiguous,
151
+ so `TDDLIST_EVIDENCE_EMPTY` stays at `error`.
152
+
153
+ Rows at `todo`, `red` and `exception` are not checked — the first two have
154
+ nothing to show yet, and a parked row records its reason in `DR-ID`, which
155
+ `TDDLIST_EXCEPTION_MISSING_DR` gates.
156
+
157
+ Freshness is **not** gated: the ledger records no run identity, so no validator
158
+ can distinguish a fresh command+result pair from a copied one. That rule stays
159
+ with the routed reviewer (`qfai-implement/SKILL.md` "Evidence hard rules").
160
+
161
+ A pointer cell satisfies these rules: the `evidence at <path>` form carries a
162
+ path, which is one of the command shapes the gate accepts. The rules reject a
163
+ bare verdict, not a pointer.
164
+
165
+ ## Selector granularity (MUST)
166
+
167
+ `Selector` is **not** restricted to a single test function: a row may own several entries, written
168
+ as a comma-separated list or a glob. What is restricted is what a row may _conflate_ — **one
169
+ independently observable boundary per selector entry**, with RED observed per entry, and a
170
+ matrix-shaped `TC-*` decomposed across rows before RED begins (`TC-Refs` is many-to-many with
171
+ `TDD-ID`). A selector that accumulates unrelated boundaries invalidates the RED observation. Rules
172
+ and examples: `selector-granularity.md`.
173
+
174
+ ## Status Lifecycle
175
+
176
+ Valid status values: `todo`, `blocked`, `red`, `green`, `refactor`, `review-fix`, `done`, `exception`.
177
+
178
+ Allowed transitions:
179
+
180
+ - `todo` -> `blocked` (the row cannot be started: an upstream defect, an
181
+ unresolved Change Request, or an unfinished row in another spec). Name the
182
+ blocker in `Blocked-By`; `TDDLIST_BLOCKED_MISSING_REF` errors without it.
183
+ - `blocked` -> `todo` (the blocker cleared). This is a **resumption, not a
184
+ backward transition**: the row never started, so nothing is being undone.
185
+ - `todo` -> `red` (write a failing test)
186
+ - `red` -> `green` (make the test pass with minimal code)
187
+ - `green` -> `refactor` (improve code quality while keeping tests green)
188
+ - `refactor` -> `done` (item complete)
189
+ - `refactor` -> `review-fix` (a blocking reviewer returned `REVISE`)
190
+ - `review-fix` -> `refactor` (rework complete; re-submit to the reviewer)
191
+ - Any active status -> `exception` (anomaly detected; record DR-ID in DR-ID column)
192
+ - `red` | `green` | `refactor` | `done` | `exception` -> `todo` — **upstream
193
+ reset**, the only legal reopen, available from every status a row can hold.
194
+ Permitted **only** when an approved upstream change (Drift Protocol step 4
195
+ rerun) invalidated the row's obligation. The invalidating CR/DR ID MUST be
196
+ recorded in the `DR-ID` column, and the reset MUST cite it in `Evidence`.
197
+ That ID MUST be retained as the row moves on through `red`, `green`,
198
+ `refactor` and `done` — clearing it on the next transition erases the only
199
+ record of why a completed row was reopened. A row swept out of `exception`
200
+ keeps the anomaly's DR-ID alongside the reset ID. A reset without a recorded
201
+ approval is a backward transition and is prohibited.
202
+ - `exception` -> `todo` — **anomaly resolved**, the item re-enters the cycle
203
+ from the start. This is the exit `exception` previously lacked; without it a
204
+ parked item could never be un-parked without a lifecycle violation. Distinct
205
+ from the upstream reset above: nothing upstream changed, so it needs no CR/DR
206
+ approval — the anomaly's own DR-ID stays in place.
207
+ - A reset row is at `todo`, so it owes no test file until it reaches `green`.
208
+ The `Test file` existence check is unchanged for `green` / `refactor` /
209
+ `done`: those statuses assert a test that ran.
210
+
211
+ - `refactor` -> `red` (**QA rejection recovery — the only re-entry**): a routed
212
+ `qa-gatekeeper` returned `REVISE` on this row's RED/GREEN evidence because the
213
+ cycle itself was wrong. Batched (T1) review defers that confirmation until
214
+ after the row has left `red`, so without this edge a rejected row could never
215
+ redo the RED it was faulted for and could never reach `done`. Cite the verdict
216
+ in `Evidence`, re-run the micro-cycle; rules:
217
+ `volume-policy.md#group-formation-states-and-transitions`.
218
+
219
+ Backward transitions are otherwise prohibited and nothing but that QA rejection
220
+ re-opens a row. Attempting `green` -> `red` must produce:
221
+ `"Backward transition prohibited: green -> red"`. The upstream reset above is
222
+ not a backward transition: it is an owner-approved re-entry, and the row starts
223
+ its cycle again from `todo`.
224
+
225
+ The one exception is an approved Change Request reset — the only sanctioned
226
+ backward transition. Preconditions and the reset procedure:
227
+ `references/change-request-reset.md`.
228
+
229
+ ### Reviewer rework is not a backward transition
230
+
231
+ A blocking reviewer's `REVISE` moves the item `refactor -> review-fix`. While at
232
+ `review-fix` the item MAY re-enter the RED/GREEN cycle as many times as the
233
+ rework needs — write the new failing test, watch it fail, make it pass — without
234
+ any of those runs counting as a backward transition. The row's status does not
235
+ change during that cycle: `review-fix -> red` and `review-fix -> green` are not
236
+ allowed transitions. When the rework is done the item returns to `refactor` and
237
+ is re-submitted.
238
+
239
+ `review-fix` is not a completion state and appears in the completion-prohibition
240
+ list. Round-by-round evidence rules: `round-evidence.md`.
241
+
242
+ ## Blocked rows
243
+
244
+ `blocked` means **cannot be started**, not "not started yet" and not "anomaly".
245
+
246
+ - It is **completion-prohibiting**, exactly like `todo`. A spec must not close
247
+ over an unimplemented obligation, and naming the blocker does not discharge it.
248
+ - It is **not** selectable. Phase Red picks the first `todo` row and skips
249
+ `blocked` ones, so the loop head stops re-issuing rows that cannot proceed.
250
+ - It is **not** `exception`. `exception` is scoped to an anomaly, requires a
251
+ `DR-*`, and satisfies spec completion — filing a blocked row there would
252
+ silently close the obligation.
253
+ - `npx qfai report` counts it inside `open` but prints it separately
254
+ (`open: N (blocked: M)`), so "not started" and "cannot start" are readable
255
+ apart without changing what completion means.
256
+
257
+ ## Exception Handling
258
+
259
+ `exception` means **anomaly, work paused** — not "accepted risk, closed". The
260
+ two are different states sharing one status today; the `DR-ID` distinguishes
261
+ them and only the accepted-risk form is completion-satisfying. Resolve a paused
262
+ item via `exception` -> `todo`.
263
+
264
+ When transitioning to `exception`:
265
+
266
+ - A DR-ID (Decision Record ID) must be recorded in the DR-ID column.
267
+ - A retained `CR-*` does not satisfy this: it records the approved reopen, not the anomaly. Add the `DR-*` alongside it (`DR-NNNN, CR-YYYYMMDD-NNNN`).
268
+ - If the DR-ID column is empty, or holds `CR-*` references only, emit error: `"exception status requires DR-ID in DR-ID column"`.
269
+
270
+ ### Where the Decision Record is written
271
+
272
+ Write it to `.qfai/decisions/DR-<id>-<slug>.md`, beside the Change
273
+ Requests, using the same `DR-*` ID scheme those files declare. Do **not** write
274
+ `07_Decisions.md` or `09_delta.md`.
275
+
276
+ Those two are upstream SSOT (`constitution/drift-protocol.md#core-rule`) and
277
+ this skill carries `[DRIFT-PROTOCOL:MANDATORY]`, so a downstream write to either
278
+ is a protocol violation — while the `exception` transition itself is an ordinary
279
+ inline step of Phase Red that `TDDLIST_EXCEPTION_MISSING_DR` blocks at `error`
280
+ without a `DR-*`. `.qfai/decisions/` is the one home that satisfies both: the
281
+ protocol whitelists **creating** a record there
282
+ (`drift-protocol.md#allowed-exceptions-minimal-whitelist`), and the managed `.gitignore` block
283
+ already tracks it as a governance record.
284
+
285
+ The upstream cross-reference is a separate, later write. If the anomaly turns
286
+ out to change an approved obligation, that is drift: raise a Change Request per
287
+ `drift-protocol.md#when-drift-is-detected`, and the owner skill's rerun is what
288
+ records the reference in `07_Decisions.md` / `09_delta.md`. Parking the row does
289
+ not require that to have happened.
290
+
291
+ ### Parked items and the `TDDLIST-001` waiver
292
+
293
+ Every `exception` row raises `TDDLIST_EXCEPTION_PARKED` (warning, rule
294
+ `TDDLIST-001`). The validator cannot read the DR to tell a paused defect from an
295
+ approved accepted risk, so the approval is recorded where QFAI already checks
296
+ approvals: a `TDDLIST-001` waiver in `.qfai/waivers.yml`.
297
+
298
+ The waiver carries `id` / `reason` / `expires` / `evidence` / `scope.paths`,
299
+ plus `match.dl_ids` listing the approved rows' `TDD-ID`s. `match.dl_ids` is not
300
+ optional — omit it and `QFAI-WAIVER-005` rejects the waiver, because a single
301
+ path-scoped entry would otherwise clear every parked row in the same ledger.
302
+
303
+ Without a matching waiver the warning stands, which is the intended signal.