rcf-lite 0.31.0 → 0.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/blueprints/delivery-ci-workflows/CHANGELOG.md +5 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6102.json +5 -1
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6103.json +5 -1
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6108.json +5 -1
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6111.json +10 -2
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6121.json +5 -1
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6122.json +5 -1
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6123.json +5 -1
- package/blueprints/deploy-cloudflare-workers/CHANGELOG.md +5 -0
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12103.json +5 -1
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12110.json +5 -1
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12113.json +1 -1
- package/blueprints/email-smtp-resend/CHANGELOG.md +5 -0
- package/blueprints/email-smtp-resend/contributions/user-stories/email-smtp-resend-us-4104.json +5 -1
- package/blueprints/email-smtp-resend/contributions/user-stories/email-smtp-resend-us-4105.json +5 -1
- package/blueprints/email-smtp-resend/contributions/user-stories/email-smtp-resend-us-4106.json +5 -1
- package/blueprints/observability-probe-endpoints/CHANGELOG.md +5 -0
- package/blueprints/observability-probe-endpoints/contributions/user-stories/observability-probe-endpoints-us-14104.json +5 -1
- package/fixtures/canary-manifest.json +9 -9
- package/package.json +1 -1
- package/rcf/code-nodes/cn-017.json +4 -2
- package/rcf/code-nodes/cn-771.json +2 -1
- 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/tacs/tac-4127-readiness-view.json +3 -2
- package/rcf/test-suites/ts-004.json +82 -2
- package/rcf/test-suites/ts-223.json +24 -2
- package/rcf/user-stories/us-18001.json +6 -1
- package/rcf/user-stories/us-202.json +25 -1
- package/releases/releases.yaml +18 -1
- package/src/cli/freeze.js +8 -0
- package/src/cli/readiness.js +2 -38
- package/src/define/interface-paths.js +47 -0
- package/src/query/gates.js +57 -14
- package/src/view/html-page.js +29 -11
- package/src/view/index.js +16 -2
- package/src/view/live-client.js +18 -10
- package/src/view/style.css +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,37 @@ 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.1] - 2026-10-06
|
|
10
|
+
|
|
11
|
+
0.32.1 ships two fixes merged since 0.32.0 (merge b6292f5) on top of the layout baseline chore. The viewer's Readiness tab now agrees with the CLI on D3 shapes:pathsResolve, and the D5 crosscut:operationalConcerns trigger reads the structured ac.scope field in addition to the legacy "[deployed]" marker. breaking: false; no behavioural changes on verbs consumers run in CI; fixes only. minAgentAction: null; the managed agent-instructions block hash did not move.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Viewer Readiness tab D3 shapes:pathsResolve now agrees with the CLI (issue 307, w-2026-10-06-dave-026).** The viewer's render path (`src/view/index.js` `renderModelToPage`) called `computeReadiness` without the `resolvedPaths` pre-walk, so D3 shapes:pathsResolve false-failed on TAC interface `path:` tokens that resolve on disk and the Readiness tab disagreed with the CLI and freeze. Hit on ai-baseline define/kickoff: CLI reported D3 4/4 passing while the viewer tab showed D3 shapes:pathsResolve failing on four TAC interface paths. Fix: thread `resolvedPaths` through the viewer compute via the same `resolveInterfacePaths` seam the CLI (PR 304, issue 302) and freeze use. The viewer compute now runs the resolve walk against `projectRoot` before `computeReadiness` and passes the resulting `resolvedPaths` set to the compute. The pure gate reads it unchanged. Additive: `renderModelToPage` also returns the computed readiness so a regression test can assert the viewer's check state without parsing HTML. Regression test in `test/view/readiness-paths-resolve-307.test.js` seeds a TAC interface whose `path:` token points at a real file and asserts the viewer compute reports D3 ok; the negative control flips the path to a missing file and asserts D3 fails with the shapes:pathsResolve finding.
|
|
16
|
+
|
|
17
|
+
- **D5 crosscut:operationalConcerns trigger reads structured `ac.scope` (issue 306, w-2026-10-06-dave-031).** `computeOperationalConcernsCheck` in `src/query/gates.js` looked only for a literal `[deployed]` marker in AC description text, so trees that carry scope as the schema-canonical field `scope: "deployed"` (like ai-baseline, with 23 deployed-scope ACs and `TAD.operationalConcerns` absent) silently passed 0/0 and the gate never fired. Fix: the trigger now also reads `ac.scope === "deployed"`; a deployed-scope AC on either form must find a non-empty `TAD.operationalConcerns`. Legacy `[deployed]` markers are unchanged. Regression fixtures in `test/query/gates.test.js` cover the ai-baseline shape (structured `ac.scope` with empty `operationalConcerns` fails) and a negative control (non-deployed-scope ACs with empty `operationalConcerns` pass).
|
|
18
|
+
|
|
19
|
+
## [0.32.0] - 2026-10-06
|
|
20
|
+
|
|
21
|
+
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.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **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.
|
|
27
|
+
|
|
28
|
+
- **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.
|
|
29
|
+
|
|
30
|
+
- **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.
|
|
31
|
+
|
|
32
|
+
- **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.
|
|
33
|
+
- **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.
|
|
34
|
+
- **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.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **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.
|
|
39
|
+
|
|
9
40
|
## [0.31.0] - 2026-10-05
|
|
10
41
|
|
|
11
42
|
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).
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- adds ownerRef on AC-6102-1 (interfaces[entry-point CLI]), AC-6103-1 (interfaces[gate-descriptor]), AC-6108-1 (interfaces[aggregate-report-schema]), AC-6111-1 (interfaces[entry-point CLI]), AC-6111-2 (interfaces[workflow-shape]), AC-6121-2 (interfaces[release-mode]), AC-6122-1 (interfaces[entry-point CLI]) and AC-6123-1 (interfaces[entry-point CLI]) so TAC-701 gate-descriptor and entry-point CLI, TAC-703 aggregate-report-schema, TAC-704 workflow-shape and entry-point CLI, TAC-705 release-mode and entry-point CLI, and TAC-706 entry-point CLI have delivering AC owners (D6 consistency:orphanInterfaces).
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
## 2.3.5 - 2026-09-11
|
|
4
9
|
|
|
5
10
|
probe-utils.writeReport no longer promotes an all-accountBoundSkipped row set to PASS via a separate override; aggregate() already returns PASS for a pure-skip set on its own and returns WARN when any WARN row is present, so an all-skipped array carrying a WARN row is no longer silently promoted. Anatomy suite tracks the strengthened shared helper.
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "the aggregate report's `runner.entryPoint` field records a project-relative path to a `.js` or `.mjs` file that exists at that path in the tree",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "fixed"
|
|
20
|
+
"disposition": "fixed",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-701-delivery-ci-workflows-gate-runner",
|
|
23
|
+
"field": "interfaces[entry-point CLI]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-6102-2",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "the file exists, its `gate` field is `validate`, its `command` field names the `rcf define validate` invocation, its `exitCode` field records the underlying process exit code, and its `stdout` and `stderr` fields carry the captured streams",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-701-delivery-ci-workflows-gate-runner",
|
|
23
|
+
"field": "interfaces[gate-descriptor]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-6103-2",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "the aggregate report's `gates[]` array contains a `coverage-strict` entry with `outcome: missing`, its `reason` field carries a non-empty string, and the aggregate `verdict` is `failed`",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-703-delivery-ci-workflows-aggregate-report",
|
|
23
|
+
"field": "interfaces[aggregate-report-schema]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-6108-2",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "the materialiser reads the file at the documented path and treats its `workflowShape` as authoritative",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-704-delivery-ci-workflows-workflow-materialiser",
|
|
23
|
+
"field": "interfaces[entry-point CLI]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-6111-2",
|
|
@@ -27,7 +31,11 @@
|
|
|
27
31
|
"then": "the materialiser processes the recognised fields (including the substitution-surface trio) and ignores the unknown fields without failing the boot-check",
|
|
28
32
|
"testable": true,
|
|
29
33
|
"scope": "runtime",
|
|
30
|
-
"disposition": "template"
|
|
34
|
+
"disposition": "template",
|
|
35
|
+
"ownerRef": {
|
|
36
|
+
"tacId": "TAC-704-delivery-ci-workflows-workflow-materialiser",
|
|
37
|
+
"field": "interfaces[workflow-shape]"
|
|
38
|
+
}
|
|
31
39
|
},
|
|
32
40
|
{
|
|
33
41
|
"id": "AC-6111-3",
|
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
"then": "the release workflow creates the release entity, runs the artefact-publish step, uploads the published artefact to the release entity, and records the just-published version identifier in the aggregate report",
|
|
28
28
|
"testable": true,
|
|
29
29
|
"scope": "runtime",
|
|
30
|
-
"disposition": "template"
|
|
30
|
+
"disposition": "template",
|
|
31
|
+
"ownerRef": {
|
|
32
|
+
"tacId": "TAC-705-delivery-ci-workflows-release-workflow",
|
|
33
|
+
"field": "interfaces[release-mode]"
|
|
34
|
+
}
|
|
31
35
|
},
|
|
32
36
|
{
|
|
33
37
|
"id": "AC-6121-3",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "the release workflow dispatches the promote workflow with `versionId` set to the just-published version identifier and waits on the promote workflow's outcome",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-705-delivery-ci-workflows-release-workflow",
|
|
23
|
+
"field": "interfaces[entry-point CLI]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-6122-2",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "a `scheduled-audit` workflow file lands at the provider's workflow path, its trigger set names a cron schedule, and its command line invokes the Node gate-runner entry point with the required check set writing to the distinct report path",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-706-delivery-ci-workflows-scheduled-audit",
|
|
23
|
+
"field": "interfaces[entry-point CLI]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-6123-2",
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- adds ownerRef on AC-12103-1 (interfaces[workerIdentity]) and AC-12110-1 (interfaces[probeUrl]) so the preview URL resolver's workerIdentity and probeUrl interfaces have delivering AC owners (D6 consistency:orphanInterfaces).
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
## 1.3.2 (register patch, 2026-09-10)
|
|
4
9
|
|
|
5
10
|
- Register: neutral wording on the ADR-1306 consequences field (closes F-1); no capability change.
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "both responses carry the `versionSha` field of V's build provenance in the health-probe body, and the two responses agree on that field",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "deployed",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-1303-deploy-cloudflare-workers-preview-url-resolver",
|
|
23
|
+
"field": "interfaces[workerIdentity]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-12103-2",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "at least one HTTP request to the production URL's `/healthz` completes within the sixty-second window; the request carries no `Authorization` header that would bypass a production auth layer; the retry count for any single probe does not exceed six",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "deployed",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-1303-deploy-cloudflare-workers-preview-url-resolver",
|
|
23
|
+
"field": "interfaces[probeUrl]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-12110-2",
|
|
@@ -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,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- adds ownerRef on AC-4106-1 (interfaces[smtpClient]), AC-4104-2 (interfaces[auditSink]) and AC-4105-1 (interfaces[verifierClock]) so TAC-401 smtpClient and TAC-402 auditSink and verifierClock have delivering AC owners (D6 consistency:orphanInterfaces).
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
## 1.1.7 - 2026-09-11
|
|
4
9
|
|
|
5
10
|
unverified-sender-refusal AC-4102-1 and AC-4102-2 rows are de-claimed to conformanceOnly. The refusal is driven by the local catch-all SMTP fixture (fixture-as-engine) and carries no Resend-returned message id, so the identifier half of the strict-evidence contract is not satisfied here. The live real-account-resend-send row (AC-4101-2) is unchanged.
|
package/blueprints/email-smtp-resend/contributions/user-stories/email-smtp-resend-us-4104.json
CHANGED
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
"then": "the response status is 401, the response body is empty, the handler invocation counter is unchanged, and the audit sink observes exactly one `webhookVerificationFailed` event",
|
|
28
28
|
"testable": true,
|
|
29
29
|
"scope": "runtime",
|
|
30
|
-
"disposition": "fixed"
|
|
30
|
+
"disposition": "fixed",
|
|
31
|
+
"ownerRef": {
|
|
32
|
+
"tacId": "TAC-402-email-smtp-resend-webhook-verifier",
|
|
33
|
+
"field": "interfaces[auditSink]"
|
|
34
|
+
}
|
|
31
35
|
},
|
|
32
36
|
{
|
|
33
37
|
"id": "AC-4104-3",
|
package/blueprints/email-smtp-resend/contributions/user-stories/email-smtp-resend-us-4105.json
CHANGED
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "the response status is 401, the response body is empty, the handler invocation counter is unchanged, and the audit sink observes exactly one `webhookReplayStale` event",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-402-email-smtp-resend-webhook-verifier",
|
|
23
|
+
"field": "interfaces[verifierClock]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-4105-2",
|
package/blueprints/email-smtp-resend/contributions/user-stories/email-smtp-resend-us-4106.json
CHANGED
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "the scan returns exactly one module and that module is the adapter module",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "fixed"
|
|
20
|
+
"disposition": "fixed",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-401-email-smtp-resend-send-adapter",
|
|
23
|
+
"field": "interfaces[smtpClient]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-4106-2",
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- adds ownerRef on AC-14104-1 (interfaces[listenerFactory]) so the listener-topology's listenerFactory interface has a delivering AC owner (D6 consistency:orphanInterfaces).
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
## 1.2.5 - 2026-09-11
|
|
4
9
|
|
|
5
10
|
Anatomy suite tracks the strengthened shared helper (WARN-only shortcut removed, log objects and content lists no longer identifiers, retained anchor existence check). No probe changes required.
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"then": "a GET against `/live` on port 9091 returns 200, a GET against `/live` on the user-traffic port returns 404 (or the project's default not-found response), and a GET against `/ready` on the user-traffic port returns 404",
|
|
18
18
|
"testable": true,
|
|
19
19
|
"scope": "runtime",
|
|
20
|
-
"disposition": "template"
|
|
20
|
+
"disposition": "template",
|
|
21
|
+
"ownerRef": {
|
|
22
|
+
"tacId": "TAC-1503-observability-probe-endpoints-listener-topology",
|
|
23
|
+
"field": "interfaces[listenerFactory]"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
27
|
"id": "AC-14104-2",
|
|
@@ -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-06T18:28:43.472Z",
|
|
6
|
+
"buildVersion": "0.32.1-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-06T18:28:43.473Z",
|
|
39
|
+
"buildVersion": "0.32.1-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-06T18:28:43.473Z",
|
|
72
|
+
"buildVersion": "0.32.1-mockdriver",
|
|
73
73
|
"fixturePromptId": "canary-prompt-03",
|
|
74
74
|
"responseWordCount": 55,
|
|
75
75
|
"grades": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rcf-lite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.1",
|
|
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",
|
|
@@ -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
|
}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
"title": "SearchButton hover/focus/active + three-state theme control styling (AA contrast in both themes)",
|
|
5
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
6
|
"implementsAcIds": [
|
|
7
|
+
"AC-202-4",
|
|
7
8
|
"AC-205-9"
|
|
8
9
|
],
|
|
9
10
|
"dependencies": [],
|
|
10
11
|
"version": "0.1.0",
|
|
11
12
|
"status": "draft",
|
|
12
13
|
"createdAt": "2026-10-05T09:59:33Z",
|
|
13
|
-
"updatedAt": "2026-10-
|
|
14
|
+
"updatedAt": "2026-10-06T10:24:19.675Z"
|
|
14
15
|
}
|
|
@@ -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"
|
|
@@ -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
|
"tacId": "TAC-4127-readiness-view",
|
|
5
5
|
"prdId": "PRD-001",
|
|
6
6
|
"tadId": "TAD-001",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"src/view/components/findings-list.js: findingsList({ heading, count, items }) returns `<div class=\"rcf-findings-list\"><h4>{heading} <span class=\"rcf-findings-list__count\">{count}</span></h4><ul>...<li><a href={anchor}>{id}</a>: {why}</li>...</ul></div>` with ids capped at 20 and a `... N more suppressed` line beyond.",
|
|
16
16
|
"src/view/components/diff.js: diff(before, after) returns a two-column rendering of two document bodies for the delta list; criterion-level before-and-after is deferred per ADR-4127.",
|
|
17
17
|
"src/view/html-page.js: Readiness button and tabpanel are the first entries; the former Overview button and panel are relabelled PRD; the data-tab=overview and id=tab-overview identifiers are preserved so existing hash anchors still resolve; the inline-script TABS array gains 'readiness' as the first entry (default active tab) and keeps the existing four entries in order.",
|
|
18
|
-
"src/view/index.js: renderModelToPage loads the freeze record, ledgers, test pointers and profile text once, invokes computeReadiness via the pure src/query/ seam, and threads the result into the model so renderPage and renderContent carry one computed object; the SSE tree-update broadcast rewalks and recomputes on every change under rcf/."
|
|
18
|
+
"src/view/index.js: renderModelToPage loads the freeze record, ledgers, test pointers and profile text once, invokes computeReadiness via the pure src/query/ seam, and threads the result into the model so renderPage and renderContent carry one computed object; the SSE tree-update broadcast rewalks and recomputes on every change under rcf/.",
|
|
19
|
+
"src/view/live-client.js: resyncTabsAfterSwap derives the list of tabs from the server-rendered nav.tabs (every [role=tab][data-tab] under it) rather than carrying a parallel array. The SSE swap re-emits every panel with the Phase 3.6 hidden-by-default layout; the resync reads the aria-selected button from the surviving header and makes exactly that panel visible for every tab the server rendered, Readiness included (AC-18001-8). The overview fallback stays for the genuinely-unknown-or-missing selection case."
|
|
19
20
|
],
|
|
20
21
|
"internalStructure": "readiness.js is a sequence of eleven small block builders calling the three shared components; each block consumes exactly one top-level field of the readiness result so a wire-level JSON diff ties to a visible block. The register ordering is picked once at the top via pickRegister (same helper the CLI uses) and threaded down. Hash anchors are built with the same `#<docId>` convention the existing tabs use so blocker-id links open the right tab and expand the right <details> through the existing resolveHash routine.",
|
|
21
22
|
"interfaces": [
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-07-29T12:57:18.038Z",
|
|
3
|
-
"updatedAt": "2026-
|
|
3
|
+
"updatedAt": "2026-10-06T10:12:36.226Z",
|
|
4
4
|
"purpose": "Verify documents render as readable curated fields with working inter-document links. AC-202-1 is pending: its static-files-no-server clause predates the Phase 3.8 server-only view (see test-suites/PENDING.md).",
|
|
5
5
|
"testLevel": "unit",
|
|
6
6
|
"acIds": [
|
|
7
7
|
"AC-202-2",
|
|
8
|
-
"AC-202-3"
|
|
8
|
+
"AC-202-3",
|
|
9
|
+
"AC-202-4",
|
|
10
|
+
"AC-202-5",
|
|
11
|
+
"AC-202-6"
|
|
9
12
|
],
|
|
10
13
|
"id": "TS-004",
|
|
11
14
|
"usId": "US-202",
|
|
@@ -38,6 +41,83 @@
|
|
|
38
41
|
"description": "the view serves from a local server process and never writes files to disk",
|
|
39
42
|
"status": "pending",
|
|
40
43
|
"testPointer": "test/view/cli.test.js::rcf audit view never writes any files to disk (regression against static mode)"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "TC-004-tabs-cannot-shrink",
|
|
47
|
+
"acId": "AC-202-4",
|
|
48
|
+
"description": "style.css nav.tabs has flex-shrink: 0 and no min-width: 0, so the tablist never clips the Build tab under a long project name",
|
|
49
|
+
"status": "passing",
|
|
50
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-4: nav.tabs cannot shrink below its intrinsic width"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "TC-004-tools-cannot-shrink",
|
|
54
|
+
"acId": "AC-202-4",
|
|
55
|
+
"description": "style.css .tools has flex-shrink: 0 so the Find button and theme control never shrink into the tablist",
|
|
56
|
+
"status": "passing",
|
|
57
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-4: .tools cannot shrink below its intrinsic width"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "TC-004-product-yields-first",
|
|
61
|
+
"acId": "AC-202-4",
|
|
62
|
+
"description": "style.css .product has min-width: 0 so it yields to the tablist and tools before clipping anything",
|
|
63
|
+
"status": "passing",
|
|
64
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-4: .product yields first via min-width:0"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "TC-004-name-min-width-zero",
|
|
68
|
+
"acId": "AC-202-4",
|
|
69
|
+
"description": "style.css .product .name has min-width: 0 so text-overflow: ellipsis triggers inside the flex .product",
|
|
70
|
+
"status": "passing",
|
|
71
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-4: .product .name has min-width:0 so the ellipsis triggers"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "TC-004-name-title-attribute",
|
|
75
|
+
"acId": "AC-202-5",
|
|
76
|
+
"description": "renderPage emits a title=\"<full name>\" attribute on .product .name so hover reveals the whole name under ellipsis",
|
|
77
|
+
"status": "passing",
|
|
78
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-5: renderPage emits title=\"<full name>\" on .product .name"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "TC-004-name-title-escaped",
|
|
82
|
+
"acId": "AC-202-5",
|
|
83
|
+
"description": "the title attribute shares the escapeHtml path with the inner text so a projectName of \"<script>\" renders as title=\"<script>\"",
|
|
84
|
+
"status": "passing",
|
|
85
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-5: the title attribute is HTML-escaped alongside the body"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "TC-004-name-title-absent-on-collapse",
|
|
89
|
+
"acId": "AC-202-5",
|
|
90
|
+
"description": "no title attribute is emitted when the product block collapses (no projectName)",
|
|
91
|
+
"status": "passing",
|
|
92
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-5: no title attribute when the product block collapses"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "TC-004-prd-productname-wins",
|
|
96
|
+
"acId": "AC-202-6",
|
|
97
|
+
"description": "resolveProductHeader prefers prd.productName over manifest.projectName when both are set",
|
|
98
|
+
"status": "passing",
|
|
99
|
+
"testPointer": "test/view/product-name-precedence.test.js::AC-202-6: prd.productName wins over manifest.projectName when both are set"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "TC-004-manifest-fallback",
|
|
103
|
+
"acId": "AC-202-6",
|
|
104
|
+
"description": "resolveProductHeader falls back to manifest.projectName when the PRD carries no productName",
|
|
105
|
+
"status": "passing",
|
|
106
|
+
"testPointer": "test/view/product-name-precedence.test.js::AC-202-6: manifest.projectName is the first fallback when PRD has no productName"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "TC-004-prd-producttitle-fallback",
|
|
110
|
+
"acId": "AC-202-6",
|
|
111
|
+
"description": "resolveProductHeader falls back to prd.productTitle when neither productName nor manifest is set",
|
|
112
|
+
"status": "passing",
|
|
113
|
+
"testPointer": "test/view/product-name-precedence.test.js::AC-202-6: prd.productTitle is the last fallback (brief-stage trees without productName or manifest)"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "TC-004-render-reflects-precedence",
|
|
117
|
+
"acId": "AC-202-6",
|
|
118
|
+
"description": "the renderPage DOM reflects the flipped precedence; the manifest value does not reach the header slice when prd.productName is set",
|
|
119
|
+
"status": "passing",
|
|
120
|
+
"testPointer": "test/view/product-name-precedence.test.js::AC-202-6: renderPage reflects the flipped precedence in the DOM"
|
|
41
121
|
}
|
|
42
122
|
],
|
|
43
123
|
"status": "draft"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-10-01T18:00:00Z",
|
|
3
|
-
"updatedAt": "2026-10-
|
|
4
|
-
"purpose": "Cover US-18001 (Readiness as tab 1 and the PRD rename) and US-18101 (shared pill, findings-list and diff components). Shares the readiness fixture builders with TS-222 so the tab and the CLI assertions ride the same shapes.",
|
|
3
|
+
"updatedAt": "2026-10-06T12:00:00Z",
|
|
4
|
+
"purpose": "Cover US-18001 (Readiness as tab 1 and the PRD rename, plus the live-client resync SSOT guard added for issue 297) and US-18101 (shared pill, findings-list and diff components). Shares the readiness fixture builders with TS-222 so the tab and the CLI assertions ride the same shapes.",
|
|
5
5
|
"testLevel": "unit",
|
|
6
6
|
"acIds": [
|
|
7
7
|
"AC-18001-1",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"AC-18001-5",
|
|
12
12
|
"AC-18001-6",
|
|
13
13
|
"AC-18001-7",
|
|
14
|
+
"AC-18001-8",
|
|
14
15
|
"AC-18101-1",
|
|
15
16
|
"AC-18101-2",
|
|
16
17
|
"AC-18101-3"
|
|
@@ -88,6 +89,27 @@
|
|
|
88
89
|
"description": "No existing Product Map or Requirements test expectation changes; only the Readiness-specific additions land.",
|
|
89
90
|
"status": "pending",
|
|
90
91
|
"testPointer": "test/view/readiness-tab.test.js::readiness tab: AC-18101-3 existing Product Map and Requirements tests retain their expectations"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "TC-223-swap-preserves-readiness",
|
|
95
|
+
"acId": "AC-18001-8",
|
|
96
|
+
"description": "resyncTabsAfterSwap keeps tab-readiness as the sole visible panel when Readiness is aria-selected and the swap left tab-overview visible (the Phase 3.6 server default). Pins issue 297: before the fix, the hard-coded TABS list in live-client.js omitted 'readiness', indexOf returned -1, selected fell back to overview, and both tab-readiness and tab-overview went visible.",
|
|
97
|
+
"status": "passing",
|
|
98
|
+
"testPointer": "test/view/live-client.test.js::AC-18001-8 resyncTabsAfterSwap keeps Readiness visible on the embed-no-hash default (issue 297)"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "TC-223-swap-ssot-every-tab",
|
|
102
|
+
"acId": "AC-18001-8",
|
|
103
|
+
"description": "resyncTabsAfterSwap honours every nav.tabs [role=tab][data-tab] the server rendered as the SSOT for the tab list. Iterating the six main tabs with each as aria-selected and the swap-default visible panel set to tab-overview, exactly the aria-selected tab's panel is visible after resync. Guards against future drift: a seventh tab added to the renderer survives a swap without an edit on live-client.",
|
|
104
|
+
"status": "passing",
|
|
105
|
+
"testPointer": "test/view/live-client.test.js::AC-18001-8 resyncTabsAfterSwap preserves every tab the DOM renders (SSOT invariant)"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "TC-223-swap-product-map-regression",
|
|
109
|
+
"acId": "AC-18001-8",
|
|
110
|
+
"description": "resyncTabsAfterSwap preserves product-map as the sole visible panel through a swap. Guards the one tab that the pre-issue-297 TABS list did include, so the SSOT rewrite does not regress the behaviour it previously had right.",
|
|
111
|
+
"status": "passing",
|
|
112
|
+
"testPointer": "test/view/live-client.test.js::AC-18001-8 resyncTabsAfterSwap preserves Product Map through a swap (regression guard)"
|
|
91
113
|
}
|
|
92
114
|
],
|
|
93
115
|
"status": "draft"
|
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
"id": "AC-18001-7",
|
|
45
45
|
"description": "[happy] Given the tab and the CLI --json output for the same tree hash, when compared, then every id rendered on the tab appears in the JSON for that hash.",
|
|
46
46
|
"testable": true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "AC-18001-8",
|
|
50
|
+
"description": "[must-not] Given any main tab (readiness, overview, requirements, architecture, build, product-map) is the active tab, when an SSE tree-update swaps #rcf-live-content innerHTML and resyncTabsAfterSwap runs, then exactly one tabpanel is visible and it is the active tab's panel. The live-client derives the tab list from the server-rendered nav.tabs rather than carrying a parallel array that can drift from page-init: every nav.tabs [role=tab][data-tab] present at swap time is honoured. Readiness is covered as issue 297's regression.",
|
|
51
|
+
"testable": true
|
|
47
52
|
}
|
|
48
53
|
],
|
|
49
54
|
"tacIds": [
|
|
@@ -51,5 +56,5 @@
|
|
|
51
56
|
],
|
|
52
57
|
"tags": [],
|
|
53
58
|
"createdAt": "2026-10-01T18:00:00Z",
|
|
54
|
-
"updatedAt": "2026-10-
|
|
59
|
+
"updatedAt": "2026-10-06T12:00:00Z"
|
|
55
60
|
}
|
|
@@ -33,8 +33,32 @@
|
|
|
33
33
|
"when": "a document links to another",
|
|
34
34
|
"then": "the link navigates to that document's section",
|
|
35
35
|
"testable": true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "AC-202-4",
|
|
39
|
+
"description": "[happy] The audit view header keeps every main tab visible and the .tools group right of them at any viewport the chrome supports: the tablist and .tools cannot shrink below their intrinsic width (flex-shrink: 0) and .product carries the shrink budget via min-width: 0 (both on the flex item and on .product .name) so the project name ellipsizes first.",
|
|
40
|
+
"given": "the viewer rendered in the standalone (non-embed) shell",
|
|
41
|
+
"when": "any viewport >= 721px the chrome supports (above the responsive @media that re-rows the tabs under 720px)",
|
|
42
|
+
"then": "the six main tabs are fully visible and the .tools group sits right of them; .product .name ellipsizes when the project name is longer than its slot and nothing in .tools overlaps a clipped tab",
|
|
43
|
+
"testable": true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "AC-202-5",
|
|
47
|
+
"description": "[happy] The .product .name span carries a title=\"<full name>\" attribute so hovering reveals the whole name when the ellipsis has clipped it; the title is HTML-escaped on the same path as the inner text, and when the product block collapses (no project name on the tree) no title attribute is emitted.",
|
|
48
|
+
"given": "a project tree with a projectName",
|
|
49
|
+
"when": "the viewer renders the header",
|
|
50
|
+
"then": "the .name span has title=\"<the full projectName>\" with the same HTML-escaping as the inner text",
|
|
51
|
+
"testable": true
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "AC-202-6",
|
|
55
|
+
"description": "[happy] The header display name prefers prd.productName, then manifest.projectName, then prd.productTitle. A rename via `rcf define update PRD-001 --set productName=...` therefore reaches the audit chrome without touching manifest.projectName (which the chain keeps for ids and paths).",
|
|
56
|
+
"given": "a tree where both manifest.projectName and prd.productName are set",
|
|
57
|
+
"when": "resolveProductHeader runs",
|
|
58
|
+
"then": "the header projectName is prd.productName; when the PRD carries no productName the fallback is manifest.projectName, then prd.productTitle",
|
|
59
|
+
"testable": true
|
|
36
60
|
}
|
|
37
61
|
],
|
|
38
62
|
"createdAt": "2026-06-25T00:00:00Z",
|
|
39
|
-
"updatedAt": "2026-
|
|
63
|
+
"updatedAt": "2026-10-06T10:12:07.288Z"
|
|
40
64
|
}
|
package/releases/releases.yaml
CHANGED
|
@@ -40,8 +40,25 @@
|
|
|
40
40
|
# `npm install rcf-lite`.
|
|
41
41
|
|
|
42
42
|
feedVersion: 1
|
|
43
|
-
latest: "0.
|
|
43
|
+
latest: "0.32.1"
|
|
44
44
|
releases:
|
|
45
|
+
- version: "0.32.1"
|
|
46
|
+
date: "2026-10-06"
|
|
47
|
+
breaking: false
|
|
48
|
+
headlines:
|
|
49
|
+
- "rcf-lite 0.32.1 fixes two readiness gates that false-paired under schema-canonical content on top of 0.32.0. The viewer's Readiness tab D3 shapes:pathsResolve now agrees with the CLI by pre-resolving TAC interface path tokens through the same resolveInterfacePaths seam (issue 307), and the D5 crosscut:operationalConcerns trigger reads the structured ac.scope field in addition to the legacy '[deployed]' marker so trees that carry scope as a schema field no longer silently pass 0/0 (issue 306)."
|
|
50
|
+
minAgentAction: null
|
|
51
|
+
notesUrl: "https://stravica.ai/docs/rcf/changelog/"
|
|
52
|
+
|
|
53
|
+
- version: "0.32.0"
|
|
54
|
+
date: "2026-10-06"
|
|
55
|
+
breaking: false
|
|
56
|
+
headlines:
|
|
57
|
+
- "rcf-lite 0.32.0 fixes two readiness and freeze gates that false-tripped under schema-canonical content: D6 consistency:orphanInterfaces now accepts the object-shape REQ.deliveredBy (issue 301), and 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)."
|
|
58
|
+
- "Viewer fixes: SSE tree-update resync preserves the active tab for every main tab including Readiness (issue 297), and the audit view header keeps the six tabs visible under long project names and prefers prd.productName (issue 295)."
|
|
59
|
+
minAgentAction: null
|
|
60
|
+
notesUrl: "https://stravica.ai/docs/rcf/changelog/"
|
|
61
|
+
|
|
45
62
|
- version: "0.31.0"
|
|
46
63
|
date: "2026-10-05"
|
|
47
64
|
breaking: true
|
package/src/cli/freeze.js
CHANGED
|
@@ -35,6 +35,7 @@ import { checkCodeNodeResolution } from '#core/store';
|
|
|
35
35
|
import { findProjectRoot } from '../view/index.js';
|
|
36
36
|
import { loadFreezeRecord, saveFreezeRecord } from '../define/freeze-record.js';
|
|
37
37
|
import { loadAllLedgers } from '../define/ledgers.js';
|
|
38
|
+
import { resolveInterfacePaths } from '../define/interface-paths.js';
|
|
38
39
|
import {
|
|
39
40
|
STAGE_ALIASES,
|
|
40
41
|
STAGE_GATES,
|
|
@@ -368,6 +369,12 @@ async function runFreeze({
|
|
|
368
369
|
// freezeableAfterAck check below is the real gate (freeze still
|
|
369
370
|
// exits 4 on an unfrozen tree); the wrap's refusal is informational
|
|
370
371
|
// here as it is on readiness.
|
|
372
|
+
// Issue 302 (2026-10-06): freeze now pre-resolves the TAC
|
|
373
|
+
// interface `path:` tokens the way readiness does, so the D3
|
|
374
|
+
// `shapes:pathsResolve` check inside freeze agrees with the
|
|
375
|
+
// readiness verdict instead of defaulting to an empty set and
|
|
376
|
+
// false-failing resolvable paths.
|
|
377
|
+
const resolvedPaths = await resolveInterfacePaths(projectRoot, tree);
|
|
371
378
|
/** @type {import('../query/readiness.js').ReadinessResult} */
|
|
372
379
|
const readiness = computeReadiness(tree, {
|
|
373
380
|
freeze: freezeForCompute,
|
|
@@ -375,6 +382,7 @@ async function runFreeze({
|
|
|
375
382
|
profileText,
|
|
376
383
|
testPointers,
|
|
377
384
|
validateErrors,
|
|
385
|
+
resolvedPaths,
|
|
378
386
|
});
|
|
379
387
|
const gated = await runWithAdmissibilityGate({
|
|
380
388
|
tree,
|
package/src/cli/readiness.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// every other query verb has.
|
|
17
17
|
|
|
18
18
|
import { parseArgs } from 'node:util';
|
|
19
|
-
import { readFile
|
|
19
|
+
import { readFile } from 'node:fs/promises';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
|
|
22
22
|
import { resolveTestPointers, walkTree } from '#core/store';
|
|
@@ -30,9 +30,9 @@ import {
|
|
|
30
30
|
STAGE_ORDER,
|
|
31
31
|
STAGE_SHORT_NAMES,
|
|
32
32
|
ackable,
|
|
33
|
-
extractInterfacePathTokens,
|
|
34
33
|
stagePolicy,
|
|
35
34
|
} from '../query/gates.js';
|
|
35
|
+
import { resolveInterfacePaths } from '../define/interface-paths.js';
|
|
36
36
|
import {
|
|
37
37
|
computeReadiness,
|
|
38
38
|
countLitmusReadersAtHash,
|
|
@@ -129,42 +129,6 @@ const STATE_LABEL = {
|
|
|
129
129
|
notApplicable: 'notApplicable',
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
/**
|
|
133
|
-
* ADR-4131 (0.30.0 PR 5): walk the tree's TAC interfaces, extract the
|
|
134
|
-
* `path:` tokens the engineer named, resolve each one against
|
|
135
|
-
* `projectRoot` on disk, and return the set of tokens that resolve.
|
|
136
|
-
* `shapes:pathsResolve` reads the set from the stage context and
|
|
137
|
-
* decides the D3 bite without I/O. The compute is pure; this helper is
|
|
138
|
-
* the one place I/O happens for the check.
|
|
139
|
-
*
|
|
140
|
-
* @param {string} projectRoot
|
|
141
|
-
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
142
|
-
* @returns {Promise<Set<string>>}
|
|
143
|
-
*/
|
|
144
|
-
async function resolveInterfacePaths(projectRoot, tree) {
|
|
145
|
-
const resolved = new Set();
|
|
146
|
-
/** @type {Set<string>} */
|
|
147
|
-
const candidates = new Set();
|
|
148
|
-
for (const tac of tree.tacs ?? []) {
|
|
149
|
-
for (const iface of tac.interfaces ?? []) {
|
|
150
|
-
const desc = typeof iface?.description === 'string' ? iface.description : '';
|
|
151
|
-
for (const token of extractInterfacePathTokens(desc)) {
|
|
152
|
-
candidates.add(token);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
await Promise.all([...candidates].map(async (token) => {
|
|
157
|
-
try {
|
|
158
|
-
await stat(join(projectRoot, token));
|
|
159
|
-
resolved.add(token);
|
|
160
|
-
} catch {
|
|
161
|
-
// Missing path stays out of the set; the gate decides whether
|
|
162
|
-
// `authoredAt: D3` lets it through.
|
|
163
|
-
}
|
|
164
|
-
}));
|
|
165
|
-
return resolved;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
132
|
/**
|
|
169
133
|
* Read `rcf/.identity/profile.md` when it exists; return null on
|
|
170
134
|
* ENOENT. Any other read error surfaces as null so the profile
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Shared define-time helper: resolve the `path:` tokens an engineer
|
|
2
|
+
// named on TAC interfaces against the project root on disk. The pure
|
|
3
|
+
// D3 `shapes:pathsResolve` check reads the resulting set from the
|
|
4
|
+
// stage context; this is the one place I/O happens for the check.
|
|
5
|
+
//
|
|
6
|
+
// Lifted out of `src/cli/readiness.js` in issue 302 (2026-10-06) so
|
|
7
|
+
// the freeze CLI can pre-resolve the same tokens and D3 agrees with
|
|
8
|
+
// readiness instead of false-failing from an empty fallback set.
|
|
9
|
+
// ADR-4131 (0.30.0 PR 5) still names readiness as the origin.
|
|
10
|
+
|
|
11
|
+
import { stat } from 'node:fs/promises';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
import { extractInterfacePathTokens } from '../query/gates.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Walk the tree's TAC interfaces, extract the `path:` tokens the
|
|
18
|
+
* engineer named, resolve each one against `projectRoot` on disk, and
|
|
19
|
+
* return the set of tokens that resolve.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} projectRoot
|
|
22
|
+
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
23
|
+
* @returns {Promise<Set<string>>}
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveInterfacePaths(projectRoot, tree) {
|
|
26
|
+
const resolved = new Set();
|
|
27
|
+
/** @type {Set<string>} */
|
|
28
|
+
const candidates = new Set();
|
|
29
|
+
for (const tac of tree.tacs ?? []) {
|
|
30
|
+
for (const iface of tac.interfaces ?? []) {
|
|
31
|
+
const desc = typeof iface?.description === 'string' ? iface.description : '';
|
|
32
|
+
for (const token of extractInterfacePathTokens(desc)) {
|
|
33
|
+
candidates.add(token);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
await Promise.all([...candidates].map(async (token) => {
|
|
38
|
+
try {
|
|
39
|
+
await stat(join(projectRoot, token));
|
|
40
|
+
resolved.add(token);
|
|
41
|
+
} catch {
|
|
42
|
+
// Missing path stays out of the set; the gate decides whether
|
|
43
|
+
// `authoredAt: D3` lets it through.
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
return resolved;
|
|
47
|
+
}
|
package/src/query/gates.js
CHANGED
|
@@ -1582,7 +1582,15 @@ function computeSecurityArchitectureCheck(ctx) {
|
|
|
1582
1582
|
function computeOperationalConcernsCheck(ctx) {
|
|
1583
1583
|
const tree = ctx.tree;
|
|
1584
1584
|
const tad = /** @type {any} */ (tree.tad ?? {});
|
|
1585
|
-
|
|
1585
|
+
// Issue 306 (2026-10-06): schemas carry AC scope as the structured
|
|
1586
|
+
// field `scope: "deployed"`; the legacy `[deployed]` description
|
|
1587
|
+
// marker (R13 2026-10-05) is kept so older trees still trigger. A
|
|
1588
|
+
// deployed-scope AC on either form must find a non-empty
|
|
1589
|
+
// TAD.operationalConcerns.
|
|
1590
|
+
const hasDeployedAc = (tree.userStories ?? []).some((us) => (us.acceptanceCriteria ?? []).some((ac) => {
|
|
1591
|
+
if (ac?.scope === 'deployed') return true;
|
|
1592
|
+
return typeof ac?.description === 'string' && ac.description.includes('[deployed]');
|
|
1593
|
+
}));
|
|
1586
1594
|
const opFail = [];
|
|
1587
1595
|
if (hasDeployedAc) {
|
|
1588
1596
|
const oc = tad.operationalConcerns;
|
|
@@ -2064,10 +2072,24 @@ function extractThenFieldTokens(thenClause) {
|
|
|
2064
2072
|
/**
|
|
2065
2073
|
* Collect every interface the tree "reaches" from an AC `ownerRef` or
|
|
2066
2074
|
* a REQ `deliveredBy` (REQ-174 companion field). The result is a Set
|
|
2067
|
-
* of `<tacId>:<name>` keys.
|
|
2068
|
-
*
|
|
2069
|
-
*
|
|
2070
|
-
*
|
|
2075
|
+
* of `<tacId>:<name>` keys.
|
|
2076
|
+
*
|
|
2077
|
+
* REQ `deliveredBy` takes two canonical shapes, both accepted here:
|
|
2078
|
+
* 1. The schema-canonical object form `{tacId, adrId?, field?}`
|
|
2079
|
+
* (`@stravica-ai/rcf-schemas` 0.6.3 `$defs.deliveredBy`). When
|
|
2080
|
+
* `tacId` is present:
|
|
2081
|
+
* - `field: interfaces[<name>]` reaches `{tacId}:{name}` (one
|
|
2082
|
+
* interface), via `parseOwnerRefInterfaceField`.
|
|
2083
|
+
* - any other `field`, or no `field`, reaches every interface
|
|
2084
|
+
* on the named TAC (the "delivered by this TAC" meaning).
|
|
2085
|
+
* An `adrId`-only deliveredBy does not reach any TAC interface.
|
|
2086
|
+
* 2. A legacy array of pointer strings: `TAC-nnnn:<name>` reaches
|
|
2087
|
+
* one interface, a bare `TAC-nnnn` reaches every interface on
|
|
2088
|
+
* that TAC.
|
|
2089
|
+
*
|
|
2090
|
+
* Issue 301 (2026-10-06): the array-only guard dropped every
|
|
2091
|
+
* schema-canonical object deliveredBy and false-positived every
|
|
2092
|
+
* blueprint TAC interface.
|
|
2071
2093
|
*
|
|
2072
2094
|
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
2073
2095
|
* @returns {Set<string>}
|
|
@@ -2085,19 +2107,40 @@ function collectReachedInterfaces(tree) {
|
|
|
2085
2107
|
out.add(`${ref.tacId}:${name}`);
|
|
2086
2108
|
}
|
|
2087
2109
|
}
|
|
2088
|
-
// REQ deliveredBy
|
|
2110
|
+
// REQ deliveredBy. Both shapes documented above.
|
|
2089
2111
|
for (const req of tree.requirements ?? []) {
|
|
2090
2112
|
const deliveredBy = /** @type {any} */ (req)?.deliveredBy;
|
|
2091
|
-
if (
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2113
|
+
if (deliveredBy == null) continue;
|
|
2114
|
+
if (Array.isArray(deliveredBy)) {
|
|
2115
|
+
for (const entry of deliveredBy) {
|
|
2116
|
+
if (typeof entry !== 'string' || entry.length === 0) continue;
|
|
2117
|
+
const m = entry.match(/^(TAC-[A-Za-z0-9-]+):(.+)$/);
|
|
2118
|
+
if (m) {
|
|
2119
|
+
out.add(`${m[1]}:${m[2]}`);
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
// Plain TAC id: every interface on that TAC is reached.
|
|
2123
|
+
const tac = (tree.tacs ?? []).find((t) => t?.tacId === entry);
|
|
2124
|
+
if (!tac) continue;
|
|
2125
|
+
for (const iface of tac.interfaces ?? []) {
|
|
2126
|
+
if (typeof iface?.name === 'string') out.add(`${tac.tacId}:${iface.name}`);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
if (typeof deliveredBy === 'object') {
|
|
2132
|
+
const tacId = /** @type {any} */ (deliveredBy).tacId;
|
|
2133
|
+
if (typeof tacId !== 'string' || tacId.length === 0) continue;
|
|
2134
|
+
const field = /** @type {any} */ (deliveredBy).field;
|
|
2135
|
+
const name = parseOwnerRefInterfaceField(field);
|
|
2136
|
+
if (name) {
|
|
2137
|
+
out.add(`${tacId}:${name}`);
|
|
2097
2138
|
continue;
|
|
2098
2139
|
}
|
|
2099
|
-
//
|
|
2100
|
-
|
|
2140
|
+
// No field, or a field that does not name an interface: reach
|
|
2141
|
+
// every interface on the named TAC (the "delivered by this TAC"
|
|
2142
|
+
// meaning carried over from the array-shape bare TAC pointer).
|
|
2143
|
+
const tac = (tree.tacs ?? []).find((t) => t?.tacId === tacId);
|
|
2101
2144
|
if (!tac) continue;
|
|
2102
2145
|
for (const iface of tac.interfaces ?? []) {
|
|
2103
2146
|
if (typeof iface?.name === 'string') out.add(`${tac.tacId}:${iface.name}`);
|
package/src/view/html-page.js
CHANGED
|
@@ -843,21 +843,33 @@ function buildSpecFilterText(fbs) {
|
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
/**
|
|
846
|
-
* Viewer UI refresh PR 2 (shell polish, Baz 2026-10-02; decision 17)
|
|
847
|
-
* the
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
846
|
+
* Viewer UI refresh PR 2 (shell polish, Baz 2026-10-02; decision 17)
|
|
847
|
+
* with the issue-295 flip (w-2026-10-06-dave-001): the one-bar header
|
|
848
|
+
* carries the project name only, sourced from `prd.productName` first
|
|
849
|
+
* (the human-readable name an owner edits via `rcf define update
|
|
850
|
+
* PRD-001 --set productName=...`), then `manifest.projectName` (the
|
|
851
|
+
* init-time value kept for ids / paths), then `prd.productTitle` (the
|
|
852
|
+
* PRD title the brief names). Decision 17 remains: project name
|
|
853
|
+
* prominent after the icon mark, tool name out of the bar, no
|
|
854
|
+
* technology chip. When the tree exposes no project name, the whole
|
|
855
|
+
* product block collapses and the brand icon stands on its own
|
|
856
|
+
* (silent empty state; CSS `.product:empty { display:none; }`).
|
|
857
|
+
*
|
|
858
|
+
* The precedence flip is a lite-native cut (US-202, Part 2 cut b):
|
|
859
|
+
* `rcf define update MANIFEST` is not addressable in the current CLI,
|
|
860
|
+
* so before this change manifest.projectName was a one-shot init value
|
|
861
|
+
* and the only way to rename the audit surface was a hand edit of
|
|
862
|
+
* manifest.json. prd.productName is already updatable via `rcf define
|
|
863
|
+
* update PRD-001 --set productName=...`; preferring it in the header
|
|
864
|
+
* means the display name is lite-native without touching the manifest
|
|
865
|
+
* (ids and paths are untouched).
|
|
854
866
|
*
|
|
855
867
|
* @param {import('./tree-model.js').BuiltTreeModel} model
|
|
856
868
|
* @returns {{ projectName: string | null }}
|
|
857
869
|
*/
|
|
858
870
|
export function resolveProductHeader(model) {
|
|
859
|
-
const projectName = model.
|
|
860
|
-
?? model.
|
|
871
|
+
const projectName = model.prd?.productName
|
|
872
|
+
?? model.manifest?.projectName
|
|
861
873
|
?? model.prd?.productTitle
|
|
862
874
|
?? null;
|
|
863
875
|
return { projectName };
|
|
@@ -870,7 +882,13 @@ function renderProductBlock(header) {
|
|
|
870
882
|
// empty state (brief) survives screen-reader output too.
|
|
871
883
|
return '<div class="product"></div>';
|
|
872
884
|
}
|
|
873
|
-
|
|
885
|
+
// w-2026-10-06-dave-001 (issue 295): .product .name clips its text
|
|
886
|
+
// with text-overflow:ellipsis so a long project name yields rather
|
|
887
|
+
// than pushing the tablist off the bar; the title attribute carries
|
|
888
|
+
// the full name so hover reveals it. Both the title and the body go
|
|
889
|
+
// through escapeHtml so the attribute is safe against injection.
|
|
890
|
+
const safe = escapeHtml(header.projectName);
|
|
891
|
+
return `<div class="product"><span class="name" title="${safe}">${safe}</span>
|
|
874
892
|
<span class="sub">review surface</span></div>`;
|
|
875
893
|
}
|
|
876
894
|
|
package/src/view/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { resolveTestPointers, walkTree } from '#core/store';
|
|
|
12
12
|
import { computeReadiness } from '../query/readiness.js';
|
|
13
13
|
import { computeQuestions } from '../query/questions.js';
|
|
14
14
|
import { loadFreezeRecord } from '../define/freeze-record.js';
|
|
15
|
+
import { resolveInterfacePaths } from '../define/interface-paths.js';
|
|
15
16
|
import { loadAllLedgers } from '../define/ledgers.js';
|
|
16
17
|
import { renderContent, renderPage } from './html-page.js';
|
|
17
18
|
import { renderProductMapGrouping } from './product-map.js';
|
|
@@ -77,11 +78,18 @@ export async function renderModelToPage({ projectRoot }) {
|
|
|
77
78
|
// Load the readiness inputs once per rewalk; the viewer is pure
|
|
78
79
|
// (never recomputes readiness). Failures load as `null` so the tab
|
|
79
80
|
// still renders when a tree is partial.
|
|
80
|
-
const [freezeRecord, ledgers, testPointers, profileText] = await Promise.all([
|
|
81
|
+
const [freezeRecord, ledgers, testPointers, profileText, resolvedPaths] = await Promise.all([
|
|
81
82
|
loadFreezeRecord({ projectRoot }).catch(() => null),
|
|
82
83
|
loadAllLedgers({ projectRoot }).catch(() => ({})),
|
|
83
84
|
resolveTestPointers({ projectRoot, tree }).catch(() => undefined),
|
|
84
85
|
readProfileText(projectRoot),
|
|
86
|
+
// Issue 307 (2026-10-06): run the same TAC-interface path
|
|
87
|
+
// resolver the CLI and freeze run so the viewer's D3
|
|
88
|
+
// `shapes:pathsResolve` check reads the resolved set from the
|
|
89
|
+
// same seam. Before this, the viewer called computeReadiness
|
|
90
|
+
// without `resolvedPaths`, D3 false-failed on paths that resolve
|
|
91
|
+
// on disk, and the Readiness tab disagreed with the CLI.
|
|
92
|
+
resolveInterfacePaths(projectRoot, tree).catch(() => undefined),
|
|
85
93
|
]);
|
|
86
94
|
|
|
87
95
|
let readiness = null;
|
|
@@ -93,6 +101,7 @@ export async function renderModelToPage({ projectRoot }) {
|
|
|
93
101
|
profileText,
|
|
94
102
|
testPointers,
|
|
95
103
|
validateErrors: errors,
|
|
104
|
+
resolvedPaths,
|
|
96
105
|
});
|
|
97
106
|
} catch (err) {
|
|
98
107
|
// A malformed tree should not block the viewer; the Readiness tab
|
|
@@ -149,7 +158,12 @@ export async function renderModelToPage({ projectRoot }) {
|
|
|
149
158
|
// list is extended before the version pin bump). One row per doc id
|
|
150
159
|
// plus one row per AC under each US.
|
|
151
160
|
const indexJson = serialiseViewIndex(model);
|
|
152
|
-
|
|
161
|
+
// Issue 307 (2026-10-06): expose the computed readiness alongside
|
|
162
|
+
// the rendered HTML so tests can assert the viewer's D3
|
|
163
|
+
// shapes:pathsResolve agrees with the CLI without parsing HTML. The
|
|
164
|
+
// viewer HTML is still the operator-facing surface; this field is
|
|
165
|
+
// additive and consumed by test code only.
|
|
166
|
+
return { fullPageHtml, contentHtml, errors, tree, pmPartials, indexJson, readiness };
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
/**
|
package/src/view/live-client.js
CHANGED
|
@@ -507,19 +507,27 @@
|
|
|
507
507
|
// the panels back in line with the aria-selected button and re-run
|
|
508
508
|
// Mermaid in the visible one.
|
|
509
509
|
function resyncTabsAfterSwap(doc, win) {
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
// Scope to
|
|
510
|
+
// Issue 297 (w-2026-10-06-dave-003, AC-18001-8): the authoritative
|
|
511
|
+
// tab list is the server-rendered nav.tabs itself, not a parallel
|
|
512
|
+
// array that can drift. Earlier the list was hand-copied here and
|
|
513
|
+
// omitted 'readiness' (added as tab 1 in US-18001); a Readiness
|
|
514
|
+
// SSE swap fell back to Overview and both panels went visible.
|
|
515
|
+
// Deriving from the DOM means a future tab cannot be forgotten
|
|
516
|
+
// the same way. Scope to nav.tabs to avoid matching pm-group-btn
|
|
517
517
|
// nodes (they also carry role="tab").
|
|
518
|
+
var buttons = doc.querySelectorAll('nav.tabs [role="tab"][data-tab]');
|
|
519
|
+
var names = [];
|
|
520
|
+
for (var b = 0; b < buttons.length; b += 1) {
|
|
521
|
+
var n = buttons[b].getAttribute ? buttons[b].getAttribute('data-tab') : null;
|
|
522
|
+
if (n) names.push(n);
|
|
523
|
+
}
|
|
518
524
|
var selectedBtn = doc.querySelector('nav.tabs [role="tab"][aria-selected="true"]');
|
|
519
525
|
var selected = selectedBtn && selectedBtn.getAttribute ? selectedBtn.getAttribute('data-tab') : null;
|
|
520
|
-
if (!selected ||
|
|
521
|
-
|
|
522
|
-
|
|
526
|
+
if (!selected || names.indexOf(selected) === -1) {
|
|
527
|
+
selected = names.indexOf('overview') !== -1 ? 'overview' : (names[0] || 'overview');
|
|
528
|
+
}
|
|
529
|
+
for (var i = 0; i < names.length; i += 1) {
|
|
530
|
+
var name = names[i];
|
|
523
531
|
var panel = doc.getElementById('tab-' + name);
|
|
524
532
|
if (!panel) continue;
|
|
525
533
|
if (name === selected) panel.removeAttribute('hidden');
|
package/src/view/style.css
CHANGED
|
@@ -236,6 +236,10 @@ header.app-header .bar {
|
|
|
236
236
|
color: var(--sv-ink);
|
|
237
237
|
overflow: hidden;
|
|
238
238
|
text-overflow: ellipsis;
|
|
239
|
+
/* w-2026-10-06-dave-001 (issue 295): .product is a flex container; the
|
|
240
|
+
ellipsis only triggers when its child is itself free to shrink below
|
|
241
|
+
intrinsic. min-width:0 unlocks that. */
|
|
242
|
+
min-width: 0;
|
|
239
243
|
}
|
|
240
244
|
|
|
241
245
|
.product .sub {
|
|
@@ -248,8 +252,12 @@ nav.tabs {
|
|
|
248
252
|
display: flex;
|
|
249
253
|
gap: 0.25rem;
|
|
250
254
|
margin-left: 1rem;
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
/* w-2026-10-06-dave-001 (issue 295): the tablist grows to fill the
|
|
256
|
+
bar but never shrinks below its intrinsic width (flex-shrink:0),
|
|
257
|
+
so every main tab stays visible and the Find button in .tools
|
|
258
|
+
never sits on top of a clipped tab. .product carries the shrink
|
|
259
|
+
budget via its own min-width:0 and the ellipsis on .product .name. */
|
|
260
|
+
flex: 1 0 auto;
|
|
253
261
|
overflow-x: auto;
|
|
254
262
|
-webkit-overflow-scrolling: touch;
|
|
255
263
|
}
|