rcf-lite 0.30.0 → 0.32.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 +67 -0
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12113.json +1 -1
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/define-draft-shapes.md +15 -10
- package/package.json +1 -1
- package/rcf/adrs/adr-4131-gates-bite-ack-recorded-override.json +3 -3
- package/rcf/adrs/adr-4137-standalone-theme-persistence.json +30 -0
- package/rcf/adrs/adr-4138-tree-wide-checks-before-scope-shortcircuit.json +35 -0
- package/rcf/code-nodes/cn-017.json +4 -2
- package/rcf/code-nodes/cn-771.json +15 -0
- package/rcf/code-nodes/cn-772.json +14 -0
- package/rcf/code-nodes/cn-773.json +16 -0
- package/rcf/code-nodes/cn-774.json +20 -0
- package/rcf/code-nodes/cn-775.json +15 -0
- package/rcf/code-nodes/cn-776.json +17 -0
- package/rcf/code-nodes/cn-777.json +21 -0
- package/rcf/code-nodes/cn-778.json +14 -0
- package/rcf/fbs/fbs-004.json +4 -1
- package/rcf/fbs/fbs-201.json +2 -1
- package/rcf/fbs/fbs-202.json +18 -2
- package/rcf/fbs/fbs-203.json +12 -4
- package/rcf/requirements/req-002.json +2 -2
- package/rcf/requirements/req-173.json +2 -2
- package/rcf/requirements/req-174.json +2 -2
- package/rcf/tacs/tac-4122-define-gates.json +7 -2
- package/rcf/tacs/tac-4127-readiness-view.json +3 -2
- package/rcf/test-suites/ts-004.json +82 -2
- package/rcf/test-suites/ts-007.json +1 -1
- package/rcf/test-suites/ts-215.json +64 -1
- package/rcf/test-suites/ts-216.json +18 -2
- package/rcf/test-suites/ts-219.json +10 -2
- package/rcf/test-suites/ts-220.json +18 -2
- package/rcf/test-suites/ts-223.json +24 -2
- package/rcf/test-suites/ts-225.json +10 -2
- package/rcf/test-suites/ts-229.json +49 -3
- package/rcf/test-suites/ts-230.json +26 -2
- package/rcf/test-suites/ts-231.json +16 -1
- package/rcf/test-suites/ts-236.json +44 -0
- package/rcf/user-stories/us-17302.json +36 -1
- package/rcf/user-stories/us-17401.json +11 -1
- package/rcf/user-stories/us-17402.json +6 -1
- package/rcf/user-stories/us-17403.json +11 -1
- package/rcf/user-stories/us-17404.json +16 -1
- package/rcf/user-stories/us-17405.json +7 -2
- package/rcf/user-stories/us-18001.json +6 -1
- package/rcf/user-stories/us-202.json +25 -1
- package/rcf/user-stories/us-205.json +6 -1
- package/rcf/user-stories/us-206.json +25 -4
- package/rcf/user-stories/us-207.json +36 -0
- package/releases/releases.yaml +20 -1
- package/src/cli/create.js +22 -2
- package/src/cli/freeze.js +8 -0
- package/src/cli/ledger.js +96 -4
- package/src/cli/readiness.js +17 -39
- package/src/core/errors/index.js +7 -1
- package/src/core/store/writer.js +181 -13
- package/src/define/interface-paths.js +47 -0
- package/src/define/ledgers.js +86 -7
- package/src/mcp/tools.js +1 -1
- package/src/query/gates.js +431 -163
- package/src/view/html-page.js +43 -13
- package/src/view/live-client.js +18 -10
- package/src/view/page-init.js +149 -8
- package/src/view/style.css +107 -20
- package/src/view/view-index.js +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,73 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.32.0] - 2026-10-06
|
|
10
|
+
|
|
11
|
+
0.32.0 ships four readiness and viewer fixes merged since 0.31.0 (merge ec9464db) as one release. D6 consistency:orphanInterfaces now accepts the schema-canonical object-shape REQ.deliveredBy (issue 301), D3 shapes:pathsResolve inside rcf define freeze pre-resolves TAC interface path tokens the way readiness does (issue 302), the deploy-cloudflare-workers blueprint reworded AC-12113-2 so D6 consistency:unsatisfiable stops reading "absent" as a field name (PR 300), the viewer's SSE tree-update resync preserves the active tab for every main tab including Readiness (issue 297), and the audit view header keeps the tabs visible under long project names and prefers prd.productName (issue 295). minAgentAction: null; the managed agent-instructions block hash did not move.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **D6 consistency:orphanInterfaces now accepts the schema-canonical object-shape REQ.deliveredBy (issue 301, w-2026-10-06-dave-020).** `collectReachedInterfaces` in `src/query/gates.js` previously accepted only array-shape `deliveredBy` and dropped the `{tacId, adrId?, field?}` object form that `@stravica-ai/rcf-schemas` 0.6.3 defines as canonical. Every schema-conforming REQ (including every blueprint-contributed REQ) therefore had its `deliveredBy` silently skipped by the orphanInterfaces reach-walk, and interfaces on blueprint TACs with no AC ownerRef read as orphans. Hit on ai-baseline define/kickoff (115/173 failing on nine applied blueprints). Fix: the walk now also handles the object form, routing `field: interfaces[<name>]` to a single interface via `parseOwnerRefInterfaceField` and a bare `tacId` to every interface on the named TAC (the "delivered by this TAC" meaning carried over from the array-shape bare pointer). Legacy array-shape pointers are unchanged. Regression fixtures in `test/query/gates.test.js` cover the object-shape interface pointer, the bare-tacId object form, and the legacy array shape.
|
|
17
|
+
|
|
18
|
+
- **D3 shapes:pathsResolve inside `rcf define freeze` now pre-resolves TAC interface `path:` tokens the way `rcf define readiness` does (issue 302, w-2026-10-06-dave-022).** `src/cli/freeze.js` called `computeReadiness` without `resolvedPaths`, so the D3 gate fell back to an empty set and false-failed every repo-relative `path:` token the engineer named on a TAC interface description, even when the file existed on disk and the readiness CLI (which pre-resolves via `resolveInterfacePaths` in `src/cli/readiness.js`) passed D3 4/4. Hit on ai-baseline define/kickoff: readiness green on D3, freeze refused with four shapes:pathsResolve findings. Fix: lifted `resolveInterfacePaths` out of `src/cli/readiness.js` into `src/define/interface-paths.js` so freeze can share it. Freeze now runs the resolve walk against `projectRoot` before `computeReadiness` and passes the resulting `resolvedPaths` set to the compute. The pure gate reads it unchanged. Regression test in `test/cli/freeze.test.js` seeds a TAC interface whose `path:` token points at a real file and asserts freeze succeeds, then flips to a missing path and asserts freeze refuses with exit 4 and surfaces the shapes:pathsResolve finding.
|
|
19
|
+
|
|
20
|
+
- **Blueprint deploy-cloudflare-workers: AC-12113-2 reworded so D6 consistency:unsatisfiable stops reading "absent" as a field name.** The description parenthetical "Cloudflare's runtime treats the absent field as the default asset-lookup-first shape" tripped extractThenFieldTokens pattern 2 (`<name> field`) which captured "absent" and no recordShape defines that field. Reworded to "treats a missing run_worker_first as the default asset-lookup-first shape"; meaning preserved, the field reference now names the real WranglerAssetsBlock field. Hit on ai-baseline define/kickoff.
|
|
21
|
+
|
|
22
|
+
- **Viewer: SSE tree-update preserves the active tab for every main tab, Readiness included (issue 297, w-2026-10-06-dave-003).** `resyncTabsAfterSwap` in `src/view/live-client.js` hard-coded a five-entry TABS array (`overview`, `requirements`, `architecture`, `build`, `product-map`) that omitted `readiness`. When Readiness was the active tab and an SSE tree-update swapped `#rcf-live-content` innerHTML (which happens about every 30s since issue 236 writes a heartbeat into `rcf/manifest.json`), the resync `indexOf` returned -1, fell back to Overview, unhid `#tab-overview` and never visited `#tab-readiness`, so both panels were visible. The viewer's standalone context still recovered a tick later when `rcfPage.init()` re-resolved the hash, but WESPA's admin SPA embed (`iframe /rcf-viewer/?embed=1&theme=...`, no hash) saw the Readiness tab silently switch to Overview. The parallel array was removed: the tab list is now derived from the server-rendered `nav.tabs` itself (every `[role="tab"][data-tab]` present at swap time) so a future tab added to the renderer cannot be forgotten by live-client. The overview fallback stays for a genuinely unknown or missing selection. AC-18001-8, CN-778. Issue 236 (heartbeat into git-tracked manifest.json) is unchanged.
|
|
23
|
+
- **Chain: live-client resync responsibility named on TAC-4127-readiness-view.** The TAC previously named `src/view/html-page.js` (now `src/view/page-init.js`) as the owner of the viewer-side tab list but did not name the live-client's post-swap resync. Added a responsibility bullet so a future reader finds both ends of the chain from one node. The TAC's substantive behaviour is unchanged.
|
|
24
|
+
- **Audit view header: tabs always visible under long project names (issue 295).** Dex (WSD) reported on the WESPA project (`manifest.projectName` = `WESPA - WSD Enterprise Semantic Platform for Analytics`, 54 chars) that the sixth main tab (Build) was clipped at 1440 and 1920 and the Find button in `.tools` sat on top of it. The .bar is `max-width: 1280px` with flex children `.brand`, `.product`, `[role=tablist]`, `.tools`; the pre-fix tablist carried `flex: 1 1 auto; min-width: 0; overflow-x: auto` so it was free to shrink below its intrinsic width while `.product` did not yield. The fix moves the shrink budget onto `.product` (via `min-width: 0` on both the flex item and on `.product .name`, so `text-overflow: ellipsis` triggers inside the flex container) and pins `nav.tabs` and `.tools` to `flex-shrink: 0` so the six tabs and the tools group hold their intrinsic widths. `.product .name` now also carries `title="<full projectName>"` so the ellipsis is reversible on hover; the attribute is HTML-escaped on the same path as the inner text. The fix holds in embed and non-embed, light and dark, with the 0.31.0 theme control present. `src/view/style.css`, `src/view/html-page.js`; US-202 gains AC-202-4 (layout rules) and AC-202-5 (title attribute); TS-004 gains the matching passing TCs; CN-017 and CN-771 updated to implement the new ACs; FBS-004 `acIds` updated.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Audit view header: project display name prefers `prd.productName` (issue 295).** Pre-fix the header read `manifest.projectName`, then fell back to `prd.productName` and `prd.productTitle`. `rcf define update MANIFEST` is not addressable (the manifest has no id in the family grammar), so `manifest.projectName` was a one-shot init value and the only way to rename the audit chrome without re-initialising was a hand edit of `manifest.json`. `prd.productName` is editable via `rcf define update PRD-001 --set productName=<value>`; preferring it in the header makes the display name lite-native without touching the manifest, which keeps owning ids and paths. `src/view/html-page.js` `resolveProductHeader`; US-202 gains AC-202-6; TS-004 gains the matching passing TCs; CN-017 updated. Follow-up for Dave: a lite-native `rcf define update MANIFEST --set projectName=<value>` (Part 2 cut a from the brief) is a separate, larger change (manifest is not a family id; the update verb would grow a MANIFEST special-case alongside the one `rcf define read` already carries) and is NOT shipped here.
|
|
29
|
+
|
|
30
|
+
## [0.31.0] - 2026-10-05
|
|
31
|
+
|
|
32
|
+
0.31.0 ships three themes from rcf-lite PRs 291, 292 and 293. The viewer gains a standalone Light | Dark | Auto theme control (ADR-4137), readable FIND and LookupModal hover and focus states in dark, and a WCAG 2.1 AA contrast audit test that blocks a merge dropping any colour pair below the floor in either theme. DEFINE step 3 rulings R1 to R8 and R13 land, with every tree-wide readiness check evaluated before any scope short-circuit through one shared helper (ADR-4138). And `--id` write-backs are idempotent: `rcf define ledger <name> add --id` and `rcf define create <kind> --id` treat a same-id replay with identical content as a no-op. Breaking under the pre-1.0 convention for three reasons: `rcf define create <kind> --id` and `rcf define ledger <name> add --id` now exit 3 on same-id-different-content; the readiness envelope for a scope-empty stage with a failing tree-wide check is `failing` (or `acknowledged` when acked at the current hash on a warn-with-ack stage), not `notApplicable`; and `shapes:draftSettled` is tree-wide, so a `[draft]` marker outside the D3 scope now fails the check. The managed agent-instructions block did not change, so no agent re-init is needed (minAgentAction null).
|
|
33
|
+
|
|
34
|
+
DEFINE step 3 rulings R1 to R8 (Baz ratified Dave's recommendations 2026-10-05 under w-2026-10-05-dave-001; the full ruling set is spec section 18). R9 to R12 already shipped in 0.30.0 and were confirmed on the same pass. PR 292 follow-ups and ruling R13 (w-2026-10-05-dave-013, Baz confirmed 2026-10-05): six items from the independent review of PR 292 plus the tree-wide-before-scope principle extended to every tree-wide check (D5 crosscut:securityArchitecture, D5 crosscut:operationalConcerns, D2 skeleton:deployAdr); R1 extended to `rcf define create`.
|
|
35
|
+
|
|
36
|
+
Viewer UI refresh amendments (w-2026-10-04-dave-001) following Baz's
|
|
37
|
+
2026-10-04 review of the shipped 0.30.0 viewer. Three gaps closed in
|
|
38
|
+
one PR on top of origin/main.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **Standalone three-state theme control (Light | Dark | Auto).** Server-rendered in the shell header's `.tools` group right of the SearchButton; keyboard-operable (`role="group"`, three `<button type="button">` with `aria-pressed`); applies the chosen theme live via `data-theme` on `<html>`. Non-embed boot precedence: an explicit `?theme=` query parameter wins; then (non-embed only) a stored value under `localStorage['rcf-view:v1:theme']`; then `auto` (the `prefers-color-scheme` media query decides). A `rcf-view-theme` postMessage from the embedding host still applies live in both modes and never writes the stored key. Design doc section 11 recommendation 6; `renderThemeControl()` in `src/view/view-index.js`; wired by `wireThemeControl()` in `src/view/page-init.js`.
|
|
43
|
+
- **ADR-4137 (standalone-theme-persistence).** The one deliberate deviation from the design doc's "storing nothing" line is scoped to the standalone case: in non-embed mode the theme control writes `rcf-view:v1:theme` so the user's choice survives reloads; under `embed=1` the control is absent from the DOM entirely and the key is never read or written (the storage boundary branches on `document.documentElement.getAttribute('data-embed')`). The key sits under the shared `rcf-view:v1:` namespace so the PR 9 localStorage namespace lint stays green.
|
|
44
|
+
- **WCAG 2.1 AA contrast audit test (US-207, TS-236).** A new static-analysis test (`test/view/contrast-aa.test.js`, helpers at `test/view/helpers/contrast.js`) parses `src/view/style.css`, resolves the `--sv-*` custom-property blocks for the light (`:root`) and dark (`:root[data-theme="dark"]`) token sets, enumerates every rule that sets `color` paired with every reachable `background` / `background-color` across state variants (default, `:hover`, `:focus`, `:focus-visible`, `:active`, `[aria-pressed="true"]`, `[aria-selected="true"]`, `.is-active`, `:disabled`), and asserts the WCAG 1.4.3 / 1.4.11 thresholds (`>= 4.5:1` text, `>= 3:1` large text and non-text UI boundaries) in both themes. Pairs that cannot be resolved statically (gradients, rgba over an unknown background) are composited against the three ancestor-surface candidates (`--sv-canvas`, `--sv-surface`, `--sv-raised`) and recorded as approximate. The suite blocks a merge that drops a pair below AA; the failure message names selector, state, theme, foreground, background, computed ratio and applied threshold.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- **R1: ledger `add` is idempotent by id.** `rcf define ledger <name> add` (brief / decisions / concerns / probes) grows an optional `--id <n>` the harness carries on a replayed turn. A repeat whose id already exists with identical content exits 0 and prints `<ledger>-ledger: unchanged <id>` (with `--json`, `{"status":"unchanged","id":<n>,"entry":{...}}`); a repeat whose id already exists with different content refuses with exit 3 and a message naming the id and the differing field keys. Identity is id-carried: a replayed turn without `--id` still mints the next id as before, so the harness is responsible for threading `--id` back. `brief add --id` with `--from` is refused as a usage error because `--from` mints N entries. `src/define/ledgers.js` `addEntry` and `src/cli/ledger.js`; AC-17302-7/8/9. See "For consumers (upgrading)" below.
|
|
49
|
+
- **R2: `shapes:draftSettled` is tree-wide.** A `[draft]` marker on any `TAC.purpose`, any interface description or any `TAD.dataArchitecture.coreEntities[].description` fails the check regardless of the D3 scope. The check's `over` label flips from `delta` to `tree` to match the ruling; it runs ahead of the D3 `notApplicable` short-circuit via the ADR-4138 helper. `src/query/gates.js` `computeDraftSettledCheck`; AC-17403-9.
|
|
50
|
+
- **R5 and R7: tree-wide checks evaluate before any scope short-circuit (new ADR-4138).** `src/query/gates.js` grows one shared helper (`treeWideFailureEnvelope`) that governs `shapes:draftSettled` (R2), `shapes:entityJoin` (R5) and `skeleton:standardsCited` (R7). A narrowed D3 scope that excludes every TAC and shaped REQ still reports an unjoined `coreEntity` or an unsettled `[draft]`; a narrowed D2 scope that excludes every REQ/PRD/TAD/brief statement still reports an uncited standards pack. The `notApplicable` fold the viewer and CLI pills already read is unchanged when nothing tree-wide fails. Delta-only checks (`shapes:templateMarkers`, `shapes:pathsResolve`, `stories:closedSets`, `stories:ownerRefResolves`) are unaffected. AC-17403-9, AC-17404-7, AC-17405-9.
|
|
51
|
+
- **R6: `extractInterfacePathTokens` whitelists the usual bare repo names.** `shapes:pathsResolve` now checks a `path: Dockerfile` token against `resolvedPaths`. The whitelist is exported as `BARE_PATH_WHITELIST` from `src/query/gates.js` and names `Dockerfile`, `Containerfile`, `Makefile`, `LICENSE`, `Procfile`, `Justfile`, `Rakefile`, `Gemfile` and `CODEOWNERS`. The slash-or-extension rule stays for everything else. AC-17404-8.
|
|
52
|
+
- **R8: `applied` satisfies `skeleton:standardsCited` alongside `waived` and a citation.** A `standards:<pack>` concern-ledger entry with disposition `applied` or `waived` satisfies the check (an `applied` pack was adopted, which is at least as strong as a citation). The CHECK_QUESTION heading reads `Every registered standards pack is cited, applied or waived`; the failing `why` reads `uncited, unapplied and unwaived`. AC-17405-5 wording amended to match.
|
|
53
|
+
- **R1 extension on `rcf define create` (PR 292 follow-up).** `rcf define create <kind> --id <id>` is now idempotent by id for every kind (`req`, `us`, `tac`, `adr`, `fbs`, `ts`, the inline `ac` and `tc`, and `cn`) with the same identity rule the ledger add path uses. A same-id replay with identical content exits 0 and prints `<id>: unchanged (<filePath>)` (the inline kinds name the parent US or TS file); nothing is written. A same-id replay with different content refuses with exit 3 and names the differing field keys; nothing is written. The stdout line is the whole output: `rcf define create` has no `--json` flag, and `--quiet` suppresses the line. Idempotency is id-carried: without `--id`, a `tc` whose derived slug collides still refuses with exit 2. An unloadable (schema-invalid) doc still refuses unconditionally (the writer cannot compare content against a doc it failed to load). The compare ignores every writer- and classifier-owned field, listed once in the exported `REPLAY_IGNORE_FIELDS` (`createdAt`, `updatedAt`, and the `shapeClassification` block the REQ classifier stamps after `create req`), so a verbatim replay is a no-op. Lifecycle fields another verb has since changed (for example a TC `status` or an FBS `executionStatus`) are content and still count as differing. `src/core/store/writer.js` grows `deepEqual`, `diffDocFields`, `REPLAY_IGNORE_FIELDS` and one shared `resolveReplay` used by the document, inline and CN paths; `src/core/errors/index.js` adds the `conflict` ErrorKind; `src/cli/create.js` prints the unchanged line, maps `conflict` to exit 3, and its `--id` help now states the rule (the `rcf_create` MCP `id` description too). AC-17302-10 to AC-17302-13. See "For consumers (upgrading)" below.
|
|
54
|
+
- **R13: tree-wide-before-scope covers every tree-wide check (ADR-4138 extended).** The shared `treeWideFailureEnvelope` helper now governs D2 `skeleton:deployAdr`, D5 `crosscut:securityArchitecture` and D5 `crosscut:operationalConcerns` alongside the three the R2/R5/R7 rulings named. A narrowed D2 scope that excludes every REQ/PRD/TAD/brief statement still reports a missing (or doubled) Deploy ADR; a narrowed D5 scope that excludes every REQ still reports an empty `TAD.securityArchitecture` when any REQ has shape `auth` or `httpApi` tree-wide, and an empty `TAD.operationalConcerns` when any AC description carries `[deployed]` tree-wide. D4 and D6 are unchanged. The helper delegates its warn-with-ack fold to `foldState` so one implementation owns the ack-at-current-hash rule. AC-17402-8, AC-17401-11, AC-17401-12, AC-17404-9.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **R4: ADR-4131 prose matches the code.** `stagePolicy()` returning `warnWithAck` for D3/D5/D6 is canon; the pre-R4 wording in the ADR that said `stagePolicy keeps returning blocking for every stage` with a separate `ackable()` is retired. The viewer pill vocabulary and the CLI text report both read `stagePolicy` directly. Code is unchanged; ADR prose only.
|
|
59
|
+
- **R3: `rcf_define_draft_shapes` prompt wording.** `guidance/define-draft-shapes.md` says the engineer drafts shapes in parallel with the PO loop and reconciles when Intent completes. The wait-for-Intent-complete wording is removed. The independence invariant (no engineer edit changes the PO question set) is already pinned by AC-18603-3. Wording change only; the managed agent-instructions block hash did not move.
|
|
60
|
+
- **PR 292 follow-up: R2 test rewritten and D3 notApplicable reason corrected.** The ADR-4138 helper's failing envelope no longer carries the stage's `notApplicable` reason: `src/cli/readiness.js` reads `stage.reason` on every state, so a failing D3 printed as "failing (no TAC ... in scope)" which read as a notApplicable reason. The helper now delegates to `foldState` and returns `{ state: 'failing' | 'acknowledged' }` with no `reason` field. The D3 `notApplicable` reason also drops its stale "and no [draft] core entity in scope" trailer: R2 moved draftSettled tree-wide, so the local-scope applicability condition is just the two sets. The R2 test fixture was rewritten per the independent review -- the pre-R2 fixture used a `[draft]` core entity, which already made the pre-R2 D3 early-return applicable and the test vacuous; the rewrite puts the `[draft]` on a TAC OUTSIDE the narrowed scope, where pre-R2 code returned `notApplicable` and the post-R2 helper reports the finding. AC-17403-9 (rewritten), AC-17403-10.
|
|
61
|
+
- **PR 292 follow-up: ADR-4138 signature and over-labels corrected.** ADR-4138's `decision` and `consequences` paragraphs were amended to match the shipped helper signature (`treeWideFailureEnvelope(stage, gate, treeWideChecks, freezeCtx)`; the pre-amendment prose named `evaluateTreeWideChecks(stage, gate, ctx, scopeIsEmpty)`, and an intermediate draft still carried a `reason` parameter). The `decision` now also states the envelope truthfully: whatever `foldState` returns (`failing`, or `acknowledged` on a warn-with-ack stage acked at the current hash), with no `reason` field. The stale sentence that said `over` labels were unchanged under R2 is replaced: R2 moved `shapes:draftSettled`'s `over` from `delta` to `tree`. The stale `ackable()` JSDoc in `src/query/gates.js` is updated to match ruling R4 (`stagePolicy` returns `warnWithAck` for D3/D5/D6, not `blocking` for every stage).
|
|
62
|
+
- **PR 292 follow-up: treeWideFailureEnvelope delegates to foldState.** The helper's warn-with-ack ack-at-current-hash fold now goes through `foldState` (`src/query/gates.js`) rather than a copy. One implementation owns the acknowledgement rule and the failing envelope is a plain `foldState` return (no `reason` field). AC-17404-9.
|
|
63
|
+
- **FIND SearchButton hover and focus states in dark.** The pre-amendment `.rcf-search-btn:hover` fell back to a hard-coded light grey (`var(--sv-surface-hover, #f0f3f8)`) because `--sv-surface-hover` was never declared; in dark that painted dark ink on light grey and made the button unreadable. The hover + focus-visible + active states now use `--sv-active` with the `--sv-control` border and the `--sv-focus` outline so the pair stays above WCAG AA in both themes. The `.rcf-search-btn` baseline also drops the undeclared `--sv-text` reference in favour of `--sv-ink`.
|
|
64
|
+
- **AA contrast across three stylesheet rules the new audit found below the 4.5:1 floor.** `footer.app-footer .sep`, `.pm-mini-superseded` and `.pm-blueprint-cap-count` all declared `color: var(--sv-control)` on `var(--sv-raised)` (3.51:1 light, 4.21:1 dark). Swapped to `color: var(--sv-muted)` (5.60:1 light, 7.78:1 dark). No new tokens added.
|
|
65
|
+
- **LookupModal and Product Map controls the strengthened audit found (PR 291 landing review).** Once state rules are merged over their base and borders are audited, the same undeclared-token defect as the SearchButton showed up in the LookupModal: `.rcf-lookup-row:hover`, `.rcf-lookup-row.is-active` and `.rcf-lookup-reload:hover` fell back to `#f0f3f8` (1.01:1 in dark) and now use `--sv-active`; `.rcf-lookup-input` fell back to `#fbfcfe` (1.10:1 in dark) and now uses `--sv-surface`, with its field border on `--sv-control` (was `--sv-border`, 1.19:1); `.rcf-lookup-close:hover` and `.pm-group-btn[aria-selected="true"]` state borders move from `--sv-border` to `--sv-control`. `.rcf-theme-btn:active` gains an explicit `--sv-active` state. No new tokens added.
|
|
66
|
+
|
|
67
|
+
### For consumers (upgrading)
|
|
68
|
+
|
|
69
|
+
- **R1 exit 3 on same-id-different-content is a behaviour change for harnesses that re-run turns.** Pre-R1, `rcf define ledger brief add --text "x"` would always mint a new entry; a replayed turn minted a duplicate. Post-R1 the harness carries `--id <n>` back on replay so a replay is a no-op; a harness that re-runs the EXACT prior invocation without `--id` still mints a duplicate as before (that path is unchanged). A harness that writes a different value at a known id is a NEW failure mode (exit 3, not exit 1 and not a silent overwrite). CI that depends on `brief add` always exiting 0 should either carry `--id` or treat exit 3 as a hard refusal. `update` remains the verb to patch content at an existing id.
|
|
70
|
+
- **R1 extension: `rcf define create --id <existing>` exit 3 on different content (PR 292 follow-up).** Pre-follow-up, `rcf define create <kind> --id <id>` always refused with exit 2 when the id existed (`id ... is already taken` for document kinds and `cn`, `id ... already exists on US-...` for `ac`, `slug collision on ...` for `tc`). Post-follow-up, on every kind, a same-id replay with identical content exits 0 and prints `<id>: unchanged (<filePath>)`; a same-id replay with different content refuses with exit 3 and names the differing field keys. Only harnesses that pass `--id` on `rcf define create` can hit this; the usual `rcf define create <kind> --parent <id>` (no `--id`, writer mints the next id or derives the TC slug) behaves exactly as before, including the exit 2 on a derived TC slug collision. The `conflict` ErrorKind is new on `src/core/errors/index.js`.
|
|
71
|
+
- The managed agent-instructions block hash did not move under R3 (`pnpm run build:managed` wrote nothing).
|
|
72
|
+
|
|
73
|
+
### For embedders
|
|
74
|
+
|
|
75
|
+
Under `embed=1` nothing changes and no new route is added. `renderPage({ embed: true })` omits the theme-control markup from the server-rendered HTML entirely; the production server does not pass `embed`, and `page-init.js` strips the `[data-rcf-theme-control]` element from the DOM on boot when `?embed=1` is on the URL. The `rcf-view:v1:theme` key is never read or written under embed. The `rcf-view-theme` postMessage contract is unchanged.
|
|
9
76
|
|
|
10
77
|
## [0.30.0] - 2026-10-03
|
|
11
78
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"id": "AC-12113-2",
|
|
24
|
-
"description": "Given the operator answers run-worker-first with true alongside a non-empty assets-directory, when the manifest is generated, then the [assets] block carries run_worker_first = true. Given the operator answers run-worker-first with false or leaves it unanswered, when the manifest is generated, then the [assets] block does NOT carry a run_worker_first key (Cloudflare's runtime treats
|
|
24
|
+
"description": "Given the operator answers run-worker-first with true alongside a non-empty assets-directory, when the manifest is generated, then the [assets] block carries run_worker_first = true. Given the operator answers run-worker-first with false or leaves it unanswered, when the manifest is generated, then the [assets] block does NOT carry a run_worker_first key (Cloudflare's runtime treats a missing run_worker_first as the default asset-lookup-first shape). Anchored to TAC-1302; probed by assets-manifest-scan.mjs with anchorAcId AC-12113-1 (the same module surfaces both facets of the shape).",
|
|
25
25
|
"given": "an applied fixture whose operator answered assets-directory = ./dist and run-worker-first = true",
|
|
26
26
|
"when": "the probe module parses the fixture wrangler.toml",
|
|
27
27
|
"then": "the parsed [assets] table carries run_worker_first equal to true; when the fixture is switched with SIMULATE_EMPTY_ASSETS=true the run_worker_first field is absent (the whole [assets] block is absent) and the probe still returns verdict pass because the bare-Worker shape is legal",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"registerCanary": [
|
|
3
3
|
{
|
|
4
|
-
"id": "rc-2026-10-
|
|
5
|
-
"createdAt": "2026-10-
|
|
6
|
-
"buildVersion": "0.
|
|
4
|
+
"id": "rc-2026-10-06-001",
|
|
5
|
+
"createdAt": "2026-10-06T16:00:03.482Z",
|
|
6
|
+
"buildVersion": "0.32.0-mockdriver",
|
|
7
7
|
"fixturePromptId": "canary-prompt-01",
|
|
8
8
|
"responseWordCount": 55,
|
|
9
9
|
"grades": {
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
"id": "rc-2026-10-
|
|
38
|
-
"createdAt": "2026-10-
|
|
39
|
-
"buildVersion": "0.
|
|
37
|
+
"id": "rc-2026-10-06-002",
|
|
38
|
+
"createdAt": "2026-10-06T16:00:03.483Z",
|
|
39
|
+
"buildVersion": "0.32.0-mockdriver",
|
|
40
40
|
"fixturePromptId": "canary-prompt-02",
|
|
41
41
|
"responseWordCount": 55,
|
|
42
42
|
"grades": {
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
|
-
"id": "rc-2026-10-
|
|
71
|
-
"createdAt": "2026-10-
|
|
72
|
-
"buildVersion": "0.
|
|
70
|
+
"id": "rc-2026-10-06-003",
|
|
71
|
+
"createdAt": "2026-10-06T16:00:03.483Z",
|
|
72
|
+
"buildVersion": "0.32.0-mockdriver",
|
|
73
73
|
"fixturePromptId": "canary-prompt-03",
|
|
74
74
|
"responseWordCount": 55,
|
|
75
75
|
"grades": {
|
|
@@ -2,19 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
This document is the content of the `rcf_define_draft_shapes` MCP
|
|
4
4
|
prompt served argument-free by the server. The agent reads it once
|
|
5
|
-
per session and runs the draft pass
|
|
6
|
-
owner
|
|
7
|
-
entities, components and interfaces so the engineer
|
|
8
|
-
whose shape is honest about what is still owed.
|
|
5
|
+
per session and runs the draft pass alongside the PO loop. The
|
|
6
|
+
product owner may or may not be in the room; the agent populates
|
|
7
|
+
`[draft]`-marked entities, components and interfaces so the engineer
|
|
8
|
+
walks into a tree whose shape is honest about what is still owed.
|
|
9
9
|
|
|
10
10
|
## When to run
|
|
11
11
|
|
|
12
|
-
Run this pass
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
Run this pass in parallel with the product-owner intent loop and
|
|
13
|
+
reconcile when Intent completes (ruling R3 2026-10-05 under
|
|
14
|
+
w-2026-10-05-dave-001). Nothing in the draft pass changes the PO
|
|
15
|
+
question set, so drafting shapes while the owner answers questions
|
|
16
|
+
cannot destabilise the loop or create a question that was not there.
|
|
17
|
+
The draft pass reads only the brief ledger, the decisions ledger and
|
|
18
|
+
the current tree; it writes only `[draft]`-marked content.
|
|
19
|
+
`shapes:draftSettled` then lists exactly what the engineer has yet to
|
|
20
|
+
own. When Intent completes (`rcf define readiness --level intent`
|
|
21
|
+
prints `levels.intentComplete.ok === true`), the drafts are already
|
|
22
|
+
in place and the engineer walks into them.
|
|
18
23
|
|
|
19
24
|
Nothing in the draft pass can fail a product-owner check. That is the
|
|
20
25
|
property that keeps L1 and the draft pass independent. If an edit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rcf-lite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-10-03T10:50:00Z",
|
|
3
|
-
"updatedAt": "2026-10-
|
|
3
|
+
"updatedAt": "2026-10-05T12:00:00Z",
|
|
4
4
|
"adrId": "ADR-4131-gates-bite-ack-recorded-override",
|
|
5
5
|
"prdId": "PRD-001",
|
|
6
6
|
"tadId": "TAD-001",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"status": "accepted",
|
|
9
9
|
"title": "The gates bite with --ack as the recorded override for D3, D5, D6",
|
|
10
10
|
"context": "DEFINE step 3 train (spec 2026-10-01) brings the substantive checks of proposal 3.2 into D3, D5 and D6 across PRs 5, 6 and 7. The 0.29.0 posture left those stages warn-with-ack: --check exits 0 with a stderr [warn] line, --ack as the recorded override. The spec section 4 gates-bite intro and decision 9 both pin the biting posture: --check exits 4 on D3, D5 and D6 as it does on blocking stages; --ack stays as the recorded override for those three because the ruleset's recordedInChain channel needs it (decision 8's pattern applied to gates, not retired with the verb). The compute remains pure; readiness.js collects candidate paths and the CLI resolves them on disk so the gate functions see a resolvedPaths map in the stage context. PR 5 lands the three D3 bite checks (shapes:templateMarkers, shapes:entityJoin, shapes:pathsResolve), the ackable(stage) companion to stagePolicy, and the --check shapes exit 4 policy; PRs 6 and 7 bite D5 and D6 on the same shape.",
|
|
11
|
-
"decision": "The gates bite with --ack as the recorded override for D3, D5 and D6. stagePolicy
|
|
12
|
-
"consequences": "A failing D3 bite now blocks CI unless the engineer either fixes the tree or writes a recorded override with --ack --reason at the current hash; the record stays visible in the freeze record (the chain of recordedInChain the ruleset reads).
|
|
11
|
+
"decision": "The gates bite with --ack as the recorded override for D3, D5 and D6. stagePolicy returns warnWithAck for D3, D5 and D6 and blocking for D1, D2, D4, D7 and D8 (ruling R4 2026-10-05: the honest label for an ackable stage is warnWithAck, and the viewer pill vocabulary and the CLI text report both key off that; the pre-R4 wording that said stagePolicy returned blocking for every stage with a separate ackable() is retired because it was never true in code, and ackable(stage) stays only as the sibling helper that the CLI --check and freeze verbs read to decide whether a failing stage honours an acknowledgement at the current hash). The readiness CLI --check <stage> policy follows that split: on a blocking stage, a failing state exits 4 (today's behaviour); on a warnWithAck stage, a failing state exits 4 unless the freeze record acknowledges that gate at the current tree hash, in which case it exits 0. --ack on any blocking stage stays a usage refusal with exit 2 (D1, D2, D4, D7, D8). The three D3 bite checks ship as pure engineer checks on src/query/gates.js: shapes:templateMarkers over delta (per-kind marker presence on interfaces[].description, ids <tacId>:<name>, why names the missing marker, drafts included), shapes:entityJoin over tree (every coreEntities[].name named by exactly one recordShape, ids TAD.entity:<name>; ruling R5 2026-10-05: the check evaluates before any scope early-return, via the shared helper ADR-4138 lands), shapes:pathsResolve over delta (any path:-like token resolves on disk against the resolvedPaths map in the stage context or the description carries authoredAt: D3; ruling R6 2026-10-05: the token scanner also honours a short whitelist of known bare repo names -- Dockerfile, Makefile, LICENSE, Procfile, Containerfile, Justfile, Rakefile, Gemfile, CODEOWNERS -- so the slash-or-extension rule stops silently skipping them). Validate findings only where a legacy tree can comply; the dogfood tree records any residual failure in KNOWN GAPS rather than weakening the check.",
|
|
12
|
+
"consequences": "A failing D3 bite now blocks CI unless the engineer either fixes the tree or writes a recorded override with --ack --reason at the current hash; the record stays visible in the freeze record (the chain of recordedInChain the ruleset reads). stagePolicy returns warnWithAck for D3/D5/D6 and blocking for D1/D2/D4/D7/D8 (ruling R4): the viewer pill vocabulary reads that directly, and the CLI --check text output names the policy label alongside the stage state. The readyToBuild fold stays unchanged because the fold reads the stage state (passed | failing | acknowledged | notApplicable), not the stage policy. The gate functions stay pure: the CLI is the module that resolves candidate paths on disk and the stage context carries the resolved map. The D3 bite surfaces three new engineer checks on the readiness tab and the text report; nothing on the productOwner side moves. --level intent and --level build exits are unchanged because the levels fold is already a derived view. Ruling R5 (shapes:entityJoin unconditional) and ruling R7 (skeleton:standardsCited tree-wide regardless of D2 scope) land through ADR-4138's shared helper, so a narrowed D2 or D3 scope that excludes every item in the local scope still reports the tree-wide finding instead of returning notApplicable. The dogfood tree keeps CI green by scoping its own readiness call as it already does; the three new checks pass on the dogfood chain because the shipped TACs name their markers truthfully.",
|
|
13
13
|
"alternativesConsidered": [
|
|
14
14
|
{
|
|
15
15
|
"name": "Retire --ack (make D3, D5, D6 plain blocking)",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-10-04T14:00:00Z",
|
|
3
|
+
"updatedAt": "2026-10-05T12:30:00Z",
|
|
4
|
+
"adrId": "ADR-4137-standalone-theme-persistence",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"tadId": "TAD-001",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"status": "accepted",
|
|
9
|
+
"title": "The in-viewer theme control persists its setting under `rcf-view:v1:theme` in standalone mode only; under `embed=1` the control is absent and the key is never read or written",
|
|
10
|
+
"context": "The viewer UI refresh design (`2026-10-02_viewer-ui-refresh-design.md`) section 9 and section 11 recommendation 6 called for a three-state Light|Dark|Auto theme control hidden under `embed=1` that stores nothing. PR 1 (0.29.0) shipped the `?theme=` query param and the `rcf-view-theme` postMessage listener but omitted the visible control. Baz's 2026-10-04 review (`w-2026-10-04-dave-001`) found three gaps: (a) no visible control in the standalone viewer, (b) the FIND SearchButton hover and focus states are unreadable in dark, (c) no systematic WCAG AA pass across the stylesheet. On (a) the design decision 'storing nothing' was written for the embedded case: the viewer shares `localStorage` with the wespa SPA under `rcf-view:v1:`, so any key the viewer writes there is a key wespa sees too. In the standalone case there is no shared SPA; a theme control that resets to the system default on every reload is a worse product than the design intended.",
|
|
11
|
+
"decision": "In non-embed mode the three-state theme control writes the user's choice to `localStorage['rcf-view:v1:theme']` (one of `light`, `dark`, `auto`) and reads it back on boot. Under `embed=1` the control is absent from the DOM entirely (not merely hidden by CSS) and the key is never read or written on load, on a `rcf-view-theme` postMessage, or on any user action inside the viewer. Boot precedence: an explicit `?theme=` query parameter wins first; then (in non-embed mode only) the stored `rcf-view:v1:theme` value, provided it is one of `light`/`dark`/`auto`; then the default (`auto`, which maps to the `prefers-color-scheme` media query via `:root:not([data-theme])`). A live `rcf-view-theme` postMessage still applies in both modes and never writes to the key. The key sits under the shared `rcf-view:v1:` namespace so the PR 9 localStorage namespace lint stays green.",
|
|
12
|
+
"consequences": "Standalone viewer users (the Readiness / Build / Review audience Baz is coaching Remi through) get a theme control that remembers their choice between reloads, matching every other dev-facing viewer. Wespa (and any other embedder) see no behavioural change: `embed=1` is still a pure-render contract, the viewer still writes nothing on their behalf and reads nothing of theirs. The one deviation from the design doc's 'storing nothing' line is named here and in the CHANGELOG so a future reader can trace it. The embed check is at the storage boundary, not at the render boundary alone, so a future refactor cannot accidentally start writing the key under embed by re-rendering the control: the control's event handler reads `document.documentElement.getAttribute('data-embed') === '1'` and refuses the write in that branch. The PR 9 lint continues to protect the shared namespace. Known transient under embed: the production server renders one page for every client and does not see the request query, so the theme control is briefly painted under `embed=1` until `page-init.js` strips it on boot; the follow-up is for the server to pass `embed` from the request query into `renderPage` so the markup is never emitted (not implemented here). The boot theme is resolved and applied once; a `rcfPage.init()` re-run after an SSE swap never overrides a host postMessage or a control choice (PR 291 landing review F1). Verification: test/view/theme-control.test.js (TC-229-theme-control-renders-non-embed, TC-229-theme-control-persists, TC-229-theme-control-absent-embed, TC-229-theme-control-query-wins, and the F1 regressions TC-229-reinit-keeps-host-theme and TC-229-reinit-keeps-control-choice).",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Never persist anywhere (literal design doc reading)",
|
|
16
|
+
"summary": "Keep the theme control stateless in both modes: every reload resets to the default (`auto`), the control only remembers the current session.",
|
|
17
|
+
"reasonNotChosen": "A worse product in standalone mode and the only reason to refuse the write (shared localStorage with wespa) does not exist outside the embedded case. The design doc's 'storing nothing' line was written against the embed hazard; in standalone there is nothing to share with."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Persist in both modes",
|
|
21
|
+
"summary": "Write `rcf-view:v1:theme` under `embed=1` too; the shared-localStorage hazard is accepted and wespa can inspect the key if it wants.",
|
|
22
|
+
"reasonNotChosen": "Breaks Dex's wespa contract: wespa pins the embed-mode behaviour by exact version, and a new key under the shared namespace is a surface change that needs a release-note bump and a wespa-side change. The storage boundary is cheap to guard and keeps the embedded case byte-identical to pre-amendment."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Persist under a non-shared key",
|
|
26
|
+
"summary": "Write the theme to `sessionStorage` or a key outside the `rcf-view:v1:` namespace in standalone mode.",
|
|
27
|
+
"reasonNotChosen": "`sessionStorage` does not survive a browser restart (the standalone user still re-picks on every new window), and a key outside `rcf-view:v1:` would break the PR 9 localStorage namespace lint. The embed-boundary branch above is the smaller change that keeps both constraints green."
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-10-05T12:00:00Z",
|
|
3
|
+
"updatedAt": "2026-10-05T19:00:00Z",
|
|
4
|
+
"adrId": "ADR-4138-tree-wide-checks-before-scope-shortcircuit",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"tadId": "TAD-001",
|
|
7
|
+
"version": "0.1.2",
|
|
8
|
+
"status": "accepted",
|
|
9
|
+
"title": "Tree-wide checks evaluate before any scope short-circuit",
|
|
10
|
+
"context": "DEFINE step 3 rulings R2 (draftSettled tree-wide), R5 (entityJoin unconditional) and R7 (standardsCited tree-wide) landed on the same shape: a stage whose scope narrows to nothing (D3 with no TAC and no shaped REQ, D2 with no resolving brief statement and no REQ/PRD/TAD) returns a `notApplicable` envelope before any check runs, which silently hides tree-wide findings. The hidden findings are real: a drafted TAD core entity is unjoined to any recordShape (R5), a registered standards pack is uncited (R7), or a `[draft]` marker sits on a TAC outside the narrowed scope (R2) even when the delta touched nothing in the stage's local scope. The pattern is the ruling, not the three instances: the review of rcf-lite PR 292 (d-2026-10-05-012, ruling R13 confirmed by Baz 2026-10-05) found three further tree-wide checks still hidden by the same short-circuit shape -- D5 `crosscut:securityArchitecture`, D5 `crosscut:operationalConcerns` and D2 `skeleton:deployAdr` -- all of which read tree-wide inputs (`tree.requirements`, `tree.userStories`, `tree.adrs`) and all of which disappear when the stage's own `reqInScope` / resolving-statement set is empty. D4 (`stories:closedSets`, `stories:ownerRefResolves` under their own `notApplicable` arm) and D6 (every check is tree-wide anyway, and the probe-ledger scan sits outside the scope early-return already) are unchanged. The fix is one shared helper the gates module applies at every stage with a tree-wide check.",
|
|
11
|
+
"decision": "A stage with a tree-wide check evaluates those checks before any scope-driven `notApplicable` early-return. `src/query/gates.js` has one shared module-internal helper `treeWideFailureEnvelope(stage, gate, treeWideChecks, freezeCtx)` that takes the already-computed per-check results as `[{ name, check }]` entries and returns either `null` (every tree-wide check ok -- the stage proceeds to its own scope early-return or full checks list) or the envelope `foldState(stage, gate, checks, freezeCtx)` returns over a `checks[]` carrying the `stage:<D>:scope` placeholder plus every tree-wide check: `{ stage, gate, state: 'failing' | 'acknowledged', checks }` with no `reason` field. The helper delegates the fold to `foldState` on every stage, so a single implementation owns the acknowledgement rule and the two cannot drift: a warn-with-ack stage (D3, D5, D6) acknowledged at the current tree hash folds to `acknowledged`, and a blocking stage (D2) always folds to `failing`. The stage function computes each tree-wide check once (so the viewer and the text report render the same check object), calls the helper first when the local scope is empty, and otherwise runs its full checks list with the tree-wide check(s) interleaved in the same slot in `checks[]`. The helper governs every tree-wide check: `shapes:draftSettled` (R2), `shapes:entityJoin` (R5), `skeleton:standardsCited` (R7), and the three R13 checks `crosscut:securityArchitecture` (D5), `crosscut:operationalConcerns` (D5), `skeleton:deployAdr` (D2). It does NOT govern delta-only checks: `shapes:templateMarkers`, `shapes:pathsResolve`, `stories:closedSets`, `stories:ownerRefResolves` and the D4 floors keep running against the stage scope and continue to short-circuit on an empty scope. R2 2026-10-05 flipped `shapes:draftSettled`'s `over` label from `delta` to `tree` to match the ruling; the other six tree-wide checks already carried `over: tree`. The CHECK_PERSONA and CHECK_QUESTION fields stay fixed to their spec section 4 values.",
|
|
12
|
+
"consequences": "A narrowed D2 scope that excludes every REQ/PRD/TAD/brief statement still reports an uncited standards pack through `skeleton:standardsCited` and a missing (or doubled) Deploy ADR through `skeleton:deployAdr`. A narrowed D3 scope that excludes every TAC and shaped REQ still reports a `[draft]` anywhere in the tree through `shapes:draftSettled` and an unjoined entity through `shapes:entityJoin`. A narrowed D5 scope that excludes every REQ still reports an empty `TAD.securityArchitecture` while an auth or httpApi REQ exists through `crosscut:securityArchitecture`, and an empty `TAD.operationalConcerns` while a deployed-scope AC exists through `crosscut:operationalConcerns`. The readiness compute stays pure (the helper is a function of the context, same shape as the stage functions). The `notApplicable` fold the viewer and CLI pills already read is unchanged in effect when nothing tree-wide fails. A failing envelope the helper returns carries no `reason`: the fold already lists the failing check names and the pre-R13 `reason` text (copied from the stage's `notApplicable` message) was misleading in the printed `readiness` line ('failing (no TAC ... in scope)'). The `over: tree` label flipped on `shapes:draftSettled` (R2); every other tree-wide check already carried it. Tests pin the ordering per newly governed check: a scope that excludes every item in the stage's local scope AND a tree-wide failure returns a `failing` envelope, not a `notApplicable` one.",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Keep the scope early-return and route tree-wide findings through D8 only",
|
|
16
|
+
"summary": "Let narrowed D2/D3/D5 scopes return `notApplicable` and surface the tree-wide failures only when D8 runs validate clean.",
|
|
17
|
+
"reasonNotChosen": "D8 is the freeze gate; a narrowed scope at D2/D3/D5 is used to triage work in flight, not to freeze. Making the engineer wait until the freeze pass to see an unjoined entity, an uncited standards pack, an empty securityArchitecture or a missing Deploy ADR hides the finding during the loop that should surface it. Spec section 4 names every tree-wide check with `over: tree`; the pre-R2/R5/R7/R13 ordering contradicted that."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Lift every tree-wide check to a new top-level pass before the stage dispatch",
|
|
21
|
+
"summary": "Add a `runTreeWideChecks(ctx)` compute at the readiness level that runs before every stage function and prepends its findings to the first failing stage.",
|
|
22
|
+
"reasonNotChosen": "Changes the readiness result shape and the viewer / CLI consumers that key off the per-stage `checks[]` array. The shared helper keeps every tree-wide check in its canonical stage so `stages[D2].checks[].name === 'skeleton:standardsCited'` still holds."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Duplicate the tree-wide check into D8",
|
|
26
|
+
"summary": "Mirror `shapes:entityJoin` and `skeleton:standardsCited` into D8's checks so a narrowed D2/D3 scope's miss still gets caught at freeze.",
|
|
27
|
+
"reasonNotChosen": "A failing D2 at freeze fails the freeze gate anyway (D8 `freeze:priorGates`). The duplication adds a second ownership for the same check and the viewer would need to pick which name wins. The helper preserves one owning stage per tree-wide check."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "Limit the helper to the three checks R2/R5/R7 named and leave securityArchitecture / operationalConcerns / deployAdr behind the scope",
|
|
31
|
+
"summary": "Govern only the three checks the DEFINE step 3 spec section 18 named at R2/R5/R7 ship time and leave the three further checks the PR 292 review surfaced where they were.",
|
|
32
|
+
"reasonNotChosen": "The principle, not the inventory, is the ruling. Baz 2026-10-05 ratified every open ruling as recommended and confirmed R13 on the same cycle. Keeping half the tree-wide checks hidden by the scope would ship a half-fix; a narrowed D5 scope that excludes an auth REQ would still miss an empty securityArchitecture even though the whole stage computes it from the whole tree."
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
"implementsAcIds": [
|
|
7
7
|
"AC-202-1",
|
|
8
8
|
"AC-202-2",
|
|
9
|
-
"AC-202-3"
|
|
9
|
+
"AC-202-3",
|
|
10
|
+
"AC-202-5",
|
|
11
|
+
"AC-202-6"
|
|
10
12
|
],
|
|
11
13
|
"dependencies": [],
|
|
12
14
|
"version": "0.1.0",
|
|
13
15
|
"status": "draft",
|
|
14
16
|
"createdAt": "2026-07-10T11:15:32.956Z",
|
|
15
|
-
"updatedAt": "2026-
|
|
17
|
+
"updatedAt": "2026-10-06T10:24:19.149Z"
|
|
16
18
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-771",
|
|
3
|
+
"path": "src/view/style.css",
|
|
4
|
+
"title": "SearchButton hover/focus/active + three-state theme control styling (AA contrast in both themes)",
|
|
5
|
+
"description": "Baz 2026-10-04 (w-2026-10-04-dave-001): the FIND SearchButton hover previously fell back to a hard-coded light grey (`var(--sv-surface-hover, #f0f3f8)`) because `--sv-surface-hover` was never declared, which painted dark ink on a light grey in dark mode. The hover + focus-visible + active states now use `--sv-active` with the `--sv-control` border and the `--sv-focus` outline so the pair stays above WCAG AA in both themes (AC-205-9). The three-state theme control (`.rcf-theme`, `.rcf-theme-btn`) sits right of the SearchButton in `.tools`; `aria-pressed=\"true\"` uses `--sv-button` / `--sv-onbutton`, hover and focus-visible use `--sv-active`, and the group is hidden under `html[data-embed=\"1\"]`.",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-202-4",
|
|
8
|
+
"AC-205-9"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": [],
|
|
11
|
+
"version": "0.1.0",
|
|
12
|
+
"status": "draft",
|
|
13
|
+
"createdAt": "2026-10-05T09:59:33Z",
|
|
14
|
+
"updatedAt": "2026-10-06T10:24:19.675Z"
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-772",
|
|
3
|
+
"path": "src/view/view-index.js",
|
|
4
|
+
"title": "renderThemeControl() shell markup for the standalone three-state theme toggle",
|
|
5
|
+
"description": "Baz 2026-10-04 (w-2026-10-04-dave-001): renders the Light|Dark|Auto group (`role=\"group\"`, three `<button>`s with `aria-pressed` and `data-rcf-theme=\"light|dark|auto\"`) stamped with `[data-rcf-theme-control]`. The buttons start with `aria-pressed=\"false\"`; `page-init.js` boots them from the resolved theme choice (query -> stored -> auto) per ADR-4137. Keyboard-operable (`<button type=\"button\">`), screen-reader-labelled (`aria-label` + `title`), server-rendered. Also keeps the existing `renderSearchButton()` and `renderLookupModal()` exports.",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-206-7"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"version": "0.1.0",
|
|
11
|
+
"status": "draft",
|
|
12
|
+
"createdAt": "2026-10-05T09:59:33Z",
|
|
13
|
+
"updatedAt": "2026-10-05T09:59:33Z"
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-773",
|
|
3
|
+
"path": "src/view/html-page.js",
|
|
4
|
+
"title": "renderPage({ embed }) wires renderThemeControl() into the shell header .tools group; embed path strips the control",
|
|
5
|
+
"description": "Baz 2026-10-04 (w-2026-10-04-dave-001): `renderPage(model, { embed })` renders the three-state theme control from `renderThemeControl()` immediately after the SearchButton inside `<div class=\"tools\">`. When `embed` is true the markup is skipped entirely (ADR-4137 server-side layer) so no `[data-rcf-theme-control]` element is emitted; the production server path never passes `embed`, and the client-side layer in `page-init.js` strips the element on boot when `?embed=1` is present.",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-206-9"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": [
|
|
10
|
+
"CN-772"
|
|
11
|
+
],
|
|
12
|
+
"version": "0.1.0",
|
|
13
|
+
"status": "draft",
|
|
14
|
+
"createdAt": "2026-10-05T09:59:33Z",
|
|
15
|
+
"updatedAt": "2026-10-05T09:59:33Z"
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-774",
|
|
3
|
+
"path": "src/view/page-init.js",
|
|
4
|
+
"title": "wireThemeControl + resolveBootTheme + THEME_STORAGE_KEY: boot precedence, non-embed persistence, embed strip",
|
|
5
|
+
"description": "Baz 2026-10-04 (w-2026-10-04-dave-001), ADR-4137: `wireThemeControl()` wires the three Light|Dark|Auto buttons and, under embed, removes the `[data-rcf-theme-control]` element from the DOM entirely instead of merely hiding it. `resolveBootTheme(query)` picks the effective theme in the pinned precedence (`?theme=` wins; then stored value under `localStorage['rcf-view:v1:theme']` in non-embed mode; then `auto`). `readStoredTheme()` and `writeStoredTheme(v)` check `isEmbedActive()` at the storage boundary and refuse the read/write under embed so the key is zero-touched. The `message` listener still applies live `rcf-view-theme` postMessages in both modes and never writes the stored key. `reflectThemeChoice(mode)` keeps `aria-pressed` in sync across live swaps.",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-206-7",
|
|
8
|
+
"AC-206-8",
|
|
9
|
+
"AC-206-9",
|
|
10
|
+
"AC-206-10"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": [
|
|
13
|
+
"CN-772",
|
|
14
|
+
"CN-773"
|
|
15
|
+
],
|
|
16
|
+
"version": "0.1.0",
|
|
17
|
+
"status": "draft",
|
|
18
|
+
"createdAt": "2026-10-05T09:59:33Z",
|
|
19
|
+
"updatedAt": "2026-10-05T09:59:33Z"
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-775",
|
|
3
|
+
"path": "test/view/helpers/contrast.js",
|
|
4
|
+
"title": "WCAG 2.1 AA contrast audit helpers (token resolver, pair enumerator, luminance math)",
|
|
5
|
+
"description": "Baz 2026-10-04 (w-2026-10-04-dave-001): the audit helper for US-207. `buildTokenMap(css, theme)` resolves the `--sv-*` custom-property blocks (light `:root` and dark `:root[data-theme=\"dark\"]`), chasing `var(--x)` aliases. `extractRules(css)` is a brace-balanced walker that handles `@media (prefers-color-scheme: dark)` scope. `resolveColour(value, tokens)` reduces a CSS colour expression to `{r,g,b,a}` and returns null for gradients/images. `composite()` composites a semi-transparent fg over a bg. `luminance()` + `contrastRatio()` implement the WCAG 2.1 relative-luminance ratio. `stateOf(selector)` tags state variants (default, hover, focus, focus-visible, active, aria-pressed, aria-selected, is-active, disabled). `thresholdFor(selector, decls)` picks 4.5:1 for text, 3:1 for large text or non-text UI boundaries. `enumeratePairs(css)` returns per-theme `PairRecord[]` with `{selector,state,theme,fg,bg,ratio,threshold,kind,pass,approximate,note}` so the test's failure line names selector, state, theme, fg, bg, ratio and threshold.",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-207-1",
|
|
8
|
+
"AC-207-2"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": [],
|
|
11
|
+
"version": "0.1.0",
|
|
12
|
+
"status": "draft",
|
|
13
|
+
"createdAt": "2026-10-05T09:59:33Z",
|
|
14
|
+
"updatedAt": "2026-10-05T09:59:33Z"
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-776",
|
|
3
|
+
"path": "test/view/contrast-aa.test.js",
|
|
4
|
+
"title": "WCAG 2.1 AA contrast audit test; exits non-zero on any failing pair with selector/state/theme/fg/bg/ratio/threshold named",
|
|
5
|
+
"description": "Baz 2026-10-04 (w-2026-10-04-dave-001): the test entry point for US-207 (AC-207-1, AC-207-2). Reads `src/view/style.css`, calls `enumeratePairs(css)` from the helper, asserts zero failing pairs across both themes, and when `RCF_WRITE_CONTRAST_REPORT=1` is set writes a BEFORE/AFTER Markdown table to `./output/contrast-report.md` for the lane evidence. On any failure the message names each pair's selector, state, theme, foreground, background, computed ratio and applied threshold (AC-207-2). A second test asserts the pair-record shape so a future refactor cannot drop the structured fields the failure message reads from.",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-207-1",
|
|
8
|
+
"AC-207-2"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": [
|
|
11
|
+
"CN-775"
|
|
12
|
+
],
|
|
13
|
+
"version": "0.1.0",
|
|
14
|
+
"status": "draft",
|
|
15
|
+
"createdAt": "2026-10-05T09:59:33Z",
|
|
16
|
+
"updatedAt": "2026-10-05T09:59:33Z"
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-777",
|
|
3
|
+
"path": "test/view/theme-control.test.js",
|
|
4
|
+
"title": "Standalone theme control behavioural tests (AC-206-7..10 under ADR-4137)",
|
|
5
|
+
"description": "Baz 2026-10-04 (w-2026-10-04-dave-001): nine node tests pinning the ADR-4137 contract. `renderPage()` emits the control in `.tools` (AC-206-7). `renderPage({ embed: true })` does not emit any `[data-rcf-theme-control]` markup (AC-206-9 server-side layer). A VM-sandboxed boot of `src/view/page-init.js` with a minimal `document`/`window`/`localStorage` shim exercises: click on a theme button updates `data-theme` live and flips `aria-pressed` (AC-206-7); non-embed boot writes `rcf-view:v1:theme` and a stored value re-applies on the next boot (AC-206-8); a `rcf-view-theme` postMessage applies live but writes nothing (AC-206-8); under `?embed=1` the control element is removed from the DOM and the storage key is zero-touched on load, on postMessage and on any user action (AC-206-9); explicit `?theme=` beats the stored value and leaves the stored key as-is (AC-206-10).",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-206-7",
|
|
8
|
+
"AC-206-8",
|
|
9
|
+
"AC-206-9",
|
|
10
|
+
"AC-206-10"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": [
|
|
13
|
+
"CN-772",
|
|
14
|
+
"CN-773",
|
|
15
|
+
"CN-774"
|
|
16
|
+
],
|
|
17
|
+
"version": "0.1.0",
|
|
18
|
+
"status": "draft",
|
|
19
|
+
"createdAt": "2026-10-05T09:59:33Z",
|
|
20
|
+
"updatedAt": "2026-10-05T09:59:33Z"
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-778",
|
|
3
|
+
"path": "src/view/live-client.js#resyncTabsAfterSwap",
|
|
4
|
+
"title": "SSE tree-update tab resync derives the tab list from the rendered nav.tabs (SSOT)",
|
|
5
|
+
"description": "After the SSE tree-update swaps `#rcf-live-content` innerHTML, the newly-inserted panels carry the Phase 3.6 server-rendered default (one visible, the rest hidden). resyncTabsAfterSwap reads every [role=\"tab\"][data-tab] under the surviving `nav.tabs`, finds the aria-selected button, and aligns `#tab-<name>` visibility so exactly the active tab is visible. The tab list is derived from the DOM rather than a parallel array in this file: before issue 297 the parallel array omitted 'readiness' and a swap while Readiness was active fell back to Overview with both panels visible. The overview fallback is kept only for a genuinely unknown or missing selection. Pairs with CN-744 and CN-745 (which this file also hosts) and with page-init.js's activateTab for the non-swap routing path (AC-18001-8).",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-18001-8"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"version": "0.1.0",
|
|
11
|
+
"status": "draft",
|
|
12
|
+
"createdAt": "2026-10-06T12:00:00Z",
|
|
13
|
+
"updatedAt": "2026-10-06T12:00:00Z"
|
|
14
|
+
}
|
package/rcf/fbs/fbs-004.json
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"AC-202-1",
|
|
10
10
|
"AC-202-2",
|
|
11
11
|
"AC-202-3",
|
|
12
|
+
"AC-202-4",
|
|
13
|
+
"AC-202-5",
|
|
14
|
+
"AC-202-6",
|
|
12
15
|
"AC-203-1",
|
|
13
16
|
"AC-203-2"
|
|
14
17
|
],
|
|
@@ -30,7 +33,7 @@
|
|
|
30
33
|
"domain": "view",
|
|
31
34
|
"notes": "The page is generated fresh each run; no cached prior state lingers between regenerations.",
|
|
32
35
|
"createdAt": "2026-06-25T00:00:00Z",
|
|
33
|
-
"updatedAt": "2026-
|
|
36
|
+
"updatedAt": "2026-10-06T10:24:20.204Z",
|
|
34
37
|
"buildOrder": 4,
|
|
35
38
|
"executionStatus": "complete",
|
|
36
39
|
"dependsOnFbsIds": [
|
package/rcf/fbs/fbs-201.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-10-01T18:00:00Z",
|
|
3
|
-
"updatedAt": "2026-10-
|
|
3
|
+
"updatedAt": "2026-10-06T12:00:00Z",
|
|
4
4
|
"acIds": [
|
|
5
5
|
"AC-18001-1",
|
|
6
6
|
"AC-18001-2",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"AC-18001-5",
|
|
10
10
|
"AC-18001-6",
|
|
11
11
|
"AC-18001-7",
|
|
12
|
+
"AC-18001-8",
|
|
12
13
|
"AC-18101-1",
|
|
13
14
|
"AC-18101-2",
|
|
14
15
|
"AC-18101-3"
|
package/rcf/fbs/fbs-202.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-10-02T16:40:00Z",
|
|
3
|
-
"updatedAt": "2026-10-
|
|
3
|
+
"updatedAt": "2026-10-05T19:00:00Z",
|
|
4
4
|
"acIds": [
|
|
5
5
|
"AC-17302-1",
|
|
6
6
|
"AC-17302-2",
|
|
@@ -86,7 +86,23 @@
|
|
|
86
86
|
"AC-17405-7",
|
|
87
87
|
"AC-17405-8",
|
|
88
88
|
"AC-17702-6",
|
|
89
|
-
"AC-17902-5"
|
|
89
|
+
"AC-17902-5",
|
|
90
|
+
"AC-17302-7",
|
|
91
|
+
"AC-17302-8",
|
|
92
|
+
"AC-17302-9",
|
|
93
|
+
"AC-17403-9",
|
|
94
|
+
"AC-17404-7",
|
|
95
|
+
"AC-17404-8",
|
|
96
|
+
"AC-17405-9",
|
|
97
|
+
"AC-17302-10",
|
|
98
|
+
"AC-17401-11",
|
|
99
|
+
"AC-17401-12",
|
|
100
|
+
"AC-17402-8",
|
|
101
|
+
"AC-17403-10",
|
|
102
|
+
"AC-17404-9",
|
|
103
|
+
"AC-17302-11",
|
|
104
|
+
"AC-17302-12",
|
|
105
|
+
"AC-17302-13"
|
|
90
106
|
],
|
|
91
107
|
"fbsId": "FBS-202",
|
|
92
108
|
"prdId": "PRD-001",
|