rcf-lite 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/bin/rcf.js +3 -1
  3. package/blueprints/application-api-rest/docs/topics.md +1 -1
  4. package/blueprints/application-spa/README.md +3 -3
  5. package/blueprints/application-spa/contributions/adrs/adr-202-application-spa-theming.json +1 -1
  6. package/blueprints/application-spa/contributions/adrs/adr-206-application-spa-iconography.json +1 -1
  7. package/blueprints/application-spa/contributions/tacs/tac-207-application-spa-token-adherence-probe.json +7 -7
  8. package/blueprints/application-spa/contributions/tacs/tac-208-application-spa-icon-adherence-probe.json +7 -7
  9. package/blueprints/application-spa/contributions/tacs/tac-209-application-spa-csp-styled-adherence-probe.json +8 -8
  10. package/blueprints/application-spa/contributions/tacs/tac-210-application-spa-external-dependency-provisioning-probe.json +8 -8
  11. package/blueprints/application-spa/contributions/tacs/tac-211-application-spa-core-flow-e2e-probe.json +8 -8
  12. package/blueprints/application-spa/contributions/user-stories/application-spa-us-1129.json +2 -2
  13. package/blueprints/application-spa/contributions/user-stories/application-spa-us-1130.json +2 -2
  14. package/blueprints/application-spa/contributions/user-stories/application-spa-us-1131.json +2 -2
  15. package/blueprints/application-spa/contributions/user-stories/application-spa-us-1132.json +2 -2
  16. package/blueprints/application-spa/contributions/user-stories/application-spa-us-1133.json +3 -3
  17. package/blueprints/application-spa/docs/topics.md +1 -1
  18. package/blueprints/delivery-ci-workflows/CHANGELOG.md +39 -0
  19. package/blueprints/delivery-ci-workflows/README.md +57 -0
  20. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/default-branch-checks.yml +55 -0
  21. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/pull-request-checks.yml +65 -0
  22. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/release.yml +71 -0
  23. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/scheduled-audit.yml +61 -0
  24. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/notes.md +61 -0
  25. package/blueprints/{ci-pipeline → delivery-ci-workflows}/assets/report-samples/per-gate.json +2 -1
  26. package/blueprints/delivery-ci-workflows/blueprint.json +87 -0
  27. package/blueprints/delivery-ci-workflows/contributions/adrs/adr-701-delivery-ci-workflows-ci-gates.json +30 -0
  28. package/blueprints/{ci-pipeline/contributions/adrs/adr-702-ci-pipeline-strict-coverage-gate.json → delivery-ci-workflows/contributions/adrs/adr-702-delivery-ci-workflows-strict-coverage-gate.json} +2 -2
  29. package/blueprints/{ci-pipeline/contributions/adrs/adr-703-ci-pipeline-node-only-runner.json → delivery-ci-workflows/contributions/adrs/adr-703-delivery-ci-workflows-node-only-runner.json} +1 -1
  30. package/blueprints/{ci-pipeline/contributions/adrs/adr-704-ci-pipeline-report-shape.json → delivery-ci-workflows/contributions/adrs/adr-704-delivery-ci-workflows-report-shape.json} +3 -3
  31. package/blueprints/delivery-ci-workflows/contributions/adrs/adr-705-delivery-ci-workflows-elicitation-surface.json +25 -0
  32. package/blueprints/delivery-ci-workflows/contributions/adrs/adr-706-delivery-ci-workflows-branch-model-defaults.json +25 -0
  33. package/blueprints/delivery-ci-workflows/contributions/adrs/adr-707-delivery-ci-workflows-release-workflow-shape.json +25 -0
  34. package/blueprints/delivery-ci-workflows/contributions/adrs/adr-708-delivery-ci-workflows-provider-hint-shape.json +25 -0
  35. package/blueprints/delivery-ci-workflows/contributions/adrs/adr-709-delivery-ci-workflows-release-artefacts.json +25 -0
  36. package/blueprints/delivery-ci-workflows/contributions/adrs/adr-710-delivery-ci-workflows-scheduled-audit.json +25 -0
  37. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-001.json +18 -0
  38. package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-002.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-002.json} +2 -2
  39. package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-003.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-003.json} +2 -2
  40. package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-004.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-004.json} +2 -2
  41. package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-005.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-005.json} +4 -4
  42. package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-006.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-006.json} +2 -2
  43. package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-007.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-007.json} +2 -2
  44. package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-008.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-008.json} +2 -2
  45. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-009.json +18 -0
  46. package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-010.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-010.json} +2 -2
  47. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-011.json +18 -0
  48. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-012.json +18 -0
  49. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-013.json +18 -0
  50. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-014.json +18 -0
  51. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-015.json +18 -0
  52. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-016.json +18 -0
  53. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-017.json +18 -0
  54. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-018.json +18 -0
  55. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-019.json +18 -0
  56. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-020.json +18 -0
  57. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-021.json +18 -0
  58. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-022.json +18 -0
  59. package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-023.json +18 -0
  60. package/blueprints/{ci-pipeline/contributions/tacs/tac-701-ci-pipeline-gate-runner.json → delivery-ci-workflows/contributions/tacs/tac-701-delivery-ci-workflows-gate-runner.json} +3 -3
  61. package/blueprints/{ci-pipeline/contributions/tacs/tac-702-ci-pipeline-gate-report.json → delivery-ci-workflows/contributions/tacs/tac-702-delivery-ci-workflows-gate-report.json} +1 -1
  62. package/blueprints/{ci-pipeline/contributions/tacs/tac-703-ci-pipeline-aggregate-report.json → delivery-ci-workflows/contributions/tacs/tac-703-delivery-ci-workflows-aggregate-report.json} +2 -2
  63. package/blueprints/delivery-ci-workflows/contributions/tacs/tac-704-delivery-ci-workflows-workflow-materialiser.json +64 -0
  64. package/blueprints/delivery-ci-workflows/contributions/tacs/tac-705-delivery-ci-workflows-release-workflow.json +51 -0
  65. package/blueprints/delivery-ci-workflows/contributions/tacs/tac-706-delivery-ci-workflows-scheduled-audit.json +38 -0
  66. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6101.json +37 -0
  67. package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6102.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6102.json} +3 -3
  68. package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6103.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6103.json} +4 -4
  69. package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6104.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6104.json} +4 -4
  70. package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6105.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6105.json} +6 -6
  71. package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6106.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6106.json} +3 -3
  72. package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6107.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6107.json} +5 -5
  73. package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6108.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6108.json} +4 -4
  74. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6109.json +36 -0
  75. package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6110.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6110.json} +3 -3
  76. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6111.json +36 -0
  77. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6112.json +36 -0
  78. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6113.json +36 -0
  79. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6114.json +46 -0
  80. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6115.json +37 -0
  81. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6116.json +28 -0
  82. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6117.json +28 -0
  83. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6118.json +37 -0
  84. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6119.json +28 -0
  85. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6120.json +28 -0
  86. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6121.json +46 -0
  87. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6122.json +46 -0
  88. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6123.json +37 -0
  89. package/blueprints/delivery-ci-workflows/docs/topics.md +61 -0
  90. package/blueprints/delivery-ci-workflows/guide/delivery-ci-workflows.md +136 -0
  91. package/blueprints/deploy-cloudflare-workers/docs/topics.md +3 -3
  92. package/blueprints/email-smtp-resend/docs/topics.md +1 -1
  93. package/blueprints/observability-essentials/README.md +2 -2
  94. package/blueprints/observability-essentials/docs/topics.md +5 -5
  95. package/blueprints/observability-probe-endpoints/docs/topics.md +2 -2
  96. package/blueprints/persistence-data-d1/README.md +2 -2
  97. package/blueprints/persistence-data-d1/assets/facade-shape/facade-module-shape.md +1 -1
  98. package/blueprints/persistence-data-d1/contributions/tacs/tac-1403-persistence-data-d1-deploy-gate.json +1 -1
  99. package/blueprints/persistence-data-d1/docs/topics.md +2 -2
  100. package/blueprints/persistence-data-d1/guide/persistence-data-d1.md +1 -1
  101. package/blueprints/persistence-data-sqlite/README.md +1 -1
  102. package/blueprints/persistence-data-sqlite/docs/topics.md +1 -1
  103. package/blueprints/security-auth-clerk/README.md +5 -3
  104. package/blueprints/security-auth-clerk/assets/middleware/workers-fetch-shape.md +123 -0
  105. package/blueprints/security-auth-clerk/assets/wiring/workers-wrangler-toml-shape.md +51 -0
  106. package/blueprints/security-auth-clerk/blueprint.json +1 -1
  107. package/blueprints/security-auth-clerk/docs/topics.md +2 -2
  108. package/blueprints/security-auth-clerk/guide/security-auth-clerk.md +9 -0
  109. package/blueprints/security-auth-keycloak/docs/topics.md +2 -2
  110. package/blueprints/security-auth-magic-link/README.md +1 -1
  111. package/blueprints/security-auth-magic-link/docs/topics.md +1 -1
  112. package/blueprints/security-auth-oauth2/README.md +1 -1
  113. package/blueprints/security-auth-oauth2/docs/topics.md +2 -2
  114. package/blueprints/security-secrets-management/README.md +1 -1
  115. package/blueprints/security-secrets-management/docs/topics.md +3 -3
  116. package/guidance/build-cycle-playbook.md +2 -2
  117. package/guidance/document-model.md +1 -1
  118. package/guidance/harness-template.md +13 -0
  119. package/guidance/managed/agent-instructions-block.hash +1 -1
  120. package/guidance/managed/agent-instructions-block.md +13 -0
  121. package/package.json +5 -2
  122. package/rcf/adrs/adr-001.json +1 -1
  123. package/rcf/adrs/adr-009.json +1 -1
  124. package/rcf/build-sequence.json +1 -1
  125. package/rcf/manifest.json +2 -2
  126. package/rcf/prd.json +2 -2
  127. package/releases/releases.yaml +116 -0
  128. package/src/blueprint/apply.js +51 -13
  129. package/src/blueprint/index.js +12 -0
  130. package/src/blueprint/library-loader.js +271 -0
  131. package/src/blueprint/library-registry.js +341 -0
  132. package/src/blueprint/list.js +38 -4
  133. package/src/blueprint/shelf-resolver.js +144 -31
  134. package/src/cli/blueprint-library.js +419 -0
  135. package/src/cli/blueprint.js +46 -9
  136. package/src/cli/guidance.js +1 -1
  137. package/src/cli/help.js +27 -1
  138. package/src/cli/version.js +673 -0
  139. package/src/cli/view.js +282 -1
  140. package/src/server/index.js +3 -0
  141. package/src/server/routes.js +15 -1
  142. package/src/server/scope-endpoint.js +105 -0
  143. package/src/view/live-client.js +253 -6
  144. package/src/view/scope.js +231 -0
  145. package/src/view/style.css +42 -0
  146. package/blueprints/ci-pipeline/README.md +0 -49
  147. package/blueprints/ci-pipeline/assets/ci-provider-examples/github-actions.yml +0 -61
  148. package/blueprints/ci-pipeline/assets/ci-provider-examples/notes.md +0 -50
  149. package/blueprints/ci-pipeline/blueprint.json +0 -46
  150. package/blueprints/ci-pipeline/contributions/adrs/adr-701-ci-pipeline-ci-gates.json +0 -25
  151. package/blueprints/ci-pipeline/contributions/requirements/ci-pipeline-req-001.json +0 -18
  152. package/blueprints/ci-pipeline/contributions/requirements/ci-pipeline-req-009.json +0 -18
  153. package/blueprints/ci-pipeline/contributions/user-stories/ci-pipeline-us-6101.json +0 -37
  154. package/blueprints/ci-pipeline/contributions/user-stories/ci-pipeline-us-6109.json +0 -36
  155. package/blueprints/ci-pipeline/docs/topics.md +0 -49
  156. package/blueprints/ci-pipeline/guide/ci-pipeline.md +0 -79
  157. package/rcf/.identity/profile.md +0 -37
  158. package/rcf/knowledge/INDEX.md +0 -12
  159. package/rcf/knowledge/README.md +0 -41
  160. package/rcf/knowledge/docs/.gitkeep +0 -0
  161. package/rcf/knowledge/notes/.gitkeep +0 -0
  162. /package/blueprints/{ci-pipeline → delivery-ci-workflows}/assets/report-samples/pipeline.json +0 -0
@@ -1,8 +1,10 @@
1
1
  // Live-view client. Loaded by the server-rendered page as
2
2
  // `<script src="/live-client.js" defer>`. Handles the SSE `/events`
3
- // stream, in-place `innerHTML` swaps into `#rcf-live-content`, and
4
- // persists `<details>` open-set + main scrollTop to `localStorage`
5
- // across every swap and every full reload.
3
+ // stream, in-place `innerHTML` swaps into `#rcf-live-content`, persists
4
+ // `<details>` open-set + main scrollTop to `localStorage` across every
5
+ // swap and every full reload, AND applies the URL-driven scope filter
6
+ // (deep-link deep-links: blueprint / node — w-2026-08-30-dave-020) on
7
+ // every load and every swap.
6
8
  //
7
9
  // Vanilla classic script (no ESM syntax) so the base HTML script tag
8
10
  // stays plain. The IIFE exposes its internals as `window.__rcfLiveClient`
@@ -10,7 +12,9 @@
10
12
  // jsdom.
11
13
  //
12
14
  // Spec: projects/rcf-build-lite/specs/phase-3.8-live-view.md D12, D13a,
13
- // D13b, D14.
15
+ // D13b, D14. Deep-link overlay: see `src/view/scope.js`
16
+ // (server-side truth) and `test/view/scope-client.test.js`
17
+ // (browser-side proofs).
14
18
 
15
19
  (function () {
16
20
  var STORAGE_NS = 'rcf-view:v1:';
@@ -18,6 +22,232 @@
18
22
  var STORAGE_SCROLL = STORAGE_NS + 'scrollTop';
19
23
  var HEARTBEAT_STALE_MS = 45000;
20
24
 
25
+ // ---- deep-link scope --------------------------------------------------
26
+
27
+ // Scope model: { blueprint: string|null, node: string|null }. Read on
28
+ // load and after every SSE swap; the DOM class hooks below drive the
29
+ // stylesheet's opacity / focus rules.
30
+
31
+ function parseUrlScope(search) {
32
+ var out = { blueprint: null, node: null };
33
+ if (typeof search !== 'string' || search.length === 0) return out;
34
+ var raw = search.charAt(0) === '?' ? search.slice(1) : search;
35
+ if (raw.length === 0) return out;
36
+ var pairs = raw.split('&');
37
+ for (var i = 0; i < pairs.length; i += 1) {
38
+ var eq = pairs[i].indexOf('=');
39
+ if (eq === -1) continue;
40
+ var key = decodeSafely(pairs[i].slice(0, eq));
41
+ var val = decodeSafely(pairs[i].slice(eq + 1));
42
+ if (typeof val !== 'string') continue;
43
+ var trimmed = val.replace(/^\s+|\s+$/g, '');
44
+ if (trimmed.length === 0) continue;
45
+ if (key === 'blueprint' && out.blueprint === null) out.blueprint = trimmed;
46
+ else if (key === 'node' && out.node === null) out.node = trimmed;
47
+ }
48
+ return out;
49
+ }
50
+
51
+ function decodeSafely(s) {
52
+ try { return decodeURIComponent(String(s).replace(/\+/g, ' ')); }
53
+ catch (e) { return null; }
54
+ }
55
+
56
+ function markScopeIds(doc, contributionIds, customisedIds) {
57
+ if (!doc) return { inScopeCount: 0, customisedCount: 0 };
58
+ clearScopeMarkers(doc);
59
+ var inSet = toIdSet(contributionIds);
60
+ var customSet = toIdSet(customisedIds);
61
+ var inScopeCount = 0;
62
+ var customisedCount = 0;
63
+ // A doc container carries data-doc-id; we walk them and mark the
64
+ // matching ones. Ancestors of an in-scope doc are also marked so
65
+ // the tree drill-down (details wrappers, tabpanels) stays visible.
66
+ var nodes = doc.querySelectorAll('[data-doc-id]');
67
+ for (var i = 0; i < nodes.length; i += 1) {
68
+ var el = nodes[i];
69
+ var id = el.getAttribute('data-doc-id');
70
+ if (!id) continue;
71
+ // The raw-json disclosures carry ids like `PRD-001::raw`;
72
+ // strip the ::raw suffix so the scope hit matches the parent
73
+ // doc's contribution id.
74
+ var canonical = id.replace(/::raw$/, '');
75
+ if (!inSet[canonical]) continue;
76
+ el.classList.add('rcf-in-scope');
77
+ inScopeCount += 1;
78
+ if (customSet[canonical]) {
79
+ el.classList.add('rcf-customised');
80
+ customisedCount += 1;
81
+ }
82
+ // Walk ancestors and mark them as containing an in-scope doc
83
+ // so the CSS can keep them at full opacity.
84
+ var cur = el.parentNode;
85
+ while (cur && cur !== doc.body) {
86
+ if (cur.classList && !cur.classList.contains('rcf-scope-ancestor')) {
87
+ cur.classList.add('rcf-scope-ancestor');
88
+ }
89
+ cur = cur.parentNode;
90
+ }
91
+ // Auto-open the ancestor <details> so the drill-down surfaces
92
+ // the contribution without a manual click.
93
+ openAncestorDetailsChain(el);
94
+ }
95
+ return { inScopeCount: inScopeCount, customisedCount: customisedCount };
96
+ }
97
+
98
+ function toIdSet(list) {
99
+ var out = Object.create(null);
100
+ if (!Array.isArray(list)) return out;
101
+ for (var i = 0; i < list.length; i += 1) {
102
+ if (typeof list[i] === 'string') out[list[i]] = true;
103
+ }
104
+ return out;
105
+ }
106
+
107
+ function openAncestorDetailsChain(node) {
108
+ var cur = node.parentNode;
109
+ while (cur && cur.tagName) {
110
+ if (cur.tagName.toLowerCase() === 'details') {
111
+ cur.open = true;
112
+ }
113
+ cur = cur.parentNode;
114
+ }
115
+ }
116
+
117
+ function clearScopeMarkers(doc) {
118
+ var scoped = doc.querySelectorAll('.rcf-in-scope, .rcf-customised, .rcf-scope-ancestor');
119
+ for (var i = 0; i < scoped.length; i += 1) {
120
+ scoped[i].classList.remove('rcf-in-scope');
121
+ scoped[i].classList.remove('rcf-customised');
122
+ scoped[i].classList.remove('rcf-scope-ancestor');
123
+ }
124
+ }
125
+
126
+ function renderScopeBanner(doc, meta) {
127
+ // Idempotent: reuse the same banner across swaps rather than
128
+ // stacking one banner per SSE tick.
129
+ if (!doc || !doc.body) return null;
130
+ var main = doc.querySelector('main');
131
+ if (!main) return null;
132
+ var banner = doc.getElementById('rcf-scope-banner');
133
+ if (!banner) {
134
+ banner = doc.createElement('aside');
135
+ banner.id = 'rcf-scope-banner';
136
+ banner.setAttribute('role', 'status');
137
+ main.insertBefore(banner, main.firstChild);
138
+ }
139
+ var lines = [];
140
+ if (meta.blueprint) {
141
+ var counted = 'scoped to blueprint ' + escapeText(meta.blueprint)
142
+ + ' (' + meta.inScopeCount + ' contribution' + (meta.inScopeCount === 1 ? '' : 's');
143
+ if (typeof meta.customisedCount === 'number' && meta.customisedCount > 0) {
144
+ counted += ', ' + meta.customisedCount + ' customised';
145
+ }
146
+ counted += ')';
147
+ lines.push(counted);
148
+ }
149
+ if (meta.node) {
150
+ lines.push('focus: ' + escapeText(meta.node));
151
+ }
152
+ if (typeof meta.warning === 'string' && meta.warning.length > 0) {
153
+ lines.push(meta.warning);
154
+ }
155
+ lines.push('<a href="?">clear scope</a>');
156
+ banner.innerHTML = lines.join(' · ');
157
+ return banner;
158
+ }
159
+
160
+ function removeScopeBanner(doc) {
161
+ if (!doc) return;
162
+ var banner = doc.getElementById('rcf-scope-banner');
163
+ if (banner && banner.parentNode) banner.parentNode.removeChild(banner);
164
+ }
165
+
166
+ function escapeText(s) {
167
+ return String(s).replace(/[&<>"']/g, function (ch) {
168
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch];
169
+ });
170
+ }
171
+
172
+ function focusNode(doc, id) {
173
+ if (!doc || !id) return false;
174
+ var target = doc.getElementById(id) || doc.querySelector('[data-doc-id="' + id.replace(/"/g, '\\"') + '"]');
175
+ if (!target) return false;
176
+ openAncestorDetailsChain(target);
177
+ if (target.tagName && target.tagName.toLowerCase() === 'details') target.open = true;
178
+ try { target.scrollIntoView({ block: 'start' }); }
179
+ catch (e) { try { target.scrollIntoView(); } catch (_e) { /* soft */ } }
180
+ return true;
181
+ }
182
+
183
+ // Fetch the scope payload from /scope.json for a given slug. Kept as
184
+ // a small helper so the tests can substitute a fake fetch impl.
185
+ function fetchScope(slug, fetchImpl) {
186
+ var f = typeof fetchImpl === 'function' ? fetchImpl : (typeof fetch === 'function' ? fetch : null);
187
+ if (!f) return Promise.reject(new Error('no fetch available'));
188
+ var url = '/scope.json?blueprint=' + encodeURIComponent(slug);
189
+ return Promise.resolve(f(url)).then(function (resp) {
190
+ if (!resp) throw new Error('scope: no response');
191
+ var okFlag = resp.ok !== undefined ? resp.ok : (resp.status >= 200 && resp.status < 300);
192
+ return Promise.resolve(resp.json ? resp.json() : null).then(function (body) {
193
+ return { ok: !!okFlag, status: resp.status, body: body };
194
+ });
195
+ });
196
+ }
197
+
198
+ // Apply the URL scope to the currently-rendered DOM. Returns a
199
+ // Promise that resolves with a summary object; never rejects (any
200
+ // failure is downgraded to a banner warning).
201
+ function applyUrlScope(deps) {
202
+ var doc = deps.document;
203
+ var win = deps.window;
204
+ var scope = parseUrlScope((win && win.location && win.location.search) || '');
205
+ if (!scope.blueprint && !scope.node) {
206
+ if (doc && doc.body) doc.body.classList.remove('rcf-scoped');
207
+ clearScopeMarkers(doc);
208
+ removeScopeBanner(doc);
209
+ return Promise.resolve({ scope: scope, applied: false });
210
+ }
211
+ if (doc && doc.body) doc.body.classList.add('rcf-scoped');
212
+
213
+ var meta = {
214
+ blueprint: scope.blueprint,
215
+ node: scope.node,
216
+ inScopeCount: 0,
217
+ customisedCount: 0,
218
+ warning: '',
219
+ };
220
+
221
+ var blueprintPart = scope.blueprint
222
+ ? fetchScope(scope.blueprint, deps.fetch).then(function (r) {
223
+ if (!r.ok) {
224
+ meta.warning = r.status === 404
225
+ ? 'blueprint ' + scope.blueprint + ' is not applied to this project'
226
+ : 'scope lookup failed (status ' + r.status + ')';
227
+ return;
228
+ }
229
+ var body = r.body || {};
230
+ var marks = markScopeIds(doc, body.contributionIds || [], body.customisedIds || []);
231
+ meta.inScopeCount = marks.inScopeCount;
232
+ meta.customisedCount = marks.customisedCount;
233
+ }).catch(function (err) {
234
+ meta.warning = 'scope lookup error: ' + (err && err.message ? err.message : 'unknown');
235
+ })
236
+ : Promise.resolve();
237
+
238
+ return blueprintPart.then(function () {
239
+ renderScopeBanner(doc, meta);
240
+ if (scope.node) {
241
+ var focused = focusNode(doc, scope.node);
242
+ if (!focused && !meta.warning) {
243
+ meta.warning = 'node ' + scope.node + ' is not present on the tree';
244
+ renderScopeBanner(doc, meta);
245
+ }
246
+ }
247
+ return { scope: scope, applied: true, meta: meta };
248
+ });
249
+ }
250
+
21
251
  // ---- state persistence ------------------------------------------------
22
252
 
23
253
  function safeGetItem(storage, key) {
@@ -142,12 +372,22 @@
142
372
  // whitelisted deltas from Phase 3.6).
143
373
  injectConnectionDot(doc);
144
374
 
145
- // Restore prior state on initial load.
375
+ // Restore prior state on initial load, then apply URL-driven scope
376
+ // so a deep-link (?blueprint=…&node=…) surfaces the target section
377
+ // even on the very first render.
378
+ var applyScope = function () { applyUrlScope(deps).catch(function () { /* soft */ }); };
146
379
  if (doc.readyState === 'loading') {
147
- doc.addEventListener('DOMContentLoaded', function () { restoreState(deps); });
380
+ doc.addEventListener('DOMContentLoaded', function () {
381
+ restoreState(deps);
382
+ applyScope();
383
+ });
148
384
  } else {
149
385
  restoreState(deps);
386
+ applyScope();
150
387
  }
388
+ // Scope tracks URL changes without a reload (agent pushes a new
389
+ // deep-link into the same tab, or the user clicks "clear scope").
390
+ win.addEventListener('popstate', applyScope);
151
391
 
152
392
  var currentVersion = null;
153
393
  var lastEventAt = Date.now();
@@ -203,6 +443,7 @@
203
443
  // the payload have no `data-processed` yet.
204
444
  resyncTabsAfterSwap(doc, win);
205
445
  restoreState(deps);
446
+ applyScope();
206
447
  setState('connected');
207
448
  });
208
449
 
@@ -319,6 +560,12 @@
319
560
  injectConnectionDot: injectConnectionDot,
320
561
  updateDot: updateDot,
321
562
  resyncTabsAfterSwap: resyncTabsAfterSwap,
563
+ parseUrlScope: parseUrlScope,
564
+ applyUrlScope: applyUrlScope,
565
+ markScopeIds: markScopeIds,
566
+ renderScopeBanner: renderScopeBanner,
567
+ removeScopeBanner: removeScopeBanner,
568
+ focusNode: focusNode,
322
569
  STORAGE_OPEN: STORAGE_OPEN,
323
570
  STORAGE_SCROLL: STORAGE_SCROLL,
324
571
  HEARTBEAT_STALE_MS: HEARTBEAT_STALE_MS,
@@ -0,0 +1,231 @@
1
+ // Deep-link scope resolution. Pure helpers shared by the server-side
2
+ // /scope.json endpoint, the CLI `audit view open` verb, and the
3
+ // live-client filter. Nothing here reads process.env, prints, or
4
+ // mutates state.
5
+ //
6
+ // Scope model (spec: w-2026-08-30-dave-020, Baz-ratified 2026-08-30):
7
+ // * blueprint scope -> the applied blueprint's namespaced
8
+ // contribution ids, read from manifest.blueprints[].contributions[].
9
+ // The applied record IS the truth (project-local), never the
10
+ // shipping blueprint source (which may drift or vanish).
11
+ // * node scope -> a single document id (any kind in tree.byId),
12
+ // resolved by the existing hash-router in the client script. Server
13
+ // validates existence so a bad id refuses at the CLI seam rather
14
+ // than opening a dead browser tab.
15
+ // * customisation set -> the subset of contribution ids whose current
16
+ // on-disk bytes differ from the shipping blueprint source's file.
17
+ // Best-effort: any read failure (source missing, unreadable) treats
18
+ // the file as "unknown" (excluded), which the renderer displays as
19
+ // plain in-scope rather than as customised.
20
+
21
+ import { readFile, stat } from 'node:fs/promises';
22
+ import { isAbsolute, join, resolve } from 'node:path';
23
+
24
+ import { loadBlueprint } from '../blueprint/loader.js';
25
+ import { resolveBlueprintSource } from '../blueprint/shelf-resolver.js';
26
+ import { isRcfError } from '../core/errors/index.js';
27
+
28
+ /**
29
+ * Parse the recognised scope keys from a URL query string.
30
+ *
31
+ * @param {URLSearchParams} params
32
+ * @returns {{ blueprint: string|null, node: string|null }}
33
+ */
34
+ export function parseUrlScope(params) {
35
+ const blueprint = normaliseString(params.get('blueprint'));
36
+ const node = normaliseString(params.get('node'));
37
+ return { blueprint, node };
38
+ }
39
+
40
+ function normaliseString(v) {
41
+ if (typeof v !== 'string') return null;
42
+ const trimmed = v.trim();
43
+ return trimmed.length === 0 ? null : trimmed;
44
+ }
45
+
46
+ /**
47
+ * Resolve the applied blueprint record for a slug.
48
+ *
49
+ * @param {object|null} manifest
50
+ * @param {string} slug
51
+ * @returns {{ found: false } | { found: true, record: object, contributionIds: string[] }}
52
+ */
53
+ export function contributionsForBlueprint(manifest, slug) {
54
+ if (!manifest || !Array.isArray(manifest.blueprints)) return { found: false };
55
+ const record = manifest.blueprints.find((b) => b?.slug === slug);
56
+ if (!record) return { found: false };
57
+ const contributionIds = Array.isArray(record.contributions)
58
+ ? record.contributions.map((c) => c?.id).filter((id) => typeof id === 'string')
59
+ : [];
60
+ return { found: true, record, contributionIds };
61
+ }
62
+
63
+ /**
64
+ * List every applied blueprint slug present in the manifest. Used by
65
+ * the client to preflight scope requests without a round trip per view.
66
+ *
67
+ * @param {object|null} manifest
68
+ * @returns {string[]}
69
+ */
70
+ export function listAppliedSlugs(manifest) {
71
+ if (!manifest || !Array.isArray(manifest.blueprints)) return [];
72
+ return manifest.blueprints
73
+ .map((b) => (typeof b?.slug === 'string' ? b.slug : null))
74
+ .filter((s) => s !== null);
75
+ }
76
+
77
+ /**
78
+ * Detect which of the applied blueprint's contribution files have been
79
+ * modified on disk since apply. Compares current file bytes to the
80
+ * shipping source file bytes. Source paths that no longer resolve are
81
+ * treated as "unknown" (excluded from the result), so a deleted
82
+ * shipping tree does not fake a full-tree customisation banner.
83
+ *
84
+ * @param {object} args
85
+ * @param {string} args.projectRoot - absolute path to the project root
86
+ * @param {object} args.record - applied blueprint record
87
+ * @param {(abs: string) => Promise<string>} [args._readFile] - test seam
88
+ * @returns {Promise<{ customisedIds: string[], missingSourceIds: string[] }>}
89
+ */
90
+ export async function detectCustomisations({ projectRoot, record, _readFile, _loadBlueprint, _resolveSource }) {
91
+ const readImpl = _readFile ?? ((abs) => readFile(abs, 'utf8'));
92
+ const loadImpl = _loadBlueprint ?? loadBlueprint;
93
+ const resolveImpl = _resolveSource ?? resolveBlueprintSource;
94
+ const customisedIds = [];
95
+ const missingSourceIds = [];
96
+ if (!record || !Array.isArray(record.contributions)) {
97
+ return { customisedIds, missingSourceIds };
98
+ }
99
+ const sourceAbs = await resolveSourceAbs(projectRoot, record.source, resolveImpl);
100
+ const sourceIndex = await loadSourceContributionIndex({ sourceAbs, loadImpl });
101
+ for (const c of record.contributions) {
102
+ if (typeof c?.id !== 'string' || typeof c?.path !== 'string') continue;
103
+ const projectAbs = resolve(projectRoot, c.path);
104
+ let currentBytes = null;
105
+ try {
106
+ currentBytes = await readImpl(projectAbs);
107
+ } catch {
108
+ // No project-side file: not a customisation (nothing on disk).
109
+ continue;
110
+ }
111
+ if (!sourceIndex) {
112
+ missingSourceIds.push(c.id);
113
+ continue;
114
+ }
115
+ // Cross-reference by CONTRIBUTION INDEX (position). The applied
116
+ // record and the shipping blueprint list contributions in the same
117
+ // order at apply time; the applied record's ids are namespaced
118
+ // (stampId) but the position map is stable and does not rely on
119
+ // any string derivation.
120
+ const sourcePathRelative = sourceIndex.byAppliedId(c.id);
121
+ if (!sourcePathRelative) {
122
+ missingSourceIds.push(c.id);
123
+ continue;
124
+ }
125
+ const sourceFileAbs = join(sourceAbs, 'contributions', sourcePathRelative);
126
+ let sourceBytes = null;
127
+ try {
128
+ sourceBytes = await readImpl(sourceFileAbs);
129
+ } catch {
130
+ missingSourceIds.push(c.id);
131
+ continue;
132
+ }
133
+ if (currentBytes !== sourceBytes) customisedIds.push(c.id);
134
+ }
135
+ return { customisedIds, missingSourceIds };
136
+ }
137
+
138
+ async function resolveSourceAbs(projectRoot, source, resolveImpl) {
139
+ if (typeof source !== 'string' || source.length === 0) return null;
140
+ if (isAbsolute(source)) return source;
141
+ // Non-path sources - a library colon ref (`wsd:auth-oauth2`), an
142
+ // `@stock/<slug>` sugar, or a bare shelf slug - need re-resolution
143
+ // through the blueprint resolver against the project registry and the
144
+ // packaged shelf. Without this, the applied-record's source is a
145
+ // token the FS cannot open and every contribution renders as
146
+ // missingSource in /scope.json (integration review d-2026-08-31-046).
147
+ // A relative filesystem path still falls through to the pre-fix
148
+ // resolve() below so an existing manifest carrying `./blueprint-foo`
149
+ // keeps its behaviour.
150
+ if (typeof projectRoot === 'string' && projectRoot.length > 0) {
151
+ const resolved = await resolveImpl(source, { projectRoot }).catch(() => null);
152
+ if (resolved && !isRcfError(resolved) && typeof resolved.resolved === 'string') {
153
+ return resolved.resolved;
154
+ }
155
+ }
156
+ // Fallback: treat as a relative filesystem path against projectRoot.
157
+ // Matches pre-fix behaviour byte-for-byte; a missing directory
158
+ // surfaces as `missingSource` via the loader below rather than here.
159
+ return typeof projectRoot === 'string' && projectRoot.length > 0
160
+ ? resolve(projectRoot, source)
161
+ : null;
162
+ }
163
+
164
+ /**
165
+ * Load the shipping blueprint's contribution list and build an
166
+ * applied-id -> source-relative-path index. Falls back to id-based
167
+ * lookup when positions don't align (a re-apply that added ids will
168
+ * still resolve the shared prefix).
169
+ */
170
+ async function loadSourceContributionIndex({ sourceAbs, loadImpl }) {
171
+ if (!sourceAbs) return null;
172
+ const loaded = await loadImpl(sourceAbs).catch(() => null);
173
+ if (!loaded || loaded.kind) return null;
174
+ const contributions = Array.isArray(loaded.contributions) ? loaded.contributions : [];
175
+ // Two indexes: exact id match (works whenever the operator used the
176
+ // default namespace, i.e. shipping id === applied id) and bare-id
177
+ // suffix match (works when the applied id carries a namespace prefix
178
+ // over a bare shipping id, e.g. `myproj-application-spa-REQ-001`
179
+ // over shipping `application-spa-REQ-001`).
180
+ const exact = new Map();
181
+ for (const sc of contributions) {
182
+ if (typeof sc?.id !== 'string' || typeof sc?.path !== 'string') continue;
183
+ exact.set(sc.id, sc.path);
184
+ }
185
+ return {
186
+ byAppliedId(appliedId) {
187
+ if (exact.has(appliedId)) return exact.get(appliedId);
188
+ // Namespace fall-back: strip the leading namespace segment and
189
+ // retry. The applied stampId only prefixes; it never rewrites
190
+ // the trailing bare id.
191
+ const idx = appliedId.indexOf('-');
192
+ if (idx > 0) {
193
+ const tail = appliedId.slice(idx + 1);
194
+ if (exact.has(tail)) return exact.get(tail);
195
+ }
196
+ return null;
197
+ },
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Best-effort node-id existence check against a tree model. Kept here
203
+ * (not in tree-model.js) so the CLI can validate a --node argument
204
+ * without pulling the full renderer pipeline in.
205
+ *
206
+ * @param {import('#core/store/walker.js').Tree} tree
207
+ * @param {string} id
208
+ * @returns {boolean}
209
+ */
210
+ export function treeHasId(tree, id) {
211
+ if (!tree || typeof id !== 'string' || id.length === 0) return false;
212
+ if (tree.byId && typeof tree.byId.get === 'function') return tree.byId.has(id);
213
+ return false;
214
+ }
215
+
216
+ /**
217
+ * Fast async-friendly stat wrapper. Handy for tests that want to prove
218
+ * the customisation check honours missing files without stubbing the
219
+ * whole fs module.
220
+ *
221
+ * @param {string} abs
222
+ * @returns {Promise<boolean>}
223
+ */
224
+ export async function fileExists(abs) {
225
+ try {
226
+ const s = await stat(abs);
227
+ return s.isFile();
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
@@ -733,3 +733,45 @@ footer {
733
733
  details.doc-details > .req-body,
734
734
  details.doc-details > .us-body { padding: 0.75rem 1rem; }
735
735
  }
736
+
737
+ /* --------------------------------------------------------------------
738
+ * Deep-link scope overlay (w-2026-08-30-dave-020)
739
+ *
740
+ * When body.rcf-scoped is present the client has applied a URL-driven
741
+ * scope (blueprint / node). Everything inside #rcf-live-content is
742
+ * dimmed except:
743
+ * - .rcf-in-scope a doc that IS in the scope set
744
+ * - .rcf-scope-ancestor an ancestor of an in-scope doc (kept full
745
+ * strength so the drill-down chain is legible)
746
+ * - .rcf-customised an in-scope doc whose bytes differ from
747
+ * the shipping blueprint source (a project
748
+ * customisation over the applied contribution)
749
+ *
750
+ * Left visible-but-dimmed rather than hidden so the reader can see
751
+ * where in the tree the scoped set sits.
752
+ * -------------------------------------------------------------------- */
753
+
754
+ body.rcf-scoped #rcf-live-content [data-doc-id]:not(.rcf-in-scope):not(.rcf-scope-ancestor) {
755
+ opacity: 0.28;
756
+ transition: opacity 120ms ease-in-out;
757
+ }
758
+ body.rcf-scoped #rcf-live-content [data-doc-id].rcf-in-scope {
759
+ opacity: 1;
760
+ outline: 2px solid #4c9a2a;
761
+ outline-offset: 2px;
762
+ }
763
+ body.rcf-scoped #rcf-live-content [data-doc-id].rcf-in-scope.rcf-customised {
764
+ outline-color: #d68f00;
765
+ box-shadow: 0 0 0 3px rgba(214, 143, 0, 0.18);
766
+ }
767
+
768
+ #rcf-scope-banner {
769
+ margin: 0 0 1rem;
770
+ padding: 0.5rem 0.8rem;
771
+ border: 1px solid #d68f00;
772
+ background: #fff7e6;
773
+ color: #6b4300;
774
+ font-size: 0.9rem;
775
+ border-radius: 4px;
776
+ }
777
+ #rcf-scope-banner a { color: #b03a2e; text-decoration: underline; }
@@ -1,49 +0,0 @@
1
- # Ci-pipeline blueprint (v1.0.0)
2
-
3
- The fifth content blueprint on the rcf-build-lite blueprint mechanism (design brief v2, ratified). Scope: a CI-provider-neutral gate suite that runs `rcf define validate` and `rcf audit coverage --strict` as required gates on every push to the default branch and every pull request into it, writes machine-readable JSON reports at stable paths, and refuses to declare a pipeline pass on any missing or failed required gate. The suite is a single Node entry-point script the CI job invokes with one line; one illustrative GitHub Actions workflow ships as a starting point and alternate providers wire the same script.
4
-
5
- ## Apply
6
-
7
- ```
8
- rcf define blueprint add <path-to>/blueprints/ci-pipeline
9
- ```
10
-
11
- Phase 1 resolves local path sources only; registry and git-ref resolution is a mechanism follow-up. Apply is idempotent; `rcf define blueprint list` shows the applied entry; `rcf define blueprint remove ci-pipeline` cleanly removes an unreferenced application.
12
-
13
- ## Anatomy
14
-
15
- | Piece | Where | What |
16
- |---|---|---|
17
- | Metadata | `blueprint.json` | Slug, version, and the 27 contributions with scope/topic on the two global ADRs |
18
- | Doc set | `contributions/` | 10 REQs, 10 USs (20 ACs), 3 TACs, 4 ADRs, all schema-valid against rcf-schemas 0.4.5 and namespaced (`ci-pipeline-REQ-001` prefix family; `ADR-701-ci-pipeline-ci-gates` suffix family) |
19
- | GitHub Actions example | `assets/ci-provider-examples/github-actions.yml` | One illustrative workflow: push and pull_request triggers on the default branch, Node and pnpm setup, single-line entry-point invocation, artefact upload of the report directory |
20
- | Alternate-provider notes | `assets/ci-provider-examples/notes.md` | The four-point mapping (job trigger event, Node setup, single-line entry-point invocation, artefact upload) for GitLab CI, CircleCI, Buildkite, and Jenkins |
21
- | Report-shape samples | `assets/report-samples/per-gate.json`, `assets/report-samples/pipeline.json` | Worked examples of a per-gate report and an aggregate report at the ratified schemas |
22
- | Guide | `guide/ci-pipeline.md` | Operator-facing: when to use it, when not, what stays your call, and how to extend the required gate set |
23
- | Coordination vocabulary | `docs/topics.md` | The two global-topic strings this blueprint contributes, the shared id band registry (application-spa, application-api-rest, security-auth-magic-link, hello-panel, persistence-data-sqlite, ci-pipeline, observability-essentials) |
24
-
25
- The doc set is contributions (copied into the project tree by `rcf define blueprint add`); the guide, assets, and docs are package-resident references. Guide rendering into `rcf/knowledge/docs/blueprint-guides/` and asset ingestion are mechanism follow-ups; until they land, the working agent reads them from the applied blueprint's source path recorded in `manifest.blueprints[].source`.
26
-
27
- ## What it contributes, and what it deliberately does not
28
-
29
- Contributed kinds: REQ, US (with inline ACs), TAC, ADR. Adherence is expressed as ACs; the blueprint ships no test files (ratified decision 5) and no code.
30
-
31
- No FBS contributions, as a matter of principle (ratified policy 2026-08-19): FBSs are the work of the implementing agent, not the blueprint. The blueprint contributes the WHAT (the gate-runner contract, the per-gate report writer contract, the aggregate report writer contract, the fixed-gate-set discipline); the implementing agent derives the HOW-tasks in the host project, where the ACs contributed here get picked up by the project's own build sequencing.
32
-
33
- Deliberately not contributed: a matrix of provider-specific configuration files (only one illustrative GitHub Actions workflow ships; alternate providers wire the same Node entry point per the guide's four-point mapping); provider-specific pipeline plugins or actions (each provider's action or plugin catalogue is a per-provider decision the project owns); a Node entry-point source file or scaffolding code (adherence is expressed as ACs, not code; the project realises the entry point in its own build cycle against the TAC contracts); a linter, formatter, or unit-test gate (out of scope at v1.0.0; a project that wants those adds them via a project-level ADR superseding ADR-701 and lists the extended gate set); a merge-queue integration (a project that wants one wires the aggregate report into the merge-queue tool the project picked); a coverage-trend dashboard (a project that wants one reads the aggregate reports over time and layers the dashboard on top).
34
-
35
- ## The two global decisions
36
-
37
- ADR-701-ci-pipeline-ci-gates ships `scope: global` on topic `ciGates`. This is the project's required-gate set: two gates in fixed order at v1.0.0 (`validate` then `coverage-strict`). A composing blueprint that ships its own required-gate opinion (adherence packs, browser-verify smokes, observability probes) conflicts here by design and expects a project-level ADR that names the extended set.
38
-
39
- ADR-702-ci-pipeline-strict-coverage-gate ships `scope: global` on topic `strictCoverageGate`. This is the project's coverage-mode posture: per-AC strict, not shallow-any. A composing blueprint that holds a different coverage posture (a shallow-any-for-early-projects blueprint, a coverage-with-grace-window blueprint) conflicts here by design.
40
-
41
- See `docs/topics.md` for the exact strings, the expected resolutions, the delineation from the application-api-rest blueprint's `logging` topic (wire-log shape, not build-time report shape), and the AC id band allocation (ci-pipeline owns 6101-6899).
42
-
43
- ## Quality bar
44
-
45
- Every push to the default branch and every pull request into it runs the RCF gate suite as a required check; `rcf define validate` runs first and its non-zero exit terminates the aggregate as failed; `rcf audit coverage --strict` runs second and its non-zero exit terminates the aggregate as failed; per-gate JSON reports at stable paths carry a fixed key set (gate, command, toolVersion, startedAt, endedAt, durationMs, exitCode, outcome, stdout, stderr); an aggregate report at `<reportDir>/pipeline.json` carries verdict, trigger, timing, runner metadata, and the ordered gates array; a missing required gate report is recorded as `outcome: missing` with a reason and flips the aggregate to `failed`; the suite continues past a failing gate to record every failure on one run; operator-visible failure summaries name the gate and the report path in the runner-log tail; the Node entry point invocation is identical locally and in CI; no CI-provider environment variable gates the entry point's control flow; the applied blueprint's source path carries one illustrative GitHub Actions workflow and a notes file mapping four points to alternate providers. Every bar is carried by ACs in the doc set, not by this README.
46
-
47
- ## Known mechanism-reach gaps
48
-
49
- None at v1.0.0. Every AC on every story is bound to at least one TAC that the host project must realise, and every AC's `then` clause is runtime-observable in the deployed pipeline surface (per-gate JSON report inspection, aggregate JSON report inspection, source-tree grep for CI-provider environment reads, workflow-file inspection under the applied blueprint's source path). The mechanism-reach principle from the authoring standard section 7 is satisfied at ship: a project that applies this blueprint and does not realise a TAC leaves an unresolved `tacIds` reference on the story that `rcf define validate` and `rcf audit coverage` refuse. The one operational responsibility a project must own is the branch-protection or equivalent merge-policy configuration on the default branch that binds the pipeline job's aggregate exit to the merge criterion (AC-6101-2); that responsibility is stated as an AC observable on the platform's own configuration surface, not a smuggled runtime probe.