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,250 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+ import { copyTreeNoSymlinks, testPromotionBoundaries } from "./promotion-boundary-test-contract.mjs";
8
+
9
+ const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
+ const fixtureRoot = path.join(repositoryRoot, "consumer-reference/fixtures/promotion");
11
+ const validator = path.join(repositoryRoot, "scripts/validate-promotion-rfc.mjs");
12
+
13
+ function readJson(relative) {
14
+ return JSON.parse(fs.readFileSync(path.join(fixtureRoot, relative), "utf8"));
15
+ }
16
+
17
+ function runArguments(args) {
18
+ const child = spawnSync(process.execPath, [validator, ...args], { cwd: repositoryRoot, encoding: "utf8" });
19
+ let report = { failures: [], ok: false, parse_error: true };
20
+ try {
21
+ report = JSON.parse(child.stdout);
22
+ } catch (error) {
23
+ if (!(error instanceof SyntaxError)) throw error;
24
+ report = { failures: [], ok: false, parse_error: true };
25
+ }
26
+ return { report, status: child.status };
27
+ }
28
+
29
+ function run(file, profileRepositoryRoot) {
30
+ const args = ["--file", file, "--json"];
31
+ if (profileRepositoryRoot) args.push("--profile-repository-root", profileRepositoryRoot);
32
+ return runArguments(args);
33
+ }
34
+
35
+ function runPolicy(file) {
36
+ return runArguments(["--policy", file, "--json"]);
37
+ }
38
+
39
+ function writeMutation(name, source, mutate) {
40
+ const value = structuredClone(readJson(source));
41
+ mutate(value);
42
+ const file = path.join(tempRoot, `${name}.json`);
43
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
44
+ return file;
45
+ }
46
+
47
+ function writePolicyMutation(root, name, mutate) {
48
+ const value = JSON.parse(fs.readFileSync(path.join(repositoryRoot, "consumer-reference/policies/shared-experimental.json"), "utf8"));
49
+ mutate(value);
50
+ const file = path.join(root, `${name}.json`);
51
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
52
+ return file;
53
+ }
54
+
55
+ function copyProfileRepository(name) {
56
+ const root = path.join(tempRoot, name);
57
+ const source = path.join(repositoryRoot, "design-engineering/reference-profiles/governed-local");
58
+ const target = path.join(root, "design-engineering/reference-profiles/governed-local");
59
+ copyTreeNoSymlinks(source, target);
60
+ return root;
61
+ }
62
+
63
+ function editProfileJson(root, relative, mutate) {
64
+ const file = path.join(root, relative);
65
+ const value = JSON.parse(fs.readFileSync(file, "utf8"));
66
+ mutate(value);
67
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
68
+ }
69
+
70
+ const fixtureManifest = JSON.parse(fs.readFileSync(path.join(fixtureRoot, "manifest.json"), "utf8"));
71
+ const invalidCases = fixtureManifest.invalid_records.map((entry) => [entry.file.replace(/\.json$/, ""), entry.file, entry.expected_code]);
72
+
73
+ const tempRoot = fs.mkdtempSync(path.join(repositoryRoot, ".tmp-pr6-promotion-"));
74
+ const repositoryTempRoot = fs.mkdtempSync(path.join(repositoryRoot, ".tmp-pr6-profile-"));
75
+ const results = [];
76
+ try {
77
+ for (const [name, relative, expected] of invalidCases) {
78
+ const child = run(path.join(fixtureRoot, relative));
79
+ const codes = child.report.failures.map((failure) => failure.code);
80
+ results.push({ actual: { codes, scaffold: child.report.scaffold === true, status: child.status }, expected, name, ok: child.status > 0 && codes.includes(expected) && child.report.scaffold !== true });
81
+ }
82
+
83
+ const editorial = writeMutation("editorial_counted", "valid-deferred-example.json", (value) => {
84
+ value.attestations = [{ attested_by: "Synthetic owner", context_id: "editorial", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "stylegallery", profile_record: "design-engineering/reference-profiles/governed-local/editorial/profile.json", relationship: "independent" }];
85
+ });
86
+ const terminal = writeMutation("terminal_counted", "valid-deferred-example.json", (value) => {
87
+ value.attestations = [{ attested_by: "Synthetic owner", context_id: "terminal", consumer_id: "terminal-reference-profile", count_toward_gate: true, organization_id: "stylegallery", profile_record: "design-engineering/reference-profiles/governed-local/terminal/profile.json", relationship: "independent" }];
88
+ });
89
+ const booleanReview = writeMutation("boolean_review", "valid-deferred-example.json", (value) => { value.review_independence = false; });
90
+ const fakeIndependence = writeMutation("fake_independence", "valid-deferred-example.json", (value) => {
91
+ value.attestations = [
92
+ { attested_by: "Synthetic owner", context_id: "one", consumer_id: "consumer-one", count_toward_gate: true, organization_id: "same-org", relationship: "independent" },
93
+ { attested_by: "Synthetic owner", context_id: "two", consumer_id: "consumer-two", count_toward_gate: true, organization_id: "same-org", relationship: "independent" }
94
+ ];
95
+ });
96
+ const duplicateIdentity = writeMutation("duplicate_identity", "valid-deferred-example.json", (value) => {
97
+ const attestation = { attested_by: "Synthetic owner", context_id: "same", consumer_id: "same-consumer", count_toward_gate: true, organization_id: "same-org", relationship: "independent" };
98
+ value.attestations = [attestation, structuredClone(attestation)];
99
+ });
100
+ const duplicateConsumer = writeMutation("duplicate_consumer", "valid-deferred-example.json", (value) => {
101
+ value.attestations = [
102
+ { attested_by: "Synthetic owner A", context_id: "one", consumer_id: "same-consumer", count_toward_gate: true, organization_id: "organization-a", relationship: "independent" },
103
+ { attested_by: "Synthetic owner B", context_id: "two", consumer_id: "same-consumer", count_toward_gate: true, organization_id: "organization-b", relationship: "independent" }
104
+ ];
105
+ });
106
+ const unknownConsumers = writeMutation("unknown_consumers", "valid-deferred-example.json", (value) => {
107
+ value.attestations = [
108
+ { attested_by: "Synthetic owner A", context_id: "one", consumer_id: "unknown-one", count_toward_gate: true, organization_id: "organization-a", relationship: "independent" },
109
+ { attested_by: "Synthetic owner B", context_id: "two", consumer_id: "unknown-two", count_toward_gate: true, organization_id: "organization-b", relationship: "independent" }
110
+ ];
111
+ });
112
+ const forgedOne = path.join(repositoryTempRoot, "forged-one.json");
113
+ const forgedTwo = path.join(repositoryTempRoot, "forged-two.json");
114
+ fs.writeFileSync(forgedOne, '{"fixture_independence":"independent","id":"forged-one"}\n');
115
+ fs.writeFileSync(forgedTwo, '{"fixture_independence":"independent","id":"forged-two"}\n');
116
+ const forgedProfiles = writeMutation("forged_profiles", "valid-deferred-example.json", (value) => {
117
+ value.attestations = [
118
+ { attested_by: "Synthetic owner A", context_id: "one", consumer_id: "forged-one", count_toward_gate: true, organization_id: "organization-a", profile_record: path.relative(repositoryRoot, forgedOne), relationship: "independent" },
119
+ { attested_by: "Synthetic owner B", context_id: "two", consumer_id: "forged-two", count_toward_gate: true, organization_id: "organization-b", profile_record: path.relative(repositoryRoot, forgedTwo), relationship: "independent" }
120
+ ];
121
+ });
122
+ const copiedProfileRoot = path.join(repositoryTempRoot, "copied");
123
+ copyTreeNoSymlinks(path.join(repositoryRoot, "design-engineering/reference-profiles/governed-local/editorial"), copiedProfileRoot);
124
+ const copiedUnregistered = writeMutation("copied_unregistered", "valid-deferred-example.json", (value) => {
125
+ value.attestations = [{ attested_by: "Synthetic owner", context_id: "copied", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "copied-org", profile_record: path.relative(repositoryRoot, path.join(copiedProfileRoot, "profile.json")), relationship: "independent" }];
126
+ });
127
+ const invalidRegisteredRoot = copyProfileRepository("invalid-registered-root");
128
+ editProfileJson(invalidRegisteredRoot, "design-engineering/reference-profiles/governed-local/editorial/profile.json", (value) => { delete value.owner; });
129
+ const invalidRegistered = writeMutation("invalid_registered", "valid-deferred-example.json", (value) => {
130
+ value.attestations = [{ attested_by: "Synthetic owner", context_id: "invalid", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "invalid-org", profile_record: "design-engineering/reference-profiles/governed-local/editorial/profile.json", relationship: "independent" }];
131
+ });
132
+ const duplicateRegistryRoot = copyProfileRepository("duplicate-registry-root");
133
+ editProfileJson(duplicateRegistryRoot, "design-engineering/reference-profiles/governed-local/terminal/profile.json", (value) => { value.id = "editorial-reference-profile"; });
134
+ const duplicateRegistryIdentity = writeMutation("duplicate_registry_identity", "valid-deferred-example.json", (value) => {
135
+ value.attestations = [{ attested_by: "Synthetic owner", context_id: "duplicate", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "duplicate-org", profile_record: "design-engineering/reference-profiles/governed-local/editorial/profile.json", relationship: "independent" }];
136
+ });
137
+ const silentRelabel = writeMutation("silent_relabel", "valid-deferred-example.json", (value) => {
138
+ value.transition = { from: "stable", to: "shared_experimental" };
139
+ });
140
+ const pathEscape = writeMutation("path_escape", "valid-deferred-example.json", (value) => {
141
+ value.attestations = [{ attested_by: "Synthetic owner", context_id: "escape", consumer_id: "escape", count_toward_gate: true, organization_id: "escape", profile_record: "../outside.json", relationship: "independent" }];
142
+ });
143
+ const profileMismatch = writeMutation("profile_mismatch", "valid-deferred-example.json", (value) => {
144
+ value.attestations = [{ attested_by: "Synthetic owner", context_id: "editorial", consumer_id: "different-profile", count_toward_gate: true, organization_id: "stylegallery", profile_record: "design-engineering/reference-profiles/governed-local/editorial/profile.json", relationship: "independent" }];
145
+ });
146
+ const profileLink = path.join(repositoryTempRoot, "profile.json");
147
+ fs.symlinkSync(path.join(repositoryRoot, "design-engineering/reference-profiles/governed-local/editorial/profile.json"), profileLink);
148
+ const profileSymlink = writeMutation("profile_symlink", "valid-deferred-example.json", (value) => {
149
+ value.attestations = [{ attested_by: "Synthetic owner", context_id: "editorial", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "stylegallery", profile_record: path.relative(repositoryRoot, profileLink), relationship: "independent" }];
150
+ });
151
+ const schemaExtra = writeMutation("schema_extra", "valid-deferred-example.json", (value) => { value.unexpected_promotion = true; });
152
+ const acceptedDecision = writeMutation("accepted_decision", "valid-deferred-example.json", (value) => { value.decision = "accepted"; });
153
+ const aestheticPromotion = writeMutation("aesthetic_promotion", "valid-deferred-example.json", (value) => { value.scope.aesthetic = true; });
154
+ const breakingWithoutMigration = writeMutation("breaking_without_migration", "valid-deferred-example.json", (value) => { value.compatibility.breaking = true; value.migration.required = false; });
155
+ const normativeWithoutRollback = writeMutation("normative_without_rollback", "valid-normative-bypass.json", (value) => { delete value.rollback; });
156
+ const normativeEndedSupport = writeMutation("normative_ended_support", "valid-normative-bypass.json", (value) => {
157
+ value.support = { capacity: "No active support capacity", status: "ended" };
158
+ });
159
+ const normativeMissingSupport = writeMutation("normative_missing_support", "valid-normative-bypass.json", (value) => { delete value.support; });
160
+ const normativeMissingCapacity = writeMutation("normative_missing_capacity", "valid-normative-bypass.json", (value) => { delete value.support.capacity; });
161
+ const weakCapacityCases = ["0", "Not available", "Absent", "Lapsed", "Negative capacity"].map((capacity, index) => [
162
+ `weak_capacity_${index + 1}`,
163
+ writeMutation(`weak_capacity_${index + 1}`, "valid-deferred-example.json", (value) => { value.support.capacity = capacity; }),
164
+ "promotion_support_required",
165
+ ]);
166
+ const stableWithoutCount = writeMutation("stable_without_count", "valid-deferred-example.json", (value) => {
167
+ value.transition = { from: "shared_experimental", to: "stable" };
168
+ value.stable_scope_decision = true;
169
+ value.provenance.debt_resolved = true;
170
+ });
171
+ const stableWithoutEvidence = writeMutation("stable_without_evidence", "valid-deferred-example.json", (value) => {
172
+ value.transition = { from: "shared_experimental", to: "stable" };
173
+ value.stable_scope_decision = true;
174
+ value.provenance.debt_resolved = true;
175
+ value.evidence = [{ id: "regression", kind: "regression", reference: "synthetic-fixture:regression", status: "failed" }];
176
+ });
177
+ const duplicateKey = path.join(tempRoot, "duplicate_key.json");
178
+ fs.writeFileSync(duplicateKey, fs.readFileSync(path.join(fixtureRoot, "valid-deferred-example.json"), "utf8").replace('"decision": "deferred",', '"decision": "deferred",\n "decision": "rejected",'));
179
+ for (const [name, file, expected, profileRepositoryRoot] of [
180
+ ["editorial_counted", editorial, "promotion_related_consumer_counted"],
181
+ ["terminal_counted", terminal, "promotion_related_consumer_counted"],
182
+ ["boolean_review", booleanReview, "review_independence_boolean"],
183
+ ["fake_independence", fakeIndependence, "promotion_independence_unproven"],
184
+ ["duplicate_identity", duplicateIdentity, "promotion_attestation_duplicate"],
185
+ ["duplicate_consumer", duplicateConsumer, "promotion_attestation_duplicate"],
186
+ ["unknown_consumers", unknownConsumers, "promotion_independence_unproven"],
187
+ ["forged_profiles", forgedProfiles, "promotion_independence_unproven"],
188
+ ["copied_unregistered", copiedUnregistered, "promotion_independence_unproven"],
189
+ ["invalid_registered", invalidRegistered, "promotion_independence_unproven", invalidRegisteredRoot],
190
+ ["duplicate_registry_identity", duplicateRegistryIdentity, "promotion_independence_unproven", duplicateRegistryRoot],
191
+ ["silent_relabel", silentRelabel, "promotion_stable_relabel_forbidden"],
192
+ ["path_escape", pathEscape, "promotion_profile_path_invalid"],
193
+ ["profile_mismatch", profileMismatch, "promotion_profile_identity_mismatch"],
194
+ ["profile_symlink", profileSymlink, "promotion_profile_path_symlink"],
195
+ ["schema_extra", schemaExtra, "promotion_schema_invalid"],
196
+ ["accepted_decision", acceptedDecision, "promotion_schema_invalid"],
197
+ ["aesthetic_promotion", aestheticPromotion, "promotion_schema_invalid"],
198
+ ["breaking_without_migration", breakingWithoutMigration, "promotion_migration_required"],
199
+ ["normative_without_rollback", normativeWithoutRollback, "promotion_schema_invalid"],
200
+ ["normative_ended_support", normativeEndedSupport, "promotion_support_required"],
201
+ ["normative_missing_support", normativeMissingSupport, "promotion_support_required"],
202
+ ["normative_missing_capacity", normativeMissingCapacity, "promotion_support_required"],
203
+ ["duplicate_key", duplicateKey, "promotion_json_invalid"],
204
+ ["stable_without_evidence", stableWithoutEvidence, "promotion_stable_evidence_required"],
205
+ ...weakCapacityCases,
206
+ ]) {
207
+ const child = run(file, profileRepositoryRoot);
208
+ const codes = child.report.failures.map((failure) => failure.code);
209
+ results.push({ actual: { codes, scaffold: child.report.scaffold === true, status: child.status }, expected, name, ok: child.status > 0 && codes.includes(expected) && child.report.scaffold !== true });
210
+ }
211
+
212
+ for (const relative of ["valid-deferred-example.json", "valid-normative-bypass.json"]) {
213
+ const child = run(path.join(fixtureRoot, relative));
214
+ results.push({ actual: { scaffold: child.report.scaffold === true, status: child.status }, expected: "ok:true", name: relative, ok: child.status === 0 && child.report.ok === true && child.report.scaffold !== true });
215
+ }
216
+ const stable = run(stableWithoutCount);
217
+ results.push({ actual: { scaffold: stable.report.scaffold === true, status: stable.status }, expected: "stable_without_numeric_threshold", name: "stable_without_count", ok: stable.status === 0 && stable.report.ok === true && stable.report.scaffold !== true });
218
+ const canonicalPolicy = runPolicy(path.join(repositoryRoot, "consumer-reference/policies/shared-experimental.json"));
219
+ results.push({ actual: { status: canonicalPolicy.status }, expected: "canonical_policy_valid", name: "canonical_policy", ok: canonicalPolicy.status === 0 && canonicalPolicy.report.ok === true });
220
+ results.push(...testPromotionBoundaries({ fixtureRoot, repositoryRoot, runArguments, tempRoot }));
221
+ for (const [name, file, expected] of [
222
+ ["stable_numeric_count", writePolicyMutation(tempRoot, "stable_numeric_count", (value) => { value.gateway.stable_numeric_threshold = 2; }), "promotion_stable_numeric_threshold_forbidden"],
223
+ ["bypass_overreach", writePolicyMutation(tempRoot, "bypass_overreach", (value) => { value.normative_bypass.waives.push("regression_proof"); }), "promotion_normative_bypass_overreach"],
224
+ ["fake_promotion_claim", writePolicyMutation(tempRoot, "fake_promotion_claim", (value) => { value.claims.promotion_occurred = true; }), "promotion_claim_forbidden"],
225
+ ["policy_unknown_field", writePolicyMutation(tempRoot, "policy_unknown_field", (value) => { value.accepted = true; }), "promotion_policy_shape_invalid"],
226
+ ["policy_nested_unknown", writePolicyMutation(tempRoot, "policy_nested_unknown", (value) => { value.gateway.stable_by_count = true; }), "promotion_policy_shape_invalid"],
227
+ ["policy_promotes_palette", writePolicyMutation(tempRoot, "policy_promotes_palette", (value) => { value.boundaries.promoted_content.push("palette"); }), "promotion_aesthetic_boundary_required"],
228
+ ["policy_promotes_alias", writePolicyMutation(tempRoot, "policy_promotes_alias", (value) => { value.boundaries.promoted_content.push("brand_identity"); }), "promotion_aesthetic_boundary_required"],
229
+ ["policy_promoted_non_string", writePolicyMutation(tempRoot, "policy_promoted_non_string", (value) => { value.boundaries.promoted_content = [42]; }), "promotion_aesthetic_boundary_required"],
230
+ ["policy_normative_aesthetic", writePolicyMutation(tempRoot, "policy_normative_aesthetic", (value) => { value.normative_bypass.categories = ["aesthetic"]; }), "promotion_normative_bypass_overreach"],
231
+ ["policy_lifecycle_incomplete", writePolicyMutation(tempRoot, "policy_lifecycle_incomplete", (value) => { value.lifecycle.failure_actions = ["deprecate_with_migration"]; }), "promotion_lifecycle_invalid"],
232
+ ["policy_excluded_non_string", writePolicyMutation(tempRoot, "policy_excluded_non_string", (value) => { value.boundaries.excluded_content.push(42); }), "promotion_aesthetic_boundary_required"],
233
+ ["policy_retains_non_string", writePolicyMutation(tempRoot, "policy_retains_non_string", (value) => { value.normative_bypass.retains.push(42); }), "promotion_normative_bypass_overreach"],
234
+ ["policy_verified_owner", writePolicyMutation(tempRoot, "policy_verified_owner", (value) => { value.owner.enforcement = "verified"; }), "promotion_policy_owner_invalid"],
235
+ ["policy_promoted_status", writePolicyMutation(tempRoot, "policy_promoted_status", (value) => { value.status = "promoted"; }), "promotion_claim_forbidden"],
236
+ ["policy_generated_mode", writePolicyMutation(tempRoot, "policy_generated_mode", (value) => { value.artifact_mode = "generated"; }), "promotion_policy_identity_invalid"],
237
+ ]) {
238
+ const child = runPolicy(file);
239
+ const codes = child.report.failures.map((failure) => failure.code);
240
+ results.push({ actual: { codes, status: child.status }, expected, name, ok: child.status > 0 && codes.includes(expected) });
241
+ }
242
+ } finally {
243
+ fs.rmSync(tempRoot, { force: true, recursive: true });
244
+ fs.rmSync(repositoryTempRoot, { force: true, recursive: true });
245
+ }
246
+
247
+ const failures = results.filter((result) => !result.ok).map((result) => `missing_semantic:${result.name}:${result.expected}`);
248
+ const report = { failures, ok: failures.length === 0, results };
249
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
250
+ if (!report.ok) process.exitCode = 1;
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
+ const validator = path.join(repositoryRoot, "scripts", "validate-consumer-reference.mjs");
11
+ const profilePath = "design-engineering/reference-profiles/governed-local/editorial/profile.json";
12
+ const terminalProfilePath = "design-engineering/reference-profiles/governed-local/terminal/profile.json";
13
+ const aliasProfilePath = "design-engineering/reference-profiles/governed-local/alias/profile.json";
14
+ const layoutSha = "775430bbaf4ee208a642220f440f6926d79c90a3";
15
+
16
+ const baseProfile = {
17
+ artifact_mode: "governed_local",
18
+ component_records: ["components/button.component.json"],
19
+ default: false,
20
+ environment_assumptions: {
21
+ reset: { body_margin: "0", box_sizing: "border-box", figure_margin: "0" },
22
+ user_agent_styles: "Preserve browser defaults except for the declared reset.",
23
+ },
24
+ example_only: true,
25
+ evidence_records: ["evidence/button.evidence.json"],
26
+ fixture_records: ["fixtures/button.fixture.json"],
27
+ fixture_independence: "related",
28
+ handoff: { record: profilePath, status: "declared" },
29
+ generated_records: ["generated/evidence-coverage.md", "generated/keyboard-matrix.md", "generated/state-matrix.md"],
30
+ id: "editorial-reference-profile",
31
+ layout_source_sha: layoutSha,
32
+ local_foundations: "local-foundations.json",
33
+ maturity: "experimental",
34
+ owner: { enforcement: "placeholder", name: "Design Engineering owner" },
35
+ profile_kind: "governed_local",
36
+ related_fixture_set_id: "layout-identity-adversarial-pair",
37
+ review_independence: "single_account",
38
+ schema_version: "1.0",
39
+ selection: { method: "profile_path", required: true },
40
+ state_records: ["states/button.states.json"],
41
+ support: { status: "active" },
42
+ tokens: "tokens.dtcg.json",
43
+ };
44
+
45
+ const tokens = {
46
+ color: {
47
+ $type: "color",
48
+ accent: { $value: { alpha: 1, colorSpace: "srgb", components: [0.55, 0.18, 0.1] } },
49
+ },
50
+ space: { $type: "dimension", page: { $value: { unit: "rem", value: 1.5 } } },
51
+ };
52
+ const terminalTokens = {
53
+ color: {
54
+ $type: "color",
55
+ accent: { $value: { alpha: 1, colorSpace: "srgb", components: [0.1, 0.9, 0.5] } },
56
+ },
57
+ space: { $type: "dimension", page: { $value: { unit: "rem", value: 1 } } },
58
+ };
59
+ const editorialFoundations = { bindings: { accent: "{color.accent}" }, identity: "Warm editorial", profile_id: "editorial-reference-profile" };
60
+ const terminalFoundations = { bindings: { accent: "{color.accent}" }, identity: "Dark terminal", profile_id: "terminal-reference-profile" };
61
+
62
+ const cases = [
63
+ { name: "valid_governed_local_profile" },
64
+ { expect: "profile_artifact_mode_governed_local", name: "local_as_external", mutate: (item) => { item.artifact_mode = "external_consumer"; } },
65
+ { expect: "profile_default_forbidden", name: "implicit_default", mutate: (item) => { item.default = true; } },
66
+ { expect: "profile_layout_source_sha_required", name: "missing_layout_sha", mutate: (item) => { delete item.layout_source_sha; } },
67
+ { expect: "profile_ua_assumption_required", name: "missing_ua_assumption", mutate: (item) => { delete item.environment_assumptions.user_agent_styles; } },
68
+ { expect: "profile_reset_assumptions_required", name: "missing_reset_assumptions", mutate: (item) => { delete item.environment_assumptions.reset; } },
69
+ { expect: "profile_explicit_selection_required", name: "implicit_selection", mutate: (item) => { item.selection.required = false; } },
70
+ { expect: "fixture_independence_related", name: "independence_independent", mutate: (item) => { item.fixture_independence = "independent"; } },
71
+ { expect: "reverse_import", name: "reverse_profile_import", patternData: "export const related_fixture_set_id = 'layout-identity-adversarial-pair';\n" },
72
+ { expect: "external_adaptation_record_forbidden", externalRecord: true, name: "durable_external_adopter" },
73
+ { equalTokens: true, expect: "profile_tokens_not_distinct", name: "single_profile_equal_pair_tokens" },
74
+ { equalFoundations: true, expect: "profile_foundations_not_distinct", name: "single_profile_equal_pair_foundations" },
75
+ { equalIdentity: true, expect: "profile_identity_not_distinct", name: "single_profile_equal_pair_identity" },
76
+ { expect: "profile_related_set_mismatch", name: "single_profile_related_set_mismatch", mutateTerminal: (item) => { item.related_fixture_set_id = "unrelated-terminal-set"; } },
77
+ { expect: "profile_tokens_symlink_escape", linkedArtifact: "tokens", name: "linked_outside_tokens" },
78
+ { expect: "profile_foundations_symlink_escape", linkedArtifact: "foundations", name: "linked_outside_foundations" },
79
+ { expect: "profile_environment_property_unknown", name: "unknown_environment_field", mutate: (item) => { item.environment_assumptions.viewport = "wide"; } },
80
+ { expect: "profile_reset_property_unknown", name: "unknown_reset_field", mutate: (item) => { item.environment_assumptions.reset.list_margin = "0"; } },
81
+ { expect: "profile_selection_property_unknown", name: "unknown_selection_field", mutate: (item) => { item.selection.fallback = "editorial"; } },
82
+ { expect: "item_property_unknown", name: "unknown_profile_top_level", mutate: (item) => { item.profile_fallback = true; } },
83
+ {
84
+ equalTokens: true,
85
+ expect: "profile_tokens_not_distinct",
86
+ name: "normalized_equivalent_selector_loads_pair",
87
+ selector: "design-engineering/reference-profiles/governed-local/editorial/../editorial/profile.json",
88
+ },
89
+ { absoluteSelector: true, expect: "item_unresolved", name: "absolute_profile_selector_rejected" },
90
+ { expect: "item_unresolved", name: "scheme_profile_selector_rejected", selector: "https://example.com/editorial/profile.json" },
91
+ { expect: "item_unresolved", name: "network_profile_selector_rejected", selector: "//server/editorial/profile.json" },
92
+ { expect: "item_unresolved", name: "escaping_profile_selector_rejected", selector: "../editorial/profile.json" },
93
+ { aliasSelector: true, expect: "item_redirect", name: "symlink_alias_profile_selector_rejected" },
94
+ ];
95
+
96
+ function writeFixture(testCase) {
97
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `stylegallery-reference-profile-${testCase.name}-`));
98
+ const profile = structuredClone(baseProfile);
99
+ const terminalProfile = structuredClone(baseProfile);
100
+ terminalProfile.handoff.record = terminalProfilePath;
101
+ terminalProfile.id = "terminal-reference-profile";
102
+ testCase.mutate?.(profile);
103
+ testCase.mutateTerminal?.(terminalProfile);
104
+ const terminalFoundation = structuredClone(terminalFoundations);
105
+ if (testCase.equalIdentity) terminalFoundation.identity = editorialFoundations.identity;
106
+ const files = {
107
+ "CATALOG.md": "# Catalog\n",
108
+ [profilePath]: `${JSON.stringify(profile, null, 2)}\n`,
109
+ "design-engineering/reference-profiles/governed-local/editorial/local-foundations.json": `${JSON.stringify(editorialFoundations)}\n`,
110
+ "design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json": `${JSON.stringify(tokens, null, 2)}\n`,
111
+ [terminalProfilePath]: `${JSON.stringify(terminalProfile, null, 2)}\n`,
112
+ "design-engineering/reference-profiles/governed-local/terminal/local-foundations.json": `${JSON.stringify(testCase.equalFoundations ? editorialFoundations : terminalFoundation)}\n`,
113
+ "design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json": `${JSON.stringify(testCase.equalTokens ? tokens : terminalTokens, null, 2)}\n`,
114
+ "layout/index.md": "# Layout\n",
115
+ "patterns/index.md": "# Patterns\n",
116
+ "quality/handoff.md": "Implementation handoff:\nConsumer reference: not_applicable\nConsumer reference reason: This fixture has no consumer-specific reference record.\n",
117
+ "scripts/pattern-data.mjs": testCase.patternData ?? "export const patterns = [];\n",
118
+ };
119
+ if (testCase.externalRecord) files["design-engineering/reference-profiles/external-adaptation/adopter.json"] = "{}\n";
120
+ for (const [relative, content] of Object.entries(files)) {
121
+ const target = path.join(root, relative);
122
+ fs.mkdirSync(path.dirname(target), { recursive: true });
123
+ fs.writeFileSync(target, content);
124
+ }
125
+ let externalRoot;
126
+ if (testCase.linkedArtifact) {
127
+ externalRoot = fs.mkdtempSync(path.join(os.tmpdir(), "stylegallery-reference-profile-outside-"));
128
+ const filename = testCase.linkedArtifact === "tokens" ? "tokens.dtcg.json" : "local-foundations.json";
129
+ const target = path.join(root, path.posix.dirname(profilePath), filename);
130
+ const external = path.join(externalRoot, filename);
131
+ fs.writeFileSync(external, files[path.posix.join(path.posix.dirname(profilePath), filename)]);
132
+ fs.rmSync(target);
133
+ fs.symlinkSync(external, target);
134
+ }
135
+ let selector = testCase.selector ?? profilePath;
136
+ if (testCase.absoluteSelector) selector = path.join(root, profilePath);
137
+ if (testCase.aliasSelector) {
138
+ const alias = path.join(root, aliasProfilePath);
139
+ fs.mkdirSync(path.dirname(alias), { recursive: true });
140
+ fs.symlinkSync(path.join(root, profilePath), alias);
141
+ selector = aliasProfilePath;
142
+ }
143
+ return { externalRoot, root, selector };
144
+ }
145
+
146
+ function runCase(testCase) {
147
+ const fixture = writeFixture(testCase);
148
+ try {
149
+ const child = spawnSync(process.execPath, [validator, "--profile", fixture.selector, "--json"], { cwd: fixture.root, encoding: "utf8" });
150
+ const output = JSON.parse(child.stdout);
151
+ const codes = Array.isArray(output.failures) ? output.failures.map((failure) => failure.code) : [];
152
+ const accepted = child.status === 0 && output.ok === true && output.scaffold !== true;
153
+ const rejected = child.status !== 0 && output.ok === false && codes.includes(testCase.expect);
154
+ return {
155
+ actual: { codes, ok: output.ok, status: child.status },
156
+ expected: testCase.expect ?? "ok:true and exit:0",
157
+ name: testCase.name,
158
+ ok: testCase.expect ? rejected : accepted,
159
+ };
160
+ } finally {
161
+ fs.rmSync(fixture.root, { force: true, recursive: true });
162
+ if (fixture.externalRoot) fs.rmSync(fixture.externalRoot, { force: true, recursive: true });
163
+ }
164
+ }
165
+
166
+ const results = cases.map(runCase);
167
+ const failures = results.filter((result) => !result.ok).map((result) => `missing_semantic:${result.name}:${result.expected}`);
168
+ const report = { failures, ok: failures.length === 0, results };
169
+ console.log(JSON.stringify(report, null, 2));
170
+ process.exitCode = report.ok ? 0 : 1;
@@ -0,0 +1,57 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+
7
+ const root = path.resolve(import.meta.dirname, "..");
8
+ const validator = path.join(root, "scripts", "validate-unity-org-wiki.mjs");
9
+
10
+ function run(cwd) {
11
+ return spawnSync(process.execPath, [validator, "--json"], { cwd, encoding: "utf8" });
12
+ }
13
+
14
+ async function main() {
15
+ const inventoryPath = path.join(root, "game-ui", "unity", "data", "unity-technologies-public-repositories.json");
16
+ const mapPath = path.join(root, "game-ui", "unity", "repository-map.md");
17
+ const wikiPath = path.join(root, "game-ui", "unity", "org-wiki.md");
18
+ const inventoryText = await readFile(inventoryPath, "utf8");
19
+ const inventory = JSON.parse(inventoryText);
20
+ const map = await readFile(mapPath, "utf8");
21
+ const wiki = await readFile(wikiPath, "utf8");
22
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), "unity-org-wiki-validator-"));
23
+
24
+ try {
25
+ await writeFile(path.join(tempRoot, "inventory.json"), inventoryText);
26
+ await writeFile(path.join(tempRoot, "map.md"), map);
27
+ await writeFile(path.join(tempRoot, "wiki.md"), wiki);
28
+ const fixtures = [
29
+ ["wrong count", inventory.slice(1), map, wiki, "expected 804 repositories"],
30
+ ["duplicate repository", [...inventory.slice(0, -1), inventory[0]], map, wiki, "duplicate repository identity"],
31
+ ["NGUI inside org", [...inventory.slice(0, -1), { ...inventory.at(-1), id: 999999999, full_name: "tasharen/ngui", html_url: "https://github.com/tasharen/ngui" }], map, wiki, "NGUI must remain external"],
32
+ ["invalid cluster", [{ ...inventory[0], primary_cluster: "unverified" }, ...inventory.slice(1)], map, wiki, "invalid primary_cluster"],
33
+ ["absolute path", inventory, map, `${wiki}\n/Users/example/private\n`, "absolute home path"],
34
+ ["wiki data drift", inventory, map, wiki.replaceAll("**804**", "**803**"), "wiki must state the 804-row boundary"],
35
+ ["repository map count drift", inventory, map.replace("804 classified rows", "803 classified rows"), wiki, "repository map must state the 804-row boundary"],
36
+ ["repository map hash drift", inventory, map.replaceAll("b1438e6e8d438bab3eff2ff02f5a10e9374fdfb89b0581ffb936a42920fa2e05", "0000000000000000000000000000000000000000000000000000000000000000"), wiki, "repository map tracked inventory hash drift"],
37
+ ["tracked inventory hash drift", [{ ...inventory[0], stargazers_count: inventory[0].stargazers_count + 1 }, ...inventory.slice(1)], map, wiki, "tracked inventory SHA-256 mismatch"],
38
+ ];
39
+
40
+ for (const [name, candidateInventory, candidateMap, candidateWiki, expected] of fixtures) {
41
+ await writeFile(path.join(tempRoot, "inventory.json"), candidateInventory === inventory ? inventoryText : JSON.stringify(candidateInventory));
42
+ await writeFile(path.join(tempRoot, "map.md"), candidateMap);
43
+ await writeFile(path.join(tempRoot, "wiki.md"), candidateWiki);
44
+ const result = spawnSync(process.execPath, [validator, "--json", "--inventory", path.join(tempRoot, "inventory.json"), "--map", path.join(tempRoot, "map.md"), "--wiki", path.join(tempRoot, "wiki.md")], { cwd: root, encoding: "utf8" });
45
+ assert.notEqual(result.status, 0, `${name} fixture unexpectedly passed`);
46
+ assert.match(`${result.stdout}${result.stderr}`, new RegExp(expected.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), `${name} fixture failed for the wrong reason`);
47
+ }
48
+
49
+ const result = run(root);
50
+ assert.equal(result.status, 0, result.stderr || result.stdout);
51
+ console.log("Unity org wiki validator fixtures passed: 9 negative, 1 positive.");
52
+ } finally {
53
+ await rm(tempRoot, { recursive: true, force: true });
54
+ }
55
+ }
56
+
57
+ await main();
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+
3
+ import assert from "node:assert/strict";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { spawnSync } from "node:child_process";
8
+
9
+ const root = path.resolve(import.meta.dirname, "..");
10
+ const validator = path.join(root, "scripts", "validate-unity-source-contracts.mjs");
11
+ const documents = [
12
+ "game-ui/unity/architecture.md",
13
+ "game-ui/unity/cli-loop.md",
14
+ "game-ui/unity/org-wiki.md",
15
+ "game-ui/unity/repository-map.md",
16
+ "game-ui/unity/ui-systems.md",
17
+ ];
18
+ const originals = Object.fromEntries(documents.map((relative) => [relative, fs.readFileSync(path.join(root, relative), "utf8")]));
19
+ const fixtures = [
20
+ {
21
+ name: "missing CLI license attribution",
22
+ relative: "game-ui/unity/cli-loop.md",
23
+ mutate: (content) => content.replace(/- Source license:.*\n/, ""),
24
+ expected: "game-ui/unity/cli-loop.md: missing pinned source license",
25
+ },
26
+ {
27
+ name: "mutable CLI body citation",
28
+ relative: "game-ui/unity/cli-loop.md",
29
+ mutate: (content) => content.replace("https://github.com/hatayama/unity-cli-loop/blob/61a0fe6d7da0aa9d0bcbc6d95944dd069c483ff0/README.md", "https://github.com/hatayama/unity-cli-loop/blob/main/README.md"),
30
+ expected: "game-ui/unity/cli-loop.md: GitHub body citation must use a non-zero 40-character revision: hatayama/unity-cli-loop",
31
+ },
32
+ {
33
+ name: "zeroed uGUI body revisions",
34
+ relative: "game-ui/unity/ui-systems.md",
35
+ mutate: (content) => content.replaceAll("9edb4420267b6652090ece4c28c38bd98746a68e", "0000000000000000000000000000000000000000"),
36
+ expected: "game-ui/unity/ui-systems.md: GitHub body citation must use a non-zero 40-character revision: Unity-Technologies/uGUI",
37
+ },
38
+ {
39
+ name: "invalid reviewed date",
40
+ relative: "game-ui/unity/architecture.md",
41
+ mutate: (content) => content.replace("reviewed_on: 2026-07-14", "reviewed_on: never"),
42
+ expected: "game-ui/unity/architecture.md: reviewed_on must be an ISO calendar date",
43
+ },
44
+ ];
45
+
46
+ function writeFixture(fixture) {
47
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), "unity-source-contracts-"));
48
+ for (const [relative, content] of Object.entries(originals)) {
49
+ const target = path.join(directory, relative);
50
+ fs.mkdirSync(path.dirname(target), { recursive: true });
51
+ fs.writeFileSync(target, relative === fixture.relative ? fixture.mutate(content) : content);
52
+ }
53
+ return directory;
54
+ }
55
+
56
+ for (const fixture of fixtures) {
57
+ const directory = writeFixture(fixture);
58
+ const result = spawnSync(process.execPath, [validator, "--json", "--root", directory], { encoding: "utf8" });
59
+ fs.rmSync(directory, { force: true, recursive: true });
60
+ assert.notEqual(result.status, 0, `${fixture.name} unexpectedly passed`);
61
+ const output = JSON.parse(result.stdout);
62
+ assert(output.failures.includes(fixture.expected), `${fixture.name} failed for the wrong reason: ${result.stdout}`);
63
+ }
64
+
65
+ const positive = spawnSync(process.execPath, [validator, "--json"], { cwd: root, encoding: "utf8" });
66
+ assert.equal(positive.status, 0, positive.stderr || positive.stdout);
67
+ console.log("Unity source contract fixtures passed: 4 negative, 1 positive.");