rcf-lite 0.28.2 → 0.29.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.
- package/CHANGELOG.md +317 -0
- package/bin/rcf.js +8 -0
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/define-draft-shapes.md +89 -0
- package/guidance/define-intake.md +97 -0
- package/guidance/define-intent.md +107 -0
- package/guidance/discover-prototype.md +67 -0
- package/guidance/discovery-prototypes.md +69 -0
- package/guidance/harness-template.md +34 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +34 -0
- package/guidance/manifest.json +9 -2
- package/package.json +1 -1
- package/rcf/adrs/adr-4120-canonical-json-hashing.json +30 -0
- package/rcf/adrs/adr-4121-define-sidecars.json +30 -0
- package/rcf/adrs/adr-4122-define-gates-blocking-and-optout.json +30 -0
- package/rcf/adrs/adr-4123-readiness-single-object.json +30 -0
- package/rcf/adrs/adr-4124-define-freeze-verb.json +30 -0
- package/rcf/adrs/adr-4126-persona-tagged-blockers-two-levels.json +40 -0
- package/rcf/adrs/adr-4127-readiness-tab-order-and-register.json +40 -0
- package/rcf/adrs/adr-4129-questions-harness-driven-loop.json +25 -0
- package/rcf/adrs/adr-4130-intake-harness-split-tool-scanned.json +20 -0
- package/rcf/adrs/adr-4132-prototypes-in-discovery-as-evidence.json +20 -0
- package/rcf/adrs/adr-4133-dag-dependency-depth-layout.json +40 -0
- package/rcf/adrs/adr-4134-index-json-route.json +35 -0
- package/rcf/adrs/adr-4135-readiness-po-register-and-adapter.json +35 -0
- package/rcf/adrs/adr-4136-embedding-host-fixture.json +35 -0
- package/rcf/code-nodes/cn-700.json +23 -0
- package/rcf/code-nodes/cn-701.json +14 -0
- package/rcf/code-nodes/cn-702.json +14 -0
- package/rcf/code-nodes/cn-703.json +16 -0
- package/rcf/code-nodes/cn-720.json +15 -0
- package/rcf/code-nodes/cn-721.json +15 -0
- package/rcf/code-nodes/cn-722.json +15 -0
- package/rcf/code-nodes/cn-723.json +18 -0
- package/rcf/code-nodes/cn-724.json +14 -0
- package/rcf/code-nodes/cn-725.json +37 -0
- package/rcf/code-nodes/cn-726.json +25 -0
- package/rcf/code-nodes/cn-727.json +23 -0
- package/rcf/code-nodes/cn-728.json +37 -0
- package/rcf/code-nodes/cn-729.json +26 -0
- package/rcf/code-nodes/cn-730.json +24 -0
- package/rcf/code-nodes/cn-731.json +20 -0
- package/rcf/code-nodes/cn-732.json +14 -0
- package/rcf/code-nodes/cn-733.json +14 -0
- package/rcf/code-nodes/cn-734.json +14 -0
- package/rcf/code-nodes/cn-735.json +20 -0
- package/rcf/code-nodes/cn-736.json +14 -0
- package/rcf/code-nodes/cn-737.json +14 -0
- package/rcf/code-nodes/cn-738.json +14 -0
- package/rcf/code-nodes/cn-739.json +18 -0
- package/rcf/code-nodes/cn-740.json +19 -0
- package/rcf/code-nodes/cn-741.json +16 -0
- package/rcf/code-nodes/cn-742.json +16 -0
- package/rcf/code-nodes/cn-743.json +15 -0
- package/rcf/code-nodes/cn-744.json +22 -0
- package/rcf/code-nodes/cn-745.json +16 -0
- package/rcf/code-nodes/cn-746.json +14 -0
- package/rcf/code-nodes/cn-747.json +18 -0
- package/rcf/code-nodes/cn-748.json +16 -0
- package/rcf/code-nodes/cn-749.json +16 -0
- package/rcf/code-nodes/cn-750.json +15 -0
- package/rcf/code-nodes/cn-751.json +18 -0
- package/rcf/code-nodes/cn-752.json +14 -0
- package/rcf/fbs/fbs-190.json +37 -0
- package/rcf/fbs/fbs-191.json +34 -0
- package/rcf/fbs/fbs-192.json +17 -0
- package/rcf/fbs/fbs-193.json +37 -0
- package/rcf/fbs/fbs-194.json +35 -0
- package/rcf/fbs/fbs-195.json +37 -0
- package/rcf/fbs/fbs-196.json +45 -0
- package/rcf/fbs/fbs-197.json +41 -0
- package/rcf/fbs/fbs-198.json +42 -0
- package/rcf/fbs/fbs-199.json +59 -0
- package/rcf/fbs/fbs-200.json +46 -0
- package/rcf/fbs/fbs-201.json +49 -0
- package/rcf/fbs/fbs-202.json +110 -0
- package/rcf/fbs/fbs-203.json +100 -0
- package/rcf/requirements/req-001.json +2 -1
- package/rcf/requirements/req-002.json +3 -2
- package/rcf/requirements/req-003.json +2 -1
- package/rcf/requirements/req-004.json +2 -1
- package/rcf/requirements/req-005.json +2 -1
- package/rcf/requirements/req-006.json +2 -1
- package/rcf/requirements/req-007.json +2 -1
- package/rcf/requirements/req-008.json +2 -1
- package/rcf/requirements/req-009.json +2 -1
- package/rcf/requirements/req-010.json +5 -2
- package/rcf/requirements/req-011.json +2 -1
- package/rcf/requirements/req-012.json +2 -1
- package/rcf/requirements/req-013.json +2 -1
- package/rcf/requirements/req-014.json +2 -1
- package/rcf/requirements/req-015.json +2 -1
- package/rcf/requirements/req-016.json +5 -2
- package/rcf/requirements/req-017.json +6 -2
- package/rcf/requirements/req-018.json +5 -2
- package/rcf/requirements/req-019.json +5 -2
- package/rcf/requirements/req-020.json +5 -2
- package/rcf/requirements/req-021.json +5 -2
- package/rcf/requirements/req-022.json +5 -2
- package/rcf/requirements/req-023.json +6 -4
- package/rcf/requirements/req-024.json +5 -2
- package/rcf/requirements/req-025.json +5 -2
- package/rcf/requirements/req-026.json +5 -2
- package/rcf/requirements/req-027.json +5 -2
- package/rcf/requirements/req-028.json +5 -2
- package/rcf/requirements/req-029.json +5 -2
- package/rcf/requirements/req-030.json +5 -2
- package/rcf/requirements/req-040.json +5 -2
- package/rcf/requirements/req-041.json +5 -2
- package/rcf/requirements/req-042.json +5 -2
- package/rcf/requirements/req-043.json +5 -2
- package/rcf/requirements/req-050.json +5 -2
- package/rcf/requirements/req-051.json +5 -2
- package/rcf/requirements/req-052.json +5 -2
- package/rcf/requirements/req-053.json +5 -2
- package/rcf/requirements/req-054.json +5 -2
- package/rcf/requirements/req-060.json +5 -2
- package/rcf/requirements/req-061.json +5 -2
- package/rcf/requirements/req-062.json +5 -2
- package/rcf/requirements/req-063.json +5 -2
- package/rcf/requirements/req-070.json +5 -2
- package/rcf/requirements/req-071.json +5 -2
- package/rcf/requirements/req-072.json +5 -2
- package/rcf/requirements/req-073.json +5 -2
- package/rcf/requirements/req-074.json +5 -2
- package/rcf/requirements/req-075.json +5 -2
- package/rcf/requirements/req-076.json +5 -2
- package/rcf/requirements/req-077.json +5 -2
- package/rcf/requirements/req-080.json +5 -2
- package/rcf/requirements/req-081.json +5 -2
- package/rcf/requirements/req-082.json +5 -2
- package/rcf/requirements/req-083.json +5 -2
- package/rcf/requirements/req-084.json +5 -2
- package/rcf/requirements/req-085.json +5 -2
- package/rcf/requirements/req-086.json +5 -2
- package/rcf/requirements/req-090.json +5 -2
- package/rcf/requirements/req-091.json +5 -2
- package/rcf/requirements/req-092.json +5 -2
- package/rcf/requirements/req-093.json +5 -2
- package/rcf/requirements/req-094.json +5 -2
- package/rcf/requirements/req-100.json +5 -2
- package/rcf/requirements/req-101.json +5 -2
- package/rcf/requirements/req-102.json +5 -2
- package/rcf/requirements/req-103.json +5 -2
- package/rcf/requirements/req-104.json +5 -2
- package/rcf/requirements/req-110.json +5 -2
- package/rcf/requirements/req-111.json +5 -2
- package/rcf/requirements/req-112.json +5 -2
- package/rcf/requirements/req-113.json +5 -2
- package/rcf/requirements/req-114.json +5 -2
- package/rcf/requirements/req-115.json +5 -2
- package/rcf/requirements/req-120.json +5 -2
- package/rcf/requirements/req-121.json +5 -2
- package/rcf/requirements/req-122.json +5 -2
- package/rcf/requirements/req-123.json +5 -2
- package/rcf/requirements/req-124.json +5 -2
- package/rcf/requirements/req-125.json +5 -2
- package/rcf/requirements/req-130.json +6 -2
- package/rcf/requirements/req-131.json +6 -2
- package/rcf/requirements/req-132.json +6 -2
- package/rcf/requirements/req-133.json +6 -2
- package/rcf/requirements/req-134.json +6 -2
- package/rcf/requirements/req-140.json +5 -2
- package/rcf/requirements/req-145.json +5 -2
- package/rcf/requirements/req-150.json +6 -2
- package/rcf/requirements/req-151.json +6 -2
- package/rcf/requirements/req-152.json +6 -2
- package/rcf/requirements/req-153.json +6 -2
- package/rcf/requirements/req-154.json +5 -2
- package/rcf/requirements/req-155.json +4 -1
- package/rcf/requirements/req-156.json +4 -1
- package/rcf/requirements/req-157.json +4 -1
- package/rcf/requirements/req-158.json +4 -1
- package/rcf/requirements/req-159.json +4 -1
- package/rcf/requirements/req-160.json +4 -1
- package/rcf/requirements/req-161.json +4 -1
- package/rcf/requirements/req-162.json +4 -1
- package/rcf/requirements/req-163.json +5 -2
- package/rcf/requirements/req-164.json +5 -2
- package/rcf/requirements/req-165.json +5 -2
- package/rcf/requirements/req-170.json +33 -0
- package/rcf/requirements/req-171.json +44 -0
- package/rcf/requirements/req-172.json +25 -0
- package/rcf/requirements/req-173.json +25 -0
- package/rcf/requirements/req-174.json +31 -0
- package/rcf/requirements/req-175.json +31 -0
- package/rcf/requirements/req-176.json +25 -0
- package/rcf/requirements/req-180.json +31 -0
- package/rcf/requirements/req-181.json +31 -0
- package/rcf/requirements/req-182.json +25 -0
- package/rcf/requirements/req-186.json +25 -0
- package/rcf/requirements/req-187.json +31 -0
- package/rcf/requirements/req-188.json +25 -0
- package/rcf/tacs/tac-4120-define-delta-and-freeze.json +21 -0
- package/rcf/tacs/tac-4121-define-ledgers.json +35 -0
- package/rcf/tacs/tac-4122-define-gates.json +61 -0
- package/rcf/tacs/tac-4123-define-readiness.json +61 -0
- package/rcf/tacs/tac-4124-define-freeze.json +28 -0
- package/rcf/tacs/tac-4127-readiness-view.json +49 -0
- package/rcf/tacs/tac-4128-define-guidance.json +53 -0
- package/rcf/tacs/tac-4129-define-questions.json +45 -0
- package/rcf/tacs/tac-4130-define-intake-brief.json +35 -0
- package/rcf/tacs/tac-4131-build-dag-view.json +39 -0
- package/rcf/tacs/tac-4132-id-lookup.json +42 -0
- package/rcf/tacs/tac-4133-readiness-po-layer.json +37 -0
- package/rcf/tacs/tac-4134-wespa-host-fixture.json +54 -0
- package/rcf/test-suites/ts-016.json +3 -3
- package/rcf/test-suites/ts-204.json +30 -0
- package/rcf/test-suites/ts-205.json +22 -0
- package/rcf/test-suites/ts-206.json +22 -0
- package/rcf/test-suites/ts-207.json +22 -0
- package/rcf/test-suites/ts-208.json +22 -0
- package/rcf/test-suites/ts-209.json +22 -0
- package/rcf/test-suites/ts-210.json +22 -0
- package/rcf/test-suites/ts-211.json +22 -0
- package/rcf/test-suites/ts-212.json +22 -0
- package/rcf/test-suites/ts-213.json +50 -0
- package/rcf/test-suites/ts-214.json +74 -0
- package/rcf/test-suites/ts-215.json +211 -0
- package/rcf/test-suites/ts-216.json +94 -0
- package/rcf/test-suites/ts-217.json +62 -0
- package/rcf/test-suites/ts-218.json +78 -0
- package/rcf/test-suites/ts-219.json +70 -0
- package/rcf/test-suites/ts-220.json +78 -0
- package/rcf/test-suites/ts-221.json +78 -0
- package/rcf/test-suites/ts-222.json +100 -0
- package/rcf/test-suites/ts-223.json +94 -0
- package/rcf/test-suites/ts-224.json +70 -0
- package/rcf/test-suites/ts-225.json +78 -0
- package/rcf/test-suites/ts-226.json +158 -0
- package/rcf/test-suites/ts-227.json +38 -0
- package/rcf/test-suites/ts-228.json +86 -0
- package/rcf/test-suites/ts-229.json +83 -0
- package/rcf/user-stories/us-15601.json +4 -4
- package/rcf/user-stories/us-17001.json +27 -0
- package/rcf/user-stories/us-17002.json +22 -0
- package/rcf/user-stories/us-17003.json +22 -0
- package/rcf/user-stories/us-17004.json +22 -0
- package/rcf/user-stories/us-17005.json +22 -0
- package/rcf/user-stories/us-17006.json +21 -0
- package/rcf/user-stories/us-17007.json +21 -0
- package/rcf/user-stories/us-17008.json +22 -0
- package/rcf/user-stories/us-17101.json +22 -0
- package/rcf/user-stories/us-17102.json +22 -0
- package/rcf/user-stories/us-17201.json +38 -0
- package/rcf/user-stories/us-17301.json +38 -0
- package/rcf/user-stories/us-17302.json +50 -0
- package/rcf/user-stories/us-17401.json +70 -0
- package/rcf/user-stories/us-17402.json +55 -0
- package/rcf/user-stories/us-17403.json +60 -0
- package/rcf/user-stories/us-17501.json +48 -0
- package/rcf/user-stories/us-17502.json +60 -0
- package/rcf/user-stories/us-17503.json +65 -0
- package/rcf/user-stories/us-17601.json +58 -0
- package/rcf/user-stories/us-18001.json +55 -0
- package/rcf/user-stories/us-18002.json +35 -0
- package/rcf/user-stories/us-18101.json +35 -0
- package/rcf/user-stories/us-18202.json +40 -0
- package/rcf/user-stories/us-18601.json +55 -0
- package/rcf/user-stories/us-18602.json +50 -0
- package/rcf/user-stories/us-18603.json +45 -0
- package/rcf/user-stories/us-18701.json +50 -0
- package/rcf/user-stories/us-18702.json +50 -0
- package/rcf/user-stories/us-18801.json +45 -0
- package/rcf/user-stories/us-204.json +59 -0
- package/rcf/user-stories/us-205.json +64 -0
- package/rcf/user-stories/us-206.json +54 -0
- package/releases/releases.yaml +31 -1
- package/src/cli/feedback.js +126 -10
- package/src/cli/freeze.js +730 -0
- package/src/cli/help.js +23 -0
- package/src/cli/ledger.js +615 -0
- package/src/cli/questions.js +288 -0
- package/src/cli/readiness.js +498 -0
- package/src/cli/view.js +29 -0
- package/src/core/store/writer.js +86 -6
- package/src/define/freeze-record.js +247 -0
- package/src/define/ledgers.js +712 -0
- package/src/feedback/redact.js +105 -19
- package/src/intake/index.js +1 -1
- package/src/intake/orchestrator.js +117 -0
- package/src/mcp/tools.js +354 -0
- package/src/query/delta.js +282 -0
- package/src/query/gates.js +1107 -0
- package/src/query/index.js +50 -0
- package/src/query/questions.js +438 -0
- package/src/query/readiness.js +574 -0
- package/src/server/index.js +60 -1
- package/src/server/routes.js +133 -4
- package/src/setup/identity-seed.js +17 -0
- package/src/view/build-dag.js +563 -0
- package/src/view/components/badge.js +41 -0
- package/src/view/components/diff.js +72 -0
- package/src/view/components/doc-row.js +62 -0
- package/src/view/components/empty-state.js +35 -0
- package/src/view/components/entity-selector.js +137 -0
- package/src/view/components/filter-bar.js +97 -0
- package/src/view/components/findings-list.js +49 -0
- package/src/view/components/index.js +12 -0
- package/src/view/components/pill.js +46 -0
- package/src/view/components/sub-tab-strip.js +43 -0
- package/src/view/components/toast.js +25 -0
- package/src/view/components-fixture.js +265 -0
- package/src/view/doc-renderers/architecture.js +304 -0
- package/src/view/doc-renderers/build.js +160 -0
- package/src/view/doc-renderers/helpers.js +2 -2
- package/src/view/doc-renderers/index.js +2 -0
- package/src/view/doc-renderers/prd.js +35 -6
- package/src/view/doc-renderers/req.js +10 -2
- package/src/view/doc-renderers/user-story.js +10 -5
- package/src/view/html-page.js +717 -300
- package/src/view/index.js +109 -3
- package/src/view/live-client.js +61 -8
- package/src/view/needs-work.js +79 -0
- package/src/view/page-init.js +2193 -0
- package/src/view/product-map.js +751 -0
- package/src/view/readiness/phrasebook.js +115 -0
- package/src/view/readiness/po-layer.js +288 -0
- package/src/view/readiness/question-adapter.js +329 -0
- package/src/view/readiness/stage-legend.js +71 -0
- package/src/view/readiness.js +505 -0
- package/src/view/style.css +2279 -266
- package/src/view/test-host/embed-client.js +121 -0
- package/src/view/test-host/fixture.js +183 -0
- package/src/view/test-host/lints.js +220 -0
- package/src/view/view-index.js +323 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# The product-owner intent loop
|
|
2
|
+
|
|
3
|
+
This document is the operator-side reference for the DEFINE step 3
|
|
4
|
+
intent-complete loop. It is the content of the `rcf_define_intent` MCP
|
|
5
|
+
prompt and the `rcf://docs/define-intent` resource. The agent reads it
|
|
6
|
+
once per session and drives the loop; the product owner never sees any
|
|
7
|
+
of it.
|
|
8
|
+
|
|
9
|
+
## The shape of a turn
|
|
10
|
+
|
|
11
|
+
A turn is four steps and nothing else.
|
|
12
|
+
|
|
13
|
+
1. Run `rcf define questions --persona productOwner` (or
|
|
14
|
+
`rcf_define_questions` over MCP). The call returns the next question
|
|
15
|
+
set from the readiness object. Nothing is stored for the loop; the
|
|
16
|
+
tree, the four ledgers (`rcf/define/`) and the profile
|
|
17
|
+
(`rcf/.identity/profile.md`) are the state.
|
|
18
|
+
2. Ask the first one to three questions in one group, in plain words,
|
|
19
|
+
with the passage from the document quoted exactly. Never show more
|
|
20
|
+
than one group at a time; never show ids, verbs or stage names
|
|
21
|
+
unless the owner asks.
|
|
22
|
+
3. Run the write-back the question carries. Every question's
|
|
23
|
+
`writeBack[]` names one command per answer kind; pick the one that
|
|
24
|
+
matches the owner's reply and run it. The write-back is idempotent:
|
|
25
|
+
running it twice does not change the tree.
|
|
26
|
+
4. Re-run `rcf define readiness --level intent` (or
|
|
27
|
+
`rcf_define_readiness`). On exit 0 the loop is finished and you
|
|
28
|
+
print the verdict (section below). On exit 4 the loop continues:
|
|
29
|
+
go back to step 1.
|
|
30
|
+
|
|
31
|
+
The loop is monotone. Each answer removes its item or converts it into
|
|
32
|
+
at most two further product-owner items (an openQuestion promoted to a
|
|
33
|
+
decision asks `wellFormed` once if the harness under-fills it; a
|
|
34
|
+
statement resolved to a new REQ asks `reqHasUs` once). No engineer
|
|
35
|
+
edit can re-open the PO loop.
|
|
36
|
+
|
|
37
|
+
## The register
|
|
38
|
+
|
|
39
|
+
The first turn's first act is replacing the Register choice list in
|
|
40
|
+
`rcf/.identity/profile.md` with `productOwner` when the person
|
|
41
|
+
driving is the product owner. The profile is operator prose; you edit
|
|
42
|
+
it directly (no verb owns it). The same holds for the Surface choice
|
|
43
|
+
list.
|
|
44
|
+
|
|
45
|
+
The PO register has three wording rules.
|
|
46
|
+
|
|
47
|
+
- Plain sentences. No method terms. "What this is for" beats
|
|
48
|
+
"capability statement". "Who uses this" beats "AC-18602-4 user story
|
|
49
|
+
elicitation".
|
|
50
|
+
- Quote the passage. When the question is about a brief statement,
|
|
51
|
+
quote the statement text in double quotes. The owner recognises their
|
|
52
|
+
own words. "Your document says 'Officers can put a loan on hold.'"
|
|
53
|
+
beats "statement 7".
|
|
54
|
+
- One group at a time. The question object groups by source span so
|
|
55
|
+
the owner thinks about one passage before moving on. Never collapse
|
|
56
|
+
two groups into one prompt.
|
|
57
|
+
|
|
58
|
+
## Starting a session
|
|
59
|
+
|
|
60
|
+
A product-owner session starts with `rcf define questions --persona
|
|
61
|
+
productOwner`. If the brief is empty, the only question the loop
|
|
62
|
+
returns is `brief:sinceFreeze`: hand me your document, or tell me in a
|
|
63
|
+
few sentences what this is for. Walk the folder with the intake prompt
|
|
64
|
+
(`rcf_define_intake`) first when the owner has supplied one; otherwise
|
|
65
|
+
take their sentences and feed them in through `rcf define ledger brief
|
|
66
|
+
add --text "<text>"`.
|
|
67
|
+
|
|
68
|
+
## Ending a session
|
|
69
|
+
|
|
70
|
+
When `rcf define readiness --level intent` returns ok, print exactly
|
|
71
|
+
these lines:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Intent-complete: yes.
|
|
75
|
+
Your document has been turned into N requirements with M user stories.
|
|
76
|
+
The engineer blockers remaining are K.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then run `rcf define readiness --level intent --json` and save the
|
|
80
|
+
output beside the source folder as `<folder>.intent-complete.json`
|
|
81
|
+
(outside `rcf/`; readiness writes nothing itself). Commit the tree.
|
|
82
|
+
The Readiness tab URL is the artefact a delivery stream attaches.
|
|
83
|
+
|
|
84
|
+
## The engineer register
|
|
85
|
+
|
|
86
|
+
The same compute runs with `--persona engineer`: the question set is
|
|
87
|
+
the engineer blockers in failing stages, and its exit is
|
|
88
|
+
`levels.readyToBuild.ok`. Per-check question templates and per-check
|
|
89
|
+
write-backs for the engineer register are NOT part of DEFINE step 3
|
|
90
|
+
PR 2 (spec §1.1: "The engineer register is the same mechanism with
|
|
91
|
+
`--persona engineer`; it is not designed here beyond that flag and
|
|
92
|
+
is what step 4 inherits."). Until step 4 lands, the engineer-persona
|
|
93
|
+
questions return the shared compute shape with empty `writeBack` and
|
|
94
|
+
`answerKinds`; the harness reads the raw blocker and runs the
|
|
95
|
+
engineer verb that matches the check by hand.
|
|
96
|
+
|
|
97
|
+
## What the loop does NOT do
|
|
98
|
+
|
|
99
|
+
- No conversation state in rcf-lite. The tree, the ledgers and the
|
|
100
|
+
profile are the state (ADR-4129).
|
|
101
|
+
- No separate findings list. Intake scan findings become
|
|
102
|
+
`openQuestion` statements and the loop reads one check
|
|
103
|
+
(`brief:openQuestions`) rather than two surfaces (ADR-4130).
|
|
104
|
+
- No stale title-hit fallback. A statement whose `resolvedBy` is a
|
|
105
|
+
REQ id outside the closed grammar fails `skeleton:resolvedBy`; the
|
|
106
|
+
question returns the title hit as a suggestion rather than the
|
|
107
|
+
compute accepting it silently.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Recording a prototype in DISCOVERY
|
|
2
|
+
|
|
3
|
+
This document is the content of the `rcf_discover_prototype` MCP
|
|
4
|
+
prompt served argument-free by the server. It walks the agent
|
|
5
|
+
through the only path a prototype takes into the chain: as evidence,
|
|
6
|
+
with code burned, and with salvage by re-derivation.
|
|
7
|
+
|
|
8
|
+
The posture is ADR-4132. The code of a prototype is reference only;
|
|
9
|
+
no shape in the project tree is promoted from it. The whole worth of
|
|
10
|
+
the method is that every requirement, story, AC and shape has a
|
|
11
|
+
known origin, and a promotion path would smuggle unowned choices
|
|
12
|
+
past the chain.
|
|
13
|
+
|
|
14
|
+
## What a prototype is
|
|
15
|
+
|
|
16
|
+
An elicitation instrument built in DISCOVERY to learn what to ask.
|
|
17
|
+
It is recorded in the intake classification as an artefact of kind
|
|
18
|
+
`other` with the prototype's name.
|
|
19
|
+
|
|
20
|
+
## What is recorded
|
|
21
|
+
|
|
22
|
+
The prototype's outputs are brief statements with source spans
|
|
23
|
+
`prototype:<name>:<observation>`:
|
|
24
|
+
|
|
25
|
+
- Capabilities: what people did with it.
|
|
26
|
+
- Constraints: what they refused or asked for; what they would not
|
|
27
|
+
do without.
|
|
28
|
+
- Open questions: what the prototype raised that the brief had not.
|
|
29
|
+
- Out of scope: what the prototype was never allowed to do.
|
|
30
|
+
|
|
31
|
+
Reactions that need a ruling become decisions with options and a
|
|
32
|
+
default (the enumerated form D7 reads).
|
|
33
|
+
|
|
34
|
+
## What is burned
|
|
35
|
+
|
|
36
|
+
The code. The burn is one brief statement, kind `constraint`:
|
|
37
|
+
|
|
38
|
+
> Prototype <name> (commit <sha>, burned <date>) is reference only;
|
|
39
|
+
> no code from it is promoted.
|
|
40
|
+
|
|
41
|
+
The build that follows starts from the tree, never from the
|
|
42
|
+
prototype. The constraint sits beside the capabilities and open
|
|
43
|
+
questions the prototype produced; the chain reads them all through
|
|
44
|
+
the same `brief:openQuestions` / `skeleton:resolvedBy` loop as any
|
|
45
|
+
document.
|
|
46
|
+
|
|
47
|
+
## Salvage by re-derivation
|
|
48
|
+
|
|
49
|
+
The only middle path. Extract requirements from a promising
|
|
50
|
+
prototype as evidence (statements sourced to its files and lines),
|
|
51
|
+
run them through intake and the PO loop like any document, and
|
|
52
|
+
rebuild from the tree. The prototype is a quarry.
|
|
53
|
+
|
|
54
|
+
"Grow the prototype" is rejected. The provenance reason is the one
|
|
55
|
+
Dex's note gives: a shape reaching the project tree without the PO
|
|
56
|
+
loop that owns every settled shape is a shape without a known
|
|
57
|
+
origin, and no amount of auditing after the fact can restore the
|
|
58
|
+
owner.
|
|
59
|
+
|
|
60
|
+
## The managed-block rule
|
|
61
|
+
|
|
62
|
+
A statement sourced to `prototype:` is evidence. A document that
|
|
63
|
+
cites prototype code as its implementation is a D2 finding the
|
|
64
|
+
engineer resolves by re-deriving. That rule is prose; no check is
|
|
65
|
+
added in 0.30.0, and the engineer running D2 sees the finding
|
|
66
|
+
through the same `skeleton:resolvedBy` surface that reports every
|
|
67
|
+
other pointer.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# DISCOVERY home for prototypes
|
|
2
|
+
|
|
3
|
+
This document is the doctrine and the ledger conventions for
|
|
4
|
+
prototypes. It is served under the slug `discovery-prototypes`,
|
|
5
|
+
through `rcf://docs/discovery-prototypes`, and through the
|
|
6
|
+
`rcf_discover_prototype` prompt that wraps it with the walk.
|
|
7
|
+
|
|
8
|
+
The posture is ADR-4132: prototypes live in DISCOVERY as evidence;
|
|
9
|
+
code is burned; salvage is re-derivation.
|
|
10
|
+
|
|
11
|
+
## What a prototype is
|
|
12
|
+
|
|
13
|
+
An elicitation instrument built in DISCOVERY to learn what to ask.
|
|
14
|
+
It is recorded in the intake classification as an artefact of kind
|
|
15
|
+
`other` with the prototype's name. The prototype belongs to
|
|
16
|
+
DISCOVERY, not DEFINE: it is a tool for learning, not a draft of
|
|
17
|
+
what gets built.
|
|
18
|
+
|
|
19
|
+
## What is recorded
|
|
20
|
+
|
|
21
|
+
Its outputs are brief statements with source spans
|
|
22
|
+
`prototype:<name>:<observation>`:
|
|
23
|
+
|
|
24
|
+
- Capabilities: what people did with it.
|
|
25
|
+
- Constraints: what they refused or asked for.
|
|
26
|
+
- Open questions: what it raised that the brief had not.
|
|
27
|
+
- Out of scope: what it was never allowed to do.
|
|
28
|
+
|
|
29
|
+
Reactions that need a ruling become decisions with options and a
|
|
30
|
+
default. The intent-complete loop then reads one check
|
|
31
|
+
(`brief:openQuestions`) and the owner answers in one place; the
|
|
32
|
+
prototype's observations ride the same ladder as every other brief
|
|
33
|
+
statement.
|
|
34
|
+
|
|
35
|
+
## What is burned
|
|
36
|
+
|
|
37
|
+
The code. The burn is one brief statement, kind `constraint`:
|
|
38
|
+
|
|
39
|
+
> Prototype <name> (commit <sha>, burned <date>) is reference only;
|
|
40
|
+
> no code from it is promoted.
|
|
41
|
+
|
|
42
|
+
The build that follows starts from the tree, never from the
|
|
43
|
+
prototype. The brief ledger holds the burn statement alongside the
|
|
44
|
+
other statements the prototype produced; nothing further is said in
|
|
45
|
+
code or in configuration.
|
|
46
|
+
|
|
47
|
+
## Salvage by re-derivation
|
|
48
|
+
|
|
49
|
+
The only middle path. Extract requirements from a promising
|
|
50
|
+
prototype as evidence (statements sourced to its files and lines),
|
|
51
|
+
run them through intake and the PO loop like any document, and
|
|
52
|
+
rebuild from the tree. The prototype is a quarry.
|
|
53
|
+
|
|
54
|
+
"Grow the prototype" is named here as rejected, with the provenance
|
|
55
|
+
reason from Dex's note: a shape reaching the project tree without
|
|
56
|
+
the PO loop that owns every settled shape is a shape without a known
|
|
57
|
+
origin, and no amount of auditing after the fact can restore the
|
|
58
|
+
owner.
|
|
59
|
+
|
|
60
|
+
## The managed-block rule
|
|
61
|
+
|
|
62
|
+
The managed block gains one rule the harness picks up through
|
|
63
|
+
`rcf doctor`:
|
|
64
|
+
|
|
65
|
+
> A statement sourced to `prototype:` is evidence. A document that
|
|
66
|
+
> cites prototype code as its implementation is a D2 finding the
|
|
67
|
+
> engineer resolves by re-deriving.
|
|
68
|
+
|
|
69
|
+
That rule is prose; no check is added in 0.30.0.
|
|
@@ -406,6 +406,40 @@ queue orchestration, PR authoring and bug triage. The
|
|
|
406
406
|
for elicitation, AC coverage depth and conversation integrity. Run `rcf
|
|
407
407
|
guidance` with no arguments to list every method document the installed
|
|
408
408
|
package ships.
|
|
409
|
+
|
|
410
|
+
### Product-owner intent sessions
|
|
411
|
+
|
|
412
|
+
A product-owner session starts with `rcf define questions --persona
|
|
413
|
+
productOwner` (or the `rcf_define_questions` MCP tool) and ends at
|
|
414
|
+
`Intent-complete: yes`. Every answer lands through the write-back the
|
|
415
|
+
question carries, then readiness recomputes; nothing about the owner's
|
|
416
|
+
intent lives only in a chat. The full loop content is in `rcf guidance
|
|
417
|
+
define-intent` and the `rcf_define_intent` prompt. Never improvise a
|
|
418
|
+
PO loop; the questions and their write-backs are the method.
|
|
419
|
+
|
|
420
|
+
### Prototype rule
|
|
421
|
+
|
|
422
|
+
A prototype is engineer entry material, not a product-owner blocker.
|
|
423
|
+
When the tree reaches `levels.intentComplete.ok`, the agent may
|
|
424
|
+
pre-populate `[draft]` entities, TAC stubs and interfaces so the
|
|
425
|
+
engineer has a starting point; those drafts never re-open the PO
|
|
426
|
+
loop and never block a freeze. If the owner asks for a visible
|
|
427
|
+
prototype before intent-complete, say so (the method is finished the
|
|
428
|
+
intent first) and offer to continue the intent loop; never produce a
|
|
429
|
+
prototype that bypasses the readiness verdict.
|
|
430
|
+
|
|
431
|
+
### Prototypes in DISCOVERY (ADR-4132)
|
|
432
|
+
|
|
433
|
+
A statement sourced to `prototype:<name>:<observation>` is evidence,
|
|
434
|
+
not implementation. A document that cites prototype code as its
|
|
435
|
+
implementation is a D2 finding the engineer resolves by re-deriving:
|
|
436
|
+
extract requirements from the prototype as evidence, run them through
|
|
437
|
+
intake and the PO loop like any document, and rebuild from the tree.
|
|
438
|
+
The prototype's code is burned (one brief statement, kind
|
|
439
|
+
`constraint`); the build that follows starts from the tree, never
|
|
440
|
+
from the prototype. "Grow the prototype" is rejected on provenance
|
|
441
|
+
grounds. The full doctrine is in `rcf guidance discovery-prototypes`
|
|
442
|
+
and the `rcf_discover_prototype` prompt.
|
|
409
443
|
```
|
|
410
444
|
|
|
411
445
|
## Customisation points
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
31dc2627d2a73f5f051a6560dd1c81eac91a87c3a2339c46720648708285e4be
|
|
@@ -393,3 +393,37 @@ queue orchestration, PR authoring and bug triage. The
|
|
|
393
393
|
for elicitation, AC coverage depth and conversation integrity. Run `rcf
|
|
394
394
|
guidance` with no arguments to list every method document the installed
|
|
395
395
|
package ships.
|
|
396
|
+
|
|
397
|
+
### Product-owner intent sessions
|
|
398
|
+
|
|
399
|
+
A product-owner session starts with `rcf define questions --persona
|
|
400
|
+
productOwner` (or the `rcf_define_questions` MCP tool) and ends at
|
|
401
|
+
`Intent-complete: yes`. Every answer lands through the write-back the
|
|
402
|
+
question carries, then readiness recomputes; nothing about the owner's
|
|
403
|
+
intent lives only in a chat. The full loop content is in `rcf guidance
|
|
404
|
+
define-intent` and the `rcf_define_intent` prompt. Never improvise a
|
|
405
|
+
PO loop; the questions and their write-backs are the method.
|
|
406
|
+
|
|
407
|
+
### Prototype rule
|
|
408
|
+
|
|
409
|
+
A prototype is engineer entry material, not a product-owner blocker.
|
|
410
|
+
When the tree reaches `levels.intentComplete.ok`, the agent may
|
|
411
|
+
pre-populate `[draft]` entities, TAC stubs and interfaces so the
|
|
412
|
+
engineer has a starting point; those drafts never re-open the PO
|
|
413
|
+
loop and never block a freeze. If the owner asks for a visible
|
|
414
|
+
prototype before intent-complete, say so (the method is finished the
|
|
415
|
+
intent first) and offer to continue the intent loop; never produce a
|
|
416
|
+
prototype that bypasses the readiness verdict.
|
|
417
|
+
|
|
418
|
+
### Prototypes in DISCOVERY (ADR-4132)
|
|
419
|
+
|
|
420
|
+
A statement sourced to `prototype:<name>:<observation>` is evidence,
|
|
421
|
+
not implementation. A document that cites prototype code as its
|
|
422
|
+
implementation is a D2 finding the engineer resolves by re-deriving:
|
|
423
|
+
extract requirements from the prototype as evidence, run them through
|
|
424
|
+
intake and the PO loop like any document, and rebuild from the tree.
|
|
425
|
+
The prototype's code is burned (one brief statement, kind
|
|
426
|
+
`constraint`); the build that follows starts from the tree, never
|
|
427
|
+
from the prototype. "Grow the prototype" is rejected on provenance
|
|
428
|
+
grounds. The full doctrine is in `rcf guidance discovery-prototypes`
|
|
429
|
+
and the `rcf_discover_prototype` prompt.
|
package/guidance/manifest.json
CHANGED
|
@@ -5,11 +5,18 @@
|
|
|
5
5
|
{"slug": "build-cycle", "file": "build-cycle.md", "title": "The RCF 5-stage build cycle"},
|
|
6
6
|
{"slug": "harness-template", "file": "harness-template.md", "title": "Drop-in harness template"},
|
|
7
7
|
{"slug": "managed/agent-instructions-block", "file": "managed/agent-instructions-block.md", "title": "The rcf doctor managed CLAUDE.md/AGENTS.md block (0.6.0 canonical text)"},
|
|
8
|
-
{"slug": "persona-programme", "file": "persona-programme.md", "title": "Persona programme: tail-interview template"}
|
|
8
|
+
{"slug": "persona-programme", "file": "persona-programme.md", "title": "Persona programme: tail-interview template"},
|
|
9
|
+
{"slug": "define-intent", "file": "define-intent.md", "title": "The product-owner intent loop (DEFINE step 3)"},
|
|
10
|
+
{"slug": "define-intake", "file": "define-intake.md", "title": "Document intake into the brief ledger (DEFINE step 3)"},
|
|
11
|
+
{"slug": "discovery-prototypes", "file": "discovery-prototypes.md", "title": "DISCOVERY home for prototypes (DEFINE step 3)"}
|
|
9
12
|
],
|
|
10
13
|
"prompts": [
|
|
11
14
|
{"name": "rcf_execute_build_cycle", "file": "build-cycle-playbook.md", "description": "How to execute the RCF 5-stage build cycle well: per-stage guidance, referee commands, failure modes, escalation rules. Fetch once per session, then drive rcf build bundle --next."},
|
|
12
|
-
{"name": "rcf_elicit_requirements", "file": "elicitation-playbook.md", "description": "How to draw a valid RCF tree out of a conversation: question frames for PRD / REQ / US / AC, the testable-AC quality bar, TAC / ADR capture, FBS queue construction, and when to stop."}
|
|
15
|
+
{"name": "rcf_elicit_requirements", "file": "elicitation-playbook.md", "description": "How to draw a valid RCF tree out of a conversation: question frames for PRD / REQ / US / AC, the testable-AC quality bar, TAC / ADR capture, FBS queue construction, and when to stop."},
|
|
16
|
+
{"name": "rcf_define_intent", "file": "define-intent.md", "description": "Drive the product-owner intent-complete loop with rcf_define_questions, write-backs through existing verbs, and the readiness verdict. Argument-free; starts with --persona productOwner and ends at levels.intentComplete.ok."},
|
|
17
|
+
{"name": "rcf_define_intake", "file": "define-intake.md", "description": "Walk a document folder brief-first, write one statement file per source with [kind] and (source: ...) markers, run brief add --from once per source, and report the scan findings as open questions. Argument-free."},
|
|
18
|
+
{"name": "rcf_define_draft_shapes", "file": "define-draft-shapes.md", "description": "Run the draft pass after L1 is green: pre-populate [draft] core entities per entity statement, [draft] TAC stubs per surface or externalSystem cluster, [draft] interfaces with real kinds inside each stub, and rcf discover req-classify over every REQ created in the loop. Argument-free."},
|
|
19
|
+
{"name": "rcf_discover_prototype", "file": "discover-prototype.md", "description": "Record a prototype in DISCOVERY as evidence: classification, brief statements with prototype:<name>:<observation> source spans, the burn statement, and the salvage-by-re-derivation path. Argument-free."}
|
|
13
20
|
],
|
|
14
21
|
"platformInvariants": [
|
|
15
22
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rcf-lite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "One-install tooling for the Requirements Confidence Framework (RCF): the unified `rcf` CLI grouped into the five RCF tool groups (discover, define, build, verify, audit) plus a small core set (init, doctor, guidance, mcp), an MCP server, the live tree viewer and the fresh-context adversarial ship-gate verifier. Consumes @stravica-ai/rcf-schemas.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-09-24T12:00:00Z",
|
|
3
|
+
"updatedAt": "2026-09-24T12:00:00Z",
|
|
4
|
+
"adrId": "ADR-4120-canonical-json-hashing",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"tadId": "TAD-001",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"status": "accepted",
|
|
9
|
+
"title": "Delta detection hashes the parsed document under canonical JSON, at standalone-document granularity",
|
|
10
|
+
"context": "The DEFINE workflow proposal (2026-09-22 v3, section 2.1) has to pick both a hashing scheme and a granularity for docHashes on the freeze record. Two choices matter and are named as residual (proposal section 2 penultimate paragraph): raw bytes versus the parsed canonical form, and criterion-level versus document-level. Raw bytes are the smallest amount of code (one hash per file read); the parsed form costs one canonicalise per document per read (milliseconds at 700 criteria). Criterion granularity gives a finer delta; document granularity keeps the model small and lets git supply the criterion-level diff. Both choices had to be recorded because the detection model is what every later slice (readiness, freeze, bundle, viewer) reads.",
|
|
11
|
+
"decision": "Hash the parsed document under canonical JSON: keys sorted recursively, no whitespace, JSON.stringify escaping. Compute the tree hash as SHA-256 over the sorted (id, docHash) pairs canonically encoded. The docHashes map has one entry per standalone document the walker exposes on tree.byId (PRD, TAD, BS, REQ, US, TAC, ADR, FBS, TS, CN, EVAL) plus one ledger:<name> entry per sidecar the caller passes; inline AC and TC ride inside their parent's hash. Granularity for the delta list is the standalone document.",
|
|
12
|
+
"consequences": "A hand reformat of a document (rewritten by rcf define update, opened in an editor and re-saved) does not surface as a change, which is what an operator expects. An edit to a criterion surfaces as its owning story changed; Readiness renders the criterion-level diff by reading the frozen story out of git (proposal section 2.2). The cost is one canonicalise per document per read; at the backstory scale of 700 criteria that is milliseconds and is only paid on readiness / freeze / bundle --next reads. If document granularity turns out too coarse in practice, ADR replacement or schemas 0.7's criterion-hash extension is the migration path; the change would not affect the wire shape of computeDelta because inline granularity would simply expand tree.byId, not the return shape.",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Hash the raw file bytes",
|
|
16
|
+
"summary": "SHA-256 over readFile output directly; skip canonical JSON entirely.",
|
|
17
|
+
"reasonNotChosen": "A CLI edit that rewrites the file (rcf define update reserialises the object every write) would report drift on a file it did not semantically touch. The operator would learn to ignore the delta list, which is the one signal the freeze exists to give."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Criterion granularity: one docHash per AC / TC on top of the document ones",
|
|
21
|
+
"summary": "Add inline-AC and inline-TC entries to docHashes so an edit to AC-207-4 shows in the delta at criterion granularity.",
|
|
22
|
+
"reasonNotChosen": "Doubles the size of docHashes for no readiness improvement: Readiness already renders the criterion-level diff by reading the frozen story from git. The size cost is real (a backstory tree has 700 criteria plus 40 stories); the render cost is not."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Hash the pretty-printed JSON body the writer produced",
|
|
26
|
+
"summary": "Hash JSON.stringify(doc, null, 2) plus a trailing newline; the same bytes the writer wrote.",
|
|
27
|
+
"reasonNotChosen": "Whitespace-only reformats (a hand edit that reorders keys or drops a trailing newline) then surface as changes. Canonical hashing is the same code with the whitespace stripped and the keys sorted; the operator sees fewer false-positive deltas."
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-09-24T12:00:00Z",
|
|
3
|
+
"updatedAt": "2026-09-24T12:00:00Z",
|
|
4
|
+
"adrId": "ADR-4121-define-sidecars",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"tadId": "TAD-001",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"status": "accepted",
|
|
9
|
+
"title": "Ledgers as rcf-lite-owned sidecars under rcf/define/ with a local schema; promote to manifest records at schemas 0.7",
|
|
10
|
+
"context": "The DEFINE proposal (2026-09-22 v3, section 2.5 and decision 5) needs a place to put four things the workflow reads and writes but the shared schema does not yet know about: brief statements (D1), decisions (D7), (concern, REQ) dispositions (D5), probe findings (D6). @stravica-ai/rcf-schemas 0.6.3 has no manifest.ledgers[] shape and adding one is a schema-package bump; the train's non-goal (train-context: 'no schema change') is to ship the workflow without touching @stravica-ai/rcf-schemas. Precedent already exists in this codebase: .rcf/preflight-secrets.local.json is an rcf-lite-owned file outside the shared schema; the loader reads only its named collection directories, so a directory the walker was not told about is invisible.",
|
|
11
|
+
"decision": "Ledgers live under rcf/define/, one JSON file each (brief-ledger.json, decisions-ledger.json, concern-ledger.json, probe-ledger.json), validated by a local schema inside src/define/ledgers.js. The freeze record sits at rcf/define/freeze.json under the same directory. rcf-lite owns the shape until @stravica-ai/rcf-schemas 0.7, at which point the sidecars promote to a manifest.freeze and manifest.ledgers[] records and the local schema is retired. The walker never reads rcf/define/ (src/core/store/loader.js's subdirFor returns null for anything not in its child-kind list), and a walker-invisibility invariant test proves it stays that way.",
|
|
12
|
+
"consequences": "A DEFINE change lands with no @stravica-ai/rcf-schemas bump: the train ships as 0.29.0 against the shipped 0.6.3 schema. The sidecars are committed alongside the rest of the rcf tree (they carry durable state: the freeze hash, brief statement ids, resolved decisions), so git supplies the history the proposal section 2.1 relies on. Second source of truth is the honest downside: until schemas 0.7 there are two places the DEFINE workflow reads (the shared tree and rcf/define/); the promotion path documented in the proposal (section 5.3) is straightforward because the local shapes were designed to line up with the manifest.freeze and manifest.ledgers[] fields the schema promotion adds. If schemas 0.7 slips past the 0.30 train, the sidecars keep working: the local schema is the contract every DEFINE gate consumes.",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Land the manifest.freeze and manifest.ledgers[] records at schemas 0.6.4 now",
|
|
16
|
+
"summary": "Bump @stravica-ai/rcf-schemas to 0.6.4 as part of this train; add manifest.freeze and manifest.ledgers[] optional records; write to the manifest instead of a sidecar.",
|
|
17
|
+
"reasonNotChosen": "The train-context ('no schema change') is a deliberate constraint on this train, ratified 2026-09-22, so the workflow can ship without another WSD-side schema round trip. Schemas 0.7 is on the roadmap for the 0.30 train; landing the sidecar first proves the shape at zero schema cost, then the promotion carries the same fields."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "One combined ledger file (rcf/define/ledger.json) with four sections",
|
|
21
|
+
"summary": "Collapse the four ledgers into one file with four top-level keys; the CLI still gates on the name.",
|
|
22
|
+
"reasonNotChosen": "The four ledgers grow independently (a probe-heavy pass writes ten probes with no decisions changed) and merge conflicts on a shared file would surface constantly. Four files with distinct id sequences is the cheaper long-term shape, and it lines up with the four manifest.ledgers[] records schemas 0.7 is expected to add."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Sidecars under .rcf/ (like preflight-secrets.local.json)",
|
|
26
|
+
"summary": "Put the four ledgers under .rcf/ so they inherit the preflight-secrets convention.",
|
|
27
|
+
"reasonNotChosen": "The preflight sidecar is gitignored on purpose (credential name-metadata). DEFINE ledgers hold durable state the workflow re-reads on every gate; they must be committed. rcf/define/ keeps them visible to the operator, alongside the tree they annotate."
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-09-24T14:30:00Z",
|
|
3
|
+
"updatedAt": "2026-09-24T14:30:00Z",
|
|
4
|
+
"adrId": "ADR-4122-define-gates-blocking-and-optout",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"tadId": "TAD-001",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"status": "accepted",
|
|
9
|
+
"title": "Blocking-vs-warn-with-ack per stage; D4 per-class opt-out reuses manifest.baselineAcOptOuts[]",
|
|
10
|
+
"context": "The DEFINE workflow proposal (2026-09-22 v3, section 3.2, decision 2) blocks where the data exists and warns-with-acknowledgement for D3, D5, D6 until the release that lands the 0.30 (renumbered from 0.31 per Baz 2026-09-24) contradiction-lite, unsatisfiable-lite, template parser and probe runner. Proposal section 3.2 also says D4's failure and must-not floors accept a 'reasoned opt-out per class, as the baseline ledger works (#239)'. The train has to decide whether the D4 opt-out reuses the existing manifest.baselineAcOptOuts[] machinery (nextOptOutId, composeOptOutRecord under src/req-baseline/opt-out.js, boo-YYYY-MM-DD-NNN ids) or gets a new sidecar under rcf/define/. Second decision that had to be recorded because slice 2's gates read it and slice 3's freeze verb writes it: how does the freeze record acknowledge a warn-with-ack gate?",
|
|
11
|
+
"decision": "Blocking in 0.29.0: D1 (define.brief), D2 (define.skeleton), D4 floors (define.stories), D7 (define.decisions), D8 (define.freeze). Warn-with-ack in 0.29.0: D3 (define.shapes), D5 (define.crosscut), D6 (define.consistency). Blocking-vs-warn-with-ack is a stage property, hard-coded in src/query/gates.js. The state fold: a stage is 'passed' when every check.ok is true; 'failing' otherwise. For D3/D5/D6 the fold flips to 'acknowledged' when the freeze record's gates[<gate>] entry names state 'acknowledged' AND at.hash === currentTreeHash. D4 per-class opt-outs reuse manifest.baselineAcOptOuts[]: baselineKey is 'defineD4:failure' or 'defineD4:mustNot'; scope is 'req' (opt-out applies to every US whose reqId matches the record's reqId) or 'project' (applies tree-wide). Slice-3 writes acknowledgements via the freeze verb; the write path is out of scope for slice 2. The opt-out records are authored by an operator through the existing req-baseline surface (or, in slice 3, an optional --optout flag on freeze); slice 2 only reads them.",
|
|
12
|
+
"consequences": "One place to author opt-outs (the existing baselineAcOptOuts array), one place to read them (the same array). No new sidecar until D4's class opt-outs mature (schemas 0.7 will move them if the shape stabilises). The freeze record's gates map becomes the single source of truth for acknowledgements, which is what slice-3's freeze verb writes and every consumer reads. The honest downside: the baselineKey field is loosely typed (free-string convention rather than an enum), so a typo lands silently (the D4 gate simply does not honour the opt-out). Mitigation: the checker prints a stderr [warn] when it sees a record with a baselineKey starting with 'defineD4:' but not matching the two known values, and the CLI docs (slice 3 / slice 6) list the accepted keys. If the shape ever needs to move, existing records can be migrated by rewriting the manifest under the same array without a schema change.",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "New sidecar rcf/define/class-optout-ledger.json for D4 per-class opt-outs",
|
|
16
|
+
"summary": "Fifth sidecar under rcf/define/ with entries { id, scope, reqId?, usId?, class: 'failure'|'mustNot', reason, addedAt }; add --class flag to rcf define ledger.",
|
|
17
|
+
"reasonNotChosen": "Adds a fifth sidecar to a proposal (decision 5) that ratified four. Proposal section 3.2 D4 explicitly says 'as the baseline ledger works (#239)', pointing at the existing surface. A fifth ledger costs a schema, a CLI branch and a walker-invisibility test for zero gain over reusing the shape."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Block D3, D5, D6 in 0.29.0 (drop warn-with-ack)",
|
|
21
|
+
"summary": "Every stage blocks; --ack is not a thing until 0.30.",
|
|
22
|
+
"reasonNotChosen": "Baz's decision 2 ratifies warn-with-ack for D3/D5/D6 until the template parser, catalogue and scans land. Blocking on cheap presence-only checks (an unknown interface kind, or a probe finding still open) refuses freezes on tree state that 0.30's richer checks would tolerate; the acknowledgement channel is what lets an operator move without inventing the check they need is not there."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Encode blocking-vs-warn-with-ack on each check rather than on the stage",
|
|
26
|
+
"summary": "Every check[i] carries a policy tag; the fold reads the tags rather than the stage.",
|
|
27
|
+
"reasonNotChosen": "The proposal is stage-scoped, not check-scoped. Per-check policy tags land as future noise for zero present gain: today every check inside a stage shares a policy. If a stage grows a check with a different policy the fold moves back to per-stage anyway (a 'failing' check on any policy is still 'failing' in the current model)."
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-09-24T14:30:00Z",
|
|
3
|
+
"updatedAt": "2026-09-24T15:48:40Z",
|
|
4
|
+
"adrId": "ADR-4123-readiness-single-object",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"tadId": "TAD-001",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"status": "accepted",
|
|
9
|
+
"title": "Readiness is one computed object; no stored stage state, no stored 'stale'",
|
|
10
|
+
"context": "The DEFINE workflow proposal (2026-09-22 v3, section 2.4, section 6, decision 4) is explicit that the CLI and the viewer tab render the same object so they cannot disagree, and that stage state is one of passed | failing | acknowledged | notApplicable with no 'stale' and no 'notStarted' because nothing is stored: every chip is the truth at the moment of the read. The Product Map (PR #238) precedent already ships this pattern for a related surface. Slice 2 has to pick both the computation shape (one pass or many; where the impact fan-out sits) and the storage shape (nothing stored, or a small cache in the freeze record).",
|
|
11
|
+
"decision": "computeReadiness returns one plain-data object per call over the tree, the freeze record and the four ledgers; nothing is stored between calls, and no per-stage 'stale' flag exists. The stage state is a pure fold over the checks[] of that stage: 'passed' when every check.ok is true, 'failing' otherwise, unless the stage is one of the three warn-with-ack stages and the freeze record's gates[<gate>] entry names 'acknowledged' at the current tree hash. The CLI (rcf define readiness) prints the object; slice-5's viewer renders the same JSON envelope over the existing tab wire (proposal section 6.3). runWithAdmissibilityGate wraps the CLI's producer, matching the ruleset toolScope { traceabilityAndQueryTools: true } switch; slice 2 records this decision because the wrap changes the shipping exit path. On refuse the wrap runs first (so the ruleset toolScope guard is honoured, NV-BL-SR-03 addendum), then the readiness compute runs anyway and the CLI prints an informational [warn] on stderr naming the refusal, because readiness is a diagnostic and refusing to report which stage blocks is the opposite of what the verb exists to do; the process still exits 0 (or the --check <stage> code) and never exits 4 solely on refuse.",
|
|
12
|
+
"consequences": "One shape, two renderers: whatever the CLI prints, the viewer prints. Absence of stored state means Readiness is stateless: git carries the history, the freeze record carries the acknowledgements, everything else is derived. The compute cost is one canonicalise per document per read (already ratified in ADR-4120) plus one computeImpact per delta id, which on the dogfood chain (~700 documents, ~30 delta ids on a small feature) is measured in single-digit seconds. If the compute ever costs too much, the mitigation is a per-render cache in the CLI or the viewer (not in the freeze record), so the shape stays the same. Honest downside: the whole-tree unfrozen case (no freeze record; every doc is 'added') computes computeImpact over every id, which on very large trees may be quadratic in fan-out size. Slice 2 mitigates by skipping the per-id fan-out when the delta is the whole tree (the delta IS the fan-out), and the CLI prints wall time so an operator can spot a regression before it becomes a defect.",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Cache the last stage[] under freeze.gates[]; only re-run gates whose delta touches their scope",
|
|
16
|
+
"summary": "Store per-stage checks + fold under freeze.gates[]; readiness reads the cache and only re-runs gates the delta invalidates.",
|
|
17
|
+
"reasonNotChosen": "Two sources of truth: the tree and the cache. Every operator correction to a story would have to invalidate the cache correctly, and any bug in the invalidation would show up as a false green. Proposal section 2.4 explicitly rules this out: 'no stale, no not-started'. Recompute is cheap enough not to earn the complexity."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Skip runWithAdmissibilityGate on the readiness verb",
|
|
21
|
+
"summary": "Readiness is a diagnostic; run it even when admissibility refuses so the operator can see what to fix.",
|
|
22
|
+
"reasonNotChosen": "The ruleset toolScope switch { traceabilityAndQueryTools: true } is a runtime-read guard (NV-BL-SR-03 addendum) and matches every other query verb's expected posture; skipping it would be an exception the checker cannot see. Slice 2 wraps the producer; a future train can add an explicit --show-anyway flag if the diagnostic case wins the argument, and record it as an ADR replacement."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Compute stages fully in parallel with Promise.all",
|
|
26
|
+
"summary": "Every stage check runs in parallel via Promise.all against a shared context.",
|
|
27
|
+
"reasonNotChosen": "The gate check functions are pure and CPU-bound (no I/O); Promise.all does not parallelise CPU on Node without workers, and worker-thread overhead is far larger than the compute cost. Sequential composition is honest about what the code does."
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-09-24T16:33:15Z",
|
|
3
|
+
"updatedAt": "2026-09-24T16:33:15Z",
|
|
4
|
+
"adrId": "ADR-4124-define-freeze-verb",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"tadId": "TAD-001",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"status": "accepted",
|
|
9
|
+
"title": "rcf define freeze refuses on any failing stage and only D3/D5/D6 accept an --ack override",
|
|
10
|
+
"context": "Proposal 2026-09-22 v3 section 2.5 defines the freeze verb as the only close verb: it runs readiness, refuses on any failing gate, accepts --ack <gate> --reason on the three warn-first gates, and writes the freeze record. Section 3.2 lists five blocking gates in 0.29.0 (D1, D2, D4 floors, D7, D8) and three warn-with-ack gates (D3, D5, D6). Baz ruling 2026-09-24 (Baz decision 2 in section 9): warn-with-ack ack is accepted only on D3 / D5 / D6, never on the blocking gates. Slice 3 has to pick (a) whether ack on a non-warn gate is a usage refusal or a runtime refusal; (b) whether the freeze verb re-runs readiness after ack (or applies acks in-memory); (c) whether admissibility refusal counts as a freeze refusal or a warning. Slice 1 shipped a freeze-record writer with override as an optional nullable field, and the P3 review note flagged the local-schema mismatch (JSDoc said required-nullable, validator said optional).",
|
|
11
|
+
"decision": "The freeze verb refuses (exit 4) on any stage state 'failing' after applying --ack acknowledgements in-memory. --ack is a compile-time contract: unknown gate names (anything outside define.shapes / define.crosscut / define.consistency and their aliases) are usage refusals (exit 2), and every --ack must be paired with a matching --reason (exit 2 on mismatched arities). Acknowledgements are applied to an in-memory freeze.gates map keyed by gate id, and each warn-with-ack stage's state is recomputed with the same fold gates.js foldState uses (WARN_WITH_ACK + at.hash === currentTreeHash). freeze then writes the freeze record with these acknowledgements persisted so slice 5's viewer and a future computeReadiness call render the stage as 'acknowledged' at that hash. Admissibility refusal is informational (ADR-4123 carried): the wrap runs first so the ruleset toolScope guard is honoured, and on refuse the CLI prints a stderr [warn] and the freeze compute continues. The freeze verb never exits 4 solely for a refused-admissibility verdict. Slice 3 also fixes the slice-1 P3 by making validateFreezeRecord require override as a nullable field, and by making saveFreezeRecord always stamp override: null on a fresh freeze so the schema surface is single-sourced before slice 4 reads it. Older records that omit the key are tolerated by loadFreezeRecord defaulting to null on read.",
|
|
12
|
+
"consequences": "One verb, one contract, one exit code path. Operators cannot silently ack a blocking gate: the ack fails at parseArgs time with a message naming the accepted set, which is easier to reason about than a runtime refusal deep in the compute. The in-memory ack recomputation keeps the write path a pure function of the tree + the acks: nothing partial is written if the compute later rejects. Persisted acks show up on the next readiness run because the freeze.gates entry stores { state: 'acknowledged', at: { hash, at, reason } } and gates.js folds against the current tree hash. Honest downside: an operator who acks a warn-with-ack gate and then edits the tree without re-freezing sees the ack disappear on the next readiness run (the hash changed, so foldState drops back to 'failing'). This matches proposal section 2.5 ('closing a change is freezing again') and is the same posture the readiness verb has today. Override-nullable-required means every existing freeze fixture (in tests, in dogfood chains) is expected to carry override: null; the loader tolerates a missing key on read (fills in null) so older records survive, but writing a record without override is a validator failure. The double-dash hygiene is a one-line change per file with a targeted golden-fixture update.",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Accept --ack on any gate; refuse at runtime with a message",
|
|
16
|
+
"summary": "Let --ack take any gate name; the freeze compute checks the ack list against the warn-with-ack set and exits 4 with an error when a blocking gate is acked.",
|
|
17
|
+
"reasonNotChosen": "Slower feedback (parseArgs is instant, the readiness compute takes seconds on a large tree), and it lets an ack land in the ack map that will never be honoured. A usage refusal at parseArgs time matches the rest of the CLI's contract (unknown --check gates are refused there too)."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Re-run computeReadiness after applying acks",
|
|
21
|
+
"summary": "Persist an intermediate freeze record with the acks, re-run computeReadiness against it, decide the exit code from the second run.",
|
|
22
|
+
"reasonNotChosen": "Two writes and two computes; the second compute reads the same tree so the only difference is the freeze.gates map. Folding in-memory with gates.js foldState is one function call and matches what a slice-5 viewer would render if it were doing the same thing."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Refuse the freeze on refused-admissibility",
|
|
26
|
+
"summary": "Treat a refused-admissibility verdict as a fatal freeze failure; exit 4 with the refusal envelope on stderr.",
|
|
27
|
+
"reasonNotChosen": "ADR-4123 already ruled that readiness is a diagnostic and refuses to hide the block. The freeze verb is the write half of the same object; refusing the freeze solely because admissibility refused means an operator with a valid freezeable tree cannot close the change until the admissibility rules unblock. The refusal is still surfaced as a [warn] on stderr so it is not silent."
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-10-01T14:30:00Z",
|
|
3
|
+
"updatedAt": "2026-10-01T14:30:00Z",
|
|
4
|
+
"adrId": "ADR-4126-persona-tagged-blockers-two-levels",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"tadId": "TAD-001",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"status": "accepted",
|
|
9
|
+
"title": "Intent-complete is a persona cut, computed, never recorded",
|
|
10
|
+
"context": "WSD needs a product-owner exit from DEFINE that a delivery stream can pick up without a hand-over meeting. Dex proposed early-stage gates; the design-spec review (2026-10-01) walked a middle path. D2 and D4 each hold both PO and engineer checks today; a single-persona tag per check would misread both. Operators also need a way to pre-populate shapes at L1 without those shapes reading as the engineer's finished work. The gate table has 20 checks in 0.29.0; two are heterogeneous and one is missing. The train is 4.5 days across three PRs; PR A owns the compute.",
|
|
11
|
+
"decision": "Every gate check in src/query/gates.js declares an owning persona (productOwner | engineer) via a code-side CHECK_PERSONA map and a one-sentence CHECK_QUESTION heading in the persona's register. The two heterogeneous checks are split: skeleton:reqFields becomes skeleton:reqIntent (productOwner: description + domain) + skeleton:reqShape (engineer: shapeClassification.shapes); decisions:allAnswered becomes decisions:wellFormed (productOwner: enumerated per #241 format) + decisions:allAnswered (engineer: zero open). A new D3 check shapes:draftSettled fails every interface whose description still carries the leading [draft] marker, so an L1-pre-populated interface is distinguishable from a settled one; shapes:tacHasInterface counts a draft as present, and shapes:kindVocabulary applies to drafts. L1 intent-complete is 'every productOwner check ok across every stage', computed by computeReadiness and folded into readiness.levels.intentComplete alongside the existing freezeable-equivalent readiness.levels.readyToBuild. Nothing is written; both levels fall out of stages[].checks[] on every read. The notApplicable placeholder (stage:<D>:scope) carries a per-stage fallback persona (STAGE_FALLBACK_PERSONA map) so every check has a persona for the table test and downstream renderers; the placeholder is always ok so it never affects a level. There is no either / shared / both persona; a check that would need both is split instead. Per-item persona override is not introduced; a failing item inherits its check's persona. Open decisions do not block L1 in 0.29.0: an enumerated decision with a default is enough for an engineer to proceed (Baz direction doc, open question 1 answered Dave-recommended). The [draft] marker lives on TAC.interfaces[].description only in 0.29.0, mirroring decision 6's AC-class bracket convention; rcf-schemas 0.6.3 is untouched and the marker migrates with AC.kind to a structured field at schemas 0.7.",
|
|
12
|
+
"consequences": "One ladder, two verdicts. Intent-complete is the ticket artefact a delivery stream reads from `rcf define readiness --level intent --json`; it is not recorded, so no second register of truth and nothing to invalidate on tree change. The persona split in gates.js is one map plus two straightforward loops (reqIntent / reqShape each evaluates every REQ independently; wellFormed / allAnswered each loop decisionEntries); every existing test except the two renamed checks keeps passing unchanged, and the dogfood tree reports two extra findings on reqIntent that were previously masked by the first-failure continue (true findings, not regressions). The notApplicable placeholder carrying a persona is a small schema surface addition that keeps the per-check vocabulary total true. The [draft] marker as a text convention is the same posture AC class carries today (decision 6); the migration cost at schemas 0.7 is one more lift. L1 pre-population is explicit: the L1 agent writes [draft] interfaces as it learns entities and routes from the brief, which makes shapes:tacHasInterface pass earlier for the engineer and leaves shapes:draftSettled as the honest list of what the engineer has yet to own. L2 draft handling is unchanged: a failing shapes:draftSettled keeps D3 failing unless acknowledged at the current hash through the existing --ack define.shapes path (0.29.0 warn-with-ack posture; step 4 makes D3 bite). The levels object cannot disagree with the CLI or the Readiness tab because all three render from the same computeReadiness output. The CLI flags that expose levels (--level, --persona) and the viewer tab land in PRs B and C respectively; this ADR only covers the compute.",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Stage-prefix persona (every check on D1/D2/D4/D7 is productOwner; D3/D5/D6/D8 is engineer)",
|
|
16
|
+
"summary": "Pin persona by stage, skip the per-check tag; no splits.",
|
|
17
|
+
"reasonNotChosen": "Blocks the PO on skeleton:tadPersistence (data stores) and skeleton:deployAdr (which hosting), both engineering decisions. Also passes with unresolved brief statements under an engineer-only D5 even though resolution is a PO question. The per-check map is the source of truth because the owner is a property of the rule, not of the stage."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Recorded L1 (freeze-at-intent)",
|
|
21
|
+
"summary": "Write an intent-complete freeze record parallel to the build freeze, with its own hash and ledger.",
|
|
22
|
+
"reasonNotChosen": "Reintroduces stored state the detection model (REQ-172) removed. Two freeze records on one tree are two chances for them to disagree, and the delivery stream's artefact is already the --json output at a tree hash. L1 is a verdict, not a commit."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "either / shared / both persona value",
|
|
26
|
+
"summary": "Allow a check to be owned by both personas when it reads both ways.",
|
|
27
|
+
"reasonNotChosen": "Hides whose question it is, which was the point of the tag. A heterogeneous check is almost always two checks under one name; splitting is clearer and cheap."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "[draft] on a new TAC.interfaces[].status field instead of description",
|
|
31
|
+
"summary": "Introduce a structured status enum on the interface shape.",
|
|
32
|
+
"reasonNotChosen": "Schema change for one piece of 0.29.0 metadata; AC class already lives on description as a bracket prefix (decision 6); the two migrate together at schemas 0.7. The 0.29.0 cost is one regex."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "Per-item persona override on failing[]",
|
|
36
|
+
"summary": "Let a failing { id, why } carry its own persona so one check can fail into both personas.",
|
|
37
|
+
"reasonNotChosen": "A failing item carries no new information its check does not already carry; the operator reads the question heading, not the item. Keeps the API surface narrower and avoids forcing every consumer to fold two persona fields."
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|