@starci/skills 1.1.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 (321) hide show
  1. package/INDEX.md +74 -0
  2. package/INDEX.vi.md +75 -0
  3. package/README.md +44 -0
  4. package/README.vi.md +43 -0
  5. package/SKILL.md +135 -0
  6. package/SKILL.vi.md +128 -0
  7. package/alias/INDEX.md +104 -0
  8. package/alias/INDEX.vi.md +104 -0
  9. package/alias/alias.json +297 -0
  10. package/bin/starci-skills.mjs +219 -0
  11. package/knowledge/INDEX.md +22 -0
  12. package/knowledge/INDEX.vi.md +22 -0
  13. package/knowledge/grammars/starci/DNA.md +146 -0
  14. package/knowledge/grammars/starci/DNA.vi.md +146 -0
  15. package/knowledge/grammars/starci/INDEX.md +25 -0
  16. package/knowledge/grammars/starci/INDEX.vi.md +25 -0
  17. package/knowledge/grammars/starci/family.md +50 -0
  18. package/knowledge/grammars/starci/family.vi.md +50 -0
  19. package/knowledge/grammars/starci/idioms.md +120 -0
  20. package/knowledge/grammars/starci/idioms.vi.md +121 -0
  21. package/knowledge/grammars/starci/playbook.md +36 -0
  22. package/knowledge/grammars/starci/playbook.vi.md +35 -0
  23. package/knowledge/patterns/be/INDEX.md +29 -0
  24. package/knowledge/patterns/be/INDEX.vi.md +29 -0
  25. package/knowledge/patterns/be/comment.md +80 -0
  26. package/knowledge/patterns/be/comment.vi.md +80 -0
  27. package/knowledge/patterns/be/error.md +86 -0
  28. package/knowledge/patterns/be/error.vi.md +86 -0
  29. package/knowledge/patterns/be/folder.md +87 -0
  30. package/knowledge/patterns/be/folder.vi.md +86 -0
  31. package/knowledge/patterns/be/function.md +80 -0
  32. package/knowledge/patterns/be/function.vi.md +80 -0
  33. package/knowledge/patterns/be/imports.md +79 -0
  34. package/knowledge/patterns/be/imports.vi.md +79 -0
  35. package/knowledge/patterns/be/naming.md +87 -0
  36. package/knowledge/patterns/be/naming.vi.md +87 -0
  37. package/knowledge/patterns/be/test.md +79 -0
  38. package/knowledge/patterns/be/test.vi.md +79 -0
  39. package/knowledge/patterns/be/typing.md +73 -0
  40. package/knowledge/patterns/be/typing.vi.md +73 -0
  41. package/knowledge/patterns/fe/INDEX.md +29 -0
  42. package/knowledge/patterns/fe/INDEX.vi.md +29 -0
  43. package/knowledge/patterns/fe/comment.md +70 -0
  44. package/knowledge/patterns/fe/comment.vi.md +70 -0
  45. package/knowledge/patterns/fe/error.md +61 -0
  46. package/knowledge/patterns/fe/error.vi.md +61 -0
  47. package/knowledge/patterns/fe/folder.md +98 -0
  48. package/knowledge/patterns/fe/folder.vi.md +95 -0
  49. package/knowledge/patterns/fe/function.md +70 -0
  50. package/knowledge/patterns/fe/function.vi.md +70 -0
  51. package/knowledge/patterns/fe/imports.md +87 -0
  52. package/knowledge/patterns/fe/imports.vi.md +87 -0
  53. package/knowledge/patterns/fe/naming.md +77 -0
  54. package/knowledge/patterns/fe/naming.vi.md +77 -0
  55. package/knowledge/patterns/fe/test.md +73 -0
  56. package/knowledge/patterns/fe/test.vi.md +73 -0
  57. package/knowledge/patterns/fe/typing.md +67 -0
  58. package/knowledge/patterns/fe/typing.vi.md +67 -0
  59. package/knowledge/ui/INDEX.md +101 -0
  60. package/knowledge/ui/INDEX.vi.md +101 -0
  61. package/knowledge/ui/composition/INDEX.md +60 -0
  62. package/knowledge/ui/composition/INDEX.vi.md +63 -0
  63. package/knowledge/ui/composition/accent.md +73 -0
  64. package/knowledge/ui/composition/accent.vi.md +74 -0
  65. package/knowledge/ui/composition/action.md +55 -0
  66. package/knowledge/ui/composition/action.vi.md +56 -0
  67. package/knowledge/ui/composition/coverage.md +37 -0
  68. package/knowledge/ui/composition/coverage.vi.md +37 -0
  69. package/knowledge/ui/composition/cta.md +79 -0
  70. package/knowledge/ui/composition/cta.vi.md +79 -0
  71. package/knowledge/ui/composition/feedback.md +55 -0
  72. package/knowledge/ui/composition/feedback.vi.md +56 -0
  73. package/knowledge/ui/composition/hierarchy.md +83 -0
  74. package/knowledge/ui/composition/hierarchy.vi.md +82 -0
  75. package/knowledge/ui/composition/layout.md +91 -0
  76. package/knowledge/ui/composition/layout.vi.md +93 -0
  77. package/knowledge/ui/composition/responsive.md +67 -0
  78. package/knowledge/ui/composition/responsive.vi.md +67 -0
  79. package/knowledge/ui/composition/state.md +105 -0
  80. package/knowledge/ui/composition/state.vi.md +106 -0
  81. package/knowledge/ui/presentation/INDEX.md +94 -0
  82. package/knowledge/ui/presentation/INDEX.vi.md +69 -0
  83. package/knowledge/ui/presentation/boundary.md +170 -0
  84. package/knowledge/ui/presentation/boundary.vi.md +169 -0
  85. package/knowledge/ui/presentation/font.md +155 -0
  86. package/knowledge/ui/presentation/font.vi.md +156 -0
  87. package/knowledge/ui/presentation/gap.md +181 -0
  88. package/knowledge/ui/presentation/gap.vi.md +181 -0
  89. package/knowledge/ui/presentation/margin.md +168 -0
  90. package/knowledge/ui/presentation/margin.vi.md +171 -0
  91. package/knowledge/ui/presentation/measure.md +174 -0
  92. package/knowledge/ui/presentation/measure.vi.md +178 -0
  93. package/knowledge/ui/presentation/overflow.md +142 -0
  94. package/knowledge/ui/presentation/overflow.vi.md +142 -0
  95. package/knowledge/ui/presentation/padding.md +293 -0
  96. package/knowledge/ui/presentation/padding.vi.md +292 -0
  97. package/knowledge/ui/presentation/surface.md +166 -0
  98. package/knowledge/ui/presentation/surface.vi.md +166 -0
  99. package/knowledge/ui/presentation/text-flow.md +123 -0
  100. package/knowledge/ui/presentation/text-flow.vi.md +123 -0
  101. package/knowledge/ui/presentation/tone.md +114 -0
  102. package/knowledge/ui/presentation/tone.vi.md +113 -0
  103. package/knowledge/ui/proof/INDEX.md +55 -0
  104. package/knowledge/ui/proof/INDEX.vi.md +55 -0
  105. package/knowledge/ui/proof/accessibility.md +67 -0
  106. package/knowledge/ui/proof/accessibility.vi.md +68 -0
  107. package/knowledge/ui/proof/contrast.md +58 -0
  108. package/knowledge/ui/proof/contrast.vi.md +57 -0
  109. package/knowledge/ui/proof/focus.md +76 -0
  110. package/knowledge/ui/proof/focus.vi.md +76 -0
  111. package/knowledge/ui/proof/motion.md +63 -0
  112. package/knowledge/ui/proof/motion.vi.md +64 -0
  113. package/knowledge/ui/proof/render-truth.md +63 -0
  114. package/knowledge/ui/proof/render-truth.vi.md +63 -0
  115. package/operators/INDEX.md +199 -0
  116. package/operators/INDEX.vi.md +199 -0
  117. package/operators/architecture-decide/errors.json +115 -0
  118. package/operators/architecture-decide/operator.json +20 -0
  119. package/operators/architecture-decide/operator.md +133 -0
  120. package/operators/architecture-decide/operator.vi.md +130 -0
  121. package/operators/architecture-decide/self-test.mjs +221 -0
  122. package/operators/architecture-decide/validate.mjs +112 -0
  123. package/operators/backend-source-apply/errors.json +66 -0
  124. package/operators/backend-source-apply/operator.json +20 -0
  125. package/operators/backend-source-apply/operator.md +189 -0
  126. package/operators/backend-source-apply/operator.vi.md +185 -0
  127. package/operators/backend-source-apply/self-test.mjs +257 -0
  128. package/operators/backend-source-apply/validate.mjs +240 -0
  129. package/operators/business-decide/errors.json +78 -0
  130. package/operators/business-decide/operator.json +20 -0
  131. package/operators/business-decide/operator.md +176 -0
  132. package/operators/business-decide/operator.vi.md +174 -0
  133. package/operators/business-decide/self-test.mjs +225 -0
  134. package/operators/business-decide/validate.mjs +277 -0
  135. package/operators/content-generate/errors.json +106 -0
  136. package/operators/content-generate/operator.json +21 -0
  137. package/operators/content-generate/operator.md +155 -0
  138. package/operators/content-generate/operator.vi.md +155 -0
  139. package/operators/content-generate/self-test.mjs +288 -0
  140. package/operators/content-generate/validate.mjs +196 -0
  141. package/operators/errors.json +178 -0
  142. package/operators/frontend-direction-decide/errors.json +135 -0
  143. package/operators/frontend-direction-decide/operator.json +21 -0
  144. package/operators/frontend-direction-decide/operator.md +167 -0
  145. package/operators/frontend-direction-decide/operator.vi.md +167 -0
  146. package/operators/frontend-direction-decide/self-test.mjs +226 -0
  147. package/operators/frontend-direction-decide/validate.mjs +146 -0
  148. package/operators/frontend-presentation-resolve/errors.json +42 -0
  149. package/operators/frontend-presentation-resolve/operator.json +19 -0
  150. package/operators/frontend-presentation-resolve/operator.md +142 -0
  151. package/operators/frontend-presentation-resolve/operator.vi.md +140 -0
  152. package/operators/frontend-presentation-resolve/self-test.mjs +163 -0
  153. package/operators/frontend-presentation-resolve/validate.mjs +143 -0
  154. package/operators/frontend-source-apply/errors.json +30 -0
  155. package/operators/frontend-source-apply/operator.json +20 -0
  156. package/operators/frontend-source-apply/operator.md +127 -0
  157. package/operators/frontend-source-apply/operator.vi.md +126 -0
  158. package/operators/frontend-source-apply/self-test.mjs +214 -0
  159. package/operators/frontend-source-apply/validate.mjs +133 -0
  160. package/operators/frontend-surface-audit/errors.json +5 -0
  161. package/operators/frontend-surface-audit/operator.json +22 -0
  162. package/operators/frontend-surface-audit/operator.md +121 -0
  163. package/operators/frontend-surface-audit/operator.vi.md +121 -0
  164. package/operators/frontend-surface-audit/self-test.mjs +185 -0
  165. package/operators/frontend-surface-audit/validate.mjs +127 -0
  166. package/operators/git-publish/errors.json +54 -0
  167. package/operators/git-publish/operator.json +20 -0
  168. package/operators/git-publish/operator.md +180 -0
  169. package/operators/git-publish/operator.vi.md +178 -0
  170. package/operators/git-publish/self-test.mjs +177 -0
  171. package/operators/git-publish/validate.mjs +106 -0
  172. package/operators/platform-operate/errors.json +90 -0
  173. package/operators/platform-operate/operator.json +22 -0
  174. package/operators/platform-operate/operator.md +162 -0
  175. package/operators/platform-operate/operator.vi.md +160 -0
  176. package/operators/platform-operate/self-test.mjs +202 -0
  177. package/operators/platform-operate/validate.mjs +193 -0
  178. package/operators/quality-verify/errors.json +54 -0
  179. package/operators/quality-verify/operator.json +20 -0
  180. package/operators/quality-verify/operator.md +185 -0
  181. package/operators/quality-verify/operator.vi.md +177 -0
  182. package/operators/quality-verify/self-test.mjs +210 -0
  183. package/operators/quality-verify/validate.mjs +197 -0
  184. package/operators/release-deploy/errors.json +158 -0
  185. package/operators/release-deploy/operator.json +23 -0
  186. package/operators/release-deploy/operator.md +183 -0
  187. package/operators/release-deploy/operator.vi.md +181 -0
  188. package/operators/release-deploy/self-test.mjs +235 -0
  189. package/operators/release-deploy/validate.mjs +129 -0
  190. package/operators/uat-verify/errors.json +78 -0
  191. package/operators/uat-verify/operator.json +25 -0
  192. package/operators/uat-verify/operator.md +162 -0
  193. package/operators/uat-verify/operator.vi.md +161 -0
  194. package/operators/uat-verify/self-test.mjs +270 -0
  195. package/operators/uat-verify/validate.mjs +202 -0
  196. package/operators/workspace-bind/errors.json +90 -0
  197. package/operators/workspace-bind/operator.json +21 -0
  198. package/operators/workspace-bind/operator.md +148 -0
  199. package/operators/workspace-bind/operator.vi.md +148 -0
  200. package/operators/workspace-bind/self-test.mjs +205 -0
  201. package/operators/workspace-bind/validate.mjs +152 -0
  202. package/package.json +50 -0
  203. package/readiness/initialization/workspaces/commit-policy.json +91 -0
  204. package/readiness/initialization/workspaces/config.schema.json +35 -0
  205. package/readiness/initialization/workspaces/device-state.schema.json +54 -0
  206. package/readiness/initialization/workspaces/local-route.schema.json +206 -0
  207. package/readiness/initialization/workspaces/portable-route.schema.json +200 -0
  208. package/resources/INDEX.md +96 -0
  209. package/resources/INDEX.vi.md +99 -0
  210. package/resources/agents/profiles/claude.json +131 -0
  211. package/resources/agents/profiles/openai.json +131 -0
  212. package/resources/orchestrator.json +71 -0
  213. package/resources/tools.json +85 -0
  214. package/routing.json +272 -0
  215. package/scripts/alias-registry.mjs +31 -0
  216. package/scripts/device-state.mjs +497 -0
  217. package/scripts/device-state.spec.mjs +18 -0
  218. package/scripts/errors-registry.mjs +68 -0
  219. package/scripts/generate-alias-doc.mjs +66 -0
  220. package/scripts/generate-grammar-dna.mjs +387 -0
  221. package/scripts/generate-operators-index.mjs +95 -0
  222. package/scripts/generate-presentation-owned.mjs +681 -0
  223. package/scripts/install-cli.spec.mjs +74 -0
  224. package/scripts/json-schema.mjs +94 -0
  225. package/scripts/operator-md.mjs +96 -0
  226. package/scripts/run-operator-self-tests.mjs +36 -0
  227. package/scripts/validate-alias.mjs +165 -0
  228. package/scripts/validate-defaults.mjs +72 -0
  229. package/scripts/validate-knowledge-citations.mjs +90 -0
  230. package/scripts/validate-operator.mjs +125 -0
  231. package/scripts/validate-request.mjs +80 -0
  232. package/scripts/validate-resources.mjs +117 -0
  233. package/scripts/validate-response.mjs +141 -0
  234. package/scripts/validate-routing.mjs +91 -0
  235. package/scripts/validate-step.mjs +50 -0
  236. package/scripts/validate-templates.mjs +226 -0
  237. package/scripts/validate-templates.spec.mjs +144 -0
  238. package/scripts/validate-workflows.mjs +106 -0
  239. package/scripts/workspace-portable.mjs +389 -0
  240. package/scripts/workspace-portable.spec.mjs +246 -0
  241. package/templates/README.md +47 -0
  242. package/templates/README.vi.md +45 -0
  243. package/templates/changes.example.md +27 -0
  244. package/templates/grammars.template.md +34 -0
  245. package/templates/kinds/architecture-decision.contract.json +14 -0
  246. package/templates/kinds/architecture-decision.skeleton.md +52 -0
  247. package/templates/kinds/backend-source-application.contract.json +10 -0
  248. package/templates/kinds/backend-source-application.skeleton.md +34 -0
  249. package/templates/kinds/business-promise-authority.contract.json +13 -0
  250. package/templates/kinds/business-promise-authority.skeleton.md +67 -0
  251. package/templates/kinds/capture.schema.json +33 -0
  252. package/templates/kinds/changes.contract.json +10 -0
  253. package/templates/kinds/changes.skeleton.md +26 -0
  254. package/templates/kinds/checks.schema.json +44 -0
  255. package/templates/kinds/claims.schema.json +165 -0
  256. package/templates/kinds/conformance.schema.json +15 -0
  257. package/templates/kinds/content-brief.contract.json +12 -0
  258. package/templates/kinds/content-brief.skeleton.md +45 -0
  259. package/templates/kinds/content-generation-receipt.contract.json +11 -0
  260. package/templates/kinds/content-generation-receipt.skeleton.md +40 -0
  261. package/templates/kinds/content-review.contract.json +11 -0
  262. package/templates/kinds/content-review.skeleton.md +45 -0
  263. package/templates/kinds/contract.schema.json +28 -0
  264. package/templates/kinds/coverage-matrix.schema.json +86 -0
  265. package/templates/kinds/coverage.schema.json +62 -0
  266. package/templates/kinds/current-state.schema.json +40 -0
  267. package/templates/kinds/delta.schema.json +95 -0
  268. package/templates/kinds/e2e.schema.json +63 -0
  269. package/templates/kinds/frontend-direction-decision.contract.json +93 -0
  270. package/templates/kinds/frontend-direction-decision.skeleton.md +68 -0
  271. package/templates/kinds/frontend-presentation-resolution.contract.json +11 -0
  272. package/templates/kinds/frontend-presentation-resolution.skeleton.md +32 -0
  273. package/templates/kinds/frontend-source-application.contract.json +10 -0
  274. package/templates/kinds/frontend-source-application.skeleton.md +30 -0
  275. package/templates/kinds/frontend-surface-audit.contract.json +52 -0
  276. package/templates/kinds/frontend-surface-audit.skeleton.md +32 -0
  277. package/templates/kinds/gate-result.schema.json +64 -0
  278. package/templates/kinds/git-publication.contract.json +82 -0
  279. package/templates/kinds/git-publication.skeleton.md +61 -0
  280. package/templates/kinds/independent-critique.contract.json +9 -0
  281. package/templates/kinds/independent-critique.skeleton.md +28 -0
  282. package/templates/kinds/inventory.schema.json +35 -0
  283. package/templates/kinds/model.schema.json +99 -0
  284. package/templates/kinds/mutations.schema.json +65 -0
  285. package/templates/kinds/platform-operation-receipt.contract.json +76 -0
  286. package/templates/kinds/platform-operation-receipt.skeleton.md +54 -0
  287. package/templates/kinds/probes.schema.json +130 -0
  288. package/templates/kinds/proof.schema.json +17 -0
  289. package/templates/kinds/quality-verification.contract.json +87 -0
  290. package/templates/kinds/quality-verification.skeleton.md +58 -0
  291. package/templates/kinds/release-deployment.contract.json +85 -0
  292. package/templates/kinds/release-deployment.skeleton.md +67 -0
  293. package/templates/kinds/route.schema.json +293 -0
  294. package/templates/kinds/stack-model.schema.json +90 -0
  295. package/templates/kinds/uat-capture.schema.json +35 -0
  296. package/templates/kinds/uat-flow-verification.contract.json +11 -0
  297. package/templates/kinds/uat-flow-verification.skeleton.md +47 -0
  298. package/templates/kinds/uat-snapshot.schema.json +96 -0
  299. package/templates/kinds/uat-verdicts.schema.json +41 -0
  300. package/templates/kinds/ui-coverage.schema.json +77 -0
  301. package/templates/kinds/verdicts.schema.json +39 -0
  302. package/templates/kinds/workspace-route-binding.contract.json +12 -0
  303. package/templates/kinds/workspace-route-binding.skeleton.md +60 -0
  304. package/templates/kinds/writes.schema.json +37 -0
  305. package/templates/operator.template.md +73 -0
  306. package/templates/patterns.template.md +31 -0
  307. package/templates/step/request.schema.json +42 -0
  308. package/templates/step/response.schema.json +144 -0
  309. package/templates/ui-composition.template.md +36 -0
  310. package/templates/ui-presentation.template.md +57 -0
  311. package/templates/ui-proof.template.md +34 -0
  312. package/workflows/README.md +37 -0
  313. package/workflows/README.vi.md +37 -0
  314. package/workflows/backend-feature.json +59 -0
  315. package/workflows/content-unit.json +19 -0
  316. package/workflows/frontend-new-surface.json +81 -0
  317. package/workflows/frontend-reconstruct.json +67 -0
  318. package/workflows/frontend-refine.json +67 -0
  319. package/workflows/frontend-with-uat.json +78 -0
  320. package/workflows/full-feature.json +104 -0
  321. package/workflows/release.json +29 -0
@@ -0,0 +1,214 @@
1
+ // Proves validate.mjs on a synthetic session branch: one conforming application on the session branch
2
+ // (one file modified, one application-owned leaf created, one path unchanged), one dry run that
3
+ // commits nothing, one blocked on WRITE_REJECTED with nothing written, and one mutation per law, each
4
+ // of which must fail with a line that names the defect.
5
+ import assert from 'node:assert/strict';
6
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
8
+ import path from 'node:path';
9
+ import { validateApplicationStep } from './validate.mjs';
10
+
11
+ const PAGE = 'app/plans/page.tsx';
12
+ const CANVAS = 'app/plans/canvas.tsx';
13
+ const LEGEND = 'app/plans/legend.tsx';
14
+ const BASE = '0f1e2d3c4b5a69788796a5b4c3d2e1f009182736';
15
+ const COMMIT = '9a8b7c6d5e4f30211203344556677889900aabbc';
16
+ const SESSION = 's-test';
17
+ const fp = (c) => `sha256:${c.repeat(64)}`;
18
+
19
+ const inventory = () => ({
20
+ treeFingerprint: fp('a'),
21
+ classNames: ['gap-6', 'gap-4'],
22
+ ruleIds: ['GAP-5', 'GAP-4'],
23
+ gaps: [],
24
+ });
25
+
26
+ const plan = ({ mode = 'apply', commit = COMMIT } = {}) => ({
27
+ mode,
28
+ base: BASE,
29
+ branch: `session/${SESSION}`,
30
+ commit,
31
+ files: [
32
+ { path: PAGE, change: 'modified', before: fp('b'), after: fp('c'), classes: ['gap-6'] },
33
+ { path: CANVAS, change: 'created', before: null, after: fp('d'), classes: [] },
34
+ { path: LEGEND, change: 'unchanged', before: fp('e'), after: fp('e'), classes: [] },
35
+ ],
36
+ });
37
+
38
+ const dryPlan = () => ({
39
+ mode: 'dry',
40
+ base: BASE,
41
+ branch: `session/${SESSION}`,
42
+ commit: null,
43
+ files: [
44
+ { path: PAGE, change: 'modified', before: fp('b'), after: null, classes: ['gap-6'] },
45
+ { path: CANVAS, change: 'created', before: null, after: null, classes: [] },
46
+ ],
47
+ });
48
+
49
+ const responseMd = ({ mode = 'apply', commit = COMMIT, files } = {}) => {
50
+ const rows = (files ?? plan().files).map(({ path: p, change, classes }) => `| \`${p}\` | ${change} | ${classes.length ? `\`${classes[0]}\`` : '—'} | ${classes.length ? '`GAP-5`' : '—'} | ${change === 'created' ? 'an application-owned leaf carrying its contract' : 'the resolved value for this node'} |`);
51
+ return `# frontend-source-application — plan-picker
52
+
53
+ ## Binding
54
+
55
+ | Field | Value |
56
+ | --- | --- |
57
+ | Target | \`plan-picker\` |
58
+ | Mode | \`${mode}\` |
59
+ | Branch | \`session/${SESSION}\` |
60
+ | Base | \`${BASE}\` |
61
+ | Commit | ${commit ? `\`${commit}\`` : '—'} |
62
+
63
+ ## Projection
64
+
65
+ | Path | Change | Classes | Claims | Why |
66
+ | --- | --- | --- | --- | --- |
67
+ ${rows.join('\n')}
68
+
69
+ ## Rejections
70
+
71
+ | Path | Value | Because |
72
+ | --- | --- | --- |
73
+
74
+ ## Fallbacks taken
75
+
76
+ | Code | Action |
77
+ | --- | --- |
78
+ `;
79
+ };
80
+
81
+ const changesMd = ({ files } = {}) => {
82
+ const rows = (files ?? plan().files).map(({ path: p, change, classes }) => `| \`${p}\` | ${change} | ${change === 'created' ? 'the application-owned leaf the direction marked' : 'the resolved value for this node'} | ${classes.length ? '`GAP-5`' : '—'} |`);
83
+ return `# changes — frontend.source.apply step-3/parallel-1
84
+
85
+ The plans page and its application-owned canvas carry the resolved spacing on the session branch.
86
+
87
+ ## Binding
88
+
89
+ | Field | Value |
90
+ | --- | --- |
91
+ | Operator | \`frontend.source.apply\` |
92
+ | Step | \`step-3/parallel-1\` |
93
+ | Checkout | \`@workspaces/fe\` at \`${BASE}\` → \`${COMMIT}\` on \`session/${SESSION}\` |
94
+ | Predecessor | \`step-2/parallel-1/response/response.md\` |
95
+
96
+ ## Files
97
+
98
+ | Path | Change | Why | Claims |
99
+ | --- | --- | --- | --- |
100
+ ${rows.join('\n')}
101
+
102
+ ## What the next step must know
103
+
104
+ - Gates to run: the lint and type gates the checkout pins for these paths.
105
+ - Surfaces to observe: the /plans route at both branches.
106
+ - Not changed on purpose: the legend, because the resolution decided nothing about it.
107
+ `;
108
+ };
109
+
110
+ const requestJson = ({ extra = {} } = {}) => ({
111
+ schemaVersion: 9,
112
+ operatorId: 'frontend.source.apply',
113
+ step: 3,
114
+ parallel: 1,
115
+ sessionId: SESSION,
116
+ contexts: [{ alias: '@workspaces/fe', head: BASE }],
117
+ requirements: { mode: 'apply', resume: null, ...extra },
118
+ inputs: {
119
+ 'frontend-presentation-resolution': 'step-2/parallel-1/response/response.md',
120
+ 'frontend-direction-decision': 'step-1/parallel-1/response/response.md',
121
+ },
122
+ resume: null,
123
+ });
124
+
125
+ const responseJson = ({ status = 'done', stop, fields, commits = [COMMIT], next = ['frontend.surface.audit'] } = {}) => ({
126
+ schemaVersion: 9,
127
+ operatorId: 'frontend.source.apply',
128
+ step: 3,
129
+ parallel: 1,
130
+ status,
131
+ ...(stop ? { stop } : {}),
132
+ fallbacks: [],
133
+ fields: fields ?? { 'frontend-source-application': 'response/response.md', changes: 'response/changes.md', writes: 'response/data/writes.json' },
134
+ commits,
135
+ next,
136
+ });
137
+
138
+ function writeBranch(files, { inventoryDoc = inventory(), withInventory = true } = {}) {
139
+ const session = mkdtempSync(path.join(tmpdir(), 'fe-apply-session-'));
140
+ const branch = path.join(session, 'step-3', 'parallel-1');
141
+ for (const d of ['request', 'response/data']) mkdirSync(path.join(branch, d), { recursive: true });
142
+ const upstream = path.join(session, 'step-2', 'parallel-1', 'response', 'data');
143
+ mkdirSync(upstream, { recursive: true });
144
+ mkdirSync(path.join(session, 'step-1', 'parallel-1', 'response'), { recursive: true });
145
+ writeFileSync(path.join(session, 'step-1', 'parallel-1', 'response', 'response.md'), '# frontend-direction-decision — plan-picker\n');
146
+ writeFileSync(path.join(session, 'step-2', 'parallel-1', 'response', 'response.md'), '# frontend-presentation-resolution — plan-picker\n');
147
+ if (withInventory) writeFileSync(path.join(upstream, 'inventory.json'), JSON.stringify(inventoryDoc, null, 2));
148
+ writeFileSync(path.join(session, 'state.json'), JSON.stringify({ id: SESSION, chain: [['2/1'], ['3/1']], steps: { '2/1': 'frontend.presentation.resolve', '3/1': 'frontend.source.apply' }, current: '3/1', status: 'running' }));
149
+ for (const [name, content] of Object.entries(files)) {
150
+ if (content === null) continue;
151
+ writeFileSync(path.join(branch, name), typeof content === 'string' ? content : JSON.stringify(content, null, 2));
152
+ }
153
+ return { branch, session };
154
+ }
155
+
156
+ const baseline = () => ({
157
+ 'request/request.json': requestJson(),
158
+ 'response/response.json': responseJson(),
159
+ 'response/response.md': responseMd(),
160
+ 'response/changes.md': changesMd(),
161
+ 'response/data/writes.json': plan(),
162
+ });
163
+
164
+ const dryRun = () => ({
165
+ 'request/request.json': requestJson({ extra: { mode: 'dry' } }),
166
+ 'response/response.json': responseJson({ commits: [] }),
167
+ 'response/response.md': responseMd({ mode: 'dry', commit: null, files: dryPlan().files }),
168
+ 'response/changes.md': changesMd({ files: dryPlan().files }),
169
+ 'response/data/writes.json': dryPlan(),
170
+ });
171
+
172
+ async function expectValid(files, label, options) {
173
+ const { branch, session } = writeBranch(files, options);
174
+ const { errors } = await validateApplicationStep(branch);
175
+ rmSync(session, { recursive: true, force: true });
176
+ assert.deepEqual(errors, [], `${label} should be valid`);
177
+ }
178
+ async function expectError(files, needle, label, options) {
179
+ const { branch, session } = writeBranch(files, options);
180
+ const { errors } = await validateApplicationStep(branch);
181
+ rmSync(session, { recursive: true, force: true });
182
+ assert.ok(errors.some((e) => e.includes(needle)), `${label}: expected an error containing "${needle}", got:\n${errors.join('\n') || '(none)'}`);
183
+ }
184
+ const mutate = (change) => { const p = plan(); change(p); return { ...baseline(), 'response/data/writes.json': p }; };
185
+
186
+ await expectValid(baseline(), 'one modified path, one application-owned leaf created, one unchanged');
187
+ await expectValid(dryRun(), 'a dry run that commits nothing');
188
+ await expectValid({ 'request/request.json': requestJson(), 'response/response.json': responseJson({ status: 'blocked', stop: 'WRITE_REJECTED', commits: [], next: [], fields: {} }) }, 'blocked on WRITE_REJECTED with nothing written');
189
+
190
+ await expectError({ ...baseline(), 'request/request.json': requestJson({ extra: { mystery: 1 } }) }, 'requirements.mystery is not a field', 'undeclared requirement');
191
+ await expectError({ ...baseline(), 'request/request.json': requestJson({ extra: { mode: 'dry' } }) }, "mode apply differs from the request's dry", 'mode re-decided in the plan');
192
+ await expectError(mutate((p) => { p.files[0].classes = ['gap-8']; }), 'writes class gap-8, which the resolution never published', 'class the resolution never published');
193
+ await expectError(mutate((p) => { p.branch = 'mtp'; }), 'only session/<sessionId> may be committed to', 'write on the person\'s branch');
194
+ await expectError(mutate((p) => { p.branch = 'session/other'; }), 'is not the session branch of', 'write on another session branch');
195
+ await expectError(mutate((p) => { p.commit = null; }), 'commits the declared write set exactly once', 'applied run without a commit');
196
+ await expectError({ ...baseline(), 'response/response.json': responseJson({ commits: [COMMIT, BASE] }) }, 'records exactly one commit, found 2', 'two commits for one write set');
197
+ await expectError({ ...baseline(), 'response/response.json': responseJson({ commits: [BASE] }) }, 'is not the commit', 'response and plan disagree on the commit');
198
+ await expectError({ ...dryRun(), 'response/response.json': responseJson({ commits: [COMMIT] }) }, 'a dry run records no commit', 'dry run that committed');
199
+ await expectError({ ...dryRun(), 'response/data/writes.json': { ...dryPlan(), commit: COMMIT } }, 'a dry run commits nothing, so commit must be null', 'dry plan carrying a commit');
200
+ await expectError(mutate((p) => { p.files = p.files.map((f) => ({ ...f, change: 'unchanged', before: f.after ?? fp('e'), after: f.after ?? fp('e'), classes: [] })); }), 'moves at least one declared path', 'nothing moved');
201
+ await expectError(mutate((p) => { p.files[0].after = p.files[0].before; }), 'reports a modification with an unchanged hash', 'modification that did not move');
202
+ await expectError(mutate((p) => { p.files[1].before = fp('f'); }), 'was created but reports a prior hash', 'creation with a prior hash');
203
+ await expectError(mutate((p) => { p.files[2].classes = ['gap-6']; }), 'is reported unchanged while carrying classes', 'unchanged file carrying classes');
204
+ await expectError(mutate((p) => { p.files.push({ ...p.files[0] }); }), 'appears twice in the plan', 'one path planned twice');
205
+ await expectError({ ...baseline(), 'response/response.md': responseMd({ commit: BASE }) }, 'Commit', 'receipt and plan disagree on the commit');
206
+ await expectError({ ...baseline(), 'response/changes.md': changesMd().replace(`| \`${LEGEND}\` | unchanged | the resolved value for this node | — |\n`, '') }, 'Files lists 2 paths, the plan carries 3', 'changes.md hides a planned path');
207
+ await expectError({ ...baseline(), 'response/changes.md': changesMd().replace('| modified |', '| deleted |') }, 'is deleted here and modified in the plan', 'changes.md contradicts the plan');
208
+ await expectError({ ...baseline(), 'response/changes.md': changesMd().replace(` on \`session/${SESSION}\``, '') }, 'does not name the session branch', 'changes.md hides the session branch');
209
+ await expectError(baseline(), 'the resolution inventory could not be read', 'inventory missing beside the receipt', { withInventory: false });
210
+ await expectError({ ...baseline(), 'response/response.md': responseMd().replace('## Rejections', '## Rejected') }, 'missing section ^## Rejections$', 'receipt section renamed');
211
+ await expectError({ ...baseline(), 'response/data/writes.json': null, 'response/response.json': responseJson({ fields: { 'frontend-source-application': 'response/response.md', changes: 'response/changes.md' } }) }, 'required output writes is not in fields', 'missing required output');
212
+ await expectError({ ...baseline(), 'response/response.json': responseJson({ status: 'blocked', stop: 'MADE_UP_CODE', commits: [], next: [] }) }, 'not a registered code', 'unknown stop code');
213
+
214
+ process.stdout.write('frontend.source.apply self-test: 3 valid branches, 22 rejected mutations\n');
@@ -0,0 +1,133 @@
1
+ // frontend.source.apply's own law over one branch, on top of the shared step check: every class in every
2
+ // written file comes from the bound resolution's inventory, which is read back beside the resolution
3
+ // receipt the request named; the write lands on the session branch and nowhere else; a dry run
4
+ // commits nothing and an applied run commits exactly once; created, modified and unchanged agree with
5
+ // the hashes; and the receipt and changes.md list exactly the files the plan carries.
6
+ import { existsSync } from 'node:fs';
7
+ import { readFile } from 'node:fs/promises';
8
+ import path from 'node:path';
9
+ import process from 'node:process';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { validateStep } from '../../scripts/validate-step.mjs';
12
+ import { tableUnder } from '../../scripts/validate-response.mjs';
13
+ import { sessionRootOf } from '../../scripts/validate-request.mjs';
14
+
15
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
16
+ const empty = (v) => v === undefined || v === null || v === '' || v === '—';
17
+
18
+ // The inventory the resolution froze sits beside its receipt: <branch>/response/data/inventory.json.
19
+ export function inventoryPathFor(resolutionRef) {
20
+ return resolutionRef.replace(/response\/[a-z-]+\.md$/, 'response/data/inventory.json');
21
+ }
22
+
23
+ export async function validateApplicationStep(branchDir, root = ROOT) {
24
+ const base = await validateStep(root, branchDir);
25
+ const errors = [...base.errors];
26
+ const { request, response, requirements = {}, present = new Set() } = base;
27
+ if (!response || response.operatorId !== 'frontend.source.apply') return { errors };
28
+ const has = (f) => existsSync(path.join(branchDir, f));
29
+ const read = (f) => readFile(path.join(branchDir, f), 'utf8');
30
+ const mode = requirements.mode ?? 'apply';
31
+
32
+ let plan = null;
33
+ if (present.has('writes') && has('response/data/writes.json')) {
34
+ try { plan = JSON.parse(await read('response/data/writes.json')); } catch { plan = null; }
35
+ }
36
+ if (!plan) {
37
+ if (response.status === 'done') errors.push('response/data/writes.json: a done branch needs the write plan');
38
+ return { errors };
39
+ }
40
+ const at = 'response/data/writes.json';
41
+
42
+ if (plan.mode !== mode) errors.push(`${at}: mode ${plan.mode} differs from the request's ${mode}`);
43
+ if (!plan.branch.startsWith('session/')) errors.push(`${at}: the write lands on ${plan.branch}; only session/<sessionId> may be committed to`);
44
+ if (request?.sessionId && plan.branch !== `session/${request.sessionId}`) errors.push(`${at}: branch ${plan.branch} is not the session branch of ${request.sessionId}`);
45
+
46
+ const commits = response.commits ?? [];
47
+ if (mode === 'dry') {
48
+ if (plan.commit !== null) errors.push(`${at}: a dry run commits nothing, so commit must be null`);
49
+ if (commits.length) errors.push('response/response.json: a dry run records no commit');
50
+ for (const file of plan.files) if (file.change !== 'unchanged' && file.after !== null && file.before !== file.after) {
51
+ errors.push(`${at}: ${file.path} reports a change under a dry run, which writes nothing`);
52
+ }
53
+ } else if (response.status === 'done') {
54
+ if (plan.commit === null) errors.push(`${at}: an applied branch commits the declared write set exactly once`);
55
+ if (commits.length !== 1) errors.push(`response/response.json: an applied branch records exactly one commit, found ${commits.length}`);
56
+ else if (plan.commit !== null && commits[0] !== plan.commit) errors.push(`response/response.json: commits[0] ${commits[0]} is not the commit ${plan.commit} the plan recorded`);
57
+ if (!plan.files.some((f) => f.change !== 'unchanged')) errors.push(`${at}: an applied branch moves at least one declared path (NO_PROGRESS otherwise)`);
58
+ }
59
+
60
+ // Every value that entered source came from the resolution; there is no other source of one.
61
+ const sessionRoot = sessionRootOf(branchDir);
62
+ const resolutionRef = request?.inputs?.['frontend-presentation-resolution'];
63
+ let inventory = null;
64
+ if (sessionRoot && resolutionRef) {
65
+ const full = path.join(sessionRoot, inventoryPathFor(resolutionRef));
66
+ if (existsSync(full)) { try { inventory = JSON.parse(await readFile(full, 'utf8')); } catch { inventory = null; } }
67
+ }
68
+ if (inventory) {
69
+ const published = new Set(inventory.classNames);
70
+ for (const file of plan.files) {
71
+ for (const className of file.classes) {
72
+ if (!published.has(className)) errors.push(`${at}: ${file.path} writes class ${className}, which the resolution never published (WRITE_REJECTED)`);
73
+ }
74
+ }
75
+ } else if (response.status === 'done') errors.push(`${at}: the resolution inventory could not be read beside the receipt the request bound (RESOLUTION_STALE)`);
76
+
77
+ const seen = new Set();
78
+ for (const file of plan.files) {
79
+ if (seen.has(file.path)) errors.push(`${at}: path ${file.path} appears twice in the plan`);
80
+ seen.add(file.path);
81
+ if (file.change === 'created' && file.before !== null) errors.push(`${at}: ${file.path} was created but reports a prior hash`);
82
+ if (file.change === 'modified') {
83
+ if (file.before === null) errors.push(`${at}: ${file.path} was modified but reports no prior hash`);
84
+ else if (file.before === file.after) errors.push(`${at}: ${file.path} reports a modification with an unchanged hash`);
85
+ }
86
+ if (file.change === 'unchanged') {
87
+ if (file.before !== file.after) errors.push(`${at}: ${file.path} is reported unchanged with a different hash`);
88
+ if (file.classes.length) errors.push(`${at}: ${file.path} is reported unchanged while carrying classes`);
89
+ }
90
+ if (file.change === 'deleted' && file.after !== null) errors.push(`${at}: ${file.path} was deleted and still reports a later hash`);
91
+ }
92
+
93
+ if (present.has('frontend-source-application') && has('response/response.md')) {
94
+ const text = await read('response/response.md');
95
+ const rel = 'response/response.md';
96
+ const binding = Object.fromEntries((tableUnder(text, '## Binding') ?? []).map(([k, v]) => [k, v]));
97
+ if (binding.Mode !== mode) errors.push(`${rel}: Mode ${binding.Mode} differs from the request's ${mode}`);
98
+ if (binding.Branch !== plan.branch) errors.push(`${rel}: Branch differs from the write plan`);
99
+ if (binding.Base !== plan.base) errors.push(`${rel}: Base differs from the write plan`);
100
+ if (binding.Commit !== (plan.commit ?? '—')) errors.push(`${rel}: Commit ${binding.Commit} differs from the write plan's ${plan.commit ?? '—'}`);
101
+ const rows = tableUnder(text, '## Projection') ?? [];
102
+ if (rows.length !== plan.files.length) errors.push(`${rel}: Projection has ${rows.length} rows, the plan has ${plan.files.length}`);
103
+ for (const [p, change] of rows) {
104
+ const file = plan.files.find((f) => f.path === p);
105
+ if (!file) { errors.push(`${rel}: Projection names ${p}, which the plan does not carry`); continue; }
106
+ if (file.change !== change) errors.push(`${rel}: ${p} is ${change} here and ${file.change} in the plan`);
107
+ }
108
+ }
109
+
110
+ if (present.has('changes') && has('response/changes.md')) {
111
+ const text = await read('response/changes.md');
112
+ const rel = 'response/changes.md';
113
+ const rows = tableUnder(text, '## Files') ?? [];
114
+ const listed = rows.map(([p]) => p).sort();
115
+ const planned = plan.files.map((f) => f.path).sort();
116
+ if (listed.join('\n') !== planned.join('\n')) errors.push(`${rel}: Files lists ${listed.length} paths, the plan carries ${planned.length}`);
117
+ for (const [p, change] of rows) {
118
+ const file = plan.files.find((f) => f.path === p);
119
+ if (!file) { errors.push(`${rel}: Files names ${p}, which the plan does not carry`); continue; }
120
+ if (file.change !== change) errors.push(`${rel}: ${p} is ${change} here and ${file.change} in the plan`);
121
+ }
122
+ const binding = Object.fromEntries((tableUnder(text, '## Binding') ?? []).map(([k, v]) => [k, v]));
123
+ if (!empty(binding.Checkout) && !binding.Checkout.includes(plan.branch)) errors.push(`${rel}: the Checkout row does not name the session branch ${plan.branch}`);
124
+ }
125
+ return { errors };
126
+ }
127
+
128
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
129
+ const target = process.argv[2];
130
+ if (!target) { process.stderr.write('usage: node validate.mjs <session>/step-N/parallel-M\n'); process.exit(2); }
131
+ const { errors } = await validateApplicationStep(path.resolve(target));
132
+ if (errors.length) { process.stderr.write(`${errors.join('\n')}\n`); process.exitCode = 1; } else process.stdout.write('valid frontend.source.apply branch\n');
133
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "schemaVersion": 9,
3
+ "note": "Stop codes frontend.surface.audit emits. INVALID_INPUT, SOURCE_DRIFT, EVIDENCE_MISSING and NO_PROGRESS come from operators/errors.json. RUNTIME_UNAVAILABLE is defined here and also emitted by uat.verify; UNKNOWN_RULE is emitted here too and is defined in operators/frontend-presentation-resolve/errors.json. Both belong in operators/errors.json with a scope list naming every operator that emits them.",
4
+ "codes": {}
5
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "schemaVersion": 9,
3
+ "id": "frontend.surface.audit",
4
+ "domain": "frontend",
5
+ "job": "Observe one rendered surface across a declared viewport and state matrix, measure every presentation value it actually produces, compare each measurement against the contract claim on that node, and return findings drawn only from the bound rule inventory.",
6
+ "package": "operator.md",
7
+ "errors": "errors.json",
8
+ "validator": "validate.mjs",
9
+ "selfTest": "self-test.mjs",
10
+ "resources": {
11
+ "profile": "sol-reviewer",
12
+ "grammarBound": true,
13
+ "tools": {
14
+ "@tools/fileread": "context-aliases",
15
+ "@tools/git": "read",
16
+ "@tools/websearch": "bounded",
17
+ "@tools/visualize": "html",
18
+ "@tools/browsercontrol": "required",
19
+ "@tools/http": "probe"
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,121 @@
1
+ # frontend.surface.audit
2
+
3
+ ## Job
4
+
5
+ Observe the committed surface at the served route across the matrix the direction's coverage implies,
6
+ measure every node that carries a claim, and judge each measurement against the published proof rules
7
+ by the owner of the node it stands on.
8
+
9
+ ## Readiness, capture and judgement are one job
10
+
11
+ Splitting them produced a familiar failure: a capture taken before the surface was ready, judged by a
12
+ step that could no longer tell, against evidence it had not itself collected. One operator that
13
+ waits, measures and judges under one receipt cannot lose that connection.
14
+
15
+ ## The surface must be the committed surface
16
+
17
+ The applied receipt names the commit it wrote, the pinned head equals that commit, and the preview
18
+ serves the session worktree at it. A head that differs is `SOURCE_DRIFT` with nothing captured,
19
+ because a measurement of another tree proves nothing about this one.
20
+
21
+ ## Measurement beats claim, always
22
+
23
+ Each node carries the identifiers it claims to satisfy, and the audit measures what the surface
24
+ actually renders. A claim is never evidence of passing: a node claiming `GAP-4` while the computed
25
+ gap measures `1.5rem` is a failure, and no amount of claiming changes the measurement. That is the
26
+ whole mechanic, and it is why the claim exists.
27
+
28
+ ## The owner decides where a failure goes
29
+
30
+ A failing claim on an application-owned node is a value the resolution has to publish again, so it
31
+ routes back to `frontend.presentation.resolve`, which is the operator that caps the rounds. A failing claim
32
+ on a Grammar component's own render is a Grammar gap: it goes to a person and into the family's own
33
+ gap table, never into a resolve loop, because no application value can fix a component. The interior
34
+ of an application-owned node carries no claim and is not audited at all; only that node's own measure
35
+ rules are.
36
+
37
+ ## The audit changes nothing
38
+
39
+ No verdict is a repair, a workaround or an instruction. A failure stays a failure in the receipt
40
+ until a resolution publishes a new value and the applier writes it, and the same surface is audited
41
+ again. That separation is why the receipt is worth anything: an operator that could fix what it found
42
+ would always be able to report a clean surface.
43
+
44
+ ## Boundary
45
+
46
+ Context is read-only, and the runtime is consumed, never owned. The operator writes only `response/`
47
+ of its own branch: the audit receipt, its captures, its screenshots and its verdicts. It does not
48
+ modify product source, the applied tree, knowledge or Grammar, repair, restyle or work around
49
+ anything it observes, start, stop, deploy or reconfigure a runtime service, cite a rule identifier
50
+ absent from the bound inventory, judge a node it did not measure, or accept a claim as evidence that
51
+ a node passes.
52
+
53
+ ## Context
54
+
55
+ | Alias | Bind | Required |
56
+ | --- | --- | --- |
57
+ | `@knowledge/ui/proof` | what only becomes true once rendered; the audit's whole rule inventory | yes |
58
+ | `@workspaces/fe` | the routed checkout at the commit the application wrote; the owners and identifiers observed there | yes |
59
+ | `@worktrees/sessions/central-runtime` | the shared runtime owner: the preview serving the session worktree at that commit | yes |
60
+ | `@knowledge/grammars/starci` | how the Core family is meant to realize Common, and where its gaps are recorded | no |
61
+
62
+ ## Inputs
63
+
64
+ | Kind | From | Required |
65
+ | --- | --- | --- |
66
+ | `frontend-source-application` | `frontend.source.apply`, the commit under observation and the claims it wrote | yes |
67
+ | `frontend-presentation-resolution` | `frontend.presentation.resolve`, the owner of every node | yes |
68
+ | `frontend-direction-decision` | `frontend.direction.decide`, the route and the coverage the matrix is derived from | yes |
69
+
70
+ ## Requirements
71
+
72
+ | Field | Type | Default | Ask |
73
+ | --- | --- | --- | --- |
74
+ | `matrix` | list | every coverage entry | The matrix entries to capture; the person may only narrow what the coverage implies |
75
+ | `readinessProbe` | choice | route-served | The floor for readiness; a state that needs data rises to `route-and-data-served` on its own |
76
+ | `resume` | token | null | The blocked branch's token when re-entering after a stop |
77
+
78
+ ## Steps
79
+
80
+ | # | Step | Params | Reads | Writes | Stops with |
81
+ | --- | --- | --- | --- | --- | --- |
82
+ | 1 | Validate the gate and resume, and confirm the head and the served route | `resume` | `request/request.json`, input `frontend-source-application` (its commit must equal the pinned head), @workspaces/fe at the frozen head, @worktrees/sessions/central-runtime (the preview serving the session worktree at that commit), @tools/git | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
83
+ | 2 | Bind the authority | — | @knowledge/ui/proof (every topic with its fingerprint and inventory), input `frontend-source-application` (the claims), input `frontend-presentation-resolution` (the owner of every node), @worktrees/sessions/central-runtime | — | — |
84
+ | 3 | Select the matrix entries | `matrix` | input `frontend-direction-decision` (the coverage: state by viewport by colour scheme) | — | — |
85
+ | 4 | Reach readiness for each entry | `readinessProbe` | @worktrees/sessions/central-runtime, @tools/http | — | `RUNTIME_UNAVAILABLE` |
86
+ | 5 | Capture and measure each entry | — | @worktrees/sessions/central-runtime, @workspaces/fe (the observed owners and the identifiers each node carries), @tools/browsercontrol | `response/artifacts/<matrixId>.png`, `response/data/captures/<matrixId>.json` | `EVIDENCE_MISSING` |
87
+ | 6 | Compare against the claims and the proof rules, judge by owner, and emit | — | @knowledge/ui/proof, @knowledge/grammars/starci, the captures, @tools/websearch | `response/data/verdicts.json`, `response/response.md`, `response/response.json`, @tools/visualize | `UNKNOWN_RULE` |
88
+
89
+ The matrix is the direction's coverage, not a new decision: `matrix` may only narrow it, and the
90
+ orchestrator may split the entries across up to three parallel branches of the same step. Every entry
91
+ produces one screenshot and one capture, and an entry that produced neither is `EVIDENCE_MISSING`
92
+ rather than a quiet omission. Every node carrying a claim is measured; a verdict is never recorded
93
+ for a node that was not.
94
+
95
+ ## Outputs
96
+
97
+ | Kind | File | Type | Required |
98
+ | --- | --- | --- | --- |
99
+ | `frontend-surface-audit` | `response/response.md` | md | yes |
100
+ | `capture` | `response/data/captures/<matrixId>.json` | data | yes |
101
+ | `screenshot` | `response/artifacts/<matrixId>.png` | artifact | yes |
102
+ | `verdicts` | `response/data/verdicts.json` | data | yes |
103
+
104
+ ## Stops
105
+
106
+ | Code | Disposition |
107
+ | --- | --- |
108
+ | `INVALID_INPUT` | terminate |
109
+ | `SOURCE_DRIFT` | terminate |
110
+ | `RUNTIME_UNAVAILABLE` | terminate |
111
+ | `EVIDENCE_MISSING` | terminate |
112
+ | `UNKNOWN_RULE` | terminate |
113
+ | `NO_PROGRESS` | terminate |
114
+
115
+ ## Next
116
+
117
+ | When | Operator |
118
+ | --- | --- |
119
+ | a claim fails on an application-owned node, so a value must be published again | `frontend.presentation.resolve` |
120
+ | every claim holds and the checkout's own gates must run | `quality.verify` |
121
+ | a claim fails on a Grammar component's own render, so a person records the family gap and publishes | `frontend.surface.audit` |
@@ -0,0 +1,121 @@
1
+ # frontend.surface.audit
2
+
3
+ ## Việc
4
+
5
+ Quan sát bề mặt đã commit ở route đang được phục vụ, qua đúng ma trận mà coverage của hướng suy ra,
6
+ đo mọi node có mang lời khai, và phán quyết từng phép đo theo luật proof đã publish, dựa trên chủ sở
7
+ hữu của node mà nó đứng lên.
8
+
9
+ ## Sẵn sàng, chụp và phán quyết là một việc
10
+
11
+ Tách chúng ra đã sinh ra một kiểu hỏng quen thuộc: một bức chụp lấy trước khi bề mặt sẵn sàng, bị
12
+ phán quyết bởi một bước không còn cách nào biết điều đó, dựa trên bằng chứng mà chính nó không thu.
13
+ Một operator vừa chờ, vừa đo, vừa phán quyết dưới một biên nhận thì không đánh mất mối nối ấy.
14
+
15
+ ## Bề mặt phải là bề mặt đã commit
16
+
17
+ Biên nhận áp dụng gọi tên commit nó đã ghi, head được ghim bằng đúng commit đó, và preview phục vụ
18
+ worktree phiên ở chính commit ấy. Head khác là `SOURCE_DRIFT` và không chụp gì cả, vì đo một cây khác
19
+ thì không chứng minh được gì về cây này.
20
+
21
+ ## Phép đo thắng lời khai, luôn luôn
22
+
23
+ Mỗi node mang những identifier nó khai là mình thoả, và lượt audit đo thứ bề mặt thực sự render ra.
24
+ Lời khai không bao giờ là bằng chứng của việc pass: một node khai `GAP-4` trong khi gap tính được đo
25
+ ra `1.5rem` là một lần hỏng, và khai bao nhiêu cũng không đổi được phép đo. Đó là toàn bộ cơ chế, và
26
+ đó là lý do lời khai tồn tại.
27
+
28
+ ## Chủ sở hữu quyết định chỗ hỏng đi về đâu
29
+
30
+ Một lời khai hỏng trên node do ứng dụng sở hữu là một giá trị mà resolution phải publish lại, nên nó
31
+ quay về `frontend.presentation.resolve`, chính operator giữ số vòng. Một lời khai hỏng trên phần render của
32
+ chính một component Grammar là một khoảng trống Grammar: nó đi tới một con người và vào bảng gap của
33
+ họ, không bao giờ đi vào vòng lặp resolve, vì không giá trị ứng dụng nào sửa được một component. Phần
34
+ ruột của node do ứng dụng sở hữu không mang lời khai nào và không bị audit; chỉ các luật đo của chính
35
+ node ấy bị audit.
36
+
37
+ ## Audit không đổi gì cả
38
+
39
+ Không phán quyết nào là một lần sửa, một cách chữa cháy hay một mệnh lệnh. Một chỗ hỏng vẫn là chỗ
40
+ hỏng trong biên nhận cho tới khi một lượt resolution publish giá trị mới và người ghi viết nó xuống,
41
+ rồi chính bề mặt ấy được audit lại. Sự tách bạch đó là lý do biên nhận này có giá trị: một operator
42
+ có thể tự sửa thứ nó tìm ra thì lúc nào cũng báo được một bề mặt sạch.
43
+
44
+ ## Ranh giới
45
+
46
+ Context chỉ đọc, và runtime là thứ được tiêu thụ chứ không được sở hữu. Operator chỉ ghi `response/`
47
+ của nhánh mình: biên nhận audit, các bức chụp, các ảnh màn hình và các phán quyết. Nó không sửa source
48
+ sản phẩm, cây đã áp dụng, knowledge hay Grammar, không sửa chữa, không tô lại, không chữa cháy bất cứ
49
+ thứ gì nó thấy, không khởi động, dừng, deploy hay cấu hình lại dịch vụ runtime, không trích một
50
+ identifier vắng mặt trong kho đã bind, không phán quyết một node nó chưa đo, và không nhận lời khai
51
+ làm bằng chứng rằng node đã pass.
52
+
53
+ ## Context
54
+
55
+ | Alias | Bind | Bắt buộc |
56
+ | --- | --- | --- |
57
+ | `@knowledge/ui/proof` | thứ chỉ thành thật khi đã render; toàn bộ kho luật của lượt audit | có |
58
+ | `@workspaces/fe` | checkout được route ở commit mà lần áp dụng đã ghi; chủ sở hữu và identifier được quan sát ở đó | có |
59
+ | `@worktrees/sessions/central-runtime` | chủ runtime dùng chung: preview đang phục vụ worktree phiên ở commit ấy | có |
60
+ | `@knowledge/grammars/starci` | họ Core được kỳ vọng hiện thực hoá Common ra sao, và gap của họ được ghi ở đâu | không |
61
+
62
+ ## Đầu vào
63
+
64
+ | Kind | Từ đâu | Bắt buộc |
65
+ | --- | --- | --- |
66
+ | `frontend-source-application` | `frontend.source.apply`, commit đang được quan sát và các lời khai nó đã ghi | có |
67
+ | `frontend-presentation-resolution` | `frontend.presentation.resolve`, chủ sở hữu của từng node | có |
68
+ | `frontend-direction-decision` | `frontend.direction.decide`, route và coverage mà ma trận suy ra từ đó | có |
69
+
70
+ ## Yêu cầu
71
+
72
+ | Field | Kiểu | Mặc định | Hỏi |
73
+ | --- | --- | --- | --- |
74
+ | `matrix` | list | every coverage entry | Các mục ma trận cần chụp; người dùng chỉ được thu hẹp thứ coverage đã suy ra |
75
+ | `readinessProbe` | choice | route-served | Mức sàn của sẵn sàng; một state cần dữ liệu tự nâng lên `route-and-data-served` |
76
+ | `resume` | token | null | Token của nhánh bị chặn khi vào lại sau một mã dừng |
77
+
78
+ ## Các bước
79
+
80
+ | # | Bước | Tham số | Đọc | Ghi | Dừng với |
81
+ | --- | --- | --- | --- | --- | --- |
82
+ | 1 | Kiểm gate, chạy lại, và xác nhận head cùng route đang phục vụ | `resume` | `request/request.json`, đầu vào `frontend-source-application` (commit của nó phải bằng head đã ghim), @workspaces/fe ở head đóng băng, @worktrees/sessions/central-runtime (preview phục vụ worktree phiên ở commit ấy), @tools/git | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
83
+ | 2 | Bind thẩm quyền | — | @knowledge/ui/proof (mọi topic kèm fingerprint và kho luật), đầu vào `frontend-source-application` (các lời khai), đầu vào `frontend-presentation-resolution` (chủ sở hữu của từng node), @worktrees/sessions/central-runtime | — | — |
84
+ | 3 | Chọn các mục ma trận | `matrix` | đầu vào `frontend-direction-decision` (coverage: state nhân viewport nhân bảng màu) | — | — |
85
+ | 4 | Chờ từng mục tới lúc sẵn sàng | `readinessProbe` | @worktrees/sessions/central-runtime, @tools/http | — | `RUNTIME_UNAVAILABLE` |
86
+ | 5 | Chụp và đo từng mục | — | @worktrees/sessions/central-runtime, @workspaces/fe (các owner được quan sát và identifier từng node mang), @tools/browsercontrol | `response/artifacts/<matrixId>.png`, `response/data/captures/<matrixId>.json` | `EVIDENCE_MISSING` |
87
+ | 6 | Đối chiếu với lời khai và luật proof, phán quyết theo chủ sở hữu, rồi phát | — | @knowledge/ui/proof, @knowledge/grammars/starci, các bức chụp, @tools/websearch | `response/data/verdicts.json`, `response/response.md`, `response/response.json`, @tools/visualize | `UNKNOWN_RULE` |
88
+
89
+ Ma trận là coverage của hướng, không phải một quyết định mới: `matrix` chỉ được thu hẹp nó, và
90
+ orchestrator có thể chia các mục ra tối đa ba nhánh song song của cùng một bậc. Mỗi mục cho đúng một
91
+ ảnh màn hình và một bức chụp, và một mục không cho cái nào là `EVIDENCE_MISSING` chứ không phải một
92
+ chỗ khuyết lặng lẽ. Mọi node mang lời khai đều được đo; không bao giờ có phán quyết cho một node chưa
93
+ được đo.
94
+
95
+ ## Đầu ra
96
+
97
+ | Kind | File | Kiểu | Bắt buộc |
98
+ | --- | --- | --- | --- |
99
+ | `frontend-surface-audit` | `response/response.md` | md | có |
100
+ | `capture` | `response/data/captures/<matrixId>.json` | data | có |
101
+ | `screenshot` | `response/artifacts/<matrixId>.png` | artifact | có |
102
+ | `verdicts` | `response/data/verdicts.json` | data | có |
103
+
104
+ ## Dừng
105
+
106
+ | Code | Xử lý |
107
+ | --- | --- |
108
+ | `INVALID_INPUT` | terminate |
109
+ | `SOURCE_DRIFT` | terminate |
110
+ | `RUNTIME_UNAVAILABLE` | terminate |
111
+ | `EVIDENCE_MISSING` | terminate |
112
+ | `UNKNOWN_RULE` | terminate |
113
+ | `NO_PROGRESS` | terminate |
114
+
115
+ ## Kế tiếp
116
+
117
+ | Khi | Operator |
118
+ | --- | --- |
119
+ | một lời khai hỏng trên node do ứng dụng sở hữu, nên phải publish lại một giá trị | `frontend.presentation.resolve` |
120
+ | mọi lời khai đều đứng vững và các cổng của chính checkout phải chạy | `quality.verify` |
121
+ | một lời khai hỏng trên phần render của chính component Grammar, nên một người ghi gap của họ rồi publish | `frontend.surface.audit` |