rcf-lite 0.28.1 → 0.28.4

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 (219) hide show
  1. package/CHANGELOG.md +310 -0
  2. package/bin/rcf.js +4 -0
  3. package/fixtures/canary-manifest.json +9 -9
  4. package/package.json +1 -1
  5. package/rcf/adrs/adr-4120-canonical-json-hashing.json +30 -0
  6. package/rcf/adrs/adr-4121-define-sidecars.json +30 -0
  7. package/rcf/adrs/adr-4122-define-gates-blocking-and-optout.json +30 -0
  8. package/rcf/adrs/adr-4123-readiness-single-object.json +30 -0
  9. package/rcf/code-nodes/cn-602.json +3 -2
  10. package/rcf/code-nodes/cn-700.json +23 -0
  11. package/rcf/code-nodes/cn-701.json +14 -0
  12. package/rcf/code-nodes/cn-702.json +14 -0
  13. package/rcf/code-nodes/cn-703.json +16 -0
  14. package/rcf/code-nodes/cn-720.json +15 -0
  15. package/rcf/code-nodes/cn-721.json +15 -0
  16. package/rcf/code-nodes/cn-722.json +15 -0
  17. package/rcf/code-nodes/cn-723.json +18 -0
  18. package/rcf/code-nodes/cn-724.json +14 -0
  19. package/rcf/code-nodes/cn-725.json +21 -0
  20. package/rcf/code-nodes/cn-726.json +17 -0
  21. package/rcf/code-nodes/cn-727.json +14 -0
  22. package/rcf/code-nodes/cn-728.json +21 -0
  23. package/rcf/code-nodes/cn-729.json +18 -0
  24. package/rcf/code-nodes/cn-730.json +14 -0
  25. package/rcf/fbs/fbs-181.json +3 -2
  26. package/rcf/fbs/fbs-190.json +37 -0
  27. package/rcf/fbs/fbs-191.json +34 -0
  28. package/rcf/fbs/fbs-192.json +17 -0
  29. package/rcf/fbs/fbs-193.json +37 -0
  30. package/rcf/fbs/fbs-194.json +35 -0
  31. package/rcf/fbs/fbs-195.json +37 -0
  32. package/rcf/fbs/fbs-196.json +37 -0
  33. package/rcf/fbs/fbs-197.json +41 -0
  34. package/rcf/requirements/req-001.json +2 -1
  35. package/rcf/requirements/req-002.json +2 -1
  36. package/rcf/requirements/req-003.json +2 -1
  37. package/rcf/requirements/req-004.json +2 -1
  38. package/rcf/requirements/req-005.json +2 -1
  39. package/rcf/requirements/req-006.json +2 -1
  40. package/rcf/requirements/req-007.json +2 -1
  41. package/rcf/requirements/req-008.json +2 -1
  42. package/rcf/requirements/req-009.json +2 -1
  43. package/rcf/requirements/req-010.json +5 -2
  44. package/rcf/requirements/req-011.json +2 -1
  45. package/rcf/requirements/req-012.json +2 -1
  46. package/rcf/requirements/req-013.json +2 -1
  47. package/rcf/requirements/req-014.json +2 -1
  48. package/rcf/requirements/req-015.json +2 -1
  49. package/rcf/requirements/req-016.json +5 -2
  50. package/rcf/requirements/req-017.json +6 -2
  51. package/rcf/requirements/req-018.json +5 -2
  52. package/rcf/requirements/req-019.json +5 -2
  53. package/rcf/requirements/req-020.json +5 -2
  54. package/rcf/requirements/req-021.json +5 -2
  55. package/rcf/requirements/req-022.json +5 -2
  56. package/rcf/requirements/req-023.json +6 -4
  57. package/rcf/requirements/req-024.json +5 -2
  58. package/rcf/requirements/req-025.json +5 -2
  59. package/rcf/requirements/req-026.json +5 -2
  60. package/rcf/requirements/req-027.json +5 -2
  61. package/rcf/requirements/req-028.json +5 -2
  62. package/rcf/requirements/req-029.json +5 -2
  63. package/rcf/requirements/req-030.json +5 -2
  64. package/rcf/requirements/req-040.json +5 -2
  65. package/rcf/requirements/req-041.json +5 -2
  66. package/rcf/requirements/req-042.json +5 -2
  67. package/rcf/requirements/req-043.json +5 -2
  68. package/rcf/requirements/req-050.json +5 -2
  69. package/rcf/requirements/req-051.json +5 -2
  70. package/rcf/requirements/req-052.json +5 -2
  71. package/rcf/requirements/req-053.json +5 -2
  72. package/rcf/requirements/req-054.json +5 -2
  73. package/rcf/requirements/req-060.json +5 -2
  74. package/rcf/requirements/req-061.json +5 -2
  75. package/rcf/requirements/req-062.json +5 -2
  76. package/rcf/requirements/req-063.json +5 -2
  77. package/rcf/requirements/req-070.json +5 -2
  78. package/rcf/requirements/req-071.json +5 -2
  79. package/rcf/requirements/req-072.json +5 -2
  80. package/rcf/requirements/req-073.json +5 -2
  81. package/rcf/requirements/req-074.json +5 -2
  82. package/rcf/requirements/req-075.json +5 -2
  83. package/rcf/requirements/req-076.json +5 -2
  84. package/rcf/requirements/req-077.json +5 -2
  85. package/rcf/requirements/req-080.json +5 -2
  86. package/rcf/requirements/req-081.json +5 -2
  87. package/rcf/requirements/req-082.json +5 -2
  88. package/rcf/requirements/req-083.json +5 -2
  89. package/rcf/requirements/req-084.json +5 -2
  90. package/rcf/requirements/req-085.json +5 -2
  91. package/rcf/requirements/req-086.json +5 -2
  92. package/rcf/requirements/req-090.json +5 -2
  93. package/rcf/requirements/req-091.json +5 -2
  94. package/rcf/requirements/req-092.json +5 -2
  95. package/rcf/requirements/req-093.json +5 -2
  96. package/rcf/requirements/req-094.json +5 -2
  97. package/rcf/requirements/req-100.json +5 -2
  98. package/rcf/requirements/req-101.json +5 -2
  99. package/rcf/requirements/req-102.json +5 -2
  100. package/rcf/requirements/req-103.json +5 -2
  101. package/rcf/requirements/req-104.json +5 -2
  102. package/rcf/requirements/req-110.json +5 -2
  103. package/rcf/requirements/req-111.json +5 -2
  104. package/rcf/requirements/req-112.json +5 -2
  105. package/rcf/requirements/req-113.json +5 -2
  106. package/rcf/requirements/req-114.json +5 -2
  107. package/rcf/requirements/req-115.json +5 -2
  108. package/rcf/requirements/req-120.json +5 -2
  109. package/rcf/requirements/req-121.json +5 -2
  110. package/rcf/requirements/req-122.json +5 -2
  111. package/rcf/requirements/req-123.json +5 -2
  112. package/rcf/requirements/req-124.json +5 -2
  113. package/rcf/requirements/req-125.json +5 -2
  114. package/rcf/requirements/req-130.json +6 -2
  115. package/rcf/requirements/req-131.json +6 -2
  116. package/rcf/requirements/req-132.json +6 -2
  117. package/rcf/requirements/req-133.json +6 -2
  118. package/rcf/requirements/req-134.json +6 -2
  119. package/rcf/requirements/req-140.json +5 -2
  120. package/rcf/requirements/req-145.json +5 -2
  121. package/rcf/requirements/req-150.json +6 -2
  122. package/rcf/requirements/req-151.json +6 -2
  123. package/rcf/requirements/req-152.json +6 -2
  124. package/rcf/requirements/req-153.json +6 -2
  125. package/rcf/requirements/req-154.json +5 -2
  126. package/rcf/requirements/req-155.json +4 -1
  127. package/rcf/requirements/req-156.json +4 -1
  128. package/rcf/requirements/req-157.json +4 -1
  129. package/rcf/requirements/req-158.json +4 -1
  130. package/rcf/requirements/req-159.json +4 -1
  131. package/rcf/requirements/req-160.json +4 -1
  132. package/rcf/requirements/req-161.json +4 -1
  133. package/rcf/requirements/req-162.json +4 -1
  134. package/rcf/requirements/req-163.json +5 -2
  135. package/rcf/requirements/req-164.json +5 -2
  136. package/rcf/requirements/req-165.json +5 -2
  137. package/rcf/requirements/req-170.json +33 -0
  138. package/rcf/requirements/req-171.json +44 -0
  139. package/rcf/requirements/req-172.json +25 -0
  140. package/rcf/requirements/req-173.json +25 -0
  141. package/rcf/requirements/req-174.json +25 -0
  142. package/rcf/requirements/req-175.json +25 -0
  143. package/rcf/tacs/tac-4103-feedback-redact.json +2 -2
  144. package/rcf/tacs/tac-4120-define-delta-and-freeze.json +21 -0
  145. package/rcf/tacs/tac-4121-define-ledgers.json +23 -0
  146. package/rcf/tacs/tac-4122-define-gates.json +25 -0
  147. package/rcf/tacs/tac-4123-define-readiness.json +25 -0
  148. package/rcf/test-suites/ts-188.json +10 -2
  149. package/rcf/test-suites/ts-191.json +19 -5
  150. package/rcf/test-suites/ts-204.json +30 -0
  151. package/rcf/test-suites/ts-205.json +22 -0
  152. package/rcf/test-suites/ts-206.json +22 -0
  153. package/rcf/test-suites/ts-207.json +22 -0
  154. package/rcf/test-suites/ts-208.json +22 -0
  155. package/rcf/test-suites/ts-209.json +22 -0
  156. package/rcf/test-suites/ts-210.json +22 -0
  157. package/rcf/test-suites/ts-211.json +22 -0
  158. package/rcf/test-suites/ts-212.json +22 -0
  159. package/rcf/test-suites/ts-213.json +50 -0
  160. package/rcf/test-suites/ts-214.json +74 -0
  161. package/rcf/test-suites/ts-215.json +67 -0
  162. package/rcf/test-suites/ts-216.json +78 -0
  163. package/rcf/test-suites/ts-217.json +62 -0
  164. package/rcf/user-stories/us-15601.json +10 -1
  165. package/rcf/user-stories/us-15701.json +4 -4
  166. package/rcf/user-stories/us-17001.json +27 -0
  167. package/rcf/user-stories/us-17002.json +22 -0
  168. package/rcf/user-stories/us-17003.json +22 -0
  169. package/rcf/user-stories/us-17004.json +22 -0
  170. package/rcf/user-stories/us-17005.json +22 -0
  171. package/rcf/user-stories/us-17006.json +21 -0
  172. package/rcf/user-stories/us-17007.json +21 -0
  173. package/rcf/user-stories/us-17008.json +22 -0
  174. package/rcf/user-stories/us-17101.json +22 -0
  175. package/rcf/user-stories/us-17102.json +22 -0
  176. package/rcf/user-stories/us-17201.json +38 -0
  177. package/rcf/user-stories/us-17301.json +38 -0
  178. package/rcf/user-stories/us-17401.json +58 -0
  179. package/rcf/user-stories/us-17501.json +48 -0
  180. package/releases/releases.yaml +31 -1
  181. package/src/browser-verify/manifest-writer.js +9 -5
  182. package/src/cli/browser-verify.js +9 -1
  183. package/src/cli/feedback.js +316 -32
  184. package/src/cli/help.js +11 -0
  185. package/src/cli/intake.js +25 -4
  186. package/src/cli/ledger.js +399 -0
  187. package/src/cli/preflight.js +24 -3
  188. package/src/cli/readiness.js +280 -0
  189. package/src/cli/review.js +9 -1
  190. package/src/cli/ui-baseline.js +14 -1
  191. package/src/core/store/index.js +5 -0
  192. package/src/core/store/validate-composed-record.js +95 -0
  193. package/src/core/store/writer.js +86 -6
  194. package/src/define/freeze-record.js +232 -0
  195. package/src/define/ledgers.js +513 -0
  196. package/src/feedback/gh.js +27 -1
  197. package/src/feedback/redact-allowlist.json +7 -0
  198. package/src/feedback/redact.js +126 -11
  199. package/src/feedback/render.js +34 -12
  200. package/src/intake/manifest-writer.js +6 -3
  201. package/src/intake/orchestrator.js +44 -7
  202. package/src/preflight/manifest-writer.js +11 -11
  203. package/src/query/delta.js +276 -0
  204. package/src/query/gates.js +832 -0
  205. package/src/query/index.js +36 -0
  206. package/src/query/readiness.js +333 -0
  207. package/src/req-baseline/sweep.js +34 -5
  208. package/src/server/index.js +37 -0
  209. package/src/server/routes.js +65 -3
  210. package/src/ui-baseline/manifest-writer.js +11 -10
  211. package/src/view/doc-renderers/helpers.js +39 -2
  212. package/src/view/doc-renderers/req.js +10 -2
  213. package/src/view/doc-renderers/tad.js +67 -6
  214. package/src/view/doc-renderers/user-story.js +10 -5
  215. package/src/view/html-page.js +347 -28
  216. package/src/view/index.js +14 -1
  217. package/src/view/live-client.js +22 -2
  218. package/src/view/product-map.js +751 -0
  219. package/src/view/style.css +383 -0
@@ -0,0 +1,74 @@
1
+ {
2
+ "createdAt": "2026-09-24T12:00:00Z",
3
+ "updatedAt": "2026-09-24T12:00:00Z",
4
+ "purpose": "Cover the delta-detection primitives (hashDocument, computeTreeHash, computeDelta) and the freeze record I/O (load, save, validate) against AC-17201-1..4 on US-17201.",
5
+ "testLevel": "unit",
6
+ "acIds": [
7
+ "AC-17201-1",
8
+ "AC-17201-2",
9
+ "AC-17201-3",
10
+ "AC-17201-4"
11
+ ],
12
+ "id": "TS-214",
13
+ "usId": "US-17201",
14
+ "title": "Delta detection and freeze record primitives",
15
+ "testCases": [
16
+ {
17
+ "id": "TC-214-delta-shape",
18
+ "acId": "AC-17201-1",
19
+ "description": "computeDelta returns the shape proposal section 2.2 pins: frozen / frozenAt / treeHash / currentTreeHash / changed / added / removed / briefSince / unchanged, and classifies changed / added / removed correctly against a divergent freeze record.",
20
+ "status": "pending",
21
+ "testPointer": "test/query/delta.test.js::delta: changed / added / removed classify against the freeze record"
22
+ },
23
+ {
24
+ "id": "TC-214-unchanged",
25
+ "acId": "AC-17201-1",
26
+ "description": "A tree that matches the freeze record produces empty changed / added / removed lists and unchanged equal to the docHashes count.",
27
+ "status": "pending",
28
+ "testPointer": "test/query/delta.test.js::delta: an unchanged tree against a matching freeze reports empty changed/added/removed"
29
+ },
30
+ {
31
+ "id": "TC-214-canonical",
32
+ "acId": "AC-17201-2",
33
+ "description": "hashDocument is invariant under a whitespace-only reformat: two documents that parse to the same object hash identically and produce zero changed entries in the delta.",
34
+ "status": "pending",
35
+ "testPointer": "test/query/delta.test.js::delta: a whitespace-only reformat of a document produces zero changed ids"
36
+ },
37
+ {
38
+ "id": "TC-214-hash-shape",
39
+ "acId": "AC-17201-2",
40
+ "description": "hashDocument returns a sha256:<64-hex> string over the canonical JSON form of the parsed document.",
41
+ "status": "pending",
42
+ "testPointer": "test/query/delta.test.js::delta: hashDocument returns a sha256:<64-hex> string over the canonical form"
43
+ },
44
+ {
45
+ "id": "TC-214-freeze-missing",
46
+ "acId": "AC-17201-3",
47
+ "description": "loadFreezeRecord returns null when rcf/define/freeze.json is absent; a malformed body raises FreezeRecordError with the parseFailure code.",
48
+ "status": "pending",
49
+ "testPointer": "test/define/freeze-record.test.js::freeze-record: a malformed JSON body raises FreezeRecordError with parseFailure code"
50
+ },
51
+ {
52
+ "id": "TC-214-freeze-schema",
53
+ "acId": "AC-17201-3",
54
+ "description": "A body with a non-sha256 docHashes value is refused with a FreezeRecordError naming the failing docHashes.<id> field.",
55
+ "status": "pending",
56
+ "testPointer": "test/define/freeze-record.test.js::freeze-record: docHashes with a non-sha256 value is refused"
57
+ },
58
+ {
59
+ "id": "TC-214-roundtrip",
60
+ "acId": "AC-17201-4",
61
+ "description": "saveFreezeRecord followed by loadFreezeRecord round-trips every field including unknown extension fields (counts, litmus, versions, note).",
62
+ "status": "pending",
63
+ "testPointer": "test/define/freeze-record.test.js::freeze-record: unknown top-level fields survive a save/load round trip"
64
+ },
65
+ {
66
+ "id": "TC-214-refuse-invalid-write",
67
+ "acId": "AC-17201-4",
68
+ "description": "saveFreezeRecord refuses an invalid record and writes nothing to disk.",
69
+ "status": "pending",
70
+ "testPointer": "test/define/freeze-record.test.js::freeze-record: saveFreezeRecord refuses to write an invalid record"
71
+ }
72
+ ],
73
+ "status": "draft"
74
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "createdAt": "2026-09-24T12:00:00Z",
3
+ "updatedAt": "2026-09-24T12:00:00Z",
4
+ "purpose": "Cover the four sidecar ledgers, the rcf define ledger CLI (add / resolve / list with --json) and the walker-invisibility invariant on rcf/define/ against AC-17301-1..4 on US-17301.",
5
+ "testLevel": "integration",
6
+ "acIds": [
7
+ "AC-17301-1",
8
+ "AC-17301-2",
9
+ "AC-17301-3",
10
+ "AC-17301-4"
11
+ ],
12
+ "id": "TS-215",
13
+ "usId": "US-17301",
14
+ "title": "Sidecar ledgers, rcf define ledger and walker invisibility",
15
+ "testCases": [
16
+ {
17
+ "id": "TC-215-brief-add-text",
18
+ "acId": "AC-17301-1",
19
+ "description": "rcf define ledger brief add --text mints id 1 with status open, kind capability by default, and writes rcf/define/brief-ledger.json.",
20
+ "status": "pending",
21
+ "testPointer": "test/cli/ledger.test.js::ledger CLI: brief add --text appends a numbered statement"
22
+ },
23
+ {
24
+ "id": "TC-215-brief-refuse-mixed",
25
+ "acId": "AC-17301-1",
26
+ "description": "rcf define ledger brief add refuses conflicting --text and --from with a usage-class error (exit 2).",
27
+ "status": "pending",
28
+ "testPointer": "test/cli/ledger.test.js::ledger CLI: brief add refuses conflicting --text and --from with a usage error"
29
+ },
30
+ {
31
+ "id": "TC-215-decisions-list-241",
32
+ "acId": "AC-17301-2",
33
+ "description": "rcf define ledger decisions list prints the numbered #241 format: question, options as 'letter) text', default and blocks labelled on their own lines.",
34
+ "status": "pending",
35
+ "testPointer": "test/cli/ledger.test.js::ledger CLI: decisions list prints the numbered #241 format"
36
+ },
37
+ {
38
+ "id": "TC-215-list-json",
39
+ "acId": "AC-17301-2",
40
+ "description": "rcf define ledger <name> list --json emits { ledger, <arrayKey>: [...] } with the same body loadLedger returns.",
41
+ "status": "pending",
42
+ "testPointer": "test/cli/ledger.test.js::ledger CLI: list --json emits the machine-readable envelope"
43
+ },
44
+ {
45
+ "id": "TC-215-brief-from-file",
46
+ "acId": "AC-17301-3",
47
+ "description": "rcf define ledger brief add --from <file> splits by non-empty lines, mints sequential ids and writes every statement with the resolved kind.",
48
+ "status": "pending",
49
+ "testPointer": "test/cli/ledger.test.js::ledger CLI: brief add --from <file> splits by non-empty line"
50
+ },
51
+ {
52
+ "id": "TC-215-parse-brief-bullets",
53
+ "acId": "AC-17301-3",
54
+ "description": "parseBriefFromFile strips leading '- ', '* ' and '<n>. ' bullets so drafts match the raw statement text.",
55
+ "status": "pending",
56
+ "testPointer": "test/define/ledgers.test.js::ledgers: parseBriefFromFile splits by non-empty lines and strips bullets"
57
+ },
58
+ {
59
+ "id": "TC-215-walker-invisible",
60
+ "acId": "AC-17301-4",
61
+ "description": "walkTree run over a project seeded with rcf/define/freeze.json, the four ledger files and a bug-bait req-999.json under rcf/define/ never surfaces any rcf/define/ path in errors or byId.",
62
+ "status": "pending",
63
+ "testPointer": "test/store/walker-invisible-define.test.js::walkTree never surfaces rcf/define/ files as documents in byId"
64
+ }
65
+ ],
66
+ "status": "draft"
67
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "createdAt": "2026-09-24T14:30:00Z",
3
+ "updatedAt": "2026-09-24T14:30:00Z",
4
+ "purpose": "Cover the DEFINE stage-gate check functions D1..D8 (AC-17401-1..8) with unit tests over hand-built TreeModel and ledger fixtures.",
5
+ "testLevel": "unit",
6
+ "acIds": [
7
+ "AC-17401-1",
8
+ "AC-17401-2",
9
+ "AC-17401-3",
10
+ "AC-17401-4",
11
+ "AC-17401-5",
12
+ "AC-17401-6",
13
+ "AC-17401-7",
14
+ "AC-17401-8"
15
+ ],
16
+ "id": "TS-216",
17
+ "usId": "US-17401",
18
+ "title": "Stage gates D1..D8 check functions",
19
+ "testCases": [
20
+ {
21
+ "id": "TC-216-shape",
22
+ "acId": "AC-17401-1",
23
+ "description": "Every check function returns { stage, gate, state, checks[] } and every entry in checks[] is { name, ok, over: 'delta'|'tree', pass, total, failing }; failing is empty when ok is true.",
24
+ "status": "pending",
25
+ "testPointer": "test/query/gates.test.js::gates: every check function returns the section 2.4 shape"
26
+ },
27
+ {
28
+ "id": "TC-216-blocking-vs-warn",
29
+ "acId": "AC-17401-2",
30
+ "description": "Blocking stages fold to failing on any check.ok false; warn-with-ack stages (D3, D5, D6) also fold to failing on any check.ok false unless freeze.gates[<gate>].state === 'acknowledged' AND at.hash === currentTreeHash.",
31
+ "status": "pending",
32
+ "testPointer": "test/query/gates.test.js::gates: warn-with-ack fold honours freeze.gates acknowledgement at the current hash"
33
+ },
34
+ {
35
+ "id": "TC-216-not-applicable",
36
+ "acId": "AC-17401-3",
37
+ "description": "notApplicable is decided per stage per section 3.2: D2 with no statement of a resolving kind and no REQ/PRD/TAD in scope; D3 with no TAC and no shaped REQ in scope; D5 with no REQ in scope; D7 with no open decisions and profile.skipReviewFor 'singleDocument' and one-document delta.",
38
+ "status": "pending",
39
+ "testPointer": "test/query/gates.test.js::gates: notApplicable decision per stage"
40
+ },
41
+ {
42
+ "id": "TC-216-d4-floors",
43
+ "acId": "AC-17401-4",
44
+ "description": "checkD4Stories enforces every REQ has US, every US has testable AC + failure AC (parsed by [failure] prefix or opt-out with baselineKey 'defineD4:failure') + must-not AC (or opt-out with 'defineD4:mustNot'), tacIds non-empty and resolving, no TODO placeholder in AC.description.",
45
+ "status": "pending",
46
+ "testPointer": "test/query/gates.test.js::gates: D4 floors and per-class opt-outs"
47
+ },
48
+ {
49
+ "id": "TC-216-d3-shapes",
50
+ "acId": "AC-17401-5",
51
+ "description": "checkD3Shapes runs only the cheap data-backed checks: every TAC in scope has at least one interface, and every interface kind is in the closed vocabulary. Unknown kinds show up in failing[].",
52
+ "status": "pending",
53
+ "testPointer": "test/query/gates.test.js::gates: D3 interface presence and kind vocabulary"
54
+ },
55
+ {
56
+ "id": "TC-216-d5-crosscut",
57
+ "acId": "AC-17401-6",
58
+ "description": "checkD5Crosscut checks TAD.securityArchitecture presence when any REQ has shape 'auth' or 'httpApi'; TAD.operationalConcerns when any AC has '[deployed]' marker; no dangling 'open' concerns on in-scope REQs.",
59
+ "status": "pending",
60
+ "testPointer": "test/query/gates.test.js::gates: D5 crosscut cheap checks"
61
+ },
62
+ {
63
+ "id": "TC-216-d6-consistency",
64
+ "acId": "AC-17401-7",
65
+ "description": "checkD6Consistency asserts validateErrors is empty tree-wide and probeOpenCount is zero; the richer scans are named seams (0.30).",
66
+ "status": "pending",
67
+ "testPointer": "test/query/gates.test.js::gates: D6 consistency validate + probe count"
68
+ },
69
+ {
70
+ "id": "TC-216-d8-freeze",
71
+ "acId": "AC-17401-8",
72
+ "description": "checkD8Freeze asserts prior stages passed/acknowledged, every AC owned by exactly one FBS, computeQueue nextActionable not null, validateErrors empty.",
73
+ "status": "pending",
74
+ "testPointer": "test/query/gates.test.js::gates: D8 tree-wide freeze checks"
75
+ }
76
+ ],
77
+ "status": "draft"
78
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "createdAt": "2026-09-24T14:30:00Z",
3
+ "updatedAt": "2026-09-24T14:30:00Z",
4
+ "purpose": "Cover computeReadiness and the rcf define readiness CLI (AC-17501-1..6) with unit tests over fixture trees + a CLI spawn, plus a targeted test of the P3 loadAllLedgers fix.",
5
+ "testLevel": "unit",
6
+ "acIds": [
7
+ "AC-17501-1",
8
+ "AC-17501-2",
9
+ "AC-17501-3",
10
+ "AC-17501-4",
11
+ "AC-17501-5",
12
+ "AC-17501-6"
13
+ ],
14
+ "id": "TS-217",
15
+ "usId": "US-17501",
16
+ "title": "computeReadiness and rcf define readiness CLI",
17
+ "testCases": [
18
+ {
19
+ "id": "TC-217-shape",
20
+ "acId": "AC-17501-1",
21
+ "description": "computeReadiness returns { tree, delta, stages, nextAction, coverage, decisions, freezeable } and freezeable is true when every stage state is in { passed, acknowledged, notApplicable }.",
22
+ "status": "pending",
23
+ "testPointer": "test/query/readiness.test.js::readiness: returns the section 2.4 shape and freezeable folds correctly"
24
+ },
25
+ {
26
+ "id": "TC-217-fanout",
27
+ "acId": "AC-17501-2",
28
+ "description": "Fan-out: computeImpact runs once per id in changed union added; impacted is the deduplicated union; impactedFbs is the subset with kind 'fbs' AND actionNeeded 're-execute'.",
29
+ "status": "pending",
30
+ "testPointer": "test/query/readiness.test.js::readiness: fan-out impacted / impactedFbs (frozen delta)"
31
+ },
32
+ {
33
+ "id": "TC-217-coverage",
34
+ "acId": "AC-17501-3",
35
+ "description": "coverage.tree is a tree-wide CoverageResult; coverage.delta is one CoverageResult per unique REQ ancestor of the delta ids.",
36
+ "status": "pending",
37
+ "testPointer": "test/query/readiness.test.js::readiness: coverage tree-wide and per REQ ancestor"
38
+ },
39
+ {
40
+ "id": "TC-217-next-action",
41
+ "acId": "AC-17501-4",
42
+ "description": "nextAction picks the first failing stage in D1..D8 order and the first failing check inside it, listing ids and a rcf define readiness --check <short-name> command; null when no stage is failing.",
43
+ "status": "pending",
44
+ "testPointer": "test/query/readiness.test.js::readiness: nextAction picks first failing stage and check"
45
+ },
46
+ {
47
+ "id": "TC-217-cli",
48
+ "acId": "AC-17501-5",
49
+ "description": "The CLI prints text summary by default and JSON with --json; --check <stage> exits 4 on blocking failure, 0 with a [warn] on warn-with-ack unacknowledged failure, 0 otherwise; admissibility refusal exits 4.",
50
+ "status": "pending",
51
+ "testPointer": "test/cli/readiness.test.js::readiness cli: text output shape and --check exit codes"
52
+ },
53
+ {
54
+ "id": "TC-217-load-all-ledgers",
55
+ "acId": "AC-17501-6",
56
+ "description": "loadAllLedgers omits ledgers whose files are absent so computeDelta records no docHash for absent ledgers; a project with only brief-ledger.json sees exactly one ledger:brief docHash.",
57
+ "status": "pending",
58
+ "testPointer": "test/define/ledgers.test.js::ledgers: loadAllLedgers omits absent ledger files"
59
+ }
60
+ ],
61
+ "status": "draft"
62
+ }
@@ -53,8 +53,17 @@
53
53
  "then": "the docs URL survives verbatim; the prose 'when the agent stops the queue' survives verbatim; the personal name 'Alice' folds to <operator>; the ledger records an operator-identity row for 'Alice' but no row for 'agent' or 'the'",
54
54
  "testable": true,
55
55
  "scope": "runtime"
56
+ },
57
+ {
58
+ "id": "AC-15601-6",
59
+ "description": "The rule-4 bare-hostname redactor treats filename tokens carrying a known file extension as NOT hostnames. A candidate hostname match whose trailing dot-label is in a bundled FILE_EXTENSIONS set (initially md, mdx, json, yml, yaml, txt, js, mjs, cjs, ts, tsx, html, css, png, svg, sh, csv, log, lock) is left intact and no ledger row is written for it (design amendment R5d). The negative lookbehind on the hostname regex additionally excludes '-' so that filename fragments embedded in a larger identifier (`Backstory-product-brief.md`) cannot start a match after a hyphen. The extension shortlist is merged at call time from an `extensions` array on src/feedback/redact-allowlist.json alongside the existing `hosts` field, so operators can extend it the same way they extend hosts. Design amendment R5e (added 0.28.3 per HQ 2026-09-22 ruling on Codex P1-5 deferred from PR #242): candidate hostnames whose trailing dot-label is a single letter are also treated as filename fragments, not hostnames. Rationale: single-letter TLDs are not registrable domains in normal prose and folding them yields more false positives (short identifiers, abbreviated references) than legitimate hostname redactions. Design amendment R5f (added 0.28.3, #243): the rule-3 email regex is bounded so it does not consume the git protocol sentinel `git@<host>` inside a URL whose scheme is `git+ssh://`, `ssh://` or `git://`, nor the bare git-remote shape `git@<host>:<owner>/<repo>` (colon followed by a non-space path). Rationale: `git@<host>` is a URI userinfo construct, not an email; folding it destroys evidence pointers copied from git remotes and clone URLs. Real email addresses in surrounding prose still fold. Design amendment R5g (added 0.28.3, #246): when the operator identity Name (rcf/.identity/profile.md ## Name) is empty, whitespace only, or contains only reserved-noun tokens (RESERVED_IDENTITY_TOKENS from AC-15601-5), buildRedactionContext emits a one-line visible warning to stderr naming the file and stating that identity folding is disabled. The warning is emitted once per redaction context build. Folding still skips (per AC-15601-5) but the operator is no longer left thinking their identity is in force.",
60
+ "given": "a fixture project with a body containing the tokens 'Backstory-product-brief.md', 'intake.json', 'report.log', 'foo.example.com', 'foo.example.com/path', 'Backstory-example.com', 'a.b.c', the URL 'git+ssh://git@github.com/owner/repo.git#v1.0.0', the URL 'ssh://git@example.internal/owner/repo.git', the bare git remote 'git@github.com:owner/repo.git', and a prose email 'alice@example.com'; and a rcf/.identity/profile.md whose ## Name line is either absent, whitespace only, or reads 'The Agent Operator' (stopword-only)",
61
+ "when": "the redactor runs over the body with the default allowlist",
62
+ "then": "'Backstory-product-brief.md', 'intake.json' and 'report.log' survive verbatim with no ledger row for a hostname substitution on their basename; 'foo.example.com' folds to <host> via rule 4; 'foo.example.com/path' folds via rule 4 bare-host (the URL rule requires a scheme; the token here has none); 'Backstory-example.com' folds to '<host>' (the trailing label is not in the extension set); 'a.b.c' survives verbatim with no hostname ledger row (single-letter trailing label per R5e); 'git+ssh://git@github.com/owner/repo.git#v1.0.0' survives verbatim - the `git@` sentinel is NOT rewritten by the email rule (R5f) and github.com is on the allowlist; 'ssh://git@example.internal/owner/repo.git' has the `git@` sentinel preserved (R5f stash) and the host `example.internal` folded to '<host>' by the extended URL rule 4 which now matches ssh/git+ssh/git schemes with optional userinfo; 'git@github.com:owner/repo.git' has the `git@` sentinel preserved (R5f) and github.com is on the allowlist so it survives verbatim; 'alice@example.com' in surrounding prose still folds to '<email>' (rule 3 email); and when the identity Name is empty/whitespace/stopword-only, stderr carries exactly one visible warning line naming rcf/.identity/profile.md and stating that identity folding is disabled. Extending the allowlist with `extensions: [\"xyz\"]` in redact-allowlist.json (or the operator overlay) makes 'file.xyz' also survive.",
63
+ "testable": true,
64
+ "scope": "runtime"
56
65
  }
57
66
  ],
58
67
  "createdAt": "2026-09-16T13:45:23.000Z",
59
- "updatedAt": "2026-09-17T13:47:03Z"
68
+ "updatedAt": "2026-09-22T13:07:00Z"
60
69
  }
@@ -20,10 +20,10 @@
20
20
  },
21
21
  {
22
22
  "id": "AC-15701-2",
23
- "description": "On submit, per pending entry, gh search issues is called for the destination repo with query \"rcf-feedback-fingerprint: <fp>\", --match body, --state open, --limit 5. Exactly one hit: gh issue comment on that number, comment body carries \"+1 from another reporter\" plus the Environment table and the redacted body only when it differs from the original by more than the environment lines. Zero hits: gh issue create. More than one hit: comment on the lowest number and mention the others.",
24
- "given": "a fake gh module (via RCF_FEEDBACK_GH_MODULE) whose search stub yields zero, one and two hits in three sequential invocations, and one pending entry per case",
25
- "when": "rcf feedback submit --yes runs three times",
26
- "then": "the zero-hit case calls gh issue create; the one-hit case calls gh issue comment on the returned number; the two-hit case calls gh issue comment on the smaller number and includes \"also see #<other>\" in the comment body",
23
+ "description": "On submit, per pending entry, gh search issues is called for the destination repo with query \"rcf-feedback-fingerprint: <fp>\", --match body, --state open, --limit 5. For every returned candidate, submit re-reads the candidate issue body via gh issue view --json body (ghIssueGetBody) and folds ONLY if the body contains a line exactly matching \"rcf-feedback-fingerprint: <fp>\" on its own; a search hit whose body does not carry that exact line is a NON-MATCH and is discarded. Anchor and target must also match the entry's anchor and target; folding across a differing anchor or target is refused even when the fingerprint lines are equal. Zero verified hits: gh issue create; the stdout row reports \"created #N (<title>)\". One or more verified hits: gh issue comment on the lowest-numbered verified issue, mention any other verified hits in the comment body, and post the FULL rendered report (title as a \"## Report\" heading, then the redacted body, then evidence and environment tables, then the fingerprint line), labelled as \"Apparent duplicate of the issue subject; posting the full report so a human can judge.\" The retired \"+1 from another reporter.\" payload is never posted. The stdout row on the fold path reports \"commented on #N (fingerprint match: <title>)\" so a wrong match is visible on the same line a human already sees.",
24
+ "given": "a fake gh module (via RCF_FEEDBACK_GH_MODULE) whose ghIssueSearch stub yields zero, one and two hits in three sequential invocations, and one pending entry per case; and a second fixture where ghIssueSearch returns a candidate whose ghIssueGetBody body carries a DIFFERENT fingerprint line to the entry; and a third fixture where the candidate body's fingerprint matches but the candidate's anchor or target field differs from the entry's",
25
+ "when": "rcf feedback submit --yes runs against each fixture",
26
+ "then": "the zero-hit case creates and prints \"created #N (<title>)\"; the one-hit case with a verified fingerprint match comments on the returned number, the comment body carries the full rendered report (title + body + evidence + environment + fingerprint), the string \"+1 from another reporter.\" is absent, and the stdout row prints \"commented on #N (fingerprint match: <title>)\"; the two-hit case comments on the smaller number and mentions the other verified number; the different-fingerprint fixture and the differing-anchor/target fixture both fall through to create rather than comment",
27
27
  "testable": true,
28
28
  "scope": "runtime"
29
29
  },
@@ -0,0 +1,27 @@
1
+ {
2
+ "usId": "US-17001",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-170",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Product Map: shape grouping",
8
+ "description": "User story under REQ-170. a product reader reviewing the requirements chain the Product Map tab to bucket requirements by shapeClassification.shapes[], with an \"Unclassified\" bucket for REQs missing the classification and a \"Business rules\" bucket for the \"none\" shape",
9
+ "asA": "a product reader reviewing the requirements chain",
10
+ "iWant": "the Product Map tab to bucket requirements by shapeClassification.shapes[], with an \"Unclassified\" bucket for REQs missing the classification and a \"Business rules\" bucket for the \"none\" shape",
11
+ "soThat": "I can see at a glance which requirements sit in each surface area (webUi, httpApi, auth, persistence, notifications, business rules) without reading every REQ body",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17001-1",
15
+ "description": "The rendered review-surface HTML carries a \"product-map\" tab (data-tab=\"product-map\") in tab order after Build sequence, with a hidden tabpanel id \"tab-product-map\" containing a shape-grouping section whose bucket headings enumerate every shape the classified REQs use plus \"Unclassified\" and \"Business rules\". A REQ classified into two shapes appears in both buckets. Rendering does not require a schema change.",
16
+ "testable": true
17
+ },
18
+ {
19
+ "id": "AC-17001-2",
20
+ "description": "Selecting the shape grouping drills each bucket to its REQs (rendered with the same doc-details helper and status pill as the Requirements tab), each REQ to its user stories, and each user story to its acceptance criteria, so the reader can go from a shape bucket to an AC in the same tab. Grouping button clicks and status filter changes push a new history entry (Back/Forward traverse them); top-level tab clicks keep their existing behaviour.",
21
+ "testable": true
22
+ }
23
+ ],
24
+ "tags": [],
25
+ "createdAt": "2026-09-21T17:00:00.000Z",
26
+ "updatedAt": "2026-09-21T17:43:06.307Z"
27
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "usId": "US-17002",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-170",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Product Map: status filter",
8
+ "description": "User story under REQ-170. a reviewer preparing a sign-off pass a status filter (all, draft, review, needsRevision, approved, superseded) on the Product Map tab that applies to whichever grouping is active",
9
+ "asA": "a reviewer preparing a sign-off pass",
10
+ "iWant": "a status filter (all, draft, review, needsRevision, approved, superseded) on the Product Map tab that applies to whichever grouping is active",
11
+ "soThat": "I can narrow the tab to just the \"approved\" or just the \"needsRevision\" REQs without switching tabs or scanning by hand",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17002-1",
15
+ "description": "The Product Map tab carries a status filter control whose options are the five authoring statuses plus \"all\", and applies to every grouping on the tab. When \"approved\" is selected the tab renders only REQs whose status is approved. Buckets that become empty under the current filter are hidden by default with a summary line (\"N empty\" - clicking reveals them), so the map keeps its shape without rendering a wall of empty headings.",
16
+ "testable": true
17
+ }
18
+ ],
19
+ "tags": [],
20
+ "createdAt": "2026-09-21T17:00:00.000Z",
21
+ "updatedAt": "2026-09-21T18:36:17.135Z"
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "usId": "US-17003",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-170",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Product Map: component-first grouping",
8
+ "description": "User story under REQ-170. a technical reviewer wanting to see requirements from the architecture down a component grouping rooted at the TAD, showing each TAC (with its dependencies) at level 1, the user stories linked to that TAC at level 2, each story's parent REQ at level 3, and its ACs at level 4, plus an \"Unmapped\" bucket for REQs no component reaches",
9
+ "asA": "a technical reviewer wanting to see requirements from the architecture down",
10
+ "iWant": "a component grouping rooted at the TAD, showing each TAC (with its dependencies) at level 1, the user stories linked to that TAC at level 2, each story's parent REQ at level 3, and its ACs at level 4, plus an \"Unmapped\" bucket for REQs no component reaches",
11
+ "soThat": "I can pivot from a component to the requirements it delivers without leaving the tab",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17003-1",
15
+ "description": "When the Product Map is grouped by component, the tab renders the TAD as the root, one section per TAC (with a short dependencies[] list drawn from the TAC document), then the user stories under that TAC (from usByTacId), then their parent REQs (from us.reqId), then the ACs. REQs reachable by no TAC are collected under an \"Unmapped\" bucket with a count in its heading.",
16
+ "testable": true
17
+ }
18
+ ],
19
+ "tags": [],
20
+ "createdAt": "2026-09-21T17:00:00.000Z",
21
+ "updatedAt": "2026-09-21T17:00:00.000Z"
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "usId": "US-17004",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-170",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Product Map: trace-coverage grouping",
8
+ "description": "User story under REQ-170. a reviewer about to sign a chain off a trace-coverage grouping that buckets each REQ by chain completeness (complete, missing stories, missing AC scope or owner, missing component owner, missing tests)",
9
+ "asA": "a reviewer about to sign a chain off",
10
+ "iWant": "a trace-coverage grouping that buckets each REQ by chain completeness (complete, missing stories, missing AC scope or owner, missing component owner, missing tests)",
11
+ "soThat": "I can see which requirements are safe to sign off and which still need work",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17004-1",
15
+ "description": "The trace-coverage grouping buckets each REQ into one of complete | missing-stories | missing-ac-scope | missing-component | missing-tests, computed from the tree-model maps (storiesByReqId, acIdsByUsId, usByTacId, tsByAcId / tcsByAcId / fbsByAcId). Each REQ row states in plain text exactly what is missing, and links to its own doc anchor so the reader can jump to it.",
16
+ "testable": true
17
+ }
18
+ ],
19
+ "tags": [],
20
+ "createdAt": "2026-09-21T17:00:00.000Z",
21
+ "updatedAt": "2026-09-21T17:00:00.000Z"
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "usId": "US-17005",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-170",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Product Map: capability grouping",
8
+ "description": "User story under REQ-170. a product reader thinking in capabilities rather than technical shape a capability grouping that reads capability:<slug> entries from REQ.tags[] and buckets requirements per capability",
9
+ "asA": "a product reader thinking in capabilities rather than technical shape",
10
+ "iWant": "a capability grouping that reads capability:<slug> entries from REQ.tags[] and buckets requirements per capability",
11
+ "soThat": "I can see the product as a set of capabilities and drill into each capability's REQs, stories and ACs",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17005-1",
15
+ "description": "The capability grouping enumerates one bucket per distinct capability:<slug> value present on any REQ across the tree. A REQ carrying multiple capability tags appears under each. REQs whose tags carry no capability:<slug> entry are collected under an \"Unclassified\" bucket. Slugs are rendered as their kebab form in the heading; named capability buckets are ordered by REQ count desc, then alphabetically, and the Unclassified bucket is always pinned last.",
16
+ "testable": true
17
+ }
18
+ ],
19
+ "tags": [],
20
+ "createdAt": "2026-09-21T17:00:00.000Z",
21
+ "updatedAt": "2026-09-21T17:43:06.511Z"
22
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "createdAt": "2026-09-21T18:36:23.922Z",
3
+ "updatedAt": "2026-09-21T18:36:52.280Z",
4
+ "description": "a product reader who returns to the tab across sessions collapsed-by-default buckets, a jump-nav index across the top, per-bucket status mini-counts, hash-encoded open buckets so a bookmarked URL restores tab plus grouping plus filter plus open buckets, and compact REQ cards with drill-down (raw JSON stays on the Requirements tab), so the tab scans in under a minute and survives a live-refresh with open state intact",
5
+ "usId": "US-17006",
6
+ "prdId": "PRD-001",
7
+ "reqId": "REQ-170",
8
+ "version": "0.1.0",
9
+ "status": "draft",
10
+ "title": "Product Map: usable interaction model",
11
+ "asA": "a product reader who returns to the Product Map across sessions",
12
+ "iWant": "collapsed-by-default buckets, a jump-nav index across the top, per-bucket status mini-counts, hash-encoded open-bucket state and compact REQ cards with drill-down (raw JSON stays on the Requirements tab)",
13
+ "soThat": "the tab scans in under a minute and a bookmarked URL restores tab, grouping, filter and the buckets I had open, and my open state survives a live refresh",
14
+ "acceptanceCriteria": [
15
+ {
16
+ "id": "AC-17006-1",
17
+ "description": "The Product Map tab renders every bucket collapsed by default, with a summary row carrying the bucket label, its total REQ count and a mini count per authoring status (draft / review / needsRevision / approved / superseded). A jump-nav rail across the top of the panel lists every bucket in the active grouping with its count; clicking a chip scrolls to that bucket and opens it. The hash schema `#tab=product-map&group=<g>&status=<s>&open=<id1>,<id2>` restores tab, grouping, status filter AND the set of open buckets on return, and every bucket-toggle pushes a new history entry so Back/Forward traverse them. Per-bucket <details> nodes carry a stable data-doc-id (\"pm-bucket:<group>:<id>\") so the live-client persists their open state across SSE refreshes.",
18
+ "testable": true
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "createdAt": "2026-09-21T18:36:34.519Z",
3
+ "updatedAt": "2026-09-21T18:36:52.512Z",
4
+ "description": "a reviewer opening the review surface on a slow link the initial HTML payload well under 3 MB by rendering only the active grouping fully; the other three groupings emit only their bucket headings (label + count) and hydrate the REQ list on demand when the reader switches grouping or opens a bucket, so first paint is fast and the tab stays interactive on 100+ REQ trees",
5
+ "usId": "US-17007",
6
+ "prdId": "PRD-001",
7
+ "reqId": "REQ-170",
8
+ "version": "0.1.0",
9
+ "status": "draft",
10
+ "title": "Product Map: lazy first-load payload",
11
+ "asA": "a reviewer opening the review surface on a slow link",
12
+ "iWant": "an initial HTML payload well under 3 MB by rendering only the active grouping fully and hydrating the other groupings on demand from a partial endpoint",
13
+ "soThat": "first paint is fast and the tab stays interactive on 100-plus REQ trees",
14
+ "acceptanceCriteria": [
15
+ {
16
+ "id": "AC-17007-1",
17
+ "description": "The Product Map first-load payload target is well under 3 MB. The server emits the shape (default) grouping fully; the other three groupings emit only their bucket headings (label + total count + per-status mini counts) and each bucket carries a data-pm-lazy marker naming its grouping and bucket id. When the reader switches to a lazy grouping OR opens a lazy bucket, the client fetches that grouping fully once (from a partial endpoint the server exposes) and hydrates the bucket into the DOM; subsequent switches use the cached partial. The pass doc states the before/after first-load bytes.",
18
+ "testable": true
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "usId": "US-17008",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-170",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Product Map: blueprint badge and by-blueprint grouping",
8
+ "description": "User story under REQ-170. a product reader auditing what came from applied blueprints vs the project's own requirements a visible blueprint badge on every REQ row an applied blueprint contributed, plus a sixth grouping that buckets requirements by contributing blueprint (with an Application bucket for the project's own (non-blueprint) REQs) and a second-level capability split inside each bucket.",
9
+ "asA": "a product reader auditing what came from applied blueprints vs the project's own requirements",
10
+ "iWant": "a blueprint badge on every REQ row that was contributed by a blueprint plus a by-blueprint grouping (one bucket per applied blueprint plus an Application bucket) with capability as the second level inside each bucket",
11
+ "soThat": "I can see at a glance which parts of the tree ride on shared blueprints and which the project owns, and drill from a blueprint to the capabilities it contributed",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17008-1",
15
+ "description": "Every REQ row inside the Product Map tab that was contributed by an applied blueprint carries a compact blueprint badge (class pm-blueprint-badge) in its summary row, in every grouping (shape, component, trace coverage, capability, and the new by-blueprint grouping itself). The GROUPINGS list on the panel additionally includes 'blueprint'; the by-blueprint grouping enumerates one bucket per entry in manifest.blueprints[] (label: the blueprint's display name or slug, count: number of contributed REQs) plus an Application bucket for the project's own (non-blueprint) REQs (REQs with no blueprint attribution). Inside each blueprint or Application bucket, requirements are split by capability (existing capability:<slug> convention) as a second level, then the usual REQ->US->AC drill-down. The source of truth for blueprint attribution is manifest.blueprints[].contributions[] (parsed by scope.contributionsForBlueprint); the id-prefix fallback (kebab-slug prefix on reqId) is used only when the manifest carries no contributions record for a given blueprint. The hash schema round-trips as #tab=product-map&group=blueprint&status=<s>&open=<ids>, and the /product-map/blueprint partial endpoint serves the grouping body for the client's lazy-render fetch. A project with no applied blueprints renders a single Application bucket cleanly (not an error, not an empty tab).",
16
+ "testable": true
17
+ }
18
+ ],
19
+ "tags": [],
20
+ "createdAt": "2026-09-22T11:59:39Z",
21
+ "updatedAt": "2026-09-22T15:00:00Z"
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "usId": "US-17101",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-171",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Dogfood chain carries capability tags on every requirement",
8
+ "description": "User story under REQ-171. the maintainer of the rcf-lite dogfood chain every REQ under rcf-lite/rcf to carry at least one capability:<slug> tag drawn from the documented taxonomy",
9
+ "asA": "the maintainer of the rcf-lite dogfood chain",
10
+ "iWant": "every REQ under rcf-lite/rcf to carry at least one capability:<slug> tag drawn from the documented taxonomy",
11
+ "soThat": "the by-capability Product Map view has something to bucket on, and the trial can be evaluated against real data rather than a fixture",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17101-1",
15
+ "description": "Every REQ under packages/rcf-lite/rcf/requirements/ carries at least one tag matching the regex ^capability:[a-z0-9]+(-[a-z0-9]+)*$, and every slug used maps to an entry in the documented capability taxonomy.",
16
+ "testable": true
17
+ }
18
+ ],
19
+ "tags": [],
20
+ "createdAt": "2026-09-21T17:00:00.000Z",
21
+ "updatedAt": "2026-09-21T17:00:00.000Z"
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "usId": "US-17102",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-171",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Capability taxonomy is documented in the rcf-lite docs",
8
+ "description": "User story under REQ-171. a reader of the rcf-lite docs deciding whether to adopt the convention the capability:<slug> convention, the initial taxonomy and the rule for adding a new capability documented alongside the rest of the rcf-lite docs",
9
+ "asA": "a reader of the rcf-lite docs deciding whether to adopt the convention",
10
+ "iWant": "the capability:<slug> convention, the initial taxonomy and the rule for adding a new capability documented alongside the rest of the rcf-lite docs",
11
+ "soThat": "a new capability is added deliberately rather than by drift, and a reader can look up what each slug means",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17102-1",
15
+ "description": "packages/rcf-lite/docs contains a capability tags section listing every slug in the initial taxonomy with a one-line meaning, plus the rule for adding a new capability (single-word verb or noun, kebab-case, must map to a product surface). The section is linked from docs/README.md so it is reachable from the docs entry point.",
16
+ "testable": true
17
+ }
18
+ ],
19
+ "tags": [],
20
+ "createdAt": "2026-09-21T17:00:00.000Z",
21
+ "updatedAt": "2026-09-21T17:00:00.000Z"
22
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "usId": "US-17201",
3
+ "prdId": "PRD-001",
4
+ "reqId": "REQ-172",
5
+ "version": "0.1.0",
6
+ "status": "draft",
7
+ "title": "Delta detection and freeze record land as reusable primitives",
8
+ "description": "User story under REQ-172. The detection-model foundation lands as pure, importable modules and one durable local record.",
9
+ "asA": "the maintainer of a rcf-lite tree",
10
+ "iWant": "hashDocument, computeTreeHash, computeDelta and the rcf/define/freeze.json reader/writer to land as pure modules exposed on the query surface, with a documented return shape that later DEFINE slices can call unchanged",
11
+ "soThat": "the gates, readiness view, freeze verb and bundle-side read all consume the same detection primitives, and a hand reformat of a document never surfaces as a change",
12
+ "acceptanceCriteria": [
13
+ {
14
+ "id": "AC-17201-1",
15
+ "description": "computeDelta(tree, freeze, ledgers) returns the exact object shape proposal 2026-09-22 v3 section 2.2 specifies (frozen, frozenAt, treeHash, currentTreeHash, changed, added, removed, briefSince, unchanged); given a matching freeze the delta reports empty changed / added / removed and unchanged equal to the number of docHashes; given divergent tree state changed / added / removed classify every id correctly against the freeze record.",
16
+ "testable": true
17
+ },
18
+ {
19
+ "id": "AC-17201-2",
20
+ "description": "hashDocument returns a sha256:<64-hex> string over the canonical JSON form of the parsed document (keys sorted recursively, no whitespace), and is invariant under a whitespace-only reformat or a key reorder: two documents that parse to the same object hash identically.",
21
+ "testable": true
22
+ },
23
+ {
24
+ "id": "AC-17201-3",
25
+ "description": "Loading rcf/define/freeze.json when the file is absent returns null; loading a malformed file (bad JSON, missing docHashes, non-sha256 hash value, negative briefStatements) raises FreezeRecordError with a code (parseFailure or schemaFailure) and a named field so a caller can log the exact failure.",
26
+ "testable": true
27
+ },
28
+ {
29
+ "id": "AC-17201-4",
30
+ "description": "Saving then loading a freeze record round-trips every documented field including extension fields the schema does not police (counts, litmus, versions, note), and saveFreezeRecord refuses to persist an invalid record: no file is written on validation failure.",
31
+ "testable": true
32
+ }
33
+ ],
34
+ "tacIds": ["TAC-4120-define-delta-and-freeze"],
35
+ "tags": [],
36
+ "createdAt": "2026-09-24T12:00:00Z",
37
+ "updatedAt": "2026-09-24T12:00:00Z"
38
+ }