rcf-lite 0.0.1 → 0.8.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 (349) hide show
  1. package/CHANGELOG.md +344 -0
  2. package/LICENSE +202 -0
  3. package/README.md +92 -4
  4. package/bin/rcf-verify.js +122 -0
  5. package/bin/rcf.js +174 -0
  6. package/bin/view-supervisor-child.mjs +14 -0
  7. package/fixtures/canary-manifest.json +103 -0
  8. package/guidance/README.md +25 -0
  9. package/guidance/build-cycle-playbook.md +492 -0
  10. package/guidance/build-cycle.md +40 -0
  11. package/guidance/document-model.md +45 -0
  12. package/guidance/elicitation-playbook.md +442 -0
  13. package/guidance/harness-template.md +246 -0
  14. package/guidance/managed/README.md +63 -0
  15. package/guidance/managed/agent-instructions-block.hash +1 -0
  16. package/guidance/managed/agent-instructions-block.md +220 -0
  17. package/guidance/managed/legacy-fragment-hashes.json +22 -0
  18. package/guidance/manifest.json +21 -0
  19. package/guidance/overview.md +48 -0
  20. package/guidance/persona-programme.md +44 -0
  21. package/package.json +71 -8
  22. package/rcf/adrs/adr-001.json +25 -0
  23. package/rcf/adrs/adr-002.json +25 -0
  24. package/rcf/adrs/adr-003.json +25 -0
  25. package/rcf/adrs/adr-004.json +20 -0
  26. package/rcf/adrs/adr-005.json +20 -0
  27. package/rcf/adrs/adr-006.json +25 -0
  28. package/rcf/adrs/adr-007.json +25 -0
  29. package/rcf/adrs/adr-008.json +25 -0
  30. package/rcf/adrs/adr-009.json +25 -0
  31. package/rcf/build-sequence.json +11 -0
  32. package/rcf/code-nodes/cn-001.json +14 -0
  33. package/rcf/code-nodes/cn-002.json +14 -0
  34. package/rcf/code-nodes/cn-003.json +15 -0
  35. package/rcf/code-nodes/cn-004.json +17 -0
  36. package/rcf/code-nodes/cn-005.json +19 -0
  37. package/rcf/code-nodes/cn-006.json +17 -0
  38. package/rcf/code-nodes/cn-007.json +16 -0
  39. package/rcf/code-nodes/cn-008.json +19 -0
  40. package/rcf/code-nodes/cn-009.json +16 -0
  41. package/rcf/code-nodes/cn-010.json +18 -0
  42. package/rcf/code-nodes/cn-011.json +18 -0
  43. package/rcf/code-nodes/cn-012.json +18 -0
  44. package/rcf/code-nodes/cn-013.json +19 -0
  45. package/rcf/code-nodes/cn-014.json +16 -0
  46. package/rcf/code-nodes/cn-015.json +16 -0
  47. package/rcf/code-nodes/cn-016.json +16 -0
  48. package/rcf/code-nodes/cn-017.json +16 -0
  49. package/rcf/code-nodes/cn-018.json +15 -0
  50. package/rcf/code-nodes/cn-019.json +16 -0
  51. package/rcf/code-nodes/cn-020.json +16 -0
  52. package/rcf/code-nodes/cn-021.json +16 -0
  53. package/rcf/code-nodes/cn-022.json +16 -0
  54. package/rcf/code-nodes/cn-023.json +16 -0
  55. package/rcf/code-nodes/cn-024.json +16 -0
  56. package/rcf/code-nodes/cn-025.json +16 -0
  57. package/rcf/code-nodes/cn-026.json +16 -0
  58. package/rcf/code-nodes/cn-027.json +16 -0
  59. package/rcf/code-nodes/cn-028.json +16 -0
  60. package/rcf/code-nodes/cn-029.json +19 -0
  61. package/rcf/code-nodes/cn-030.json +14 -0
  62. package/rcf/code-nodes/cn-031.json +14 -0
  63. package/rcf/code-nodes/cn-032.json +14 -0
  64. package/rcf/code-nodes/cn-033.json +14 -0
  65. package/rcf/code-nodes/cn-034.json +14 -0
  66. package/rcf/code-nodes/cn-035.json +14 -0
  67. package/rcf/code-nodes/cn-036.json +14 -0
  68. package/rcf/code-nodes/cn-037.json +14 -0
  69. package/rcf/code-nodes/cn-038.json +14 -0
  70. package/rcf/code-nodes/cn-039.json +14 -0
  71. package/rcf/code-nodes/cn-040.json +14 -0
  72. package/rcf/code-nodes/cn-041.json +14 -0
  73. package/rcf/code-nodes/cn-042.json +14 -0
  74. package/rcf/code-nodes/cn-043.json +14 -0
  75. package/rcf/code-nodes/cn-044.json +14 -0
  76. package/rcf/code-nodes/cn-045.json +14 -0
  77. package/rcf/code-nodes/cn-046.json +14 -0
  78. package/rcf/code-nodes/cn-047.json +14 -0
  79. package/rcf/code-nodes/cn-048.json +14 -0
  80. package/rcf/code-nodes/cn-049.json +14 -0
  81. package/rcf/code-nodes/cn-050.json +14 -0
  82. package/rcf/code-nodes/cn-051.json +14 -0
  83. package/rcf/code-nodes/cn-052.json +14 -0
  84. package/rcf/code-nodes/cn-053.json +14 -0
  85. package/rcf/code-nodes/cn-054.json +14 -0
  86. package/rcf/code-nodes/cn-055.json +14 -0
  87. package/rcf/code-nodes/cn-056.json +14 -0
  88. package/rcf/code-nodes/cn-057.json +14 -0
  89. package/rcf/fbs/fbs-001.json +49 -0
  90. package/rcf/fbs/fbs-002.json +42 -0
  91. package/rcf/fbs/fbs-003.json +37 -0
  92. package/rcf/fbs/fbs-004.json +39 -0
  93. package/rcf/fbs/fbs-005.json +38 -0
  94. package/rcf/fbs/fbs-006.json +48 -0
  95. package/rcf/fbs/fbs-007.json +39 -0
  96. package/rcf/fbs/fbs-008.json +40 -0
  97. package/rcf/fbs/fbs-009.json +36 -0
  98. package/rcf/fbs/fbs-010.json +41 -0
  99. package/rcf/fbs/fbs-011.json +36 -0
  100. package/rcf/fbs/fbs-012.json +46 -0
  101. package/rcf/fbs/fbs-013.json +42 -0
  102. package/rcf/fbs/fbs-014.json +49 -0
  103. package/rcf/fbs/fbs-015.json +32 -0
  104. package/rcf/manifest.json +17 -0
  105. package/rcf/prd.json +47 -0
  106. package/rcf/requirements/req-001.json +19 -0
  107. package/rcf/requirements/req-002.json +19 -0
  108. package/rcf/requirements/req-003.json +19 -0
  109. package/rcf/requirements/req-004.json +19 -0
  110. package/rcf/requirements/req-005.json +19 -0
  111. package/rcf/requirements/req-006.json +19 -0
  112. package/rcf/requirements/req-007.json +19 -0
  113. package/rcf/requirements/req-008.json +19 -0
  114. package/rcf/requirements/req-009.json +19 -0
  115. package/rcf/tacs/tac-001.json +45 -0
  116. package/rcf/tacs/tac-002.json +109 -0
  117. package/rcf/tacs/tac-003.json +40 -0
  118. package/rcf/tacs/tac-004.json +51 -0
  119. package/rcf/tacs/tac-005.json +52 -0
  120. package/rcf/tacs/tac-006.json +104 -0
  121. package/rcf/tacs/tac-007.json +38 -0
  122. package/rcf/tacs/tac-008.json +51 -0
  123. package/rcf/tad.json +51 -0
  124. package/rcf/test-suites/PENDING.md +23 -0
  125. package/rcf/test-suites/ts-001.json +38 -0
  126. package/rcf/test-suites/ts-002.json +38 -0
  127. package/rcf/test-suites/ts-003.json +43 -0
  128. package/rcf/test-suites/ts-004.json +44 -0
  129. package/rcf/test-suites/ts-005.json +30 -0
  130. package/rcf/test-suites/ts-006.json +36 -0
  131. package/rcf/test-suites/ts-007.json +43 -0
  132. package/rcf/test-suites/ts-008.json +37 -0
  133. package/rcf/test-suites/ts-009.json +38 -0
  134. package/rcf/test-suites/ts-010.json +38 -0
  135. package/rcf/test-suites/ts-011.json +44 -0
  136. package/rcf/test-suites/ts-012.json +36 -0
  137. package/rcf/test-suites/ts-013.json +38 -0
  138. package/rcf/test-suites/ts-014.json +38 -0
  139. package/rcf/test-suites/ts-015.json +38 -0
  140. package/rcf/test-suites/ts-016.json +37 -0
  141. package/rcf/test-suites/ts-017.json +38 -0
  142. package/rcf/test-suites/ts-018.json +38 -0
  143. package/rcf/test-suites/ts-019.json +37 -0
  144. package/rcf/test-suites/ts-020.json +46 -0
  145. package/rcf/test-suites/ts-021.json +46 -0
  146. package/rcf/test-suites/ts-022.json +46 -0
  147. package/rcf/test-suites/ts-023.json +46 -0
  148. package/rcf/test-suites/ts-024.json +46 -0
  149. package/rcf/test-suites/ts-025.json +52 -0
  150. package/rcf/user-stories/us-101.json +40 -0
  151. package/rcf/user-stories/us-102.json +40 -0
  152. package/rcf/user-stories/us-201.json +40 -0
  153. package/rcf/user-stories/us-202.json +40 -0
  154. package/rcf/user-stories/us-203.json +32 -0
  155. package/rcf/user-stories/us-301.json +40 -0
  156. package/rcf/user-stories/us-302.json +40 -0
  157. package/rcf/user-stories/us-303.json +40 -0
  158. package/rcf/user-stories/us-304.json +40 -0
  159. package/rcf/user-stories/us-401.json +40 -0
  160. package/rcf/user-stories/us-402.json +40 -0
  161. package/rcf/user-stories/us-403.json +40 -0
  162. package/rcf/user-stories/us-501.json +40 -0
  163. package/rcf/user-stories/us-502.json +40 -0
  164. package/rcf/user-stories/us-503.json +40 -0
  165. package/rcf/user-stories/us-601.json +40 -0
  166. package/rcf/user-stories/us-602.json +40 -0
  167. package/rcf/user-stories/us-701.json +40 -0
  168. package/rcf/user-stories/us-702.json +40 -0
  169. package/rcf/user-stories/us-801.json +49 -0
  170. package/rcf/user-stories/us-802.json +49 -0
  171. package/rcf/user-stories/us-803.json +49 -0
  172. package/rcf/user-stories/us-804.json +49 -0
  173. package/rcf/user-stories/us-805.json +49 -0
  174. package/rcf/user-stories/us-901.json +40 -0
  175. package/src/.gitkeep +0 -0
  176. package/src/admissibility/enforce.js +142 -0
  177. package/src/admissibility/index.js +8 -0
  178. package/src/admissibility/markers.js +104 -0
  179. package/src/admissibility/scope-lint.js +163 -0
  180. package/src/browser-verify/auth-smoke.js +109 -0
  181. package/src/browser-verify/index.js +29 -0
  182. package/src/browser-verify/invariants.js +336 -0
  183. package/src/browser-verify/manifest-writer.js +189 -0
  184. package/src/browser-verify/runner.js +171 -0
  185. package/src/build/bundle.js +198 -0
  186. package/src/build/formatters/json.js +18 -0
  187. package/src/build/formatters/markdown.js +406 -0
  188. package/src/build/index.js +14 -0
  189. package/src/build/mark.js +177 -0
  190. package/src/build/queue.js +285 -0
  191. package/src/cli/browser-verify.js +231 -0
  192. package/src/cli/build.js +584 -0
  193. package/src/cli/coverage.js +219 -0
  194. package/src/cli/create.js +358 -0
  195. package/src/cli/delete.js +127 -0
  196. package/src/cli/design.js +353 -0
  197. package/src/cli/doctor.js +548 -0
  198. package/src/cli/fbs.js +171 -0
  199. package/src/cli/finalise.js +377 -0
  200. package/src/cli/guidance.js +206 -0
  201. package/src/cli/help.js +156 -0
  202. package/src/cli/impact.js +119 -0
  203. package/src/cli/init.js +282 -0
  204. package/src/cli/intake.js +153 -0
  205. package/src/cli/link.js +128 -0
  206. package/src/cli/mcp.js +160 -0
  207. package/src/cli/preflight.js +220 -0
  208. package/src/cli/read.js +168 -0
  209. package/src/cli/req-baseline.js +269 -0
  210. package/src/cli/req-classify.js +135 -0
  211. package/src/cli/review.js +295 -0
  212. package/src/cli/test-suite.js +221 -0
  213. package/src/cli/trace.js +172 -0
  214. package/src/cli/ui-baseline.js +292 -0
  215. package/src/cli/ui-classify.js +108 -0
  216. package/src/cli/update.js +197 -0
  217. package/src/cli/validate.js +168 -0
  218. package/src/cli/view.js +452 -0
  219. package/src/core/baseline-catalog/data/auth.json +42 -0
  220. package/src/core/baseline-catalog/data/http-api.json +42 -0
  221. package/src/core/baseline-catalog/data/notifications.json +33 -0
  222. package/src/core/baseline-catalog/data/persistence.json +33 -0
  223. package/src/core/baseline-catalog/data/web-ui.json +60 -0
  224. package/src/core/baseline-catalog/index.js +121 -0
  225. package/src/core/errors/index.js +167 -0
  226. package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
  227. package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
  228. package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
  229. package/src/core/isolation/index.js +60 -0
  230. package/src/core/mcp/framing.js +103 -0
  231. package/src/core/mcp/index.js +8 -0
  232. package/src/core/mcp/server.js +228 -0
  233. package/src/core/patterns/register-canary.js +209 -0
  234. package/src/core/patterns/req-shapes.js +158 -0
  235. package/src/core/patterns/services.js +358 -0
  236. package/src/core/patterns/ui-shapes.js +166 -0
  237. package/src/core/store/cn-resolve.js +134 -0
  238. package/src/core/store/derive-deps.js +93 -0
  239. package/src/core/store/ids.js +78 -0
  240. package/src/core/store/index.js +20 -0
  241. package/src/core/store/init.js +255 -0
  242. package/src/core/store/loader.js +214 -0
  243. package/src/core/store/tp-resolve.js +176 -0
  244. package/src/core/store/validator.js +191 -0
  245. package/src/core/store/walker.js +944 -0
  246. package/src/core/store/writer.js +1879 -0
  247. package/src/design/index.js +11 -0
  248. package/src/design/writer.js +271 -0
  249. package/src/finalise/detect.js +151 -0
  250. package/src/finalise/index.js +31 -0
  251. package/src/finalise/ingest.js +160 -0
  252. package/src/finalise/install.js +119 -0
  253. package/src/finalise/ship-without-verified.js +131 -0
  254. package/src/finalise/spawn.js +84 -0
  255. package/src/intake/fidelity.js +105 -0
  256. package/src/intake/index.js +6 -0
  257. package/src/intake/manifest-writer.js +100 -0
  258. package/src/intake/orchestrator.js +138 -0
  259. package/src/intake/validate.js +80 -0
  260. package/src/mcp/map-errors.js +131 -0
  261. package/src/mcp/prompts.js +49 -0
  262. package/src/mcp/resources.js +244 -0
  263. package/src/mcp/tools.js +1212 -0
  264. package/src/preflight/design-shapes.js +185 -0
  265. package/src/preflight/index.js +90 -0
  266. package/src/preflight/manifest-writer.js +264 -0
  267. package/src/preflight/scanner.js +206 -0
  268. package/src/preflight/secrets.js +134 -0
  269. package/src/preflight/session.js +246 -0
  270. package/src/query/attestation.js +285 -0
  271. package/src/query/coverage.js +308 -0
  272. package/src/query/formatters/json.js +21 -0
  273. package/src/query/formatters/mermaid.js +209 -0
  274. package/src/query/formatters/table.js +203 -0
  275. package/src/query/impact.js +173 -0
  276. package/src/query/index.js +13 -0
  277. package/src/query/refuse-on-admissibility.js +73 -0
  278. package/src/query/trace.js +345 -0
  279. package/src/register-canary/fixture-loader.js +87 -0
  280. package/src/register-canary/index.js +10 -0
  281. package/src/register-canary/record-writer.js +132 -0
  282. package/src/register-canary/runner.js +156 -0
  283. package/src/req-baseline/gate.js +86 -0
  284. package/src/req-baseline/index.js +27 -0
  285. package/src/req-baseline/open-candidates.js +143 -0
  286. package/src/req-baseline/opt-out.js +195 -0
  287. package/src/req-baseline/sweep.js +230 -0
  288. package/src/req-detection/classifier.js +181 -0
  289. package/src/req-detection/index.js +9 -0
  290. package/src/req-detection/persist.js +55 -0
  291. package/src/review/index.js +325 -0
  292. package/src/review/mutation.js +117 -0
  293. package/src/review/ui-baseline-drift.js +138 -0
  294. package/src/ruleset/index.js +140 -0
  295. package/src/ruleset/ruleset.json +146 -0
  296. package/src/server/index.js +178 -0
  297. package/src/server/routes.js +110 -0
  298. package/src/server/sse.js +118 -0
  299. package/src/setup/agent-setup.js +362 -0
  300. package/src/setup/identity-seed.js +104 -0
  301. package/src/setup/knowledge-seed.js +123 -0
  302. package/src/setup/managed-block.js +193 -0
  303. package/src/setup/managed-gitignore.js +166 -0
  304. package/src/setup/managed-markers.js +49 -0
  305. package/src/ui-baseline/defaults.js +119 -0
  306. package/src/ui-baseline/index.js +25 -0
  307. package/src/ui-baseline/manifest-writer.js +282 -0
  308. package/src/ui-baseline/session.js +178 -0
  309. package/src/ui-detection/classifier.js +192 -0
  310. package/src/verify/chain/index.js +221 -0
  311. package/src/verify/cli/cleanup.js +61 -0
  312. package/src/verify/cli/help.js +56 -0
  313. package/src/verify/cli/mcp.js +98 -0
  314. package/src/verify/cli/provision.js +71 -0
  315. package/src/verify/cli/report.js +71 -0
  316. package/src/verify/cli/run.js +155 -0
  317. package/src/verify/engine/brief.js +87 -0
  318. package/src/verify/engine/index.js +177 -0
  319. package/src/verify/engine/launcher.js +307 -0
  320. package/src/verify/mcp/tools.js +107 -0
  321. package/src/verify/profile/index.js +146 -0
  322. package/src/verify/provision/index.js +256 -0
  323. package/src/verify/report/index.js +139 -0
  324. package/src/verify/report/renderer.js +118 -0
  325. package/src/verify/verdict/index.js +313 -0
  326. package/src/view/doc-renderers/adr.js +44 -0
  327. package/src/view/doc-renderers/build-sequence.js +40 -0
  328. package/src/view/doc-renderers/fbs.js +128 -0
  329. package/src/view/doc-renderers/helpers.js +159 -0
  330. package/src/view/doc-renderers/index.js +12 -0
  331. package/src/view/doc-renderers/prd.js +45 -0
  332. package/src/view/doc-renderers/req.js +43 -0
  333. package/src/view/doc-renderers/tac.js +38 -0
  334. package/src/view/doc-renderers/tad.js +74 -0
  335. package/src/view/doc-renderers/test-suite.js +45 -0
  336. package/src/view/doc-renderers/user-story.js +63 -0
  337. package/src/view/html-page.js +462 -0
  338. package/src/view/index.js +63 -0
  339. package/src/view/live-client.js +338 -0
  340. package/src/view/mermaid-diagram.js +178 -0
  341. package/src/view/style.css +735 -0
  342. package/src/view/tree-model.js +152 -0
  343. package/src/view/vendored/mermaid.min.js +2607 -0
  344. package/src/view-supervisor/index.js +26 -0
  345. package/src/view-supervisor/logs.js +32 -0
  346. package/src/view-supervisor/manifest-writer.js +178 -0
  347. package/src/view-supervisor/persist-until.js +85 -0
  348. package/src/view-supervisor/supervisor.js +276 -0
  349. package/src/watch/index.js +152 -0
@@ -0,0 +1,104 @@
1
+ // Source-comment marker scanner (NV-BL-ADM-04).
2
+ //
3
+ // Scans a set of source files for admission markers from the ratified
4
+ // vocabulary carried on the shared standards ruleset
5
+ // (`sourceCommentMarkers[]`). Any match is a finding. Per ratified
6
+ // ruling-sheet item 16, the only permitted override channel is an ADR
7
+ // recording the deferral for a genuine external-boundary blocker; the
8
+ // generic recorded-override channel (NV-BL-ADM-05) does not cover
9
+ // source markers.
10
+ //
11
+ // This module produces findings only. Wiring the gate into
12
+ // `rcf build --mark complete` is a build-lite verb change that reads
13
+ // the FBS's build-sequence files and calls `scanSourceForMarkers` for
14
+ // each; the CLI change belongs in that verb's PR, not here.
15
+
16
+ import { readFile } from 'node:fs/promises';
17
+
18
+ import { rcfError } from '#core/errors';
19
+ import { getRuleset } from '#ruleset';
20
+
21
+ /**
22
+ * Compile the ruleset's marker vocabulary into a single case-insensitive
23
+ * pattern. Markers are ratified as case-insensitive per NV-BL-ADM-04, so
24
+ * we build the pattern with the `i` flag. Longest markers first so
25
+ * "v1 refinement" wins over "v1" if a shorter marker is ever added to
26
+ * the vocabulary in future releases.
27
+ *
28
+ * @param {Array<{ marker: string, caseInsensitive: boolean }>} markers
29
+ * @returns {RegExp}
30
+ */
31
+ function compileMarkerPattern(markers) {
32
+ const sorted = [...markers].sort((a, b) => b.marker.length - a.marker.length);
33
+ const alternation = sorted
34
+ .map((m) => m.marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
35
+ .join('|');
36
+ // Boundary-free by design: matches a marker anywhere in a comment
37
+ // ("PLACEHOLDER function" matches; "// placeholder" matches; a random
38
+ // word like "todos" also matches, which is the trade-off named in the
39
+ // ruleset's NV-BL-ADM-04 notes -- false positives on unrelated tokens
40
+ // are cheaper than false negatives on real deferrals.
41
+ return new RegExp(`(${alternation})`, 'ig');
42
+ }
43
+
44
+ /**
45
+ * Scan a single source string for the ratified marker vocabulary.
46
+ * Returns one finding per match, with line + column offsets. Empty
47
+ * input returns an empty findings array.
48
+ *
49
+ * @param {string} source
50
+ * @param {object} [opts]
51
+ * @param {string} [opts.filePath] - decorates each finding for callers
52
+ * @param {Array<{ marker: string, caseInsensitive: boolean }>} [opts.markers]
53
+ * @returns {Promise<import('#core/errors').RcfError[]>}
54
+ */
55
+ export async function scanSourceStringForMarkers(source, { filePath, markers } = {}) {
56
+ if (typeof source !== 'string' || source.length === 0) return [];
57
+ const vocab = markers ?? (await getRuleset()).sourceCommentMarkers;
58
+ const pattern = compileMarkerPattern(vocab);
59
+ const findings = [];
60
+ let match;
61
+ while ((match = pattern.exec(source)) !== null) {
62
+ const before = source.slice(0, match.index);
63
+ const line = before.split('\n').length;
64
+ const lastNewline = before.lastIndexOf('\n');
65
+ const column = match.index - (lastNewline === -1 ? -1 : lastNewline);
66
+ findings.push(rcfError({
67
+ kind: 'validation',
68
+ message: `NV-BL-ADM-04: source-comment admission marker "${match[1]}" at ${filePath ?? '<source>'}:${line}:${column}`,
69
+ filePath: filePath ?? null,
70
+ rule: 'NV-BL-ADM-04',
71
+ }));
72
+ }
73
+ return findings;
74
+ }
75
+
76
+ /**
77
+ * Scan a set of files by path for the ratified marker vocabulary. IO
78
+ * failures on a single file are captured as `ioFailure` findings; other
79
+ * files continue to scan. Order is deterministic in the input file list.
80
+ *
81
+ * @param {string[]} filePaths - absolute paths to scan
82
+ * @returns {Promise<import('#core/errors').RcfError[]>}
83
+ */
84
+ export async function scanFilesForMarkers(filePaths) {
85
+ if (!Array.isArray(filePaths) || filePaths.length === 0) return [];
86
+ const ruleset = await getRuleset();
87
+ const vocab = ruleset.sourceCommentMarkers;
88
+ const findings = [];
89
+ for (const filePath of filePaths) {
90
+ try {
91
+ const source = await readFile(filePath, 'utf8');
92
+ const perFile = await scanSourceStringForMarkers(source, { filePath, markers: vocab });
93
+ findings.push(...perFile);
94
+ } catch (err) {
95
+ findings.push(rcfError({
96
+ kind: 'ioFailure',
97
+ message: `NV-BL-ADM-04: failed to read source for marker scan: ${err.message}`,
98
+ filePath,
99
+ rule: 'NV-BL-ADM-04',
100
+ }));
101
+ }
102
+ }
103
+ return findings;
104
+ }
@@ -0,0 +1,163 @@
1
+ // Chain-admissibility scope checks (NV-BL-ADM-02, NV-BL-ADM-03).
2
+ //
3
+ // The scope-tag vocabulary itself lives on rcf-schemas 0.4.3
4
+ // (`common.$defs.scopeTag`). This module consumes the tags off ACs
5
+ // and TCs and enforces the admissibility rules the shared standards
6
+ // ruleset references (`NV-BL-ADM-02` for AC scope classification and
7
+ // `NV-BL-ADM-03` for TC scope >= AC scope).
8
+ //
9
+ // Refuse-first, override-recorded per NV-BL-ADM-05; source-comment
10
+ // markers are governed separately by NV-BL-ADM-04 and its ADR-only
11
+ // override channel.
12
+
13
+ import { rcfError } from '#core/errors';
14
+
15
+ /**
16
+ * Rank each scope so we can compare "TC scope >= AC scope" numerically.
17
+ * `unclassified` is the migration state; ranked -1 so a TC-scoped
18
+ * unclassified against a runtime-scope AC surfaces as a mismatch.
19
+ * @type {Record<string, number>}
20
+ */
21
+ const SCOPE_RANK = Object.freeze({
22
+ library: 1,
23
+ runtime: 2,
24
+ deployed: 3,
25
+ unclassified: -1,
26
+ });
27
+
28
+ /**
29
+ * True when the tag is a known scope value per the shared vocabulary.
30
+ * @param {unknown} tag
31
+ * @returns {boolean}
32
+ */
33
+ function isKnownScope(tag) {
34
+ return typeof tag === 'string' && Object.prototype.hasOwnProperty.call(SCOPE_RANK, tag);
35
+ }
36
+
37
+ /**
38
+ * NV-BL-ADM-02: every AC must carry a known scope tag. An AC without a
39
+ * scope, or with a non-vocabulary value, produces an admissibility
40
+ * finding. Per the ruleset's `unclassifiedMigrationTolerance` block,
41
+ * an AC scoped `unclassified` is currently tolerated (findings not
42
+ * emitted); callers who want to enforce full migration pass
43
+ * `tolerateUnclassified: false`.
44
+ *
45
+ * @param {object} tree - walkTree output
46
+ * @param {object} [opts]
47
+ * @param {boolean} [opts.tolerateUnclassified] - default true (migration state)
48
+ * @returns {import('#core/errors').RcfError[]}
49
+ */
50
+ export function scanAcScopeCoverage(tree, { tolerateUnclassified = true } = {}) {
51
+ const findings = [];
52
+ for (const us of tree.userStories ?? []) {
53
+ for (const ac of us.acceptanceCriteria ?? []) {
54
+ const scope = ac?.scope;
55
+ if (scope === undefined) {
56
+ findings.push(rcfError({
57
+ kind: 'validation',
58
+ message: `NV-BL-ADM-02: AC ${ac.id} on US ${us.usId} carries no scope tag`,
59
+ documentId: ac.id,
60
+ field: 'scope',
61
+ rule: 'NV-BL-ADM-02',
62
+ }));
63
+ continue;
64
+ }
65
+ if (!isKnownScope(scope)) {
66
+ findings.push(rcfError({
67
+ kind: 'validation',
68
+ message: `NV-BL-ADM-02: AC ${ac.id} on US ${us.usId} carries an unknown scope tag "${scope}"`,
69
+ documentId: ac.id,
70
+ field: 'scope',
71
+ rule: 'NV-BL-ADM-02',
72
+ }));
73
+ continue;
74
+ }
75
+ if (scope === 'unclassified' && !tolerateUnclassified) {
76
+ findings.push(rcfError({
77
+ kind: 'validation',
78
+ message: `NV-BL-ADM-02: AC ${ac.id} on US ${us.usId} still scoped "unclassified" after the migration window`,
79
+ documentId: ac.id,
80
+ field: 'scope',
81
+ rule: 'NV-BL-ADM-02',
82
+ }));
83
+ }
84
+ }
85
+ }
86
+ return findings;
87
+ }
88
+
89
+ /**
90
+ * NV-BL-ADM-03: for each AC, every bound TC's scope must be equal to
91
+ * or wider than the AC's scope. A library-scope TC bound to a
92
+ * runtime-scope AC surfaces as a mismatch. TCs with no scope tag are
93
+ * flagged the same way ACs are in NV-BL-ADM-02: absent = finding
94
+ * (bootstrap: unclassified tolerated).
95
+ *
96
+ * @param {object} tree - walkTree output
97
+ * @param {object} [opts]
98
+ * @param {boolean} [opts.tolerateUnclassified] - default true
99
+ * @returns {import('#core/errors').RcfError[]}
100
+ */
101
+ export function scanTcScopeVsAc(tree, { tolerateUnclassified = true } = {}) {
102
+ const findings = [];
103
+ const acScope = new Map();
104
+ for (const us of tree.userStories ?? []) {
105
+ for (const ac of us.acceptanceCriteria ?? []) {
106
+ if (ac?.id) acScope.set(ac.id, ac.scope);
107
+ }
108
+ }
109
+ for (const ts of tree.testSuites ?? []) {
110
+ for (const tc of ts.testCases ?? []) {
111
+ if (!tc?.id || !tc?.acId) continue;
112
+ const tcScope = tc?.scope;
113
+ const acTag = acScope.get(tc.acId);
114
+ if (acTag === undefined || !isKnownScope(acTag) || acTag === 'unclassified') {
115
+ // The AC's own scope problem surfaces via scanAcScopeCoverage;
116
+ // this scan is silent for ACs the classifier could not read.
117
+ continue;
118
+ }
119
+ if (tcScope === undefined) {
120
+ findings.push(rcfError({
121
+ kind: 'validation',
122
+ message: `NV-BL-ADM-03: TC ${tc.id} on TS ${ts.id} carries no scope tag (bound AC ${tc.acId} is scope=${acTag})`,
123
+ documentId: tc.id,
124
+ field: 'scope',
125
+ rule: 'NV-BL-ADM-03',
126
+ }));
127
+ continue;
128
+ }
129
+ if (!isKnownScope(tcScope)) {
130
+ findings.push(rcfError({
131
+ kind: 'validation',
132
+ message: `NV-BL-ADM-03: TC ${tc.id} on TS ${ts.id} carries an unknown scope tag "${tcScope}"`,
133
+ documentId: tc.id,
134
+ field: 'scope',
135
+ rule: 'NV-BL-ADM-03',
136
+ }));
137
+ continue;
138
+ }
139
+ if (tcScope === 'unclassified') {
140
+ if (!tolerateUnclassified) {
141
+ findings.push(rcfError({
142
+ kind: 'validation',
143
+ message: `NV-BL-ADM-03: TC ${tc.id} on TS ${ts.id} still scoped "unclassified" after the migration window`,
144
+ documentId: tc.id,
145
+ field: 'scope',
146
+ rule: 'NV-BL-ADM-03',
147
+ }));
148
+ }
149
+ continue;
150
+ }
151
+ if (SCOPE_RANK[tcScope] < SCOPE_RANK[acTag]) {
152
+ findings.push(rcfError({
153
+ kind: 'validation',
154
+ message: `NV-BL-ADM-03: TC ${tc.id} scope "${tcScope}" is narrower than the AC ${tc.acId} scope "${acTag}"; a bound TC must be equal to or wider than the AC scope`,
155
+ documentId: tc.id,
156
+ field: 'scope',
157
+ rule: 'NV-BL-ADM-03',
158
+ }));
159
+ }
160
+ }
161
+ }
162
+ return findings;
163
+ }
@@ -0,0 +1,109 @@
1
+ // Auth-REQ smoke checks (ui-design-gate-0.7.0-spec §9).
2
+ //
3
+ // A small pack of curl-shape checks that run at Finalise for any FBS
4
+ // binding an auth REQ. Composes with Track A's preflight rec 3
5
+ // (`uiBaseline.defaults.authFlow.htmlLoginPageRequired`); when the
6
+ // baseline says API-only, the pack is skipped.
7
+ //
8
+ // Directly addresses the cold-run defect where `/login` returned 404
9
+ // for the full run. Implemented as fetch-shaped checks against a
10
+ // runtime URL; the runner (injectable) is the ambient `fetch` by
11
+ // default. Callers pass `runtimeUrl` (e.g. http://127.0.0.1:3000).
12
+
13
+ /**
14
+ * @typedef {object} AuthSmokeResult
15
+ * @property {string} check
16
+ * @property {number} [status]
17
+ * @property {string} [contentType]
18
+ * @property {'pass'|'warn'|'fail'} verdict
19
+ * @property {string} [detail]
20
+ */
21
+
22
+ /**
23
+ * @typedef {object} AuthSmokeRunnerInput
24
+ * @property {(url: string, init?: object) => Promise<{ status: number, headers: { get: (name: string) => string|null } }>} fetch
25
+ * @property {string} runtimeUrl
26
+ */
27
+
28
+ const HTML_CONTENT_TYPE_RE = /^text\/html\b/i;
29
+
30
+ /**
31
+ * True when a FBS should run the auth smoke pack. Reads (in priority
32
+ * order) the FBS's own dependsOnServices auth categories, then falls
33
+ * back to the manifest's uiBaseline.defaults.authFlow.smokeChecksRequired.
34
+ *
35
+ * @param {object} fbs
36
+ * @param {object|null} uiBaseline
37
+ * @returns {boolean}
38
+ */
39
+ export function shouldRunAuthSmokeChecks(fbs, uiBaseline) {
40
+ const services = Array.isArray(fbs?.dependsOnServices) ? fbs.dependsOnServices : [];
41
+ const authCategories = new Set(['auth', 'oauth', 'identityProvider', 'emailAuth']);
42
+ const bindsAuth = services.some((s) => authCategories.has(String(s?.serviceCategory ?? '')));
43
+ const baselineRequires = uiBaseline?.defaults?.authFlow?.htmlLoginPageRequired === true
44
+ && uiBaseline?.defaults?.authFlow?.smokeChecksRequired !== false;
45
+ return bindsAuth || baselineRequires;
46
+ }
47
+
48
+ /**
49
+ * Run the smoke pack against `runtimeUrl`. Returns one result per check.
50
+ *
51
+ * @param {AuthSmokeRunnerInput} args
52
+ * @returns {Promise<AuthSmokeResult[]>}
53
+ */
54
+ export async function runAuthSmokeChecks({ fetch, runtimeUrl }) {
55
+ /** @type {AuthSmokeResult[]} */
56
+ const results = [];
57
+ results.push(await runOne({
58
+ check: 'GET /login', fetch,
59
+ request: () => fetch(joinUrl(runtimeUrl, '/login')),
60
+ accept: (status, contentType) => {
61
+ if (status !== 200) return { verdict: 'fail', detail: `status ${status} (expected 200)` };
62
+ if (!HTML_CONTENT_TYPE_RE.test(contentType ?? '')) return { verdict: 'fail', detail: `content-type '${contentType ?? '(missing)'}' (expected text/html)` };
63
+ return { verdict: 'pass' };
64
+ },
65
+ }));
66
+ results.push(await runOne({
67
+ check: 'POST /logout', fetch,
68
+ request: () => fetch(joinUrl(runtimeUrl, '/logout'), { method: 'POST', redirect: 'manual' }),
69
+ accept: (status) => {
70
+ if (status === 200 || status === 302 || status === 303) return { verdict: 'pass' };
71
+ return { verdict: 'fail', detail: `status ${status} (expected 200 | 302 | 303)` };
72
+ },
73
+ }));
74
+ results.push(await runOne({
75
+ check: 'GET /login/verify?token=',
76
+ fetch,
77
+ request: () => fetch(joinUrl(runtimeUrl, '/login/verify?token='), { redirect: 'manual' }),
78
+ accept: (status) => {
79
+ if (status === 400 || status === 401 || status === 403) return { verdict: 'pass' };
80
+ if (status === 302 || status === 303) return { verdict: 'pass' };
81
+ if (status === 200) return { verdict: 'fail', detail: 'status 200 (empty-token accepted; regression on the empty-token vuln)' };
82
+ return { verdict: 'fail', detail: `status ${status} (expected 400 | 401 | 403 | 302 | 303)` };
83
+ },
84
+ }));
85
+ return results;
86
+ }
87
+
88
+ async function runOne({ check, request, accept }) {
89
+ try {
90
+ const response = await request();
91
+ const status = response?.status;
92
+ const contentType = response?.headers?.get ? response.headers.get('content-type') : null;
93
+ const outcome = accept(status, contentType);
94
+ return {
95
+ check,
96
+ status,
97
+ ...(contentType ? { contentType } : {}),
98
+ verdict: outcome.verdict,
99
+ ...(outcome.detail ? { detail: outcome.detail } : {}),
100
+ };
101
+ } catch (err) {
102
+ return { check, verdict: 'fail', detail: `request failed: ${err.message}` };
103
+ }
104
+ }
105
+
106
+ function joinUrl(base, path) {
107
+ const trimmed = base.replace(/\/+$/, '');
108
+ return `${trimmed}${path.startsWith('/') ? path : `/${path}`}`;
109
+ }
@@ -0,0 +1,29 @@
1
+ // Public surface for the browser-verify module
2
+ // (ui-design-gate-0.7.0-spec §8, §9). CLI handler at
3
+ // `src/cli/browser-verify.js`.
4
+
5
+ export {
6
+ UI_INVARIANTS_V1,
7
+ compareTopLevelStructure,
8
+ foldInvariantsForRecord,
9
+ runInvariantsForCapture,
10
+ } from './invariants.js';
11
+
12
+ export {
13
+ runAuthSmokeChecks,
14
+ shouldRunAuthSmokeChecks,
15
+ } from './auth-smoke.js';
16
+
17
+ export {
18
+ aggregateVerdict,
19
+ composeBrowserVerificationRecord,
20
+ nextBrowserVerificationId,
21
+ writeBrowserVerificationAck,
22
+ writeBrowserVerificationRecord,
23
+ } from './manifest-writer.js';
24
+
25
+ export {
26
+ composeOperatorSessionRecord,
27
+ runAgentScreenshotCritique,
28
+ stubBrowserDriver,
29
+ } from './runner.js';