stylegallery 0.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 (346) hide show
  1. package/AGENTS.md +156 -0
  2. package/CATALOG.md +69 -0
  3. package/DOMAINS.md +65 -0
  4. package/GOVERNANCE.md +222 -0
  5. package/GUIDE.md +59 -0
  6. package/README.md +313 -0
  7. package/consumer-reference/adapters/style-dictionary.config.mjs +36 -0
  8. package/consumer-reference/agent-native/README.md +157 -0
  9. package/consumer-reference/agent-native/registry.json +640 -0
  10. package/consumer-reference/agent-native/schema/agent-native.schema.json +848 -0
  11. package/consumer-reference/agent-native/schema/epistemic.schema.json +14 -0
  12. package/consumer-reference/agent-native/schema/execution.schema.json +12 -0
  13. package/consumer-reference/agent-native/schema/identity.schema.json +10 -0
  14. package/consumer-reference/agent-native/schema/learning.schema.json +12 -0
  15. package/consumer-reference/agent-native/schema/manifest.schema.json +6 -0
  16. package/consumer-reference/agent-native/schema/operation.schema.json +12 -0
  17. package/consumer-reference/agent-native/schema/protocol-binding.schema.json +9 -0
  18. package/consumer-reference/agent-native/schema/retrieval.schema.json +10 -0
  19. package/consumer-reference/baselines/calibration.json +260 -0
  20. package/consumer-reference/baselines/manifest.json +21 -0
  21. package/consumer-reference/contract.md +83 -0
  22. package/consumer-reference/fixtures/consumer-conformance/e2e-fixture.mjs +282 -0
  23. package/consumer-reference/fixtures/consumer-conformance/manifest.json +102 -0
  24. package/consumer-reference/fixtures/consumer-conformance/runtime-proof.mjs +32 -0
  25. package/consumer-reference/fixtures/consumer-conformance/valid-migration-result.json +18 -0
  26. package/consumer-reference/fixtures/consumer-conformance/valid-migration.json +142 -0
  27. package/consumer-reference/fixtures/invalid-stable-ended-support.json +19 -0
  28. package/consumer-reference/fixtures/promotion/invalid-blocking-evidence.json +26 -0
  29. package/consumer-reference/fixtures/promotion/invalid-count-only.json +29 -0
  30. package/consumer-reference/fixtures/promotion/invalid-missing-owner-migration.json +25 -0
  31. package/consumer-reference/fixtures/promotion/invalid-normative-bypass-no-regression.json +25 -0
  32. package/consumer-reference/fixtures/promotion/invalid-related-counted-independent.json +45 -0
  33. package/consumer-reference/fixtures/promotion/invalid-stable-ended-support.json +26 -0
  34. package/consumer-reference/fixtures/promotion/manifest.json +15 -0
  35. package/consumer-reference/fixtures/promotion/valid-deferred-example.json +67 -0
  36. package/consumer-reference/fixtures/promotion/valid-normative-bypass.json +67 -0
  37. package/consumer-reference/fixtures/token-portability/invalid-cycle.json +7 -0
  38. package/consumer-reference/fixtures/token-portability/invalid-dangling-alias.json +6 -0
  39. package/consumer-reference/fixtures/token-portability/invalid-extends.json +4 -0
  40. package/consumer-reference/fixtures/token-portability/invalid-json-pointer.json +7 -0
  41. package/consumer-reference/fixtures/token-portability/invalid-resolver.json +9 -0
  42. package/consumer-reference/fixtures/token-portability/invalid-type-mismatch.json +10 -0
  43. package/consumer-reference/fixtures/token-portability/invalid-unknown-reserved.json +6 -0
  44. package/consumer-reference/fixtures/token-portability/invalid-untested-type.json +6 -0
  45. package/consumer-reference/fixtures/token-portability/valid-border.json +14 -0
  46. package/consumer-reference/fixtures/token-portability/valid-reference.json +32 -0
  47. package/consumer-reference/fixtures/token-portability/valid-whole-alias.json +7 -0
  48. package/consumer-reference/fixtures/valid-deprecated.json +21 -0
  49. package/consumer-reference/fixtures/valid-experimental.json +19 -0
  50. package/consumer-reference/generated/manifest.json +40 -0
  51. package/consumer-reference/generated/tokens.css +12 -0
  52. package/consumer-reference/index.md +32 -0
  53. package/consumer-reference/policies/shared-experimental.json +37 -0
  54. package/consumer-reference/schema/ax-evidence.schema.json +30 -0
  55. package/consumer-reference/schema/baseline-manifest.schema.json +55 -0
  56. package/consumer-reference/schema/calibration-record.schema.json +190 -0
  57. package/consumer-reference/schema/capture-session.schema.json +161 -0
  58. package/consumer-reference/schema/component-state.schema.json +150 -0
  59. package/consumer-reference/schema/consumer-conformance-record.schema.json +365 -0
  60. package/consumer-reference/schema/dom-evidence.schema.json +38 -0
  61. package/consumer-reference/schema/evidence-record.schema.json +81 -0
  62. package/consumer-reference/schema/fixture-manifest.schema.json +32 -0
  63. package/consumer-reference/schema/item.schema.json +188 -0
  64. package/consumer-reference/schema/page-evidence-manifest.schema.json +216 -0
  65. package/consumer-reference/schema/page-evidence-session.schema.json +119 -0
  66. package/consumer-reference/schema/portable-tokens.schema.json +104 -0
  67. package/consumer-reference/schema/promotion-rfc.schema.json +219 -0
  68. package/consumer-reference/schema/runtime-evidence-manifest.schema.json +33 -0
  69. package/consumer-reference/schema/visual-evidence.schema.json +31 -0
  70. package/design-engineering/consumer-migration-readiness.md +124 -0
  71. package/design-engineering/index.md +28 -0
  72. package/design-engineering/interface-craft.md +107 -0
  73. package/design-engineering/reference-profiles/external-adaptation/index.md +12 -0
  74. package/design-engineering/reference-profiles/governed-local/editorial/components/button.component.json +125 -0
  75. package/design-engineering/reference-profiles/governed-local/editorial/evidence/button.evidence.json +3504 -0
  76. package/design-engineering/reference-profiles/governed-local/editorial/fixtures/button.fixture.json +65 -0
  77. package/design-engineering/reference-profiles/governed-local/editorial/generated/evidence-coverage.md +23 -0
  78. package/design-engineering/reference-profiles/governed-local/editorial/generated/keyboard-matrix.md +19 -0
  79. package/design-engineering/reference-profiles/governed-local/editorial/generated/state-matrix.md +21 -0
  80. package/design-engineering/reference-profiles/governed-local/editorial/local-foundations.json +12 -0
  81. package/design-engineering/reference-profiles/governed-local/editorial/profile.json +43 -0
  82. package/design-engineering/reference-profiles/governed-local/editorial/states/button.states.json +195 -0
  83. package/design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json +17 -0
  84. package/design-engineering/reference-profiles/governed-local/index.md +26 -0
  85. package/design-engineering/reference-profiles/governed-local/terminal/components/button.component.json +125 -0
  86. package/design-engineering/reference-profiles/governed-local/terminal/evidence/button.evidence.json +3504 -0
  87. package/design-engineering/reference-profiles/governed-local/terminal/fixtures/button.fixture.json +65 -0
  88. package/design-engineering/reference-profiles/governed-local/terminal/generated/evidence-coverage.md +23 -0
  89. package/design-engineering/reference-profiles/governed-local/terminal/generated/keyboard-matrix.md +19 -0
  90. package/design-engineering/reference-profiles/governed-local/terminal/generated/state-matrix.md +21 -0
  91. package/design-engineering/reference-profiles/governed-local/terminal/local-foundations.json +12 -0
  92. package/design-engineering/reference-profiles/governed-local/terminal/profile.json +43 -0
  93. package/design-engineering/reference-profiles/governed-local/terminal/states/button.states.json +195 -0
  94. package/design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json +17 -0
  95. package/design-engineering/reference-profiles/index.md +19 -0
  96. package/game-ui/classification.md +82 -0
  97. package/game-ui/index.md +35 -0
  98. package/game-ui/reference-record.md +62 -0
  99. package/game-ui/screen-hierarchy.md +107 -0
  100. package/game-ui/unity/architecture.md +166 -0
  101. package/game-ui/unity/cli-loop.md +84 -0
  102. package/game-ui/unity/data/unity-technologies-public-repositories.json +26654 -0
  103. package/game-ui/unity/org-wiki.md +146 -0
  104. package/game-ui/unity/repository-map.md +102 -0
  105. package/game-ui/unity/ui-systems.md +85 -0
  106. package/guides/decision-tree.md +149 -0
  107. package/guides/documentation-mode-taxonomy.md +157 -0
  108. package/guides/layout-brief.md +220 -0
  109. package/guides/vocabulary.md +148 -0
  110. package/guides/webpage-generation-workflow.md +185 -0
  111. package/index.md +36 -0
  112. package/layout/index.md +26 -0
  113. package/motion/index.md +24 -0
  114. package/motion/practice-reference.md +145 -0
  115. package/motion/review-workflow.md +86 -0
  116. package/motion/vocabulary.md +82 -0
  117. package/package.json +78 -0
  118. package/patterns/centering/center.md +87 -0
  119. package/patterns/centering/index.md +6 -0
  120. package/patterns/centering/super-center.md +89 -0
  121. package/patterns/containment/box.md +87 -0
  122. package/patterns/containment/clamped-card.md +86 -0
  123. package/patterns/containment/content-limiter.md +87 -0
  124. package/patterns/containment/fluid-styles.md +86 -0
  125. package/patterns/containment/index.md +8 -0
  126. package/patterns/grid-repetition/card-grid.md +88 -0
  127. package/patterns/grid-repetition/columns.md +87 -0
  128. package/patterns/grid-repetition/dense-grid.md +88 -0
  129. package/patterns/grid-repetition/grid-wrapper.md +89 -0
  130. package/patterns/grid-repetition/index.md +12 -0
  131. package/patterns/grid-repetition/masonry-approx.md +87 -0
  132. package/patterns/grid-repetition/page-grid.md +90 -0
  133. package/patterns/grid-repetition/ram-grid.md +88 -0
  134. package/patterns/grid-repetition/twelve-span-grid.md +92 -0
  135. package/patterns/in-line-grouping/badge-list.md +94 -0
  136. package/patterns/in-line-grouping/breadcrumb.md +89 -0
  137. package/patterns/in-line-grouping/cluster.md +89 -0
  138. package/patterns/in-line-grouping/deconstructed-pancake.md +92 -0
  139. package/patterns/in-line-grouping/index.md +13 -0
  140. package/patterns/in-line-grouping/pagination.md +89 -0
  141. package/patterns/in-line-grouping/reel.md +90 -0
  142. package/patterns/in-line-grouping/split-nav.md +97 -0
  143. package/patterns/in-line-grouping/tab-strip.md +88 -0
  144. package/patterns/in-line-grouping/wrap-row.md +89 -0
  145. package/patterns/index.md +13 -0
  146. package/patterns/media-fit/frame.md +95 -0
  147. package/patterns/media-fit/icon-frame.md +87 -0
  148. package/patterns/media-fit/index.md +6 -0
  149. package/patterns/overlay-exception/imposter.md +95 -0
  150. package/patterns/overlay-exception/index.md +6 -0
  151. package/patterns/overlay-exception/overlay-stack.md +89 -0
  152. package/patterns/split-sidebar/index.md +12 -0
  153. package/patterns/split-sidebar/list-detail.md +95 -0
  154. package/patterns/split-sidebar/main-with-rail.md +95 -0
  155. package/patterns/split-sidebar/media-object.md +102 -0
  156. package/patterns/split-sidebar/sidebar.md +101 -0
  157. package/patterns/split-sidebar/split-screen.md +92 -0
  158. package/patterns/split-sidebar/sticky-aside.md +97 -0
  159. package/patterns/split-sidebar/supporting-pane.md +95 -0
  160. package/patterns/split-sidebar/switcher.md +93 -0
  161. package/patterns/stacking/feed.md +88 -0
  162. package/patterns/stacking/index.md +8 -0
  163. package/patterns/stacking/line-up.md +95 -0
  164. package/patterns/stacking/stack.md +87 -0
  165. package/patterns/stacking/step-nav.md +88 -0
  166. package/patterns/viewport-shell/cover.md +90 -0
  167. package/patterns/viewport-shell/fixed-sidenav-shell.md +97 -0
  168. package/patterns/viewport-shell/holy-grail.md +112 -0
  169. package/patterns/viewport-shell/index.md +11 -0
  170. package/patterns/viewport-shell/panel-layout.md +95 -0
  171. package/patterns/viewport-shell/scroll-body-shell.md +93 -0
  172. package/patterns/viewport-shell/sticky-footer.md +92 -0
  173. package/patterns/viewport-shell/sticky-header.md +86 -0
  174. package/platform-guides/apple-interaction.md +89 -0
  175. package/platform-guides/index.md +26 -0
  176. package/quality/claim-records/index.md +3 -0
  177. package/quality/claim-records/samples.md +92 -0
  178. package/quality/claim-records/stylegallery-multidomain-scope.md +42 -0
  179. package/quality/claims.md +77 -0
  180. package/quality/evidence/accessibility.md +58 -0
  181. package/quality/evidence/consumer-migration.md +42 -0
  182. package/quality/evidence/design-rationale.md +34 -0
  183. package/quality/evidence/executable-evidence.md +79 -0
  184. package/quality/evidence/families.md +56 -0
  185. package/quality/evidence/hci-evaluation.md +36 -0
  186. package/quality/evidence/index.md +16 -0
  187. package/quality/evidence/pattern-contract-audit.md +96 -0
  188. package/quality/evidence/search-metadata-polyhierarchy.md +86 -0
  189. package/quality/evidence/sources.md +43 -0
  190. package/quality/evidence/tokens.md +33 -0
  191. package/quality/evidence/visual-qa.md +41 -0
  192. package/quality/gates/accessibility-evidence.md +60 -0
  193. package/quality/gates/consumer-migration-evidence.md +53 -0
  194. package/quality/gates/design-claim.md +56 -0
  195. package/quality/gates/harmony-evaluation.md +78 -0
  196. package/quality/gates/index.md +13 -0
  197. package/quality/gates/layout.md +52 -0
  198. package/quality/gates/rationale.md +53 -0
  199. package/quality/gates/visual-evidence.md +47 -0
  200. package/quality/index.md +55 -0
  201. package/quality/principles.md +58 -0
  202. package/recipes/article-page.md +52 -0
  203. package/recipes/command-surface.md +55 -0
  204. package/recipes/dashboard.md +56 -0
  205. package/recipes/form-flow.md +55 -0
  206. package/recipes/homepage.md +89 -0
  207. package/recipes/index.md +13 -0
  208. package/recipes/list-detail.md +55 -0
  209. package/recipes/primitive-to-recipe-matrix.md +83 -0
  210. package/recipes/saas-settings.md +52 -0
  211. package/scripts/agent-native/a2a-projection.mjs +248 -0
  212. package/scripts/agent-native/agui-projection.mjs +178 -0
  213. package/scripts/agent-native/canonical-json.mjs +109 -0
  214. package/scripts/agent-native/cli-adapter.mjs +68 -0
  215. package/scripts/agent-native/events.mjs +113 -0
  216. package/scripts/agent-native/execution.mjs +227 -0
  217. package/scripts/agent-native/fixture.mjs +159 -0
  218. package/scripts/agent-native/identity.mjs +184 -0
  219. package/scripts/agent-native/immutable-store.mjs +102 -0
  220. package/scripts/agent-native/knowledge.mjs +168 -0
  221. package/scripts/agent-native/learning-records.mjs +244 -0
  222. package/scripts/agent-native/learning.mjs +247 -0
  223. package/scripts/agent-native/mcp-adapter.mjs +173 -0
  224. package/scripts/agent-native/queries.mjs +173 -0
  225. package/scripts/agent-native/registry.mjs +154 -0
  226. package/scripts/agent-native/retrieval.mjs +171 -0
  227. package/scripts/agent-native/self-description.mjs +160 -0
  228. package/scripts/artifact-metadata.mjs +20 -0
  229. package/scripts/audit-evidence-freshness.mjs +308 -0
  230. package/scripts/baseline-contract.mjs +189 -0
  231. package/scripts/baseline-schema-parity.mjs +40 -0
  232. package/scripts/build-reference-artifacts.mjs +127 -0
  233. package/scripts/calibration-raw-contract.mjs +172 -0
  234. package/scripts/capture-session-contract.mjs +187 -0
  235. package/scripts/component-state-artifact-fixture.mjs +44 -0
  236. package/scripts/component-state-contract.mjs +99 -0
  237. package/scripts/component-state-semantics.mjs +128 -0
  238. package/scripts/component-state-workflow-contract.mjs +67 -0
  239. package/scripts/component-state-workflow-negative-cases.mjs +64 -0
  240. package/scripts/consumer-conformance-contract.mjs +198 -0
  241. package/scripts/consumer-evidence-governance-contract.mjs +210 -0
  242. package/scripts/consumer-reference-case-runner.mjs +93 -0
  243. package/scripts/consumer-reference-schema.mjs +155 -0
  244. package/scripts/create-component-state-session.mjs +100 -0
  245. package/scripts/create-page-evidence-session.mjs +139 -0
  246. package/scripts/domain-document-boundaries.mjs +40 -0
  247. package/scripts/domain-validation-checks.mjs +141 -0
  248. package/scripts/evidence-artifact-contract.mjs +191 -0
  249. package/scripts/finalize-component-state-evidence.mjs +165 -0
  250. package/scripts/finalize-page-evidence.mjs +197 -0
  251. package/scripts/generate-consumer-reference-evidence.mjs +154 -0
  252. package/scripts/generate-patterns.mjs +218 -0
  253. package/scripts/governance-matrix-contract.mjs +73 -0
  254. package/scripts/governance-matrix-negative-cases.mjs +114 -0
  255. package/scripts/governance-policy-contract.mjs +49 -0
  256. package/scripts/governance-test-fixture.mjs +321 -0
  257. package/scripts/governance-workflow-negative-cases.mjs +241 -0
  258. package/scripts/governed-profile-registry.mjs +125 -0
  259. package/scripts/json-schema-formats.mjs +31 -0
  260. package/scripts/markdown-structure.mjs +45 -0
  261. package/scripts/page-artifact-metadata.mjs +124 -0
  262. package/scripts/page-evidence-contract.mjs +297 -0
  263. package/scripts/page-evidence-fixture.mjs +271 -0
  264. package/scripts/pattern-data.mjs +325 -0
  265. package/scripts/profile-record-contract.mjs +140 -0
  266. package/scripts/promotion-attestation-contract.mjs +91 -0
  267. package/scripts/promotion-boundary-test-contract.mjs +92 -0
  268. package/scripts/promotion-fixture-inventory.mjs +109 -0
  269. package/scripts/promotion-governance-contract.mjs +28 -0
  270. package/scripts/reference-artifact-path-contract.mjs +28 -0
  271. package/scripts/reference-profile-contract.mjs +114 -0
  272. package/scripts/reference-token-contract.mjs +156 -0
  273. package/scripts/run-consumer-page-evidence-ci.mjs +170 -0
  274. package/scripts/sg-mcp.mjs +16 -0
  275. package/scripts/sg.mjs +27 -0
  276. package/scripts/strict-json.mjs +72 -0
  277. package/scripts/summarize-sentinel-calibration.mjs +98 -0
  278. package/scripts/test-agent-adapter-conformance.mjs +250 -0
  279. package/scripts/test-agent-conformance-receipt.mjs +106 -0
  280. package/scripts/test-agent-events.mjs +81 -0
  281. package/scripts/test-agent-execution-effects.mjs +114 -0
  282. package/scripts/test-agent-execution-fixtures.mjs +140 -0
  283. package/scripts/test-agent-execution-kernel.mjs +39 -0
  284. package/scripts/test-agent-execution-learning.mjs +130 -0
  285. package/scripts/test-agent-execution-lifecycle.mjs +143 -0
  286. package/scripts/test-agent-execution-safety.mjs +158 -0
  287. package/scripts/test-agent-execution-views.mjs +117 -0
  288. package/scripts/test-agent-identity-integrity-cases.mjs +160 -0
  289. package/scripts/test-agent-knowledge-kernel.mjs +183 -0
  290. package/scripts/test-agent-learning.mjs +246 -0
  291. package/scripts/test-agent-mcp.mjs +246 -0
  292. package/scripts/test-agent-native-schemas.mjs +215 -0
  293. package/scripts/test-agent-projections.mjs +249 -0
  294. package/scripts/test-agent-registry-mcp-integrity.mjs +215 -0
  295. package/scripts/test-agent-registry-mutations.mjs +110 -0
  296. package/scripts/test-audit-evidence-freshness.mjs +177 -0
  297. package/scripts/test-component-state-sentinel.mjs +35 -0
  298. package/scripts/test-component-state-source-contract.mjs +83 -0
  299. package/scripts/test-consumer-conformance-sentinel.mjs +165 -0
  300. package/scripts/test-consumer-reference-sentinel.mjs +31 -0
  301. package/scripts/test-generate-consumer-reference-evidence.mjs +124 -0
  302. package/scripts/test-json-cli-output.mjs +151 -0
  303. package/scripts/test-json-schema-formats.mjs +40 -0
  304. package/scripts/test-reference-adapters.mjs +199 -0
  305. package/scripts/test-renderer-purity.mjs +34 -0
  306. package/scripts/test-sg-cli.mjs +129 -0
  307. package/scripts/test-summarize-sentinel-calibration.mjs +226 -0
  308. package/scripts/test-validate-baseline-manifest.mjs +234 -0
  309. package/scripts/test-validate-component-state-artifacts.mjs +243 -0
  310. package/scripts/test-validate-component-state.mjs +106 -0
  311. package/scripts/test-validate-consumer-conformance.mjs +427 -0
  312. package/scripts/test-validate-consumer-reference-cli.mjs +69 -0
  313. package/scripts/test-validate-consumer-reference.mjs +263 -0
  314. package/scripts/test-validate-domains.mjs +357 -0
  315. package/scripts/test-validate-governance.mjs +292 -0
  316. package/scripts/test-validate-ia.mjs +262 -0
  317. package/scripts/test-validate-okf.mjs +73 -0
  318. package/scripts/test-validate-page-evidence.mjs +302 -0
  319. package/scripts/test-validate-patterns.mjs +157 -0
  320. package/scripts/test-validate-promotion-rfc.mjs +250 -0
  321. package/scripts/test-validate-reference-profiles.mjs +170 -0
  322. package/scripts/test-validate-unity-org-wiki.mjs +57 -0
  323. package/scripts/test-validate-unity-source-contracts.mjs +67 -0
  324. package/scripts/test-validate-vocabulary.mjs +174 -0
  325. package/scripts/test-validate-webpage-workflow.mjs +293 -0
  326. package/scripts/validate-baseline-manifest.mjs +44 -0
  327. package/scripts/validate-catalog.mjs +65 -0
  328. package/scripts/validate-component-state.mjs +141 -0
  329. package/scripts/validate-consumer-conformance.mjs +477 -0
  330. package/scripts/validate-consumer-reference.mjs +288 -0
  331. package/scripts/validate-domains.mjs +237 -0
  332. package/scripts/validate-governance.mjs +248 -0
  333. package/scripts/validate-ia.mjs +136 -0
  334. package/scripts/validate-links.mjs +66 -0
  335. package/scripts/validate-okf.mjs +104 -0
  336. package/scripts/validate-page-evidence.mjs +191 -0
  337. package/scripts/validate-patterns.mjs +246 -0
  338. package/scripts/validate-promotion-rfc.mjs +224 -0
  339. package/scripts/validate-reference-artifacts.mjs +125 -0
  340. package/scripts/validate-renderer-purity.mjs +56 -0
  341. package/scripts/validate-unity-org-wiki.mjs +146 -0
  342. package/scripts/validate-unity-source-contracts.mjs +107 -0
  343. package/scripts/validate-vocabulary.mjs +115 -0
  344. package/scripts/validate-webpage-workflow.mjs +254 -0
  345. package/scripts/visual-expectation-contract.mjs +20 -0
  346. package/scripts/workflow-action-contract.mjs +163 -0
@@ -0,0 +1,210 @@
1
+ import { immutableActionPins } from "./governance-policy-contract.mjs";
2
+ import { workflowActionFailures } from "./workflow-action-contract.mjs";
3
+
4
+ const validatePath = ".github/workflows/validate.yml";
5
+ const freshnessPath = ".github/workflows/evidence-freshness.yml";
6
+
7
+ function workflowJob(workflow, id) {
8
+ const marker = ` ${id}:\n`;
9
+ const start = workflow.indexOf(marker);
10
+ if (start === -1) return "";
11
+ const remainder = workflow.slice(start + marker.length);
12
+ const next = remainder.search(/^ [a-zA-Z0-9_-]+:/m);
13
+ return marker + (next === -1 ? remainder : remainder.slice(0, next));
14
+ }
15
+
16
+ function missing(relative, content, fragments) {
17
+ return fragments.flatMap(([fragment, label = fragment]) => content.includes(fragment) ? [] : [`${relative}: missing ${label}`]);
18
+ }
19
+
20
+ function missingCommands(relative, content, fragments) {
21
+ const commands = content.split("\n").map((line) => line.trim()).filter(Boolean);
22
+ return fragments.flatMap(([fragment, label = fragment]) => commands.some((command) => {
23
+ let candidate = command;
24
+ const assignment = /^[A-Za-z_][A-Za-z0-9_]*=(?:"(?:[^"\\]|\\.)*"|'[^']*'|[^\s]+)?\s+/;
25
+ if (candidate.startsWith("env ")) candidate = candidate.slice(4);
26
+ while (true) {
27
+ const withoutUnset = candidate.replace(/^-u\s+[A-Za-z_][A-Za-z0-9_]*\s+/, "");
28
+ if (withoutUnset !== candidate) {
29
+ candidate = withoutUnset;
30
+ continue;
31
+ }
32
+ const withoutAssignment = candidate.replace(assignment, "");
33
+ if (withoutAssignment === candidate) break;
34
+ candidate = withoutAssignment;
35
+ }
36
+ if (candidate !== fragment && !candidate.startsWith(`${fragment} `)) return false;
37
+ const suffix = candidate.slice(fragment.length);
38
+ return !/(?:&&|\|\||[;&|])/.test(suffix);
39
+ }) ? [] : [`${relative}: missing ${label}`]);
40
+ }
41
+
42
+ function hasIf(lines, indentation) {
43
+ const pattern = new RegExp(`^ {${indentation}}(?:-\\s+)?["']?if["']?:\\s*`);
44
+ return lines.some((line) => pattern.test(line));
45
+ }
46
+
47
+ function hasLiteralDisabledIf(lines, indentation) {
48
+ const pattern = new RegExp(`^ {${indentation}}(?:-\\s+)?["']?if["']?:\\s*(?:false|['"]false['"]|\\$\\{\\{\\s*false\\s*\\}\\})(?:\\s+#.*)?$`, "i");
49
+ return lines.some((line) => pattern.test(line));
50
+ }
51
+
52
+ function executableRunText(workflow) {
53
+ const lines = workflow.split("\n");
54
+ const commands = [];
55
+ const stepsLine = lines.findIndex((line) => /^\s*steps:\s*$/.test(line));
56
+ if (stepsLine === -1) return "";
57
+ const stepsIndent = lines[stepsLine].match(/^\s*/)[0].length;
58
+ for (let index = stepsLine + 1; index < lines.length;) {
59
+ const start = lines[index].match(new RegExp(`^ {${stepsIndent + 2}}-\\s+`));
60
+ if (!start) {
61
+ if (lines[index].trim() && lines[index].match(/^\s*/)[0].length <= stepsIndent) break;
62
+ index += 1;
63
+ continue;
64
+ }
65
+ let end = index + 1;
66
+ while (end < lines.length && !new RegExp(`^ {${stepsIndent + 2}}-\\s+`).test(lines[end]) && (!lines[end].trim() || lines[end].match(/^\s*/)[0].length > stepsIndent)) end += 1;
67
+ const step = lines.slice(index, end);
68
+ if (!hasIf(step, stepsIndent + 2) && !hasIf(step, stepsIndent + 4)) {
69
+ const runIndex = step.findIndex((line) => new RegExp(`^(?: {${stepsIndent + 2}}-\\s+| {${stepsIndent + 4}})run:`).test(line));
70
+ if (runIndex !== -1) {
71
+ const match = step[runIndex].match(/^\s*(?:-\s+)?run:\s*(.*)$/);
72
+ const scalar = match[1].replace(/\s+#.*$/, "").trim();
73
+ if (scalar && !["|", ">", "|-", ">-"].includes(scalar)) commands.push(scalar);
74
+ if (["|", ">", "|-", ">-"].includes(scalar)) {
75
+ const blockCommands = [];
76
+ let heredoc;
77
+ for (const raw of step.slice(runIndex + 1)) {
78
+ const command = raw.trim();
79
+ if (!command || command.startsWith("#")) continue;
80
+ if (heredoc) {
81
+ if (command === heredoc) heredoc = undefined;
82
+ continue;
83
+ }
84
+ const opener = command.match(/<<-?\s*['"]?([A-Za-z0-9_]+)['"]?/);
85
+ blockCommands.push(command.replace(/\s+#.*$/, "").trim());
86
+ if (opener) heredoc = opener[1];
87
+ }
88
+ if (scalar.startsWith(">")) commands.push(blockCommands.join(" "));
89
+ else commands.push(...blockCommands);
90
+ }
91
+ }
92
+ }
93
+ index = end;
94
+ }
95
+ const logicalCommands = [];
96
+ let logical = "";
97
+ for (const command of commands) {
98
+ logical = logical ? `${logical} ${command}` : command;
99
+ if (logical.endsWith("\\")) {
100
+ logical = logical.slice(0, -1).trimEnd();
101
+ continue;
102
+ }
103
+ logicalCommands.push(logical);
104
+ logical = "";
105
+ }
106
+ if (logical) logicalCommands.push(logical);
107
+ return logicalCommands.join("\n");
108
+ }
109
+
110
+ export function consumerEvidenceGovernanceFailures({ evidence, freshnessWorkflow, governance, validationWorkflow }) {
111
+ const failures = [];
112
+ failures.push(...missing("GOVERNANCE.md", governance, [
113
+ ["scheduled_stale_audit: deferred"],
114
+ ["scheduled_evidence_audit: active_advisory"],
115
+ ["Decision: run a weekly advisory audit only for evidence records that already declare `expires_at` or `review_by`."],
116
+ ["No repository-wide maximum age or inferred time-to-live applies."],
117
+ ["Malformed records and auditor failures remain blocking even though due-date findings are advisory."],
118
+ [freshnessPath],
119
+ ["Monday at 05:00 UTC"],
120
+ ]));
121
+ failures.push(...missing("quality/evidence/executable-evidence.md", evidence, [
122
+ ["Explicit evidence deadlines are audited weekly without inventing a repository-wide maximum age."],
123
+ ["Malformed records and auditor failures remain blocking even though due-date findings are advisory."],
124
+ ]));
125
+ const validationCommands = executableRunText(workflowJob(validationWorkflow, "validate"));
126
+ failures.push(...missingCommands(`${validatePath}: validate`, validationCommands, [
127
+ ["node -c scripts/consumer-evidence-governance-contract.mjs"],
128
+ ["node -c scripts/json-schema-formats.mjs"],
129
+ ["node -c scripts/consumer-conformance-contract.mjs"],
130
+ ["node -c scripts/validate-consumer-conformance.mjs"],
131
+ ["node -c scripts/test-validate-consumer-conformance.mjs"],
132
+ ["node -c consumer-reference/fixtures/consumer-conformance/e2e-fixture.mjs"],
133
+ ["node -c scripts/page-evidence-contract.mjs"],
134
+ ["node -c scripts/page-evidence-fixture.mjs"],
135
+ ["node -c scripts/create-page-evidence-session.mjs"],
136
+ ["node -c scripts/finalize-page-evidence.mjs"],
137
+ ["node -c scripts/validate-page-evidence.mjs"],
138
+ ["node -c scripts/test-validate-page-evidence.mjs"],
139
+ ["node -c scripts/audit-evidence-freshness.mjs"],
140
+ ["node -c scripts/test-audit-evidence-freshness.mjs"],
141
+ ["node -c tests/fixtures/consumer-conformance-scenarios.mjs"],
142
+ ["node -c tests/helpers/render-consumer-conformance.mjs"],
143
+ ["node -c tests/consumer-conformance.spec.mjs"],
144
+ ["node -c scripts/test-consumer-conformance-sentinel.mjs"],
145
+ ["node scripts/test-validate-consumer-conformance.mjs --case valid-runtime-proof --json"],
146
+ ["node scripts/test-validate-consumer-conformance.mjs --json"],
147
+ ["node scripts/test-validate-page-evidence.mjs --json"],
148
+ ["node scripts/test-audit-evidence-freshness.mjs --json"],
149
+ [`node -e 'JSON.parse(require("fs").readFileSync("consumer-reference/schema/consumer-conformance-record.schema.json"))'`],
150
+ [`node -e 'JSON.parse(require("fs").readFileSync("consumer-reference/schema/page-evidence-session.schema.json"))'`],
151
+ [`node -e 'JSON.parse(require("fs").readFileSync("consumer-reference/schema/page-evidence-manifest.schema.json"))'`],
152
+ ]));
153
+
154
+ const blocking = workflowJob(validationWorkflow, "consumer-conformance");
155
+ if (!blocking) failures.push(`${validatePath}: missing consumer-conformance job`);
156
+ else {
157
+ if (hasLiteralDisabledIf(blocking.split("\n"), 4)) failures.push(`${validatePath}: consumer-conformance job must be enabled`);
158
+ if (hasIf(blocking.split("\n"), 4)) failures.push(`${validatePath}: consumer-conformance job must be unconditional`);
159
+ if (hasIf(blocking.split("\n"), 6) || hasIf(blocking.split("\n"), 8)) failures.push(`${validatePath}: consumer-conformance: required command steps must be unconditional`);
160
+ if (/^\s+continue-on-error:/m.test(blocking)) failures.push(`${validatePath}: consumer-conformance must be blocking`);
161
+ failures.push(...missing(`${validatePath}: consumer-conformance`, blocking, [
162
+ ["mcr.microsoft.com/playwright:v1.61.0-noble@sha256:57b65fdc9ceabe0ef613124c7bbe2babcf9362c4d85e382fe3b03604e84b428a"],
163
+ ]));
164
+ failures.push(...missingCommands(`${validatePath}: consumer-conformance`, executableRunText(blocking), [
165
+ ["npx playwright test tests/consumer-conformance.spec.mjs --project=chromium --reporter=line"],
166
+ ["node scripts/test-consumer-conformance-sentinel.mjs --json"],
167
+ ]));
168
+ }
169
+ const capture = workflowJob(validationWorkflow, "consumer-page-evidence");
170
+ if (!capture) failures.push(`${validatePath}: missing consumer-page-evidence job`);
171
+ else {
172
+ if (!/^\s+continue-on-error:\s*true\s*$/m.test(capture)) failures.push(`${validatePath}: consumer-page-evidence must be nonblocking`);
173
+ failures.push(...missing(`${validatePath}: consumer-page-evidence`, capture, [
174
+ ["CONSUMER_CONFORMANCE_CAPTURE_DIR: .tmp/consumer-page-evidence"],
175
+ ["actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02"],
176
+ ["retention-days: 14"],
177
+ ]));
178
+ failures.push(...missingCommands(`${validatePath}: consumer-page-evidence`, executableRunText(capture), [
179
+ ["node scripts/create-page-evidence-session.mjs"],
180
+ ["npx playwright test tests/consumer-conformance.spec.mjs --project=chromium --reporter=line"],
181
+ ["node scripts/finalize-page-evidence.mjs"],
182
+ ["node scripts/validate-page-evidence.mjs"],
183
+ ]));
184
+ }
185
+
186
+ failures.push(...missing(freshnessPath, freshnessWorkflow, [
187
+ ["workflow_dispatch:"],
188
+ ["cron: '0 5 * * 1'", "Monday 05:00 UTC schedule"],
189
+ ["contents: read"],
190
+ ["persist-credentials: false"],
191
+ ["evidence-freshness-report.json", "JSON report"],
192
+ ["::warning file=", "GitHub warning emission"],
193
+ ["if: always()", "always-upload evidence"],
194
+ ["actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02", "immutable upload action"],
195
+ ["retention-days: 14", "14-day retention"],
196
+ ]));
197
+ failures.push(...missing(freshnessPath, executableRunText(freshnessWorkflow), [
198
+ ["scripts/audit-evidence-freshness.mjs", "freshness auditor"],
199
+ ["--record consumer-reference/baselines/calibration.json", "explicit calibration record inventory"],
200
+ ["--mode advisory"],
201
+ ]));
202
+ if (/continue-on-error\s*:/i.test(freshnessWorkflow)) failures.push(`${freshnessPath}: continue-on-error is forbidden because malformed records and tool failures must block`);
203
+ for (const pin of immutableActionPins) {
204
+ if ((pin.includes("actions/checkout") || pin.includes("actions/setup-node") || pin.includes("actions/upload-artifact")) && !freshnessWorkflow.includes(pin)) {
205
+ failures.push(`${freshnessPath}: missing immutable action pin ${pin}`);
206
+ }
207
+ }
208
+ failures.push(...workflowActionFailures(freshnessWorkflow, freshnessPath));
209
+ return failures;
210
+ }
@@ -0,0 +1,93 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { validateItemSchema } from "./consumer-reference-schema.mjs";
7
+
8
+ const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
+ const validator = path.join(repositoryRoot, "scripts", "validate-consumer-reference.mjs");
10
+ const itemPath = "consumer-reference/fixtures/item.json";
11
+
12
+ function findingCodes(output) {
13
+ if (!Array.isArray(output.failures)) return [];
14
+ return output.failures.flatMap((failure) => {
15
+ if (typeof failure === "string") return [failure];
16
+ return typeof failure?.code === "string" ? [failure.code] : [];
17
+ });
18
+ }
19
+
20
+ function writeFixture(testCase, baseItem) {
21
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `stylegallery-consumer-reference-${testCase.name}-`));
22
+ let externalRoot;
23
+ const item = Object.hasOwn(testCase, "value") ? structuredClone(testCase.value) : structuredClone(baseItem);
24
+ testCase.mutate?.(item);
25
+ const files = {
26
+ "CATALOG.md": "# Catalog\n",
27
+ [itemPath]: `${JSON.stringify(item, null, 2)}\n`,
28
+ "layout/index.md": testCase.layout ?? "# Layout\n",
29
+ "patterns/index.md": "# Patterns\n",
30
+ "quality/handoff.md": "Implementation handoff:\nConsumer reference: not_applicable\nConsumer reference reason: This fixture has no consumer-specific reference record.\n",
31
+ "scripts/pattern-data.mjs": testCase.patternData ?? "export const patterns = [];\n",
32
+ ...(testCase.extraFiles ?? {}),
33
+ };
34
+ for (const [relative, content] of Object.entries(files)) {
35
+ const target = path.join(root, relative);
36
+ fs.mkdirSync(path.dirname(target), { recursive: true });
37
+ fs.writeFileSync(target, content);
38
+ }
39
+ if (testCase.link) {
40
+ const link = path.join(root, "consumer-reference", "fixtures", "redirect.json");
41
+ if (testCase.link === "inside") {
42
+ fs.symlinkSync(path.join(root, itemPath), link);
43
+ } else {
44
+ externalRoot = fs.mkdtempSync(path.join(os.tmpdir(), "stylegallery-consumer-reference-outside-"));
45
+ const external = path.join(externalRoot, "record.json");
46
+ fs.writeFileSync(external, "{}\n");
47
+ fs.symlinkSync(external, link);
48
+ }
49
+ }
50
+ if (testCase.itemLink) {
51
+ const itemTarget = path.join(root, itemPath);
52
+ fs.rmSync(itemTarget);
53
+ if (testCase.itemLink === "inside") {
54
+ const actual = path.join(root, "consumer-reference", "fixtures", "actual-item.json");
55
+ fs.writeFileSync(actual, `${JSON.stringify(item, null, 2)}\n`);
56
+ fs.symlinkSync(actual, itemTarget);
57
+ } else {
58
+ externalRoot = fs.mkdtempSync(path.join(os.tmpdir(), "stylegallery-consumer-reference-item-outside-"));
59
+ const external = path.join(externalRoot, "item.json");
60
+ fs.writeFileSync(external, `${JSON.stringify(item, null, 2)}\n`);
61
+ fs.symlinkSync(external, itemTarget);
62
+ }
63
+ }
64
+ return { externalRoot, item, root };
65
+ }
66
+
67
+ export function makeConsumerReferenceCaseRunner({ baseItem, schema }) {
68
+ return function runCase(testCase) {
69
+ const fixture = writeFixture(testCase, baseItem);
70
+ try {
71
+ const child = spawnSync(process.execPath, [validator, "--item", itemPath, "--json"], {
72
+ cwd: fixture.root,
73
+ encoding: "utf8",
74
+ });
75
+ const output = JSON.parse(child.stdout);
76
+ const codes = findingCodes(output);
77
+ const accepted = child.status === 0 && output.ok && output.scaffold !== true;
78
+ const rejected = child.status !== 0 && !output.ok && codes.includes(testCase.expect);
79
+ const schemaCodes = validateItemSchema(fixture.item, schema).map((finding) => finding.code);
80
+ const schemaParity = testCase.schemaValid === undefined || (schemaCodes.length === 0) === testCase.schemaValid;
81
+ return {
82
+ actual: { codes, ok: output.ok, scaffold: output.scaffold === true, schemaCodes, status: child.status },
83
+ expected: testCase.expect ?? "ok:true and exit:0",
84
+ name: testCase.name,
85
+ ok: (testCase.expect === null ? accepted : rejected) && schemaParity,
86
+ rules: testCase.rules ?? [],
87
+ };
88
+ } finally {
89
+ fs.rmSync(fixture.root, { force: true, recursive: true });
90
+ if (fixture.externalRoot) fs.rmSync(fixture.externalRoot, { force: true, recursive: true });
91
+ }
92
+ };
93
+ }
@@ -0,0 +1,155 @@
1
+ const fieldCodes = {
2
+ artifact_mode: "artifact_mode_invalid",
3
+ default: "profile_default_forbidden",
4
+ example_only: "profile_example_only_required",
5
+ fixture_independence: "fixture_independence_related",
6
+ id: "item_id_invalid",
7
+ layout_source_sha: "profile_layout_source_sha_required",
8
+ local_foundations: "profile_foundations_path_required",
9
+ maturity: "maturity_invalid",
10
+ profile_kind: "profile_kind_governed_local",
11
+ related_fixture_set_id: "profile_related_fixture_set_required",
12
+ removal_trigger: "removal_trigger_invalid",
13
+ replacement: "replacement_invalid",
14
+ review_independence: "review_independence_single_account",
15
+ schema_version: "schema_version_invalid",
16
+ tokens: "profile_tokens_path_required",
17
+ };
18
+
19
+ export function isPlainObject(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21
+ }
22
+
23
+ function scalarMatches(value, definition) {
24
+ if (definition.type === "string" && typeof value !== "string") return false;
25
+ if (definition.minLength !== undefined && Array.from(value).length < definition.minLength) return false;
26
+ if (definition.pattern !== undefined && !new RegExp(definition.pattern).test(value)) return false;
27
+ if (definition.const !== undefined && value !== definition.const) return false;
28
+ return definition.enum === undefined || definition.enum.includes(value);
29
+ }
30
+
31
+ function unknownProperties(value, definitions) {
32
+ return Object.keys(value).filter((key) => !Object.hasOwn(definitions, key));
33
+ }
34
+
35
+ function missingProperties(value, required) {
36
+ return required.filter((key) => !Object.hasOwn(value, key));
37
+ }
38
+
39
+ function validateProfileObjects(item, schema, add) {
40
+ if (Object.hasOwn(item, "profile_kind")) {
41
+ const governed = schema.allOf.find((rule) => rule.if?.required?.includes("profile_kind"))?.then;
42
+ for (const property of governed?.required ?? []) if (!Object.hasOwn(item, property)) add("profile_field_required", `governed profile requires ${property}`);
43
+ for (const property of ["component_records", "evidence_records", "fixture_records", "generated_records", "state_records"]) {
44
+ if (!Object.hasOwn(item, property)) continue;
45
+ const value = item[property];
46
+ const definition = schema.properties[property];
47
+ const validItems = Array.isArray(value) && value.every((entry) => {
48
+ if (definition.items.const !== undefined) return entry === definition.items.const;
49
+ return definition.items.enum.includes(entry);
50
+ });
51
+ if (!Array.isArray(value) || value.length < definition.minItems || new Set(value).size !== value.length || !validItems) add("profile_reference_array_invalid", `${property} does not satisfy the governed profile schema`);
52
+ }
53
+ }
54
+ if (Object.hasOwn(item, "environment_assumptions")) {
55
+ const environment = item.environment_assumptions;
56
+ const definition = schema.properties.environment_assumptions;
57
+ if (!isPlainObject(environment)) {
58
+ add("profile_environment_object_required", "environment_assumptions must be an object");
59
+ } else {
60
+ for (const property of unknownProperties(environment, definition.properties)) add("profile_environment_property_unknown", `unknown environment assumption ${property}`);
61
+ if (!Object.hasOwn(environment, "user_agent_styles") || !scalarMatches(environment.user_agent_styles, definition.properties.user_agent_styles)) add("profile_ua_assumption_required", "user_agent_styles must be a non-empty string");
62
+ const reset = environment.reset;
63
+ if (!isPlainObject(reset)) {
64
+ add("profile_reset_assumptions_required", "reset assumptions must be an object");
65
+ } else {
66
+ for (const property of unknownProperties(reset, definition.properties.reset.properties)) add("profile_reset_property_unknown", `unknown reset assumption ${property}`);
67
+ for (const property of definition.properties.reset.required) {
68
+ if (!Object.hasOwn(reset, property) || !scalarMatches(reset[property], definition.properties.reset.properties[property])) add("profile_reset_assumptions_required", `reset requires ${property}`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ if (Object.hasOwn(item, "selection")) {
74
+ const selection = item.selection;
75
+ const definition = schema.properties.selection;
76
+ if (!isPlainObject(selection)) {
77
+ add("profile_selection_object_required", "selection must be an object");
78
+ } else {
79
+ for (const property of unknownProperties(selection, definition.properties)) add("profile_selection_property_unknown", `unknown selection property ${property}`);
80
+ for (const property of definition.required) {
81
+ if (!Object.hasOwn(selection, property) || !scalarMatches(selection[property], definition.properties[property])) add("profile_explicit_selection_required", `selection requires ${property}`);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ export function validateItemSchema(item, schema) {
88
+ const findings = [];
89
+ const add = (code, message) => findings.push({ code, message });
90
+ if (!isPlainObject(item)) {
91
+ add("item_object_required", "item must be a JSON object");
92
+ return findings;
93
+ }
94
+
95
+ for (const property of unknownProperties(item, schema.properties)) {
96
+ add("item_property_unknown", `unknown item property ${property}`);
97
+ }
98
+ for (const property of missingProperties(item, schema.required)) {
99
+ add(property === "handoff" ? "handoff_required" : "item_field_required", `missing required field ${property}`);
100
+ }
101
+ for (const [property, code] of Object.entries(fieldCodes)) {
102
+ if (Object.hasOwn(item, property) && !scalarMatches(item[property], schema.properties[property])) {
103
+ add(code, `${property} does not satisfy the item schema`);
104
+ }
105
+ }
106
+
107
+ if (Object.hasOwn(item, "owner")) {
108
+ const definition = schema.properties.owner;
109
+ if (!isPlainObject(item.owner)) {
110
+ add("owner_object_required", "owner must be an object");
111
+ } else {
112
+ for (const property of unknownProperties(item.owner, definition.properties)) add("owner_property_unknown", `unknown owner property ${property}`);
113
+ for (const property of missingProperties(item.owner, definition.required)) add(property === "name" ? "owner_name_required" : "owner_enforcement_placeholder", `owner requires ${property}`);
114
+ if (Object.hasOwn(item.owner, "enforcement") && !scalarMatches(item.owner.enforcement, definition.properties.enforcement)) add("owner_enforcement_placeholder", "owner.enforcement must be placeholder");
115
+ if (Object.hasOwn(item.owner, "name") && !scalarMatches(item.owner.name, definition.properties.name)) add("owner_name_invalid", "owner.name must be a non-empty string");
116
+ }
117
+ }
118
+
119
+ if (Object.hasOwn(item, "support")) {
120
+ const definition = schema.properties.support;
121
+ if (!isPlainObject(item.support)) {
122
+ add("support_object_required", "support must be an object");
123
+ } else {
124
+ for (const property of unknownProperties(item.support, definition.properties)) add("support_property_unknown", `unknown support property ${property}`);
125
+ for (const property of missingProperties(item.support, definition.required)) add("support_status_required", `support requires ${property}`);
126
+ if (Object.hasOwn(item.support, "status") && !scalarMatches(item.support.status, definition.properties.status)) add("support_status_invalid", "support.status must be active or ended");
127
+ }
128
+ }
129
+
130
+ if (Object.hasOwn(item, "handoff")) {
131
+ if (!isPlainObject(item.handoff)) {
132
+ add("handoff_object_required", "handoff must be an object");
133
+ } else {
134
+ const variants = schema.properties.handoff.oneOf;
135
+ const definition = variants.find((candidate) => candidate.properties.status.const === item.handoff.status);
136
+ if (!definition) {
137
+ add("handoff_status_invalid", "handoff status must be declared or not_applicable");
138
+ } else {
139
+ for (const property of unknownProperties(item.handoff, definition.properties)) add("handoff_property_unknown", `unknown handoff property ${property}`);
140
+ for (const property of missingProperties(item.handoff, definition.required)) add(property === "record" ? "record_required" : "not_applicable_reason_sentence", `handoff requires ${property}`);
141
+ if (definition.properties.record && Object.hasOwn(item.handoff, "record") && !scalarMatches(item.handoff.record, definition.properties.record)) add("record_schema_invalid", "record does not satisfy the schema path pattern");
142
+ if (definition.properties.reason && Object.hasOwn(item.handoff, "reason") && !scalarMatches(item.handoff.reason, definition.properties.reason)) add("not_applicable_reason_sentence", "not_applicable requires the schema sentence rule");
143
+ }
144
+ }
145
+ }
146
+
147
+ validateProfileObjects(item, schema, add);
148
+
149
+ if (item.maturity === "stable" && item.support?.status === "ended") add("stable_support_ended", "stable maturity cannot have ended support");
150
+ if (item.maturity === "deprecated") {
151
+ if (!Object.hasOwn(item, "replacement") || !Object.hasOwn(item, "removal_trigger")) add("deprecated_migration_required", "deprecated items require replacement and removal_trigger");
152
+ }
153
+ if (typeof item.review_independence === "boolean") add("review_independence_boolean", "review_independence does not accept boolean aliases");
154
+ return findings;
155
+ }
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { execFileSync } from "node:child_process";
8
+ import { fileURLToPath } from "node:url";
9
+ import { chromium } from "playwright";
10
+ import {
11
+ canonicalIntended,
12
+ canonicalSourceManifest,
13
+ dirtyRelevantSources,
14
+ repositoryGitArgs,
15
+ sha256,
16
+ } from "./capture-session-contract.mjs";
17
+ import { compileSchemas } from "./component-state-contract.mjs";
18
+
19
+ const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
20
+ const options = {
21
+ json: false,
22
+ output: undefined,
23
+ profileRoot: path.join(repositoryRoot, "design-engineering/reference-profiles/governed-local"),
24
+ };
25
+ const failures = [];
26
+
27
+ for (let index = 2; index < process.argv.length; index += 1) {
28
+ const argument = process.argv[index];
29
+ if (argument === "--json") options.json = true;
30
+ else if (["--output", "--root"].includes(argument)) {
31
+ const value = process.argv[index + 1];
32
+ if (!value) failures.push({ code: "argument_value_required", message: `${argument} requires a value`, path: "<cli>" });
33
+ else {
34
+ if (argument === "--output") options.output = path.resolve(value);
35
+ if (argument === "--root") options.profileRoot = path.resolve(value);
36
+ index += 1;
37
+ }
38
+ } else failures.push({ code: "argument_unknown", message: `unsupported argument ${argument}`, path: "<cli>" });
39
+ }
40
+
41
+ if (!options.output) failures.push({ code: "argument_value_required", message: "--output is required", path: "<cli>" });
42
+ else if (path.basename(options.output) !== "capture-session.json") failures.push({ code: "capture_session_path_invalid", message: "receipt filename must be capture-session.json", path: options.output });
43
+ else if (fs.existsSync(options.output)) failures.push({ code: "capture_session_replay", message: "capture session receipt already exists and cannot be overwritten", path: options.output });
44
+
45
+ function git(...args) {
46
+ return execFileSync("git", repositoryGitArgs(repositoryRoot, ...args), { cwd: repositoryRoot, encoding: "utf8" }).trim();
47
+ }
48
+
49
+ const executable = chromium.executablePath();
50
+ const revisionSegment = executable.split(path.sep).find((segment) => /^chromium(?:_headless_shell)?-\d+$/.test(segment));
51
+ const packageJson = JSON.parse(fs.readFileSync(path.join(repositoryRoot, "package.json"), "utf8"));
52
+ const intended = canonicalIntended(options.profileRoot, failures);
53
+ const revision = git("rev-parse", "HEAD");
54
+ if (process.env.GITHUB_SHA && process.env.GITHUB_SHA !== revision) failures.push({ code: "capture_revision_mismatch", message: `GITHUB_SHA ${process.env.GITHUB_SHA} does not match checked-out HEAD ${revision}`, path: repositoryRoot });
55
+ let source;
56
+ try {
57
+ source = canonicalSourceManifest(repositoryRoot, options.profileRoot);
58
+ const dirty = dirtyRelevantSources(repositoryRoot, options.profileRoot);
59
+ if (dirty.length > 0) failures.push({ code: "capture_source_dirty", message: `relevant capture sources must be committed: ${dirty.join(", ")}`, path: repositoryRoot });
60
+ } catch (error) {
61
+ failures.push({ code: "capture_source_unreadable", message: error instanceof Error ? error.message : String(error), path: options.profileRoot });
62
+ }
63
+ const receipt = {
64
+ attempt: Number(process.env.GITHUB_RUN_ATTEMPT ?? 1),
65
+ branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || git("branch", "--show-current") || "detached",
66
+ environment: {
67
+ browser: execFileSync(executable, ["--version"], { encoding: "utf8" }).trim(),
68
+ browser_revision: revisionSegment ?? path.basename(executable),
69
+ container_image: process.env.SENTINEL_CONTAINER_IMAGE ?? `local-host:${os.release()}`,
70
+ kind: "browser",
71
+ lockfile_sha256: sha256(fs.readFileSync(path.join(repositoryRoot, "package-lock.json"))),
72
+ node: process.version,
73
+ platform: process.platform === "linux" && process.arch === "x64" ? "linux/amd64" : `${process.platform}/${process.arch}`,
74
+ playwright: packageJson.devDependencies["@playwright/test"],
75
+ viewport: "1024x768",
76
+ },
77
+ intended,
78
+ nonce: crypto.randomBytes(32).toString("hex"),
79
+ record_kind: "component_state_capture_session",
80
+ repository: process.env.GITHUB_REPOSITORY ?? "changeroa/StyleGallery",
81
+ revision,
82
+ schema_version: "1.0",
83
+ session_id: crypto.randomUUID(),
84
+ source,
85
+ started_at: new Date().toISOString(),
86
+ };
87
+
88
+ const schemas = compileSchemas(path.join(repositoryRoot, "consumer-reference/schema"));
89
+ if (!schemas.capture(receipt)) {
90
+ for (const error of schemas.capture.errors ?? []) failures.push({ code: "capture_session_schema_invalid", message: `${error.instancePath || "/"} ${error.message}`, path: options.output ?? "<cli>" });
91
+ }
92
+ if (failures.length === 0) {
93
+ fs.mkdirSync(path.dirname(options.output), { recursive: true });
94
+ fs.writeFileSync(options.output, `${JSON.stringify(receipt, null, 2)}\n`);
95
+ }
96
+ const result = { failures, intendedArtifacts: intended.reduce((sum, profile) => sum + profile.scenarios.reduce((count, scenario) => count + scenario.channels.length, 0), 0), ok: failures.length === 0, receipt: options.output };
97
+ if (options.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
98
+ else if (result.ok) process.stdout.write(`created component-state capture session ${receipt.session_id}\n`);
99
+ else process.stderr.write(`${failures.map((failure) => `${failure.code}: ${failure.path}: ${failure.message}`).join("\n")}\n`);
100
+ if (!result.ok) process.exitCode = 1;