rcf-lite 0.20.0 → 0.21.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 (229) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/blueprints/application-admin-console/CHANGELOG.md +11 -0
  3. package/blueprints/application-admin-console/README.md +98 -0
  4. package/blueprints/application-admin-console/blueprint.json +226 -0
  5. package/blueprints/application-admin-console/contributions/adrs/adr-2201-application-admin-console-capability-vocabulary.json +25 -0
  6. package/blueprints/application-admin-console/contributions/adrs/adr-2202-application-admin-console-baseline-roles.json +25 -0
  7. package/blueprints/application-admin-console/contributions/adrs/adr-2203-application-admin-console-invite-transport.json +20 -0
  8. package/blueprints/application-admin-console/contributions/adrs/adr-2204-application-admin-console-audit-retention.json +20 -0
  9. package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-001.json +18 -0
  10. package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-002.json +18 -0
  11. package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-003.json +18 -0
  12. package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-004.json +18 -0
  13. package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-005.json +18 -0
  14. package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-006.json +18 -0
  15. package/blueprints/application-admin-console/contributions/tacs/tac-2201-application-admin-console-shell.json +40 -0
  16. package/blueprints/application-admin-console/contributions/tacs/tac-2202-application-admin-console-capability-discovery.json +44 -0
  17. package/blueprints/application-admin-console/contributions/tacs/tac-2203-application-admin-console-permission-matrix.json +34 -0
  18. package/blueprints/application-admin-console/contributions/tacs/tac-2204-application-admin-console-audit-view.json +40 -0
  19. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21101.json +34 -0
  20. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21102.json +34 -0
  21. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21103.json +34 -0
  22. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21104.json +28 -0
  23. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21105.json +34 -0
  24. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21106.json +27 -0
  25. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21107.json +27 -0
  26. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21108.json +27 -0
  27. package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21109.json +33 -0
  28. package/blueprints/application-admin-console/docs/topics.md +35 -0
  29. package/blueprints/application-admin-console/guide/application-admin-console.md +29 -0
  30. package/blueprints/application-admin-console/probe-packs/application-admin-console.pack.mjs +182 -0
  31. package/blueprints/application-api-rest/docs/topics.md +5 -0
  32. package/blueprints/application-charts/CHANGELOG.md +12 -0
  33. package/blueprints/application-charts/README.md +73 -0
  34. package/blueprints/application-charts/blueprint.json +26 -0
  35. package/blueprints/application-charts/contributions/adrs/adr-1901-application-charts-engine-choice.json +25 -0
  36. package/blueprints/application-charts/contributions/adrs/adr-1902-application-charts-accessible-palette.json +25 -0
  37. package/blueprints/application-charts/contributions/adrs/adr-1903-application-charts-motion.json +25 -0
  38. package/blueprints/application-charts/contributions/requirements/application-charts-req-001.json +15 -0
  39. package/blueprints/application-charts/contributions/requirements/application-charts-req-002.json +15 -0
  40. package/blueprints/application-charts/contributions/requirements/application-charts-req-003.json +15 -0
  41. package/blueprints/application-charts/contributions/requirements/application-charts-req-004.json +15 -0
  42. package/blueprints/application-charts/contributions/tacs/tac-1901-application-charts-render-shell.json +42 -0
  43. package/blueprints/application-charts/contributions/tacs/tac-1902-application-charts-keyboard-traversal.json +40 -0
  44. package/blueprints/application-charts/contributions/user-stories/application-charts-us-18101.json +35 -0
  45. package/blueprints/application-charts/contributions/user-stories/application-charts-us-18102.json +35 -0
  46. package/blueprints/application-charts/contributions/user-stories/application-charts-us-18103.json +35 -0
  47. package/blueprints/application-charts/contributions/user-stories/application-charts-us-18104.json +35 -0
  48. package/blueprints/application-charts/contributions/user-stories/application-charts-us-18105.json +35 -0
  49. package/blueprints/application-charts/contributions/user-stories/application-charts-us-18106.json +35 -0
  50. package/blueprints/application-charts/docs/topics.md +37 -0
  51. package/blueprints/application-charts/guide/application-charts.md +44 -0
  52. package/blueprints/application-charts/probe-packs/application-charts.pack.mjs +191 -0
  53. package/blueprints/application-dashboard/CHANGELOG.md +10 -0
  54. package/blueprints/application-dashboard/README.md +102 -0
  55. package/blueprints/application-dashboard/assets/guidance/dashboard-design.md +124 -0
  56. package/blueprints/application-dashboard/blueprint.json +29 -0
  57. package/blueprints/application-dashboard/contributions/adrs/adr-2001-application-dashboard-primary-kpi-enum.json +25 -0
  58. package/blueprints/application-dashboard/contributions/adrs/adr-2002-application-dashboard-timeframe-presets.json +25 -0
  59. package/blueprints/application-dashboard/contributions/adrs/adr-2003-application-dashboard-export-formats.json +25 -0
  60. package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-001.json +15 -0
  61. package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-002.json +15 -0
  62. package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-003.json +15 -0
  63. package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-004.json +15 -0
  64. package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-005.json +15 -0
  65. package/blueprints/application-dashboard/contributions/tacs/tac-2001-application-dashboard-tile-grid.json +47 -0
  66. package/blueprints/application-dashboard/contributions/tacs/tac-2002-application-dashboard-filter-chrome.json +41 -0
  67. package/blueprints/application-dashboard/contributions/tacs/tac-2003-application-dashboard-export-handle.json +35 -0
  68. package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19101.json +35 -0
  69. package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19102.json +35 -0
  70. package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19103.json +35 -0
  71. package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19104.json +35 -0
  72. package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19105.json +35 -0
  73. package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19106.json +35 -0
  74. package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19107.json +35 -0
  75. package/blueprints/application-dashboard/docs/topics.md +43 -0
  76. package/blueprints/application-dashboard/guide/application-dashboard.md +48 -0
  77. package/blueprints/application-dashboard/probe-packs/application-dashboard.pack.mjs +231 -0
  78. package/blueprints/application-datatable/CHANGELOG.md +8 -0
  79. package/blueprints/application-datatable/README.md +43 -0
  80. package/blueprints/application-datatable/blueprint.json +33 -0
  81. package/blueprints/application-datatable/contributions/adrs/adr-1801-application-datatable-pattern-choice.json +25 -0
  82. package/blueprints/application-datatable/contributions/adrs/adr-1802-application-datatable-url-state.json +20 -0
  83. package/blueprints/application-datatable/contributions/adrs/adr-1803-application-datatable-page-size.json +20 -0
  84. package/blueprints/application-datatable/contributions/adrs/adr-1804-application-datatable-selection-persistence.json +20 -0
  85. package/blueprints/application-datatable/contributions/requirements/application-datatable-req-001.json +15 -0
  86. package/blueprints/application-datatable/contributions/requirements/application-datatable-req-002.json +15 -0
  87. package/blueprints/application-datatable/contributions/requirements/application-datatable-req-003.json +15 -0
  88. package/blueprints/application-datatable/contributions/requirements/application-datatable-req-004.json +15 -0
  89. package/blueprints/application-datatable/contributions/requirements/application-datatable-req-005.json +15 -0
  90. package/blueprints/application-datatable/contributions/requirements/application-datatable-req-006.json +15 -0
  91. package/blueprints/application-datatable/contributions/tacs/tac-1801-application-datatable-shell.json +50 -0
  92. package/blueprints/application-datatable/contributions/tacs/tac-1802-application-datatable-query-adapter.json +39 -0
  93. package/blueprints/application-datatable/contributions/tacs/tac-1803-application-datatable-selection-model.json +28 -0
  94. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17101.json +44 -0
  95. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17102.json +44 -0
  96. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17103.json +44 -0
  97. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17104.json +44 -0
  98. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17105.json +44 -0
  99. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17106.json +44 -0
  100. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17107.json +44 -0
  101. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17108.json +44 -0
  102. package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17109.json +44 -0
  103. package/blueprints/application-datatable/docs/topics.md +35 -0
  104. package/blueprints/application-datatable/guide/application-datatable.md +38 -0
  105. package/blueprints/application-datatable/probe-packs/application-datatable.pack.mjs +240 -0
  106. package/blueprints/application-error-handling/docs/topics.md +5 -0
  107. package/blueprints/application-notifications-in-app/CHANGELOG.md +9 -0
  108. package/blueprints/application-notifications-in-app/README.md +80 -0
  109. package/blueprints/application-notifications-in-app/blueprint.json +30 -0
  110. package/blueprints/application-notifications-in-app/contributions/adrs/adr-2101-application-notifications-in-app-live-region-role-mapping.json +25 -0
  111. package/blueprints/application-notifications-in-app/contributions/adrs/adr-2102-application-notifications-in-app-toast-timeout-floor.json +25 -0
  112. package/blueprints/application-notifications-in-app/contributions/adrs/adr-2103-application-notifications-in-app-retention-window.json +25 -0
  113. package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-001.json +15 -0
  114. package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-002.json +15 -0
  115. package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-003.json +15 -0
  116. package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-004.json +15 -0
  117. package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-005.json +15 -0
  118. package/blueprints/application-notifications-in-app/contributions/tacs/tac-2101-application-notifications-in-app-live-region.json +46 -0
  119. package/blueprints/application-notifications-in-app/contributions/tacs/tac-2102-application-notifications-in-app-centre.json +52 -0
  120. package/blueprints/application-notifications-in-app/contributions/tacs/tac-2103-application-notifications-in-app-preferences.json +40 -0
  121. package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20101.json +35 -0
  122. package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20102.json +35 -0
  123. package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20103.json +35 -0
  124. package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20104.json +35 -0
  125. package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20105.json +35 -0
  126. package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20106.json +26 -0
  127. package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20107.json +26 -0
  128. package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20108.json +26 -0
  129. package/blueprints/application-notifications-in-app/docs/topics.md +39 -0
  130. package/blueprints/application-notifications-in-app/guide/application-notifications-in-app.md +47 -0
  131. package/blueprints/application-notifications-in-app/probe-packs/application-notifications-in-app.pack.mjs +328 -0
  132. package/blueprints/application-spa/docs/topics.md +5 -0
  133. package/blueprints/delivery-ci-workflows/docs/topics.md +5 -0
  134. package/blueprints/deploy-cloudflare-workers/docs/topics.md +5 -0
  135. package/blueprints/email-smtp-resend/docs/topics.md +5 -0
  136. package/blueprints/observability-essentials/docs/topics.md +5 -0
  137. package/blueprints/observability-logging/CHANGELOG.md +5 -0
  138. package/blueprints/observability-logging/README.md +1 -1
  139. package/blueprints/observability-logging/blueprint.json +113 -21
  140. package/blueprints/observability-logging/docs/topics.md +5 -0
  141. package/blueprints/observability-probe-endpoints/docs/topics.md +5 -0
  142. package/blueprints/persistence-data-d1/docs/topics.md +5 -0
  143. package/blueprints/persistence-data-sqlite/docs/topics.md +5 -0
  144. package/blueprints/security-auth-clerk/CHANGELOG.md +5 -0
  145. package/blueprints/security-auth-clerk/blueprint.json +2 -1
  146. package/blueprints/security-auth-clerk/docs/topics.md +5 -0
  147. package/blueprints/security-auth-keycloak/CHANGELOG.md +5 -0
  148. package/blueprints/security-auth-keycloak/blueprint.json +2 -1
  149. package/blueprints/security-auth-keycloak/docs/topics.md +5 -0
  150. package/blueprints/security-auth-magic-link/CHANGELOG.md +5 -0
  151. package/blueprints/security-auth-magic-link/blueprint.json +2 -1
  152. package/blueprints/security-auth-magic-link/docs/topics.md +5 -0
  153. package/blueprints/security-auth-oauth2/CHANGELOG.md +5 -0
  154. package/blueprints/security-auth-oauth2/blueprint.json +2 -1
  155. package/blueprints/security-auth-oauth2/docs/topics.md +5 -0
  156. package/blueprints/security-secrets-management/docs/topics.md +5 -0
  157. package/fixtures/canary-manifest.json +9 -9
  158. package/package.json +2 -2
  159. package/rcf/code-nodes/cn-106.json +13 -0
  160. package/rcf/code-nodes/cn-107.json +14 -0
  161. package/rcf/code-nodes/cn-108.json +13 -0
  162. package/rcf/code-nodes/cn-109.json +12 -0
  163. package/rcf/code-nodes/cn-110.json +12 -0
  164. package/rcf/code-nodes/cn-111.json +14 -0
  165. package/rcf/code-nodes/cn-112.json +14 -0
  166. package/rcf/code-nodes/cn-113.json +12 -0
  167. package/rcf/code-nodes/cn-114.json +13 -0
  168. package/rcf/code-nodes/cn-115.json +13 -0
  169. package/rcf/code-nodes/cn-116.json +12 -0
  170. package/rcf/code-nodes/cn-117.json +13 -0
  171. package/rcf/code-nodes/cn-118.json +13 -0
  172. package/rcf/code-nodes/cn-119.json +13 -0
  173. package/rcf/code-nodes/cn-120.json +13 -0
  174. package/rcf/code-nodes/cn-121.json +12 -0
  175. package/rcf/code-nodes/cn-122.json +12 -0
  176. package/rcf/code-nodes/cn-123.json +13 -0
  177. package/rcf/code-nodes/cn-124.json +13 -0
  178. package/rcf/code-nodes/cn-125.json +13 -0
  179. package/rcf/code-nodes/cn-126.json +13 -0
  180. package/rcf/code-nodes/cn-127.json +12 -0
  181. package/rcf/code-nodes/cn-128.json +14 -0
  182. package/rcf/code-nodes/cn-129.json +14 -0
  183. package/rcf/code-nodes/cn-130.json +14 -0
  184. package/rcf/code-nodes/cn-131.json +15 -0
  185. package/rcf/code-nodes/cn-132.json +12 -0
  186. package/rcf/code-nodes/cn-133.json +13 -0
  187. package/rcf/fbs/fbs-036.json +22 -0
  188. package/rcf/fbs/fbs-037.json +19 -0
  189. package/rcf/fbs/fbs-038.json +19 -0
  190. package/rcf/fbs/fbs-039.json +21 -0
  191. package/rcf/fbs/fbs-040.json +20 -0
  192. package/rcf/fbs/fbs-041.json +13 -0
  193. package/rcf/fbs/fbs-042.json +13 -0
  194. package/rcf/requirements/req-017.json +18 -0
  195. package/rcf/requirements/req-018.json +18 -0
  196. package/rcf/requirements/req-019.json +38 -0
  197. package/rcf/requirements/req-020.json +13 -0
  198. package/rcf/requirements/req-021.json +13 -0
  199. package/rcf/requirements/req-022.json +13 -0
  200. package/rcf/requirements/req-023.json +14 -0
  201. package/rcf/test-suites/ts-046.json +78 -0
  202. package/rcf/test-suites/ts-047.json +54 -0
  203. package/rcf/test-suites/ts-048.json +54 -0
  204. package/rcf/test-suites/ts-049.json +70 -0
  205. package/rcf/test-suites/ts-050.json +69 -0
  206. package/rcf/test-suites/ts-051.json +22 -0
  207. package/rcf/test-suites/ts-052.json +20 -0
  208. package/rcf/user-stories/us-1701.json +56 -0
  209. package/rcf/user-stories/us-1801.json +40 -0
  210. package/rcf/user-stories/us-1901.json +40 -0
  211. package/rcf/user-stories/us-2001.json +50 -0
  212. package/rcf/user-stories/us-2101.json +45 -0
  213. package/rcf/user-stories/us-2201.json +60 -0
  214. package/rcf/user-stories/us-2301.json +51 -0
  215. package/releases/releases.yaml +11 -1
  216. package/src/blueprint/apply.js +98 -1
  217. package/src/blueprint/capabilities.js +318 -0
  218. package/src/blueprint/loader.js +247 -0
  219. package/src/browser-verify/boot.js +180 -0
  220. package/src/browser-verify/index.js +13 -0
  221. package/src/browser-verify/manifest-writer.js +31 -6
  222. package/src/browser-verify/pack-browser.js +294 -0
  223. package/src/browser-verify/pack-loader.js +176 -0
  224. package/src/browser-verify/pack-runner.js +193 -0
  225. package/src/browser-verify/pack-schema.js +155 -0
  226. package/src/browser-verify/runner.js +24 -1
  227. package/src/cli/blueprint.js +92 -0
  228. package/src/cli/browser-verify.js +147 -7
  229. package/src/core/errors/index.js +7 -0
@@ -0,0 +1,155 @@
1
+ // Pack schema validator for blueprint-shipped probe packs
2
+ // (visual round T-0, US-1701 AC-1701-1/2). CN-106.
3
+ //
4
+ // Validates a pack module's exported default object against the
5
+ // section 3.2 shape. Enforces the field-level rules the loader must
6
+ // refuse at load time (packName grammar and blueprint-slug prefix,
7
+ // version semver, blueprintSlug matches directory, appliesTo function
8
+ // referencing one of the three legal scoping seams, checks[] non-empty
9
+ // with an id / severity / description / async run per entry).
10
+ //
11
+ // Returns a plain-JS shape (not RcfError): the loader wraps refusals
12
+ // in its own diagnostic aggregate, so a single validation call reports
13
+ // every issue on one pack instead of stopping at the first.
14
+
15
+ const PACK_NAME_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
16
+ const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/;
17
+ const CHECK_ID_RE = /^AC-\d{3,}-\d+$/;
18
+ const SEVERITIES = new Set(['block', 'warn', 'advisory']);
19
+
20
+ const APPLIES_TO_TOKENS = [
21
+ { key: 'route', matcher: /\broute\b|\bnavModel\b|\bpath\b/ },
22
+ { key: 'tacIds', matcher: /\btacIds?\b/ },
23
+ { key: 'blueprintTag', matcher: /blueprint:/ },
24
+ ];
25
+
26
+ /**
27
+ * Validate a pack module's exported default object.
28
+ *
29
+ * @param {object} args
30
+ * @param {object} args.mod the imported module (its `.default` is the pack object).
31
+ * @param {string} args.blueprintSlug the owning blueprint slug (directory).
32
+ * @param {string} args.packAbsPath absolute path of the pack file, for error context.
33
+ * @returns {{ ok: true, pack: object } | { ok: false, errors: Array<{ field: string, message: string }> }}
34
+ */
35
+ export function validatePackModule({ mod, blueprintSlug, packAbsPath }) {
36
+ const errors = [];
37
+ const pack = mod?.default;
38
+ if (!pack || typeof pack !== 'object') {
39
+ return { ok: false, errors: [{ field: 'default', message: `pack ${packAbsPath}: missing default export (expected an object)` }] };
40
+ }
41
+
42
+ if (typeof pack.packName !== 'string' || pack.packName.length === 0) {
43
+ errors.push({ field: 'packName', message: `pack ${packAbsPath}: packName must be a non-empty string` });
44
+ } else if (!PACK_NAME_RE.test(pack.packName)) {
45
+ errors.push({ field: 'packName', message: `pack ${packAbsPath}: packName '${pack.packName}' must match ${PACK_NAME_RE}` });
46
+ } else if (pack.packName !== blueprintSlug && !pack.packName.startsWith(`${blueprintSlug}-`)) {
47
+ errors.push({ field: 'packName', message: `pack ${packAbsPath}: packName '${pack.packName}' must equal the blueprint slug '${blueprintSlug}' or start with '${blueprintSlug}-'` });
48
+ }
49
+
50
+ if (typeof pack.version !== 'string' || !SEMVER_RE.test(pack.version)) {
51
+ errors.push({ field: 'version', message: `pack ${packAbsPath}: version must be semver (got ${JSON.stringify(pack.version)})` });
52
+ }
53
+
54
+ if (typeof pack.blueprintSlug !== 'string' || pack.blueprintSlug.length === 0) {
55
+ errors.push({ field: 'blueprintSlug', message: `pack ${packAbsPath}: blueprintSlug must be a non-empty string` });
56
+ } else if (pack.blueprintSlug !== blueprintSlug) {
57
+ errors.push({ field: 'blueprintSlug', message: `pack ${packAbsPath}: blueprintSlug '${pack.blueprintSlug}' must match the enclosing directory '${blueprintSlug}'` });
58
+ }
59
+
60
+ if (typeof pack.appliesTo !== 'function') {
61
+ errors.push({ field: 'appliesTo', message: `pack ${packAbsPath}: appliesTo is required and must be a function ({ fbs, uiBaseline, manifest }) => boolean` });
62
+ } else {
63
+ const source = String(pack.appliesTo);
64
+ const matched = APPLIES_TO_TOKENS.filter((t) => t.matcher.test(source)).map((t) => t.key);
65
+ if (matched.length === 0) {
66
+ errors.push({
67
+ field: 'appliesTo',
68
+ message: `pack ${packAbsPath}: appliesTo must reference one of { route (or navModel/path), tacIds, blueprint:<slug> tag }; got source ${trimForError(source)}. The default '() => true' predicate is refused; scope the pack to the FBS surface it probes.`,
69
+ });
70
+ }
71
+ }
72
+
73
+ if (pack.boot !== undefined && pack.boot !== null && typeof pack.boot !== 'object') {
74
+ errors.push({ field: 'boot', message: `pack ${packAbsPath}: boot must be an object or null when present (fields bootCommand, waitForUrl, waitForSelector are optional)` });
75
+ }
76
+
77
+ if (!Array.isArray(pack.checks) || pack.checks.length === 0) {
78
+ errors.push({ field: 'checks', message: `pack ${packAbsPath}: checks must be a non-empty array` });
79
+ } else {
80
+ const seenIds = new Set();
81
+ pack.checks.forEach((check, index) => {
82
+ const at = `checks[${index}]`;
83
+ if (!check || typeof check !== 'object') {
84
+ errors.push({ field: at, message: `pack ${packAbsPath}: ${at} must be an object` });
85
+ return;
86
+ }
87
+ if (typeof check.id !== 'string' || !CHECK_ID_RE.test(check.id)) {
88
+ errors.push({ field: `${at}.id`, message: `pack ${packAbsPath}: ${at}.id must match ${CHECK_ID_RE} (got ${JSON.stringify(check.id)})` });
89
+ } else if (seenIds.has(check.id)) {
90
+ errors.push({ field: `${at}.id`, message: `pack ${packAbsPath}: ${at}.id '${check.id}' duplicates an earlier check on the same pack` });
91
+ } else {
92
+ seenIds.add(check.id);
93
+ }
94
+ if (!SEVERITIES.has(check.severity)) {
95
+ errors.push({ field: `${at}.severity`, message: `pack ${packAbsPath}: ${at}.severity must be one of ${[...SEVERITIES].join(' | ')} (got ${JSON.stringify(check.severity)})` });
96
+ }
97
+ if (typeof check.description !== 'string' || check.description.length === 0) {
98
+ errors.push({ field: `${at}.description`, message: `pack ${packAbsPath}: ${at}.description must be a non-empty string` });
99
+ }
100
+ if (typeof check.run !== 'function') {
101
+ errors.push({ field: `${at}.run`, message: `pack ${packAbsPath}: ${at}.run must be an async function` });
102
+ }
103
+ if (check.dependsOn !== undefined && !(typeof check.dependsOn === 'string' || (Array.isArray(check.dependsOn) && check.dependsOn.every((d) => typeof d === 'string')))) {
104
+ errors.push({ field: `${at}.dependsOn`, message: `pack ${packAbsPath}: ${at}.dependsOn must be a string or an array of strings (pre-check ids)` });
105
+ }
106
+ });
107
+ }
108
+
109
+ if (pack.preChecks !== undefined) {
110
+ if (!Array.isArray(pack.preChecks)) {
111
+ errors.push({ field: 'preChecks', message: `pack ${packAbsPath}: preChecks must be an array when present` });
112
+ } else {
113
+ const seenPre = new Set();
114
+ pack.preChecks.forEach((pre, index) => {
115
+ const at = `preChecks[${index}]`;
116
+ if (!pre || typeof pre !== 'object') {
117
+ errors.push({ field: at, message: `pack ${packAbsPath}: ${at} must be an object` });
118
+ return;
119
+ }
120
+ if (typeof pre.id !== 'string' || pre.id.length === 0) {
121
+ errors.push({ field: `${at}.id`, message: `pack ${packAbsPath}: ${at}.id must be a non-empty string` });
122
+ } else if (seenPre.has(pre.id)) {
123
+ errors.push({ field: `${at}.id`, message: `pack ${packAbsPath}: ${at}.id '${pre.id}' duplicates an earlier pre-check on the same pack` });
124
+ } else {
125
+ seenPre.add(pre.id);
126
+ }
127
+ if (!SEVERITIES.has(pre.severity)) {
128
+ errors.push({ field: `${at}.severity`, message: `pack ${packAbsPath}: ${at}.severity must be one of ${[...SEVERITIES].join(' | ')} (got ${JSON.stringify(pre.severity)})` });
129
+ }
130
+ if (typeof pre.description !== 'string' || pre.description.length === 0) {
131
+ errors.push({ field: `${at}.description`, message: `pack ${packAbsPath}: ${at}.description must be a non-empty string` });
132
+ }
133
+ if (typeof pre.run !== 'function') {
134
+ errors.push({ field: `${at}.run`, message: `pack ${packAbsPath}: ${at}.run must be an async function` });
135
+ }
136
+ });
137
+ }
138
+ }
139
+
140
+ if (errors.length > 0) return { ok: false, errors };
141
+ return { ok: true, pack };
142
+ }
143
+
144
+ function trimForError(s) {
145
+ const trimmed = String(s).replace(/\s+/g, ' ').trim();
146
+ return trimmed.length > 80 ? `${trimmed.slice(0, 77)}...` : trimmed;
147
+ }
148
+
149
+ export const PACK_SCHEMA_INTERNALS = Object.freeze({
150
+ PACK_NAME_RE,
151
+ SEMVER_RE,
152
+ CHECK_ID_RE,
153
+ SEVERITIES,
154
+ APPLIES_TO_TOKENS,
155
+ });
@@ -20,6 +20,7 @@
20
20
  import { runInvariantsForCapture, foldInvariantsForRecord, compareTopLevelStructure } from './invariants.js';
21
21
  import { runAuthSmokeChecks, shouldRunAuthSmokeChecks } from './auth-smoke.js';
22
22
  import { composeBrowserVerificationRecord } from './manifest-writer.js';
23
+ import { runProbePacksForFbs } from './pack-runner.js';
23
24
 
24
25
  /**
25
26
  * @typedef {object} BrowserCapture
@@ -63,7 +64,8 @@ export const stubBrowserDriver = Object.freeze({
63
64
  * @returns {Promise<object>} the composed browserVerification record
64
65
  */
65
66
  export async function runAgentScreenshotCritique({
66
- tree, fbs, runtimeUrl, runtimeProfile, browserDriver, fetch, artefactDir, critiqueNotes, now = new Date(),
67
+ tree, fbs, runtimeUrl, runtimeProfile, browserDriver, fetch, artefactDir, critiqueNotes,
68
+ probePacks = [], packBrowser = null, packNameFilter, projectRoot = null, now = new Date(),
67
69
  }) {
68
70
  const routes = fbs?.designStage?.navModel?.routes ?? [];
69
71
  const themes = readThemesFromBaseline(tree.manifest?.uiBaseline);
@@ -124,6 +126,26 @@ export async function runAgentScreenshotCritique({
124
126
  authSmokeChecks = await runAuthSmokeChecks({ fetch, runtimeUrl });
125
127
  }
126
128
 
129
+ // Probe-pack pass: runs after invariants and auth-smoke
130
+ // (visual-round-spec-2026-09-04 §3.4).
131
+ let packRecords = [];
132
+ if (Array.isArray(probePacks) && probePacks.length > 0) {
133
+ const result = await runProbePacksForFbs({
134
+ packs: probePacks,
135
+ fbs,
136
+ uiBaseline: tree.manifest?.uiBaseline ?? null,
137
+ manifest: tree.manifest ?? null,
138
+ projectRoot,
139
+ browser: packBrowser,
140
+ fetch,
141
+ runtimeUrl,
142
+ routes,
143
+ themes,
144
+ packNameFilter,
145
+ });
146
+ packRecords = result.probePacks;
147
+ }
148
+
127
149
  return composeBrowserVerificationRecord({
128
150
  manifest: tree.manifest,
129
151
  fbsId: fbs.fbsId,
@@ -133,6 +155,7 @@ export async function runAgentScreenshotCritique({
133
155
  routesChecked,
134
156
  invariantChecks,
135
157
  authSmokeChecks,
158
+ probePacks: packRecords,
136
159
  notes,
137
160
  now,
138
161
  });
@@ -184,6 +184,14 @@ const OPTION_SPEC = {
184
184
  // Companion-suggestion mechanism (core-companions spec section 2).
185
185
  companion: { type: 'string', multiple: true },
186
186
  'no-companion-suggestions': { type: 'boolean' },
187
+ // Capability-declaration mechanism (visual round T-5 spec 5.5.1,
188
+ // 5.5.2). --allow-no-auth-yet is the override for the
189
+ // requiresAppliedCapabilities gate. --answer <id>=<value> is
190
+ // repeatable; --answers <file> reads a JSON object mapping elicit
191
+ // ids to values. Both feed the elicitation phase in applyBlueprint.
192
+ 'allow-no-auth-yet': { type: 'boolean' },
193
+ answer: { type: 'string', multiple: true },
194
+ answers: { type: 'string' },
187
195
  };
188
196
 
189
197
  /**
@@ -286,9 +294,54 @@ export async function main(argv, deps = {}) {
286
294
  }
287
295
  }
288
296
  }
297
+ // Capability-declaration mechanism (visual round T-5). Compose
298
+ // the operator-supplied elicit answers bag from --answers <file>
299
+ // (JSON object) and --answer <id>=<value> (repeatable). Later
300
+ // --answer entries override --answers file entries so a shell
301
+ // one-liner beats a stored default without editing the file.
302
+ let elicitAnswers = {};
303
+ if (typeof parsed.values.answers === 'string' && parsed.values.answers.length > 0) {
304
+ try {
305
+ const raw = await (await import('node:fs/promises')).readFile(parsed.values.answers, 'utf8');
306
+ const parsedBag = JSON.parse(raw);
307
+ if (typeof parsedBag !== 'object' || parsedBag === null || Array.isArray(parsedBag)) {
308
+ stderr.write(`[error] blueprint add: --answers file must contain a JSON object of id -> value.\n`);
309
+ return 2;
310
+ }
311
+ elicitAnswers = { ...parsedBag };
312
+ } catch (err) {
313
+ stderr.write(`[error] blueprint add: --answers file read failed: ${err.message}\n`);
314
+ return 2;
315
+ }
316
+ }
317
+ if (Array.isArray(parsed.values.answer)) {
318
+ for (const entry of parsed.values.answer) {
319
+ const eq = entry.indexOf('=');
320
+ if (eq < 1) {
321
+ stderr.write(`[error] blueprint add: --answer expects <id>=<value>; got '${entry}'.\n`);
322
+ return 2;
323
+ }
324
+ elicitAnswers[entry.slice(0, eq)] = entry.slice(eq + 1);
325
+ }
326
+ }
327
+ // Custom-auth capability elicits (visual round T-5 spec 5.5
328
+ // "Risks"): providesCapability elicits fire pre-refusal. On a
329
+ // TTY, prompt the operator for any unanswered custom-auth elicits
330
+ // through readline so a fresh project with no shelf auth can pass
331
+ // the requiresAppliedCapabilities gate by declaring what its own
332
+ // auth provides. Non-interactive runs skip the prompt and read
333
+ // answers from the bag only.
334
+ const stdin = deps.stdin ?? process.stdin;
335
+ const isTty = Boolean(stdin && stdin.isTTY);
336
+ const customAuthReadLine = isTty
337
+ ? await createCustomAuthReadLine({ stdin, stdout })
338
+ : null;
289
339
  const result = await applyBlueprint({
290
340
  projectRoot, tree, source,
291
341
  namespaceOverride: parsed.values.namespace,
342
+ allowNoAuthYet: parsed.values['allow-no-auth-yet'] === true,
343
+ elicitAnswers,
344
+ customAuthReadLine,
292
345
  // Library-qualified resolves rewire the applied identity under
293
346
  // the library prefix and forward the library's declared bands
294
347
  // for the apply-time gate (spec §5.3, §8.3). The qualified typed
@@ -321,6 +374,19 @@ export async function main(argv, deps = {}) {
321
374
  }
322
375
  }
323
376
  if (isRcfError(result)) {
377
+ // Capability-declaration refusal (visual round T-5 spec 5.5.1)
378
+ // is exit 3, and the message is spec-verbatim (built by
379
+ // buildRefusalMessage in blueprint/capabilities.js): print it
380
+ // as-is on stderr, no [error] prefix. Every other rcfError
381
+ // stays exit 2 with the [error] prefix.
382
+ if (result.kind === 'requiresAppliedCapabilities') {
383
+ if (parsed.values.json) {
384
+ stderr.write(`${JSON.stringify({ refused: true, error: { kind: result.kind, message: result.message } })}\n`);
385
+ } else {
386
+ stderr.write(`${result.message}\n`);
387
+ }
388
+ return 3;
389
+ }
324
390
  if (parsed.values.json) {
325
391
  stderr.write(`${JSON.stringify({ refused: true, error: { kind: result.kind, message: result.message } })}\n`);
326
392
  } else {
@@ -759,3 +825,29 @@ function parseResolveOptions(rawList, reason) {
759
825
  }
760
826
  return { value: out };
761
827
  }
828
+
829
+ /**
830
+ * Build a readline-backed prompt function for custom-auth capability
831
+ * elicits (visual round T-5 spec 5.5 "Risks"). Wraps node:readline in
832
+ * a promise so `runCustomAuthCapabilityElicits` can `await` each line.
833
+ * Only used when stdin is a TTY; the callback closes the interface on
834
+ * first shutdown to avoid leaking file descriptors.
835
+ *
836
+ * @param {{ stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream }} deps
837
+ * @returns {Promise<(prompt: string) => Promise<string>>}
838
+ */
839
+ async function createCustomAuthReadLine({ stdin, stdout }) {
840
+ const { createInterface } = await import('node:readline');
841
+ const rl = createInterface({ input: stdin, output: stdout, terminal: true });
842
+ let closed = false;
843
+ return (prompt) => new Promise((resolve) => {
844
+ if (closed) return resolve('');
845
+ rl.question(prompt, (answer) => {
846
+ if (!closed) {
847
+ try { rl.close(); } catch { /* fine */ }
848
+ closed = true;
849
+ }
850
+ resolve(answer);
851
+ });
852
+ });
853
+ }
@@ -18,13 +18,18 @@
18
18
 
19
19
  import { parseArgs } from 'node:util';
20
20
  import process from 'node:process';
21
+ import { isAbsolute, resolve } from 'node:path';
21
22
 
22
23
  import { walkTree } from '#core/store';
23
24
  import { writeUnexpectedFailure, rcfError } from '#core/errors';
24
25
 
25
26
  import { findProjectRoot } from '../view/index.js';
26
27
  import {
28
+ bootIfNeeded,
27
29
  composeOperatorSessionRecord,
30
+ createPackBrowser,
31
+ loadProbePacks,
32
+ pickBootFromPacks,
28
33
  runAgentScreenshotCritique,
29
34
  stubBrowserDriver,
30
35
  writeBrowserVerificationAck,
@@ -40,6 +45,9 @@ const OPTION_SPEC = {
40
45
  quiet: { type: 'boolean' },
41
46
  ack: { type: 'boolean' },
42
47
  notes: { type: 'string' },
48
+ 'probe-pack': { type: 'string' },
49
+ 'no-boot': { type: 'boolean' },
50
+ 'no-browser': { type: 'boolean' },
43
51
  help: { type: 'boolean' },
44
52
  };
45
53
 
@@ -61,6 +69,22 @@ Options:
61
69
  --quiet Suppress non-error confirmations.
62
70
  --ack Mark operatorAckAt on the latest record for
63
71
  this FBS (used to clear a warn verdict).
72
+ --probe-pack <name> Restrict this run to one blueprint-shipped
73
+ probe pack by packName. When omitted, every
74
+ discovered pack whose appliesTo() matches
75
+ this FBS runs. Refuses (exit 2) when the
76
+ name resolves to no discovered pack.
77
+ --no-browser Skip the real headless browser (probe packs
78
+ that call browser.* will see browser=null).
79
+ Default: provision a headless browser via
80
+ the pinned Playwright MCP so packs can drive
81
+ real DOM interactions.
82
+ --no-boot Do not run a pack's declared bootCommand
83
+ when the runtime URL is unreachable. Default:
84
+ when any pack declares a boot block and the
85
+ runtime is not answering, spawn bootCommand
86
+ from the project root, wait for waitForUrl
87
+ then waitForSelector, then run packs.
64
88
  --help Print this help.
65
89
 
66
90
  Exit codes:
@@ -163,13 +187,77 @@ export async function main(argv, deps = {}) {
163
187
  stderr.write('[error] usage browser-verify: no fetch available in the runtime; supply deps.fetch\n');
164
188
  return 2;
165
189
  }
166
- record = await runAgentScreenshotCritique({
167
- tree, fbs, runtimeUrl, runtimeProfile: profile,
168
- browserDriver, fetch: fetchFn,
169
- artefactDir: `.rcf/artefacts/${fbsId.toLowerCase()}`,
170
- critiqueNotes: flags.notes,
171
- now,
172
- });
190
+ // Probe-pack discovery: gather packs from every applied blueprint
191
+ // on the manifest, then apply --probe-pack filter if any.
192
+ const appliedBlueprints = readAppliedBlueprints({ manifest: tree.manifest, projectRoot });
193
+ const { packs, errors: packErrors } = await loadProbePacks({ appliedBlueprints, projectRoot });
194
+ if (packErrors.length > 0) {
195
+ for (const e of packErrors) stderr.write(`[error] usage browser-verify: probe-pack loader refused ${e.packAbsPath}: ${e.message}\n`);
196
+ return 2;
197
+ }
198
+ const packNameFilter = typeof flags['probe-pack'] === 'string' && flags['probe-pack'].length > 0
199
+ ? flags['probe-pack']
200
+ : undefined;
201
+ if (packNameFilter && !packs.some((p) => p.packName === packNameFilter)) {
202
+ const discovered = packs.map((p) => p.packName).sort();
203
+ stderr.write(`[error] usage browser-verify: --probe-pack '${packNameFilter}' resolves to no discovered pack. Discovered packs: ${discovered.length === 0 ? '(none)' : discovered.join(', ')}.\n`);
204
+ return 2;
205
+ }
206
+
207
+ const filteredPacks = packNameFilter
208
+ ? packs.filter((p) => p.packName === packNameFilter)
209
+ : packs;
210
+ const provisionBrowser = deps.createPackBrowser ?? createPackBrowser;
211
+ const bootProbe = deps.bootIfNeeded ?? bootIfNeeded;
212
+
213
+ let packBrowser = deps.packBrowser ?? null;
214
+ const cleanups = [];
215
+ try {
216
+ if (packBrowser === null && filteredPacks.length > 0 && !flags['no-browser']) {
217
+ packBrowser = await provisionBrowser({
218
+ projectRoot,
219
+ logger: (line) => { if (!flags.quiet) stderr.write(`${line}\n`); },
220
+ });
221
+ cleanups.push(async () => { try { await packBrowser.close(); } catch { /* fine */ } });
222
+ }
223
+
224
+ if (!flags['no-boot'] && filteredPacks.length > 0) {
225
+ const boot = pickBootFromPacks(filteredPacks);
226
+ if (boot) {
227
+ const bootOutcome = await bootProbe({
228
+ boot,
229
+ runtimeUrl,
230
+ projectRoot,
231
+ fetch: fetchFn,
232
+ browser: packBrowser,
233
+ logger: (line) => { if (!flags.quiet) stderr.write(`${line}\n`); },
234
+ });
235
+ for (const n of bootOutcome.notes) if (!flags.quiet) stderr.write(`[boot] ${n}\n`);
236
+ if (bootOutcome.started) {
237
+ cleanups.push(async () => { try { await bootOutcome.stop(); } catch { /* fine */ } });
238
+ }
239
+ }
240
+ }
241
+
242
+ record = await runAgentScreenshotCritique({
243
+ tree, fbs, runtimeUrl, runtimeProfile: profile,
244
+ browserDriver, fetch: fetchFn,
245
+ artefactDir: `.rcf/artefacts/${fbsId.toLowerCase()}`,
246
+ critiqueNotes: flags.notes,
247
+ probePacks: packs,
248
+ packBrowser,
249
+ packNameFilter,
250
+ projectRoot,
251
+ now,
252
+ });
253
+ } finally {
254
+ // LIFO cleanup: close browser before stopping the server so a
255
+ // page.close talking to a killed server never hangs the CLI.
256
+ while (cleanups.length > 0) {
257
+ const step = cleanups.pop();
258
+ try { await step(); } catch { /* fine */ }
259
+ }
260
+ }
173
261
  }
174
262
  } catch (err) {
175
263
  writeUnexpectedFailure(
@@ -203,6 +291,41 @@ export async function main(argv, deps = {}) {
203
291
  return record.verdict === 'pass' ? 0 : 4;
204
292
  }
205
293
 
294
+ /**
295
+ * Read applied blueprints off the manifest as an array of
296
+ * `{ slug, absPath }` suitable for `loadProbePacks`.
297
+ *
298
+ * The applied-blueprint record carries a `source` field (see
299
+ * `src/blueprint/apply.js`). For a shelf apply the source is the
300
+ * shelf-relative slug; for a local-path apply it is an absolute
301
+ * path; for a library apply it is `<library>:<slug>`. We resolve
302
+ * to the shelf path shipped alongside the CLI package or, in the
303
+ * monorepo, the repo-root shelf. Missing directories are skipped
304
+ * without error (the CLI later checks discovered vs. requested).
305
+ */
306
+ function readAppliedBlueprints({ manifest, projectRoot }) {
307
+ const list = Array.isArray(manifest?.blueprints) ? manifest.blueprints : [];
308
+ const out = [];
309
+ for (const entry of list) {
310
+ if (!entry || typeof entry.slug !== 'string' || entry.slug.length === 0) continue;
311
+ const absPath = resolveBlueprintAbsPath({ entry, projectRoot });
312
+ if (absPath) out.push({ slug: entry.slug, absPath });
313
+ }
314
+ return out;
315
+ }
316
+
317
+ function resolveBlueprintAbsPath({ entry, projectRoot }) {
318
+ const { slug, source } = entry;
319
+ if (typeof source === 'string' && source.length > 0) {
320
+ if (isAbsolute(source)) return source;
321
+ if (source.startsWith('./') || source.startsWith('../')) {
322
+ return resolve(projectRoot, source);
323
+ }
324
+ }
325
+ const cwd = resolve(projectRoot, 'blueprints', slug);
326
+ return cwd;
327
+ }
328
+
206
329
  function defaultUrlForProfile(profile) {
207
330
  if (profile === 'ci') return 'http://127.0.0.1:3000';
208
331
  if (profile === 'deployed') return '';
@@ -227,5 +350,22 @@ function renderSummary({ stdout, record }) {
227
350
  stdout.write(` [${c.verdict}] ${c.check} (status=${c.status ?? 'n/a'})${detail}\n`);
228
351
  }
229
352
  }
353
+ if (Array.isArray(record.probePacks) && record.probePacks.length > 0) {
354
+ stdout.write(' probe packs:\n');
355
+ for (const p of record.probePacks) {
356
+ if (p.applicable === false) {
357
+ stdout.write(` [skip] ${p.packName} (${p.blueprintSlug}): ${p.detail ?? 'not applicable to this FBS'}\n`);
358
+ continue;
359
+ }
360
+ for (const pc of p.preChecks ?? []) {
361
+ const detail = pc.detail ? ` - ${pc.detail}` : '';
362
+ stdout.write(` [${pc.verdict}] ${p.packName} pre-check ${pc.id}${detail}\n`);
363
+ }
364
+ for (const c of p.checks ?? []) {
365
+ const detail = c.detail ? ` - ${c.detail}` : '';
366
+ stdout.write(` [${c.verdict}] ${p.packName} ${c.id}${detail}\n`);
367
+ }
368
+ }
369
+ }
230
370
  stdout.write(` verdict: ${record.verdict}\n\n`);
231
371
  }
@@ -42,6 +42,13 @@ const VALID_KINDS = new Set([
42
42
  // individually schema-clean) and from `brokenReference` (nothing
43
43
  // dangles; the graph is over-connected, not under-connected).
44
44
  'duplicateId',
45
+ // Visual round T-5 (spec section 5.5.1): a consumer blueprint whose
46
+ // requiresAppliedCapabilities gate refuses because no applied
47
+ // blueprint declares any of the required capabilities AND the CLI
48
+ // did not pass the allow-skip flag. The apply verb returns this kind
49
+ // with the spec-verbatim refusal message; the CLI translates it to
50
+ // exit 3 (not exit 2) with the message printed as-is on stderr.
51
+ 'requiresAppliedCapabilities',
45
52
  ]);
46
53
 
47
54
  /**