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,127 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import StyleDictionary from "style-dictionary";
7
+ import { adapter, createStyleDictionaryConfig } from "../consumer-reference/adapters/style-dictionary.config.mjs";
8
+ import { inspectArtifactPath } from "./reference-artifact-path-contract.mjs";
9
+ import { validatePortableTokens } from "./reference-token-contract.mjs";
10
+ import { parseStrictJson } from "./strict-json.mjs";
11
+
12
+ const root = process.cwd();
13
+ const defaults = {
14
+ adapter: "style-dictionary",
15
+ manifest: "consumer-reference/generated/manifest.json",
16
+ output: "consumer-reference/generated/tokens.css",
17
+ source: "consumer-reference/fixtures/token-portability/valid-reference.json",
18
+ };
19
+
20
+ function parseArguments() {
21
+ const options = { ...defaults, failOnWarning: false, json: false };
22
+ for (let index = 2; index < process.argv.length; index += 1) {
23
+ const argument = process.argv[index];
24
+ if (argument === "--json") options.json = true;
25
+ else if (argument === "--fail-on-warning") options.failOnWarning = true;
26
+ else if (["--adapter", "--manifest", "--output", "--source"].includes(argument)) {
27
+ const value = process.argv[index + 1];
28
+ if (!value) return { failure: { code: "argument_value_required", message: `${argument} requires a value`, path: "<cli>" }, options };
29
+ options[argument.slice(2)] = value;
30
+ index += 1;
31
+ } else return { failure: { code: "argument_unknown", message: `unsupported argument ${argument}`, path: "<cli>" }, options };
32
+ }
33
+ return { options };
34
+ }
35
+
36
+ function hash(content) {
37
+ return `sha256:${crypto.createHash("sha256").update(content).digest("hex")}`;
38
+ }
39
+
40
+ function declarations(css) {
41
+ return [...css.matchAll(/^\s*(--[a-z0-9-]+):\s*([^;]+);\s*$/gm)].map((match) => ({ name: match[1], value: match[2].trim() }));
42
+ }
43
+
44
+ function emit(report, json) {
45
+ const text = json ? JSON.stringify(report, null, 2) : report.ok ? `ok: ${report.outputCount} token declarations` : report.failures.map((item) => `${item.code}: ${item.message}`).join("\n");
46
+ (json || report.ok ? console.log : console.error)(text);
47
+ }
48
+
49
+ const parsed = parseArguments();
50
+ const options = parsed.options;
51
+ const source = path.resolve(root, options.source);
52
+ const output = path.resolve(root, options.output);
53
+ const manifestPath = path.resolve(root, options.manifest);
54
+ const failures = parsed.failure ? [parsed.failure] : [];
55
+ const warnings = [];
56
+ if (!parsed.failure) {
57
+ for (const [code, target] of [
58
+ ["token_source_untrusted", source],
59
+ ["artifact_output_untrusted", output],
60
+ ["artifact_manifest_untrusted", manifestPath],
61
+ ]) {
62
+ const inspection = inspectArtifactPath(root, target, code !== "token_source_untrusted");
63
+ if (!inspection.ok) failures.push({ code, message: `${target} must be a contained regular non-symlink file`, path: path.relative(root, target) || "." });
64
+ }
65
+ }
66
+ let sourceBytes;
67
+ let document;
68
+ if (failures.length === 0) {
69
+ try {
70
+ sourceBytes = fs.readFileSync(source);
71
+ document = parseStrictJson(sourceBytes.toString("utf8"));
72
+ } catch (error) {
73
+ const code = error instanceof SyntaxError ? "token_json_invalid" : "token_source_unresolved";
74
+ failures.push({ code, message: error instanceof Error ? error.message : String(error), path: options.source });
75
+ }
76
+ }
77
+ let contract = { failures: [], tokens: [] };
78
+ if (document !== undefined) {
79
+ contract = validatePortableTokens(document);
80
+ failures.push(...contract.failures);
81
+ }
82
+ if (options.adapter !== adapter.name) failures.push({ code: "adapter_unsupported", message: `adapter must be ${adapter.name}`, path: "<cli>" });
83
+ if (failures.length === 0) {
84
+ fs.mkdirSync(path.dirname(output), { recursive: true });
85
+ const dictionary = new StyleDictionary(createStyleDictionaryConfig(source, output), { verbosity: "silent", warnings: options.failOnWarning ? "error" : "warn" });
86
+ try {
87
+ await dictionary.buildAllPlatforms();
88
+ } catch (error) {
89
+ failures.push({ code: "adapter_build_failed", message: error instanceof Error ? error.message : String(error), path: options.source });
90
+ }
91
+ }
92
+ let css = "";
93
+ if (failures.length === 0 && !fs.existsSync(output)) failures.push({ code: "artifact_output_missing", message: "adapter produced no CSS output", path: options.output });
94
+ if (failures.length === 0) {
95
+ css = fs.readFileSync(output, "utf8");
96
+ const emitted = declarations(css);
97
+ const expected = contract.tokens.map((token) => `--${token.path.replaceAll(".", "-")}`);
98
+ const names = emitted.map((item) => item.name);
99
+ if (emitted.length === 0) failures.push({ code: "artifact_zero_count", message: "adapter produced zero declarations", path: options.output });
100
+ if (new Set(names).size !== names.length) failures.push({ code: "artifact_declaration_duplicate", message: "adapter produced a duplicate declaration", path: options.output });
101
+ for (const name of expected.filter((name) => !names.includes(name))) failures.push({ code: "artifact_declaration_missing", message: `adapter omitted ${name}`, path: options.output });
102
+ if (css.includes("[object Object]")) failures.push({ code: "artifact_object_sentinel", message: "adapter emitted [object Object]", path: options.output });
103
+ if (/\{[a-z0-9.-]+\}|\b(?:null|undefined)\b/.test(css)) failures.push({ code: "artifact_unresolved_value", message: "adapter emitted an unresolved value", path: options.output });
104
+ if (contract.tokens.length !== emitted.length) failures.push({ code: "artifact_count_mismatch", message: "source and output token counts differ", path: options.output });
105
+ if (warnings.length > 0 && options.failOnWarning) failures.push({ code: "adapter_warning", message: "adapter emitted a warning", path: options.output });
106
+ if (failures.length === 0) {
107
+ const manifest = {
108
+ adapter,
109
+ declarations: emitted,
110
+ inputHash: hash(sourceBytes),
111
+ outputCount: emitted.length,
112
+ outputFile: path.relative(path.dirname(manifestPath), output).replaceAll(path.sep, "/"),
113
+ outputHash: hash(css),
114
+ schemaVersion: "1.0",
115
+ sourceCount: contract.tokens.length,
116
+ sourceFile: path.relative(path.dirname(manifestPath), source).replaceAll(path.sep, "/"),
117
+ warnings,
118
+ };
119
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
120
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
121
+ }
122
+ }
123
+ const report = parsed.failure
124
+ ? { failures: [parsed.failure], ok: false, warnings }
125
+ : { adapter, failures, manifest: options.manifest, ok: failures.length === 0, output: options.output, outputCount: failures.length === 0 ? contract.tokens.length : 0, warnings };
126
+ emit(report, options.json);
127
+ process.exitCode = report.ok ? 0 : 1;
@@ -0,0 +1,172 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { isDeepStrictEqual } from "node:util";
4
+ import { BASELINE_ENVIRONMENT, BASELINE_METADATA_SHA256, BASELINE_REFERENCE, finding, sha256 } from "./baseline-contract.mjs";
5
+ import { parseStrictJson } from "./strict-json.mjs";
6
+
7
+ const EXPECTED_TEST = "canonical consumer reference preserves computed layout before its locator screenshot";
8
+ const HASH_PATTERN = /^[0-9a-f]{64}$/;
9
+ const REQUIRED_FILES = new Set(["actual.png", "ax.txt", "comparison.json", "dom.html", "exit.json", "metadata.json", "playwright.json"]);
10
+ const ALLOWED_FILES = new Set([...REQUIRED_FILES, "playwright.stderr"]);
11
+ const FILE_LIMITS = new Map([
12
+ ["actual.png", 5 * 1024 * 1024], ["ax.txt", 1024 * 1024], ["comparison.json", 1024 * 1024],
13
+ ["dom.html", 1024 * 1024], ["exit.json", 1024 * 1024], ["metadata.json", 1024 * 1024],
14
+ ["playwright.json", 10 * 1024 * 1024], ["playwright.stderr", 1024 * 1024],
15
+ ]);
16
+
17
+ function isRecord(value) {
18
+ return value !== null && typeof value === "object" && !Array.isArray(value);
19
+ }
20
+
21
+ function readJson(file, code, failures) {
22
+ if (!fs.existsSync(file)) {
23
+ failures.push(finding(code, file, "required raw calibration JSON is missing"));
24
+ return undefined;
25
+ }
26
+ try {
27
+ const stat = fs.lstatSync(file);
28
+ const limit = FILE_LIMITS.get(path.basename(file)) ?? 1024 * 1024;
29
+ if (stat.isSymbolicLink() || !stat.isFile() || stat.size > limit) throw new Error(`JSON evidence must be a regular file no larger than ${limit} bytes`);
30
+ return parseStrictJson(fs.readFileSync(file, "utf8"));
31
+ } catch (error) {
32
+ failures.push(finding(`${code}_invalid`, file, error instanceof Error ? error.message : String(error)));
33
+ return undefined;
34
+ }
35
+ }
36
+
37
+ function rejectUnknown(value, allowed, code, file, failures) {
38
+ if (!isRecord(value)) return;
39
+ for (const key of Object.keys(value).filter((key) => !allowed.has(key))) {
40
+ failures.push(finding(code, file, `unsupported property ${key}`));
41
+ }
42
+ }
43
+
44
+ function validateExit(exit, expectedRun, file, failures) {
45
+ if (!isRecord(exit)) {
46
+ failures.push(finding("calibration_exit_invalid", file, "exit record must be an object"));
47
+ return false;
48
+ }
49
+ rejectUnknown(exit, new Set(["exit_code", "run", "schema_version"]), "calibration_exit_property_unknown", file, failures);
50
+ if (exit.schema_version !== "1.0" || exit.run !== expectedRun || exit.exit_code !== 0) {
51
+ failures.push(finding("calibration_exit_unsuccessful", file, "exit record must identify this run with exit code zero"));
52
+ return false;
53
+ }
54
+ return true;
55
+ }
56
+
57
+ function validatePlaywright(report, file, failures) {
58
+ if (!isRecord(report)) {
59
+ failures.push(finding("calibration_playwright_result_invalid", file, "Playwright result must be an object"));
60
+ return false;
61
+ }
62
+ const project = report.config?.projects?.[0];
63
+ const suite = report.suites?.[0];
64
+ const spec = suite?.specs?.[0];
65
+ const test = spec?.tests?.[0];
66
+ const result = test?.results?.[0];
67
+ const identityMatches = report.config?.version === "1.61.0"
68
+ && report.config?.workers === 1
69
+ && report.config?.projects?.length === 1
70
+ && project?.id === "chromium"
71
+ && project?.name === "chromium"
72
+ && report.suites?.length === 1
73
+ && suite?.file === "consumer-reference-sentinels.spec.mjs"
74
+ && suite?.specs?.length === 1
75
+ && spec?.title === EXPECTED_TEST
76
+ && spec?.tests?.length === 1
77
+ && test?.projectId === "chromium"
78
+ && test?.projectName === "chromium";
79
+ if (!identityMatches) failures.push(finding("calibration_playwright_identity", file, "result must contain exactly the canonical Chromium sentinel test"));
80
+ const completionMatches = spec?.ok === true
81
+ && test?.expectedStatus === "passed"
82
+ && test?.status === "expected"
83
+ && test?.results?.length === 1
84
+ && result?.status === "passed"
85
+ && Array.isArray(result?.errors)
86
+ && result.errors.length === 0
87
+ && Array.isArray(report.errors)
88
+ && report.errors.length === 0
89
+ && report.stats?.expected === 1
90
+ && report.stats?.skipped === 0
91
+ && report.stats?.unexpected === 0
92
+ && report.stats?.flaky === 0;
93
+ if (!completionMatches) failures.push(finding("calibration_playwright_incomplete", file, "canonical test must pass once with no skipped, flaky, interrupted, or unexpected result"));
94
+ return identityMatches && completionMatches;
95
+ }
96
+
97
+ function validateMetadata(metadata, expectedRun, file, failures) {
98
+ if (!isRecord(metadata)) {
99
+ failures.push(finding("calibration_metadata_invalid", file, "metadata must be an object"));
100
+ return false;
101
+ }
102
+ const allowed = new Set(["architecture", "ax_sha256", "dom_sha256", "environment", "metadata_sha256", "png_sha256", "reference", "run", "schema_version"]);
103
+ rejectUnknown(metadata, allowed, "calibration_metadata_property_unknown", file, failures);
104
+ const hashesValid = [metadata.ax_sha256, metadata.dom_sha256, metadata.metadata_sha256, metadata.png_sha256].every((value) => typeof value === "string" && HASH_PATTERN.test(value));
105
+ const valid = metadata.schema_version === "1.0"
106
+ && metadata.run === expectedRun
107
+ && metadata.architecture === "amd64"
108
+ && hashesValid
109
+ && metadata.metadata_sha256 === BASELINE_METADATA_SHA256
110
+ && isDeepStrictEqual(metadata.environment, BASELINE_ENVIRONMENT)
111
+ && isDeepStrictEqual(metadata.reference, BASELINE_REFERENCE);
112
+ if (!valid) failures.push(finding("calibration_metadata_mismatch", file, "metadata shape, identity, hashes, environment, and reference must be canonical"));
113
+ return valid;
114
+ }
115
+
116
+ function validateComparison(comparison, expectedRun, actualHash, file, failures) {
117
+ if (!isRecord(comparison)) {
118
+ failures.push(finding("calibration_comparison_invalid", file, "comparison proof must be an object"));
119
+ return false;
120
+ }
121
+ const allowed = new Set(["actual_sha256", "assertion", "diff_pixels", "expected_sha256", "max_diff_pixels", "run", "schema_version", "status", "threshold"]);
122
+ rejectUnknown(comparison, allowed, "calibration_comparison_property_unknown", file, failures);
123
+ const valid = comparison.schema_version === "1.0"
124
+ && comparison.run === expectedRun
125
+ && comparison.assertion === "visual_geometry_matches_proposed_baseline"
126
+ && comparison.status === "passed"
127
+ && comparison.threshold === 0
128
+ && comparison.max_diff_pixels === 0
129
+ && comparison.diff_pixels === 0
130
+ && comparison.actual_sha256 === actualHash
131
+ && comparison.expected_sha256 === BASELINE_REFERENCE.baseline.sha256
132
+ && comparison.actual_sha256 === comparison.expected_sha256;
133
+ if (!valid) failures.push(finding("calibration_comparison_mismatch", file, "comparison must prove the exact zero-diff baseline assertion"));
134
+ return valid;
135
+ }
136
+
137
+ export function parseCalibrationRun(root, expectedRun, failures) {
138
+ const entries = fs.readdirSync(root, { withFileTypes: true });
139
+ for (const entry of entries) {
140
+ if (!entry.isFile() || !ALLOWED_FILES.has(entry.name)) failures.push(finding("calibration_raw_file_unknown", path.join(root, entry.name), "run directory contains unsupported or duplicate evidence"));
141
+ else if (fs.lstatSync(path.join(root, entry.name)).size > FILE_LIMITS.get(entry.name)) failures.push(finding("calibration_raw_file_oversized", path.join(root, entry.name), `${entry.name} exceeds its calibration evidence byte limit`));
142
+ }
143
+ for (const name of REQUIRED_FILES) if (!entries.some((entry) => entry.isFile() && entry.name === name)) failures.push(finding("calibration_raw_file_missing", path.join(root, name), `${name} is required exactly once`));
144
+ const metadata = readJson(path.join(root, "metadata.json"), "calibration_metadata_missing", failures);
145
+ const exit = readJson(path.join(root, "exit.json"), "calibration_exit_missing", failures);
146
+ const report = readJson(path.join(root, "playwright.json"), "calibration_playwright_missing", failures);
147
+ const comparison = readJson(path.join(root, "comparison.json"), "calibration_comparison_missing", failures);
148
+ const metadataValid = validateMetadata(metadata, expectedRun, path.join(root, "metadata.json"), failures);
149
+ const exitValid = validateExit(exit, expectedRun, path.join(root, "exit.json"), failures);
150
+ const reportValid = validatePlaywright(report, path.join(root, "playwright.json"), failures);
151
+ const artifactFields = [["actual.png", "png_sha256"], ["dom.html", "dom_sha256"], ["ax.txt", "ax_sha256"]];
152
+ let artifactsValid = metadataValid;
153
+ for (const [name, field] of artifactFields) {
154
+ const file = path.join(root, name);
155
+ if (!fs.existsSync(file) || fs.lstatSync(file).isSymbolicLink() || !fs.lstatSync(file).isFile() || fs.lstatSync(file).size > FILE_LIMITS.get(name) || !metadata || sha256(fs.readFileSync(file)) !== metadata[field]) {
156
+ failures.push(finding("calibration_artifact_hash_mismatch", file, `${field} differs from raw artifact bytes`));
157
+ artifactsValid = false;
158
+ }
159
+ }
160
+ const comparisonValid = validateComparison(comparison, expectedRun, metadata?.png_sha256, path.join(root, "comparison.json"), failures);
161
+ if (!(metadataValid && exitValid && reportValid && artifactsValid && comparisonValid)) return undefined;
162
+ return {
163
+ architecture: metadata.architecture,
164
+ ax_sha256: metadata.ax_sha256,
165
+ completed: true,
166
+ dom_sha256: metadata.dom_sha256,
167
+ metadata_sha256: metadata.metadata_sha256,
168
+ png_sha256: metadata.png_sha256,
169
+ run: expectedRun,
170
+ screenshot_diff_pixels: comparison.diff_pixels,
171
+ };
172
+ }
@@ -0,0 +1,187 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ import { resolveProfileRecords } from "./profile-record-contract.mjs";
6
+ import { parseStrictJson } from "./strict-json.mjs";
7
+
8
+ const captureSourcePaths = Object.freeze([
9
+ "package-lock.json",
10
+ "package.json",
11
+ "playwright.config.mjs",
12
+ "consumer-reference/schema/ax-evidence.schema.json",
13
+ "consumer-reference/schema/capture-session.schema.json",
14
+ "consumer-reference/schema/component-state.schema.json",
15
+ "consumer-reference/schema/dom-evidence.schema.json",
16
+ "consumer-reference/schema/evidence-record.schema.json",
17
+ "consumer-reference/schema/fixture-manifest.schema.json",
18
+ "consumer-reference/schema/runtime-evidence-manifest.schema.json",
19
+ "consumer-reference/schema/visual-evidence.schema.json",
20
+ "scripts/artifact-metadata.mjs",
21
+ "scripts/capture-session-contract.mjs",
22
+ "scripts/component-state-contract.mjs",
23
+ "scripts/component-state-semantics.mjs",
24
+ "scripts/create-component-state-session.mjs",
25
+ "scripts/profile-record-contract.mjs",
26
+ "scripts/strict-json.mjs",
27
+ "scripts/visual-expectation-contract.mjs",
28
+ "tests/component-state-evidence.spec.mjs",
29
+ "tests/helpers/render-component-state.mjs",
30
+ ]);
31
+
32
+ function finding(code, file, message) {
33
+ return { code, message, path: file };
34
+ }
35
+
36
+ export function sha256(bytes) {
37
+ return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
38
+ }
39
+
40
+ export function repositoryGitArgs(repositoryRoot, ...args) {
41
+ return ["-c", `safe.directory=${repositoryRoot}`, ...args];
42
+ }
43
+
44
+ function compare(left, right) {
45
+ return left < right ? -1 : left > right ? 1 : 0;
46
+ }
47
+
48
+ function profileSourceFiles(profileRoot) {
49
+ if (!fs.existsSync(profileRoot)) return [];
50
+ const sources = [];
51
+ const profiles = fs.readdirSync(profileRoot, { withFileTypes: true })
52
+ .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(profileRoot, entry.name, "profile.json")))
53
+ .sort((left, right) => compare(left.name, right.name));
54
+ for (const profile of profiles) {
55
+ const profileFile = path.join(profileRoot, profile.name, "profile.json");
56
+ const record = parseStrictJson(fs.readFileSync(profileFile, "utf8"));
57
+ const references = [
58
+ "profile.json",
59
+ record.local_foundations,
60
+ record.tokens,
61
+ ...(record.component_records ?? []),
62
+ ...(record.fixture_records ?? []),
63
+ ...(record.state_records ?? []),
64
+ ];
65
+ for (const reference of references) {
66
+ if (typeof reference !== "string") continue;
67
+ if (path.posix.isAbsolute(reference) || path.win32.isAbsolute(reference) || reference.includes("\\") || path.posix.normalize(reference) !== reference || reference.split("/").some((segment) => segment === "." || segment === "..")) {
68
+ throw new Error(`unsafe profile source reference ${profile.name}/${reference}`);
69
+ }
70
+ sources.push({
71
+ file: path.join(profileRoot, profile.name, reference),
72
+ logicalPath: `profiles/${profile.name}/${reference.split(path.sep).join("/")}`,
73
+ });
74
+ }
75
+ }
76
+ return sources;
77
+ }
78
+
79
+ export function relevantSourceFiles(repositoryRoot, profileRoot) {
80
+ return [
81
+ ...captureSourcePaths.map((reference) => ({ file: path.join(repositoryRoot, reference), logicalPath: reference })),
82
+ ...profileSourceFiles(profileRoot),
83
+ ].sort((left, right) => compare(left.logicalPath, right.logicalPath));
84
+ }
85
+
86
+ export function canonicalSourceManifest(repositoryRoot, profileRoot) {
87
+ const files = relevantSourceFiles(repositoryRoot, profileRoot).map(({ file, logicalPath }) => {
88
+ const stat = fs.lstatSync(file);
89
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`capture source must be a regular non-symlink file: ${logicalPath}`);
90
+ const bytes = fs.readFileSync(file);
91
+ return { byte_length: bytes.length, path: logicalPath, sha256: sha256(bytes) };
92
+ });
93
+ return { files, sha256: sha256(Buffer.from(JSON.stringify(files))) };
94
+ }
95
+
96
+ export function sourceManifestMatches(source, repositoryRoot, profileRoot) {
97
+ try {
98
+ return sameJson(source, canonicalSourceManifest(repositoryRoot, profileRoot));
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ export function dirtyRelevantSources(repositoryRoot, profileRoot) {
105
+ const repositoryPrefix = `${path.resolve(repositoryRoot)}${path.sep}`;
106
+ const tracked = relevantSourceFiles(repositoryRoot, profileRoot)
107
+ .map(({ file }) => path.resolve(file))
108
+ .filter((file) => file.startsWith(repositoryPrefix))
109
+ .map((file) => path.relative(repositoryRoot, file));
110
+ if (tracked.length === 0) return [];
111
+ const output = execFileSync("git", repositoryGitArgs(repositoryRoot, "status", "--porcelain=v1", "--untracked-files=all", "--", ...tracked), { cwd: repositoryRoot, encoding: "utf8" });
112
+ return output.split("\n").filter(Boolean).sort(compare);
113
+ }
114
+
115
+ export function sessionLink(receipt, receiptSha256) {
116
+ return {
117
+ attempt: receipt.attempt,
118
+ branch: receipt.branch,
119
+ environment: receipt.environment,
120
+ nonce: receipt.nonce,
121
+ receipt_sha256: receiptSha256,
122
+ revision: receipt.revision,
123
+ session_id: receipt.session_id,
124
+ source: receipt.source,
125
+ started_at: receipt.started_at,
126
+ };
127
+ }
128
+
129
+ export function readCaptureSession(file, validate, failures) {
130
+ if (!fs.existsSync(file)) {
131
+ failures.push(finding("capture_session_missing", file, "capture session receipt is missing"));
132
+ return undefined;
133
+ }
134
+ const stat = fs.lstatSync(file);
135
+ if (!stat.isFile() || stat.isSymbolicLink()) {
136
+ failures.push(finding("capture_session_type_invalid", file, "capture session receipt must be a regular non-symlink file"));
137
+ return undefined;
138
+ }
139
+ const bytes = fs.readFileSync(file);
140
+ let receipt;
141
+ try {
142
+ receipt = parseStrictJson(bytes.toString("utf8"));
143
+ } catch (error) {
144
+ failures.push(finding("capture_session_json_invalid", file, error instanceof Error ? error.message : String(error)));
145
+ return undefined;
146
+ }
147
+ if (!validate(receipt)) {
148
+ for (const error of validate.errors ?? []) failures.push(finding("capture_session_schema_invalid", file, `${error.instancePath || "/"} ${error.message}`));
149
+ }
150
+ const digest = sha256(bytes);
151
+ return {
152
+ artifact: { byte_length: bytes.length, media_type: "application/json", path: "capture-session.json", sha256: digest },
153
+ bytes,
154
+ digest,
155
+ link: sessionLink(receipt, digest),
156
+ receipt,
157
+ };
158
+ }
159
+
160
+ export function canonicalIntended(profileRoot, failures) {
161
+ if (!fs.existsSync(profileRoot)) return [];
162
+ const intended = [];
163
+ for (const entry of fs.readdirSync(profileRoot, { withFileTypes: true }).filter((item) => item.isDirectory()).sort((left, right) => compare(left.name, right.name))) {
164
+ const root = path.join(profileRoot, entry.name);
165
+ if (!fs.existsSync(path.join(root, "profile.json"))) continue;
166
+ const resolved = resolveProfileRecords(root, failures);
167
+ const fixture = resolved?.records.fixture[0]?.value;
168
+ if (!resolved || !fixture) continue;
169
+ intended.push({
170
+ profile_id: resolved.profile.id,
171
+ profile_name: entry.name,
172
+ scenarios: fixture.scenarios.map((scenario) => ({ channels: [...scenario.required_channels].sort(compare), id: scenario.id })).sort((left, right) => compare(left.id, right.id)),
173
+ });
174
+ }
175
+ return intended;
176
+ }
177
+
178
+ export function sameJson(left, right) {
179
+ return JSON.stringify(left) === JSON.stringify(right);
180
+ }
181
+
182
+ export function withinSession(capturedAt, startedAt, completedAt) {
183
+ const captured = Date.parse(capturedAt);
184
+ const started = Date.parse(startedAt);
185
+ const completed = Date.parse(completedAt);
186
+ return Number.isFinite(captured) && Number.isFinite(started) && Number.isFinite(completed) && captured >= started && captured <= completed;
187
+ }
@@ -0,0 +1,44 @@
1
+ import zlib from "node:zlib";
2
+
3
+ function crc32(bytes) {
4
+ let crc = 0xffffffff;
5
+ for (const byte of bytes) {
6
+ crc ^= byte;
7
+ for (let bit = 0; bit < 8; bit += 1) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
8
+ }
9
+ return (crc ^ 0xffffffff) >>> 0;
10
+ }
11
+
12
+ function pngChunk(type, data) {
13
+ const name = Buffer.from(type, "ascii");
14
+ const output = Buffer.alloc(12 + data.length);
15
+ output.writeUInt32BE(data.length, 0);
16
+ name.copy(output, 4);
17
+ data.copy(output, 8);
18
+ output.writeUInt32BE(crc32(Buffer.concat([name, data])), 8 + data.length);
19
+ return output;
20
+ }
21
+
22
+ export function makePng(red) {
23
+ const width = 64;
24
+ const height = 64;
25
+ const header = Buffer.alloc(13);
26
+ header.writeUInt32BE(width, 0);
27
+ header.writeUInt32BE(height, 4);
28
+ header[8] = 8;
29
+ header[9] = 6;
30
+ const row = Buffer.alloc(1 + (width * 4));
31
+ for (let pixel = 0; pixel < width; pixel += 1) row.set([red, 64, 128, 255], 1 + (pixel * 4));
32
+ return Buffer.concat([
33
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
34
+ pngChunk("IHDR", header),
35
+ pngChunk("IDAT", zlib.deflateSync(Buffer.concat(Array.from({ length: height }, () => row)))),
36
+ pngChunk("IEND", Buffer.alloc(0)),
37
+ ]);
38
+ }
39
+
40
+ const canonicalPng = makePng(32);
41
+
42
+ export function syntheticImage(profile, scenario) {
43
+ return Buffer.concat([canonicalPng, Buffer.from(`${profile}:${scenario}`)]);
44
+ }
@@ -0,0 +1,99 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import Ajv2020 from "ajv/dist/2020.js";
4
+ import {
5
+ validateComponentSemantics,
6
+ validateEvidenceSemantics,
7
+ validateFixtureSemantics,
8
+ validateStateSemantics,
9
+ } from "./component-state-semantics.mjs";
10
+ import { validateEvidenceArtifacts as validateArtifactSet } from "./evidence-artifact-contract.mjs";
11
+ import { resolveProfileRecords } from "./profile-record-contract.mjs";
12
+ import { parseStrictJson } from "./strict-json.mjs";
13
+
14
+ function finding(code, file, message) {
15
+ return { code, message, path: file };
16
+ }
17
+
18
+ export function readRecord(file, failures) {
19
+ try {
20
+ return parseStrictJson(fs.readFileSync(file, "utf8"));
21
+ } catch (error) {
22
+ failures.push(finding(error instanceof SyntaxError ? "record_invalid_json" : "record_unreadable", file, error.message));
23
+ return undefined;
24
+ }
25
+ }
26
+
27
+ function readSchema(schemaRoot, name) {
28
+ const file = path.join(schemaRoot, name);
29
+ try {
30
+ return parseStrictJson(fs.readFileSync(file, "utf8"));
31
+ } catch (cause) {
32
+ const error = new Error(`component schema is not strict JSON: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
33
+ error.code = "component_schema_json_invalid";
34
+ error.path = file;
35
+ throw error;
36
+ }
37
+ }
38
+
39
+ export function compileSchemas(schemaRoot) {
40
+ const ajv = new Ajv2020({ allErrors: true, formats: { "date-time": true }, strict: false });
41
+ const captureSchema = readSchema(schemaRoot, "capture-session.schema.json");
42
+ ajv.addSchema(captureSchema);
43
+ const evidenceSchema = readSchema(schemaRoot, "evidence-record.schema.json");
44
+ const validateEvidence = ajv.compile(evidenceSchema);
45
+ return {
46
+ ax: ajv.compile(readSchema(schemaRoot, "ax-evidence.schema.json")),
47
+ capture: ajv.getSchema(captureSchema.$id),
48
+ component: ajv.compile(readSchema(schemaRoot, "component-state.schema.json")),
49
+ dom: ajv.compile(readSchema(schemaRoot, "dom-evidence.schema.json")),
50
+ evidence: validateEvidence,
51
+ fixture: ajv.compile(readSchema(schemaRoot, "fixture-manifest.schema.json")),
52
+ item: ajv.compile(readSchema(schemaRoot, "item.schema.json")),
53
+ runtime: ajv.compile(readSchema(schemaRoot, "runtime-evidence-manifest.schema.json")),
54
+ visual: ajv.compile(readSchema(schemaRoot, "visual-evidence.schema.json")),
55
+ };
56
+ }
57
+
58
+ function addSchemaFailures(validate, value, file, code, failures) {
59
+ if (validate(value)) return;
60
+ for (const error of validate.errors ?? []) failures.push(finding(code, file, `${error.instancePath || "/"} ${error.message}`));
61
+ }
62
+
63
+ export function validateProfile(profileRoot, schemas) {
64
+ const failures = [];
65
+ const resolved = resolveProfileRecords(profileRoot, failures);
66
+ if (!resolved) return failures;
67
+ addSchemaFailures(schemas.item, resolved.profile, resolved.profileFile, "profile_schema_invalid", failures);
68
+ const files = Object.fromEntries(["component", "evidence", "fixture", "states"].map((kind) => [kind, resolved.records[kind][0]?.path ?? resolved.profileFile]));
69
+ const records = Object.fromEntries(["component", "evidence", "fixture", "states"].map((kind) => [kind, resolved.records[kind][0]?.value]));
70
+ if (records.component) addSchemaFailures(schemas.component, records.component, files.component, "component_schema_invalid", failures);
71
+ if (records.states) addSchemaFailures(schemas.component, records.states, files.states, "state_schema_invalid", failures);
72
+ if (records.fixture) addSchemaFailures(schemas.fixture, records.fixture, files.fixture, "fixture_schema_invalid", failures);
73
+ if (records.evidence) {
74
+ addSchemaFailures(schemas.evidence, records.evidence, files.evidence, "evidence_schema_invalid", failures);
75
+ }
76
+ if (records.component) validateComponentSemantics(records.component, files.component, failures);
77
+ if (records.states && records.component) validateStateSemantics(records.states, records.component, files.states, failures);
78
+ if (records.fixture && records.states && records.component) validateFixtureSemantics(records.fixture, records.states, records.component, files.fixture, failures);
79
+ if (records.fixture && records.states && records.evidence) validateEvidenceSemantics(records.evidence, records.fixture, records.states, files.evidence, failures);
80
+ return failures;
81
+ }
82
+
83
+ export function validateEvidenceArtifacts(profileRoot, artifactRoot, evidenceOverride, schemas, session) {
84
+ const failures = [];
85
+ const resolved = resolveProfileRecords(profileRoot, failures);
86
+ const evidenceRecord = resolved?.records.evidence[0];
87
+ const file = evidenceRecord?.path ?? resolved?.profileFile ?? path.join(profileRoot, "profile.json");
88
+ const evidence = evidenceOverride ?? evidenceRecord?.value;
89
+ const fixture = resolved?.records.fixture[0]?.value;
90
+ const states = resolved?.records.states[0]?.value;
91
+ if (evidence && fixture && states) {
92
+ if (evidenceOverride) {
93
+ addSchemaFailures(schemas.evidence, evidence, file, "evidence_schema_invalid", failures);
94
+ validateEvidenceSemantics(evidence, fixture, states, file, failures);
95
+ }
96
+ failures.push(...validateArtifactSet({ artifactRoot, evidence, evidenceFile: file, fixture, profileId: resolved.profile.id, schemas, session, states }));
97
+ }
98
+ return failures;
99
+ }