stylegallery 0.1.0 → 0.1.1

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 (230) hide show
  1. package/AGENTS.md +4 -1
  2. package/DOMAINS.md +2 -6
  3. package/GOVERNANCE.md +10 -9
  4. package/README.md +23 -127
  5. package/consumer-reference/agent-native/v2/admission-policy.json +646 -0
  6. package/consumer-reference/agent-native/v2/material-registry.json +1 -0
  7. package/consumer-reference/agent-native/v2/schema/admission-policy.schema.json +50 -0
  8. package/consumer-reference/agent-native/v2/schema/material-manifest.schema.json +17 -0
  9. package/consumer-reference/agent-native/v2/schema/material-record.schema.json +20 -0
  10. package/consumer-reference/contract.md +1 -1
  11. package/consumer-reference/index.md +5 -3
  12. package/layout/index.md +124 -0
  13. package/log.md +51 -0
  14. package/package.json +181 -22
  15. package/scripts/agent-native/v2/experimental-extension-registry.mjs +143 -0
  16. package/scripts/agent-native/v2/extensions/a2a-projection.mjs +264 -0
  17. package/scripts/agent-native/v2/extensions/agui-projection.mjs +191 -0
  18. package/scripts/agent-native/v2/material-admission.mjs +295 -0
  19. package/scripts/agent-native/v2/material-cli-adapter.mjs +134 -0
  20. package/scripts/agent-native/v2/material-context.mjs +251 -0
  21. package/scripts/agent-native/v2/material-identity.mjs +71 -0
  22. package/scripts/agent-native/v2/material-mcp-adapter.mjs +253 -0
  23. package/scripts/agent-native/v2/material-operation-registry.mjs +123 -0
  24. package/scripts/agent-native/v2/material-queries.mjs +398 -0
  25. package/scripts/agent-native/v2/material-registry.mjs +319 -0
  26. package/scripts/sg-material-mcp.mjs +98 -0
  27. package/scripts/sg-material.mjs +27 -0
  28. package/consumer-reference/adapters/style-dictionary.config.mjs +0 -36
  29. package/consumer-reference/baselines/calibration.json +0 -260
  30. package/consumer-reference/baselines/manifest.json +0 -21
  31. package/consumer-reference/fixtures/consumer-conformance/e2e-fixture.mjs +0 -282
  32. package/consumer-reference/fixtures/consumer-conformance/manifest.json +0 -102
  33. package/consumer-reference/fixtures/consumer-conformance/runtime-proof.mjs +0 -32
  34. package/consumer-reference/fixtures/consumer-conformance/valid-migration-result.json +0 -18
  35. package/consumer-reference/fixtures/consumer-conformance/valid-migration.json +0 -142
  36. package/consumer-reference/fixtures/invalid-stable-ended-support.json +0 -19
  37. package/consumer-reference/fixtures/promotion/invalid-blocking-evidence.json +0 -26
  38. package/consumer-reference/fixtures/promotion/invalid-count-only.json +0 -29
  39. package/consumer-reference/fixtures/promotion/invalid-missing-owner-migration.json +0 -25
  40. package/consumer-reference/fixtures/promotion/invalid-normative-bypass-no-regression.json +0 -25
  41. package/consumer-reference/fixtures/promotion/invalid-related-counted-independent.json +0 -45
  42. package/consumer-reference/fixtures/promotion/invalid-stable-ended-support.json +0 -26
  43. package/consumer-reference/fixtures/promotion/manifest.json +0 -15
  44. package/consumer-reference/fixtures/promotion/valid-deferred-example.json +0 -67
  45. package/consumer-reference/fixtures/promotion/valid-normative-bypass.json +0 -67
  46. package/consumer-reference/fixtures/token-portability/invalid-cycle.json +0 -7
  47. package/consumer-reference/fixtures/token-portability/invalid-dangling-alias.json +0 -6
  48. package/consumer-reference/fixtures/token-portability/invalid-extends.json +0 -4
  49. package/consumer-reference/fixtures/token-portability/invalid-json-pointer.json +0 -7
  50. package/consumer-reference/fixtures/token-portability/invalid-resolver.json +0 -9
  51. package/consumer-reference/fixtures/token-portability/invalid-type-mismatch.json +0 -10
  52. package/consumer-reference/fixtures/token-portability/invalid-unknown-reserved.json +0 -6
  53. package/consumer-reference/fixtures/token-portability/invalid-untested-type.json +0 -6
  54. package/consumer-reference/fixtures/token-portability/valid-border.json +0 -14
  55. package/consumer-reference/fixtures/token-portability/valid-reference.json +0 -32
  56. package/consumer-reference/fixtures/token-portability/valid-whole-alias.json +0 -7
  57. package/consumer-reference/fixtures/valid-deprecated.json +0 -21
  58. package/consumer-reference/fixtures/valid-experimental.json +0 -19
  59. package/consumer-reference/generated/manifest.json +0 -40
  60. package/consumer-reference/generated/tokens.css +0 -12
  61. package/consumer-reference/policies/shared-experimental.json +0 -37
  62. package/consumer-reference/schema/ax-evidence.schema.json +0 -30
  63. package/consumer-reference/schema/baseline-manifest.schema.json +0 -55
  64. package/consumer-reference/schema/calibration-record.schema.json +0 -190
  65. package/consumer-reference/schema/capture-session.schema.json +0 -161
  66. package/consumer-reference/schema/component-state.schema.json +0 -150
  67. package/consumer-reference/schema/consumer-conformance-record.schema.json +0 -365
  68. package/consumer-reference/schema/dom-evidence.schema.json +0 -38
  69. package/consumer-reference/schema/evidence-record.schema.json +0 -81
  70. package/consumer-reference/schema/fixture-manifest.schema.json +0 -32
  71. package/consumer-reference/schema/item.schema.json +0 -188
  72. package/consumer-reference/schema/page-evidence-manifest.schema.json +0 -216
  73. package/consumer-reference/schema/page-evidence-session.schema.json +0 -119
  74. package/consumer-reference/schema/portable-tokens.schema.json +0 -104
  75. package/consumer-reference/schema/promotion-rfc.schema.json +0 -219
  76. package/consumer-reference/schema/runtime-evidence-manifest.schema.json +0 -33
  77. package/consumer-reference/schema/visual-evidence.schema.json +0 -31
  78. package/design-engineering/consumer-migration-readiness.md +0 -124
  79. package/design-engineering/interface-craft.md +0 -107
  80. package/design-engineering/reference-profiles/external-adaptation/index.md +0 -12
  81. package/design-engineering/reference-profiles/governed-local/editorial/components/button.component.json +0 -125
  82. package/design-engineering/reference-profiles/governed-local/editorial/evidence/button.evidence.json +0 -3504
  83. package/design-engineering/reference-profiles/governed-local/editorial/fixtures/button.fixture.json +0 -65
  84. package/design-engineering/reference-profiles/governed-local/editorial/generated/evidence-coverage.md +0 -23
  85. package/design-engineering/reference-profiles/governed-local/editorial/generated/keyboard-matrix.md +0 -19
  86. package/design-engineering/reference-profiles/governed-local/editorial/generated/state-matrix.md +0 -21
  87. package/design-engineering/reference-profiles/governed-local/editorial/local-foundations.json +0 -12
  88. package/design-engineering/reference-profiles/governed-local/editorial/states/button.states.json +0 -195
  89. package/design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json +0 -17
  90. package/design-engineering/reference-profiles/governed-local/terminal/components/button.component.json +0 -125
  91. package/design-engineering/reference-profiles/governed-local/terminal/evidence/button.evidence.json +0 -3504
  92. package/design-engineering/reference-profiles/governed-local/terminal/fixtures/button.fixture.json +0 -65
  93. package/design-engineering/reference-profiles/governed-local/terminal/generated/evidence-coverage.md +0 -23
  94. package/design-engineering/reference-profiles/governed-local/terminal/generated/keyboard-matrix.md +0 -19
  95. package/design-engineering/reference-profiles/governed-local/terminal/generated/state-matrix.md +0 -21
  96. package/design-engineering/reference-profiles/governed-local/terminal/local-foundations.json +0 -12
  97. package/design-engineering/reference-profiles/governed-local/terminal/profile.json +0 -43
  98. package/design-engineering/reference-profiles/governed-local/terminal/states/button.states.json +0 -195
  99. package/design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json +0 -17
  100. package/game-ui/classification.md +0 -82
  101. package/game-ui/reference-record.md +0 -62
  102. package/game-ui/screen-hierarchy.md +0 -107
  103. package/game-ui/unity/architecture.md +0 -166
  104. package/game-ui/unity/cli-loop.md +0 -84
  105. package/game-ui/unity/data/unity-technologies-public-repositories.json +0 -26654
  106. package/game-ui/unity/org-wiki.md +0 -146
  107. package/game-ui/unity/repository-map.md +0 -102
  108. package/game-ui/unity/ui-systems.md +0 -85
  109. package/motion/practice-reference.md +0 -145
  110. package/motion/review-workflow.md +0 -86
  111. package/motion/vocabulary.md +0 -82
  112. package/platform-guides/apple-interaction.md +0 -89
  113. package/scripts/agent-native/events.mjs +0 -113
  114. package/scripts/agent-native/immutable-store.mjs +0 -102
  115. package/scripts/artifact-metadata.mjs +0 -20
  116. package/scripts/audit-evidence-freshness.mjs +0 -308
  117. package/scripts/baseline-contract.mjs +0 -189
  118. package/scripts/baseline-schema-parity.mjs +0 -40
  119. package/scripts/build-reference-artifacts.mjs +0 -127
  120. package/scripts/calibration-raw-contract.mjs +0 -172
  121. package/scripts/capture-session-contract.mjs +0 -187
  122. package/scripts/component-state-artifact-fixture.mjs +0 -44
  123. package/scripts/component-state-contract.mjs +0 -99
  124. package/scripts/component-state-semantics.mjs +0 -128
  125. package/scripts/component-state-workflow-contract.mjs +0 -67
  126. package/scripts/component-state-workflow-negative-cases.mjs +0 -64
  127. package/scripts/consumer-conformance-contract.mjs +0 -198
  128. package/scripts/consumer-evidence-governance-contract.mjs +0 -210
  129. package/scripts/consumer-reference-case-runner.mjs +0 -93
  130. package/scripts/consumer-reference-schema.mjs +0 -155
  131. package/scripts/create-component-state-session.mjs +0 -100
  132. package/scripts/create-page-evidence-session.mjs +0 -139
  133. package/scripts/domain-document-boundaries.mjs +0 -40
  134. package/scripts/domain-validation-checks.mjs +0 -141
  135. package/scripts/evidence-artifact-contract.mjs +0 -191
  136. package/scripts/finalize-component-state-evidence.mjs +0 -165
  137. package/scripts/finalize-page-evidence.mjs +0 -197
  138. package/scripts/generate-consumer-reference-evidence.mjs +0 -154
  139. package/scripts/generate-patterns.mjs +0 -218
  140. package/scripts/governance-matrix-contract.mjs +0 -73
  141. package/scripts/governance-matrix-negative-cases.mjs +0 -114
  142. package/scripts/governance-policy-contract.mjs +0 -49
  143. package/scripts/governance-test-fixture.mjs +0 -321
  144. package/scripts/governance-workflow-negative-cases.mjs +0 -241
  145. package/scripts/governed-profile-registry.mjs +0 -125
  146. package/scripts/json-schema-formats.mjs +0 -31
  147. package/scripts/markdown-structure.mjs +0 -45
  148. package/scripts/page-artifact-metadata.mjs +0 -124
  149. package/scripts/page-evidence-contract.mjs +0 -297
  150. package/scripts/page-evidence-fixture.mjs +0 -271
  151. package/scripts/pattern-data.mjs +0 -325
  152. package/scripts/profile-record-contract.mjs +0 -140
  153. package/scripts/promotion-attestation-contract.mjs +0 -91
  154. package/scripts/promotion-boundary-test-contract.mjs +0 -92
  155. package/scripts/promotion-fixture-inventory.mjs +0 -109
  156. package/scripts/promotion-governance-contract.mjs +0 -28
  157. package/scripts/reference-artifact-path-contract.mjs +0 -28
  158. package/scripts/reference-profile-contract.mjs +0 -114
  159. package/scripts/reference-token-contract.mjs +0 -156
  160. package/scripts/run-consumer-page-evidence-ci.mjs +0 -170
  161. package/scripts/summarize-sentinel-calibration.mjs +0 -98
  162. package/scripts/test-agent-adapter-conformance.mjs +0 -250
  163. package/scripts/test-agent-conformance-receipt.mjs +0 -106
  164. package/scripts/test-agent-events.mjs +0 -81
  165. package/scripts/test-agent-execution-effects.mjs +0 -114
  166. package/scripts/test-agent-execution-fixtures.mjs +0 -140
  167. package/scripts/test-agent-execution-kernel.mjs +0 -39
  168. package/scripts/test-agent-execution-learning.mjs +0 -130
  169. package/scripts/test-agent-execution-lifecycle.mjs +0 -143
  170. package/scripts/test-agent-execution-safety.mjs +0 -158
  171. package/scripts/test-agent-execution-views.mjs +0 -117
  172. package/scripts/test-agent-identity-integrity-cases.mjs +0 -160
  173. package/scripts/test-agent-knowledge-kernel.mjs +0 -183
  174. package/scripts/test-agent-learning.mjs +0 -246
  175. package/scripts/test-agent-mcp.mjs +0 -246
  176. package/scripts/test-agent-native-schemas.mjs +0 -215
  177. package/scripts/test-agent-projections.mjs +0 -249
  178. package/scripts/test-agent-registry-mcp-integrity.mjs +0 -215
  179. package/scripts/test-agent-registry-mutations.mjs +0 -110
  180. package/scripts/test-audit-evidence-freshness.mjs +0 -177
  181. package/scripts/test-component-state-sentinel.mjs +0 -35
  182. package/scripts/test-component-state-source-contract.mjs +0 -83
  183. package/scripts/test-consumer-conformance-sentinel.mjs +0 -165
  184. package/scripts/test-consumer-reference-sentinel.mjs +0 -31
  185. package/scripts/test-generate-consumer-reference-evidence.mjs +0 -124
  186. package/scripts/test-json-cli-output.mjs +0 -151
  187. package/scripts/test-json-schema-formats.mjs +0 -40
  188. package/scripts/test-reference-adapters.mjs +0 -199
  189. package/scripts/test-renderer-purity.mjs +0 -34
  190. package/scripts/test-sg-cli.mjs +0 -129
  191. package/scripts/test-summarize-sentinel-calibration.mjs +0 -226
  192. package/scripts/test-validate-baseline-manifest.mjs +0 -234
  193. package/scripts/test-validate-component-state-artifacts.mjs +0 -243
  194. package/scripts/test-validate-component-state.mjs +0 -106
  195. package/scripts/test-validate-consumer-conformance.mjs +0 -427
  196. package/scripts/test-validate-consumer-reference-cli.mjs +0 -69
  197. package/scripts/test-validate-consumer-reference.mjs +0 -263
  198. package/scripts/test-validate-domains.mjs +0 -357
  199. package/scripts/test-validate-governance.mjs +0 -292
  200. package/scripts/test-validate-ia.mjs +0 -262
  201. package/scripts/test-validate-okf.mjs +0 -73
  202. package/scripts/test-validate-page-evidence.mjs +0 -302
  203. package/scripts/test-validate-patterns.mjs +0 -157
  204. package/scripts/test-validate-promotion-rfc.mjs +0 -250
  205. package/scripts/test-validate-reference-profiles.mjs +0 -170
  206. package/scripts/test-validate-unity-org-wiki.mjs +0 -57
  207. package/scripts/test-validate-unity-source-contracts.mjs +0 -67
  208. package/scripts/test-validate-vocabulary.mjs +0 -174
  209. package/scripts/test-validate-webpage-workflow.mjs +0 -293
  210. package/scripts/validate-baseline-manifest.mjs +0 -44
  211. package/scripts/validate-catalog.mjs +0 -65
  212. package/scripts/validate-component-state.mjs +0 -141
  213. package/scripts/validate-consumer-conformance.mjs +0 -477
  214. package/scripts/validate-consumer-reference.mjs +0 -288
  215. package/scripts/validate-domains.mjs +0 -237
  216. package/scripts/validate-governance.mjs +0 -248
  217. package/scripts/validate-ia.mjs +0 -136
  218. package/scripts/validate-links.mjs +0 -66
  219. package/scripts/validate-okf.mjs +0 -104
  220. package/scripts/validate-page-evidence.mjs +0 -191
  221. package/scripts/validate-patterns.mjs +0 -246
  222. package/scripts/validate-promotion-rfc.mjs +0 -224
  223. package/scripts/validate-reference-artifacts.mjs +0 -125
  224. package/scripts/validate-renderer-purity.mjs +0 -56
  225. package/scripts/validate-unity-org-wiki.mjs +0 -146
  226. package/scripts/validate-unity-source-contracts.mjs +0 -107
  227. package/scripts/validate-vocabulary.mjs +0 -115
  228. package/scripts/validate-webpage-workflow.mjs +0 -254
  229. package/scripts/visual-expectation-contract.mjs +0 -20
  230. package/scripts/workflow-action-contract.mjs +0 -163
@@ -1,224 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import Ajv2020 from "ajv/dist/2020.js";
7
- import { isPlainObject } from "./consumer-reference-schema.mjs";
8
- import { validateGovernedProfileInventory } from "./governed-profile-registry.mjs";
9
- import { validatePromotionAttestations } from "./promotion-attestation-contract.mjs";
10
- import { loadPromotionFixtureInventory, resolvePromotionJsonFile } from "./promotion-fixture-inventory.mjs";
11
- import { parseStrictJson } from "./strict-json.mjs";
12
-
13
- const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
- const options = {
15
- files: [],
16
- fixtureRoot: path.join(repositoryRoot, "consumer-reference/fixtures/promotion"),
17
- json: false,
18
- policies: [],
19
- profileRepositoryRoot: repositoryRoot,
20
- profileRepositoryRootExplicit: false,
21
- };
22
- const argumentFailures = [];
23
-
24
- for (let index = 2; index < process.argv.length; index += 1) {
25
- const argument = process.argv[index];
26
- if (argument === "--json") options.json = true;
27
- else if (argument === "--file") {
28
- const value = process.argv[index + 1];
29
- if (!value || value.startsWith("--")) argumentFailures.push({ code: "argument_value_required", message: "--file requires a JSON path", path: "<cli>" });
30
- else {
31
- options.files.push(path.resolve(process.cwd(), value));
32
- index += 1;
33
- }
34
- } else if (argument === "--policy") {
35
- const value = process.argv[index + 1];
36
- if (!value || value.startsWith("--")) argumentFailures.push({ code: "argument_value_required", message: "--policy requires a JSON path", path: "<cli>" });
37
- else {
38
- options.policies.push(path.resolve(process.cwd(), value));
39
- index += 1;
40
- }
41
- } else if (argument === "--profile-repository-root") {
42
- const value = process.argv[index + 1];
43
- if (!value || value.startsWith("--")) argumentFailures.push({ code: "argument_value_required", message: "--profile-repository-root requires a repository path", path: "<cli>" });
44
- else {
45
- options.profileRepositoryRoot = path.resolve(process.cwd(), value);
46
- options.profileRepositoryRootExplicit = true;
47
- index += 1;
48
- }
49
- } else if (argument === "--promotion-fixture-root") {
50
- const value = process.argv[index + 1];
51
- if (!value || value.startsWith("--")) argumentFailures.push({ code: "argument_value_required", message: "--promotion-fixture-root requires a repository path", path: "<cli>" });
52
- else {
53
- options.fixtureRoot = path.resolve(process.cwd(), value);
54
- index += 1;
55
- }
56
- } else argumentFailures.push({ code: "argument_unknown", message: `unsupported argument ${argument}`, path: "<cli>" });
57
- }
58
-
59
- const failures = [...argumentFailures];
60
- const warnings = [];
61
- const fixtureInventory = loadPromotionFixtureInventory({ fixtureRoot: options.fixtureRoot, repositoryRoot });
62
- failures.push(...fixtureInventory.failures);
63
- let validateSchema = () => false;
64
- try {
65
- if (fixtureInventory.schema) validateSchema = new Ajv2020({ allErrors: true, strict: true }).compile(fixtureInventory.schema);
66
- } catch (error) {
67
- failures.push({ code: "promotion_fixture_inventory_invalid", message: error instanceof Error ? error.message : String(error), path: "consumer-reference/schema/promotion-rfc.schema.json" });
68
- }
69
- const files = options.files.length > 0
70
- ? options.files
71
- : options.policies.length > 0
72
- ? []
73
- : fixtureInventory.validFiles;
74
- const policies = options.files.length === 0 && options.policies.length === 0
75
- ? [fixtureInventory.policyFile].filter(Boolean)
76
- : options.policies;
77
-
78
- function finding(code, message, recordPath) {
79
- return { code, message, path: recordPath };
80
- }
81
-
82
- function sameStringSet(actual, expected) {
83
- return Array.isArray(actual) && actual.length === expected.length && actual.every((item) => typeof item === "string") && new Set(actual).size === expected.length && expected.every((item) => actual.includes(item));
84
- }
85
-
86
- function validateSemantics({ canonicalExample, value, recordPath, failures, warnings }) {
87
- if (!isPlainObject(value)) return;
88
- if (canonicalExample && (value.decision !== "deferred" || !Array.isArray(value.attestations) || value.attestations.length !== 0)) {
89
- failures.push(finding("promotion_canonical_example_invalid", "canonical examples must remain deferred with zero attestations", recordPath));
90
- }
91
- if (typeof value.review_independence === "boolean") failures.push(finding("review_independence_boolean", "review_independence must use the canonical single_account enum", recordPath));
92
- if (!isPlainObject(value.owner) || typeof value.owner.migration !== "string" || !isPlainObject(value.migration)) {
93
- failures.push(finding("promotion_owner_migration_required", "promotion proposals require migration ownership and a migration plan", recordPath));
94
- }
95
- if (value.compatibility?.breaking === true && value.migration?.required !== true) {
96
- failures.push(finding("promotion_migration_required", "breaking compatibility changes require an enabled migration plan", recordPath));
97
- }
98
- if (value.eligibility_basis === "consumer_count_only") failures.push(finding("promotion_count_only_forbidden", "consumer count cannot replace evidence and lifecycle duties", recordPath));
99
- if (isPlainObject(value.support) && value.transition?.to === "stable" && value.support.status === "ended") {
100
- failures.push(finding("stable_support_ended", "stable promotion requires active support", recordPath));
101
- }
102
- if (value.support?.status !== "active" || value.support?.capacity !== "committed") {
103
- failures.push(finding("promotion_support_required", "promotion eligibility requires active, affirmative support capacity", recordPath));
104
- }
105
- if (value.transition?.from === "stable" && value.transition?.to === "shared_experimental") {
106
- failures.push(finding("promotion_stable_relabel_forbidden", "a stable contract must be maintained, rolled back, or deprecated with migration", recordPath));
107
- }
108
- const transition = `${value.transition?.from ?? ""}->${value.transition?.to ?? ""}`;
109
- if (!["consumer_local->shared_experimental", "shared_experimental->stable", "stable->shared_experimental"].includes(transition)) {
110
- failures.push(finding("promotion_transition_invalid", `${transition} is not a governed promotion transition`, recordPath));
111
- }
112
- const evidence = Array.isArray(value.evidence) ? value.evidence.filter(isPlainObject) : [];
113
- const evidenceIds = evidence.map((entry) => entry.id).filter((id) => typeof id === "string");
114
- if (new Set(evidenceIds).size !== evidenceIds.length) failures.push(finding("promotion_evidence_duplicate", "evidence IDs must be unique", recordPath));
115
- if (evidence.some((entry) => entry.kind === "blocking" && entry.status === "failed")) {
116
- failures.push(finding("promotion_blocking_evidence_failed", "failed blocking evidence prevents eligibility", recordPath));
117
- }
118
- const regressionPassed = evidence.some((entry) => entry.kind === "regression" && entry.status === "passed");
119
- const claimScopedPassed = evidence.some((entry) => entry.kind === "claim_scoped" && entry.status === "passed");
120
- if (value.normative_bypass === "normative_correctness" && !regressionPassed) {
121
- failures.push(finding("promotion_normative_regression_required", "normative bypass waives adoption count only and requires passing regression evidence", recordPath));
122
- }
123
- if (value.normative_bypass === "normative_correctness" && value.scope?.kind !== "normative_correctness") {
124
- failures.push(finding("promotion_normative_scope_required", "normative bypass requires a normative correctness scope", recordPath));
125
- }
126
- if (value.transition?.to === "stable" && (!claimScopedPassed || !regressionPassed)) {
127
- failures.push(finding("promotion_stable_evidence_required", "stable requires passing claim-scoped and regression evidence", recordPath));
128
- }
129
- const attestationResult = validatePromotionAttestations({ inventory: governedProfileInventory, profileRoot: options.profileRepositoryRoot, recordPath, value });
130
- failures.push(...attestationResult.failures);
131
- const counted = attestationResult.eligibleCount;
132
- if (value.transition?.from === "consumer_local" && value.transition?.to === "shared_experimental" && value.normative_bypass === "none" && counted < 2) {
133
- warnings.push(finding("promotion_consumer_count_below_gateway", "deferred proposal has fewer than two independent consumers", recordPath));
134
- }
135
- if (value.transition?.to === "stable" && (value.stable_scope_decision !== true || value.provenance?.debt_resolved !== true)) {
136
- failures.push(finding("promotion_stable_readiness_required", "stable requires explicit scope decision and resolved provenance debt; no numeric count substitutes", recordPath));
137
- }
138
- }
139
-
140
- function validatePolicy(value, recordPath, failures) {
141
- if (!isPlainObject(value)) {
142
- failures.push(finding("promotion_policy_shape_invalid", "promotion policy must be an object", recordPath));
143
- return;
144
- }
145
- const allowed = ["artifact_mode", "boundaries", "claims", "gateway", "id", "lifecycle", "normative_bypass", "owner", "review_independence", "schema_version", "status"];
146
- if (Object.keys(value).some((key) => !allowed.includes(key))) failures.push(finding("promotion_policy_shape_invalid", "promotion policy contains an unknown field", recordPath));
147
- const nestedShapes = [
148
- [value.boundaries, ["excluded_content", "promoted_content"], "boundaries"],
149
- [value.claims, ["adopter_attestations", "promotion_occurred"], "claims"],
150
- [value.gateway, ["applies_to", "minimum_independent_consumers", "stable_numeric_threshold", "transition"], "gateway"],
151
- [value.gateway?.transition, ["from", "to"], "gateway.transition"],
152
- [value.lifecycle, ["failure_actions", "stable_relabel"], "lifecycle"],
153
- [value.normative_bypass, ["categories", "retains", "waives"], "normative_bypass"],
154
- [value.owner, ["enforcement", "name"], "owner"],
155
- ];
156
- for (const [candidate, keys, label] of nestedShapes) {
157
- if (!isPlainObject(candidate) || Object.keys(candidate).some((key) => !keys.includes(key))) failures.push(finding("promotion_policy_shape_invalid", `promotion policy ${label} shape is invalid`, recordPath));
158
- }
159
- if (value.review_independence !== "single_account") failures.push(finding(typeof value.review_independence === "boolean" ? "review_independence_boolean" : "promotion_policy_shape_invalid", "policy review_independence must be single_account", recordPath));
160
- if (value.gateway?.transition?.from !== "consumer_local" || value.gateway?.transition?.to !== "shared_experimental" || value.gateway?.applies_to !== "invariant_eligibility_only" || value.gateway?.minimum_independent_consumers !== 2) {
161
- failures.push(finding("promotion_gateway_invalid", "the >=2 gateway applies only to consumer-local invariant eligibility for shared-experimental", recordPath));
162
- }
163
- if (value.gateway?.stable_numeric_threshold !== null) failures.push(finding("promotion_stable_numeric_threshold_forbidden", "stable has no numeric adoption threshold", recordPath));
164
- const categories = ["accessibility", "correctness", "security", "standards"];
165
- const retained = ["compatibility", "deprecation", "migration", "owner_acceptance", "regression_proof", "rollback", "scope_control", "support_capacity"];
166
- if (!sameStringSet(value.normative_bypass?.categories, categories) || !sameStringSet(value.normative_bypass?.waives, ["adoption_count"]) || !sameStringSet(value.normative_bypass?.retains, retained)) {
167
- failures.push(finding("promotion_normative_bypass_overreach", "normative bypass may waive adoption count only", recordPath));
168
- }
169
- if (value.claims?.promotion_occurred !== false || value.claims?.adopter_attestations !== 0) failures.push(finding("promotion_claim_forbidden", "the policy records no promotion and zero adopter attestations", recordPath));
170
- if (value.status !== "active") failures.push(finding("promotion_claim_forbidden", "the policy status must remain active governance, not a promoted decision", recordPath));
171
- if (value.schema_version !== "1.0" || value.id !== "shared-experimental-policy" || value.artifact_mode !== "schema_only") failures.push(finding("promotion_policy_identity_invalid", "promotion policy identity and artifact mode are canonical", recordPath));
172
- if (value.owner?.enforcement !== "placeholder" || value.owner?.name !== "Repository governance owner") failures.push(finding("promotion_policy_owner_invalid", "promotion policy must preserve placeholder owner truth", recordPath));
173
- const excluded = ["component_skin", "imagery", "motion_character", "palette", "typography"];
174
- const promoted = ["claim_scoped_evidence", "invariant_behavior", "invariant_semantics"];
175
- if (!sameStringSet(value.boundaries?.excluded_content, excluded)) {
176
- failures.push(finding("promotion_aesthetic_boundary_required", "aesthetic identity must remain excluded from promotion", recordPath));
177
- }
178
- if (!sameStringSet(value.boundaries?.promoted_content, promoted)) failures.push(finding("promotion_aesthetic_boundary_required", "promoted content must be the invariant-only canonical set", recordPath));
179
- if (value.lifecycle?.stable_relabel !== "forbidden" || !sameStringSet(value.lifecycle?.failure_actions, ["deprecate_with_migration", "maintain", "restrict", "rollback"])) {
180
- failures.push(finding("promotion_lifecycle_invalid", "failed stable contracts require explicit lifecycle action and migration", recordPath));
181
- }
182
- }
183
-
184
- const governedProfileInventory = validateGovernedProfileInventory({ root: options.profileRepositoryRoot });
185
- if (options.profileRepositoryRootExplicit && governedProfileInventory.failures.length > 0) {
186
- failures.push(finding("promotion_profile_registry_invalid", "explicit governed profile repository must fully validate", "<profile-repository-root>"));
187
- }
188
- let checked = 0;
189
- for (const file of files) {
190
- const recordPath = path.relative(repositoryRoot, file) || file;
191
- const resolvedFile = resolvePromotionJsonFile({ file, repositoryRoot }, failures);
192
- if (!resolvedFile) continue;
193
- let value;
194
- try {
195
- value = parseStrictJson(fs.readFileSync(resolvedFile, "utf8"));
196
- checked += 1;
197
- } catch (error) {
198
- failures.push(finding("promotion_json_invalid", error instanceof Error ? error.message : "promotion JSON is invalid", recordPath));
199
- continue;
200
- }
201
- validateSemantics({ canonicalExample: fixtureInventory.validFiles.includes(resolvedFile), failures, recordPath, value, warnings });
202
- if (!validateSchema(value)) {
203
- for (const error of validateSchema.errors ?? []) failures.push(finding("promotion_schema_invalid", `${error.instancePath || "/"} ${error.message}`, recordPath));
204
- }
205
- }
206
- for (const file of policies) {
207
- const recordPath = path.relative(repositoryRoot, file) || file;
208
- const resolvedFile = resolvePromotionJsonFile({ file, repositoryRoot }, failures);
209
- if (!resolvedFile) continue;
210
- try {
211
- const value = parseStrictJson(fs.readFileSync(resolvedFile, "utf8"));
212
- checked += 1;
213
- validatePolicy(value, recordPath, failures);
214
- } catch (error) {
215
- failures.push(finding("promotion_json_invalid", error instanceof Error ? error.message : "promotion policy JSON is invalid", recordPath));
216
- }
217
- }
218
-
219
- const uniqueFailures = [...new Map(failures.map((entry) => [`${entry.code}:${entry.path}:${entry.message}`, entry])).values()];
220
- const result = { checked, failures: uniqueFailures, ok: uniqueFailures.length === 0, warnings };
221
- if (options.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
222
- else if (result.ok) process.stdout.write(`ok: ${checked} promotion proposal records\n`);
223
- else process.stderr.write(`${result.failures.map((entry) => `${entry.code}: ${entry.path}: ${entry.message}`).join("\n")}\n`);
224
- if (!result.ok) process.exitCode = 1;
@@ -1,125 +0,0 @@
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 { spawnSync } from "node:child_process";
7
- import { fileURLToPath } from "node:url";
8
- import { adapter } from "../consumer-reference/adapters/style-dictionary.config.mjs";
9
- import { inspectArtifactPath } from "./reference-artifact-path-contract.mjs";
10
- import { validatePortableTokens } from "./reference-token-contract.mjs";
11
- import { parseStrictJson } from "./strict-json.mjs";
12
-
13
- function hash(content) {
14
- return `sha256:${crypto.createHash("sha256").update(content).digest("hex")}`;
15
- }
16
-
17
- function cssDeclarations(css) {
18
- return [...css.matchAll(/^\s*(--[a-z0-9-]+):\s*([^;]+);\s*$/gm)].map((match) => ({ name: match[1], value: match[2].trim() }));
19
- }
20
-
21
- function parseArguments() {
22
- let json = false;
23
- let manifest = "consumer-reference/generated/manifest.json";
24
- const failures = [];
25
- for (let index = 2; index < process.argv.length; index += 1) {
26
- const argument = process.argv[index];
27
- if (argument === "--json") json = true;
28
- else if (argument === "--manifest") {
29
- manifest = process.argv[index + 1];
30
- if (!manifest) failures.push({ code: "argument_value_required", message: "--manifest requires a value", path: "<cli>" });
31
- else index += 1;
32
- } else failures.push({ code: "argument_unknown", message: `unsupported argument ${argument}`, path: "<cli>" });
33
- }
34
- return { failures, json, manifest };
35
- }
36
-
37
- const options = parseArguments();
38
- const failures = [...options.failures];
39
- const warnings = [];
40
- const trustRoot = process.cwd();
41
- const manifestPath = path.resolve(trustRoot, options.manifest || "missing.json");
42
- let manifest;
43
- const manifestInspection = inspectArtifactPath(trustRoot, manifestPath, false);
44
- if (!manifestInspection.ok && manifestInspection.reason === "missing") {
45
- failures.push({ code: "artifact_manifest_missing", message: "manifest does not exist", path: options.manifest });
46
- failures.push({ code: "artifact_output_missing", message: "output cannot be located without a manifest", path: options.manifest });
47
- } else if (!manifestInspection.ok) {
48
- failures.push({ code: "artifact_manifest_untrusted", message: "manifest must be a contained regular non-symlink file", path: options.manifest });
49
- } else {
50
- try {
51
- manifest = parseStrictJson(fs.readFileSync(manifestPath, "utf8"));
52
- } catch (error) {
53
- failures.push({ code: "artifact_manifest_invalid", message: error instanceof Error ? error.message : String(error), path: options.manifest });
54
- }
55
- }
56
- if (manifest !== undefined) {
57
- const allowedManifestProperties = new Set(["adapter", "declarations", "inputHash", "outputCount", "outputFile", "outputHash", "schemaVersion", "sourceCount", "sourceFile", "warnings"]);
58
- if (manifest.scaffold === true) failures.push({ code: "artifact_scaffold_forbidden", message: "manifest must not contain a scaffold success sentinel", path: options.manifest });
59
- for (const key of Object.keys(manifest).filter((key) => !allowedManifestProperties.has(key))) failures.push({ code: "artifact_manifest_property_unknown", message: `manifest contains unsupported property ${key}`, path: options.manifest });
60
- if (manifest.schemaVersion !== "1.0") failures.push({ code: "artifact_schema_version", message: "manifest schemaVersion must be 1.0", path: options.manifest });
61
- if (manifest.adapter?.name !== adapter.name || manifest.adapter?.version !== adapter.version) failures.push({ code: "artifact_adapter_mismatch", message: "manifest adapter pin is invalid", path: options.manifest });
62
- if (!Number.isInteger(manifest.sourceCount) || !Number.isInteger(manifest.outputCount) || manifest.sourceCount <= 0 || manifest.outputCount <= 0) failures.push({ code: "artifact_zero_count", message: "source and output counts must be positive integers", path: options.manifest });
63
- if (manifest.sourceCount !== manifest.outputCount) failures.push({ code: "artifact_count_mismatch", message: "source and output counts differ", path: options.manifest });
64
- if (!Array.isArray(manifest.warnings)) failures.push({ code: "artifact_warnings_invalid", message: "warnings must be an array", path: options.manifest });
65
- else if (manifest.warnings.length > 0) failures.push({ code: "artifact_warning", message: "manifest contains adapter warnings", path: options.manifest });
66
- const declared = Array.isArray(manifest.declarations) ? manifest.declarations : [];
67
- if (!Array.isArray(manifest.declarations)) failures.push({ code: "artifact_declarations_invalid", message: "declarations must be an array", path: options.manifest });
68
- const declarationNames = declared.map((item) => item?.name).filter((name) => typeof name === "string");
69
- if (new Set(declarationNames).size !== declarationNames.length) failures.push({ code: "artifact_declaration_duplicate", message: "manifest contains duplicate declarations", path: options.manifest });
70
- const sourcePath = typeof manifest.sourceFile === "string" ? path.resolve(path.dirname(manifestPath), manifest.sourceFile) : "";
71
- const outputPath = typeof manifest.outputFile === "string" ? path.resolve(path.dirname(manifestPath), manifest.outputFile) : "";
72
- const sourceInspection = sourcePath ? inspectArtifactPath(trustRoot, sourcePath, false) : { ok: false, reason: "missing" };
73
- const outputInspection = outputPath ? inspectArtifactPath(trustRoot, outputPath, false) : { ok: false, reason: "missing" };
74
- if (!sourceInspection.ok && sourceInspection.reason === "missing") failures.push({ code: "artifact_source_missing", message: "manifest source file does not exist", path: options.manifest });
75
- else if (!sourceInspection.ok) failures.push({ code: "token_source_untrusted", message: "manifest source must be a contained regular non-symlink file", path: options.manifest });
76
- else {
77
- const sourceBytes = fs.readFileSync(sourcePath);
78
- if (manifest.inputHash !== hash(sourceBytes)) failures.push({ code: "artifact_input_hash_mismatch", message: "input hash does not match source", path: options.manifest });
79
- try {
80
- const contract = validatePortableTokens(parseStrictJson(sourceBytes.toString("utf8")));
81
- failures.push(...contract.failures);
82
- if (contract.tokens.length !== manifest.sourceCount) failures.push({ code: "artifact_source_count_mismatch", message: "source count does not match canonical tokens", path: options.manifest });
83
- const expected = contract.tokens.map((token) => `--${token.path.replaceAll(".", "-")}`);
84
- for (const name of expected.filter((name) => !declarationNames.includes(name))) failures.push({ code: "artifact_declaration_missing", message: `manifest omitted ${name}`, path: options.manifest });
85
- } catch (error) {
86
- failures.push({ code: "artifact_source_invalid", message: error instanceof Error ? error.message : String(error), path: options.manifest });
87
- }
88
- }
89
- if (!outputInspection.ok && outputInspection.reason === "missing") failures.push({ code: "artifact_output_missing", message: "manifest output file does not exist", path: options.manifest });
90
- else if (!outputInspection.ok) failures.push({ code: "artifact_output_untrusted", message: "manifest output must be a contained regular non-symlink file", path: options.manifest });
91
- else {
92
- const css = fs.readFileSync(outputPath, "utf8");
93
- const emitted = cssDeclarations(css);
94
- const emittedNames = emitted.map((item) => item.name);
95
- if (manifest.outputHash !== hash(css)) failures.push({ code: "artifact_output_hash_mismatch", message: "output hash does not match CSS", path: options.manifest });
96
- if (emitted.length === 0) failures.push({ code: "artifact_zero_count", message: "CSS contains zero declarations", path: options.manifest });
97
- if (new Set(emittedNames).size !== emittedNames.length) failures.push({ code: "artifact_declaration_duplicate", message: "CSS contains duplicate declarations", path: options.manifest });
98
- for (const name of declarationNames.filter((name) => !emittedNames.includes(name))) failures.push({ code: "artifact_declaration_missing", message: `CSS omitted ${name}`, path: options.manifest });
99
- for (const declaration of declared) {
100
- const actual = emitted.find((item) => item.name === declaration?.name);
101
- if (actual && actual.value !== declaration.value) failures.push({ code: "artifact_declaration_mismatch", message: `CSS value for ${declaration.name} differs from the manifest`, path: options.manifest });
102
- }
103
- if (emitted.length !== manifest.outputCount || emitted.length !== declarationNames.length) failures.push({ code: "artifact_count_mismatch", message: "CSS, manifest count, and declarations differ", path: options.manifest });
104
- if (css.includes("[object Object]")) failures.push({ code: "artifact_object_sentinel", message: "CSS contains [object Object]", path: options.manifest });
105
- if (/\{[a-z0-9.-]+\}|\b(?:null|undefined)\b/.test(css)) failures.push({ code: "artifact_unresolved_value", message: "CSS contains an unresolved value", path: options.manifest });
106
- if (sourceInspection.ok) {
107
- const rebuildRoot = fs.mkdtempSync(path.join(trustRoot, ".tmp-stylegallery-reference-validation-"));
108
- try {
109
- const expectedOutput = path.join(rebuildRoot, "tokens.css");
110
- const expectedManifest = path.join(rebuildRoot, "manifest.json");
111
- const builder = path.join(path.dirname(fileURLToPath(import.meta.url)), "build-reference-artifacts.mjs");
112
- const child = spawnSync(process.execPath, [builder, "--source", sourcePath, "--output", expectedOutput, "--manifest", expectedManifest, "--adapter", "style-dictionary", "--fail-on-warning", "--json"], { cwd: process.cwd(), encoding: "utf8" });
113
- if (child.status !== 0 || !fs.existsSync(expectedOutput)) failures.push({ code: "artifact_canonical_rebuild_failed", message: "canonical source could not be rebuilt with the pinned adapter", path: options.manifest });
114
- else if (fs.readFileSync(expectedOutput, "utf8") !== css) failures.push({ code: "artifact_source_output_mismatch", message: "CSS differs from the pinned adapter output derived from canonical source", path: options.manifest });
115
- } finally {
116
- fs.rmSync(rebuildRoot, { force: true, recursive: true });
117
- }
118
- }
119
- }
120
- }
121
- const uniqueFailures = [...new Map(failures.map((item) => [`${item.code}:${item.path}:${item.message}`, item])).values()];
122
- const report = { failures: uniqueFailures, manifest: options.manifest, ok: uniqueFailures.length === 0, warnings };
123
- const text = options.json ? JSON.stringify(report, null, 2) : report.ok ? "ok: reference artifacts" : uniqueFailures.map((item) => `${item.code}: ${item.message}`).join("\n");
124
- (options.json || report.ok ? console.log : console.error)(text);
125
- process.exitCode = report.ok ? 0 : 1;
@@ -1,56 +0,0 @@
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 { fileURLToPath, pathToFileURL } from "node:url";
7
-
8
- const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
- let renderer = "tests/helpers/render-consumer-reference.mjs";
10
- let json = false;
11
- const failures = [];
12
- for (let index = 2; index < process.argv.length; index += 1) {
13
- const argument = process.argv[index];
14
- if (argument === "--json") json = true;
15
- else if (argument === "--renderer" && process.argv[index + 1]) {
16
- renderer = process.argv[index + 1];
17
- index += 1;
18
- } else failures.push({ code: "argument_unknown", message: `unsupported argument ${argument}`, path: "<cli>" });
19
- }
20
-
21
- function hashProtectedSources() {
22
- const files = [path.join(repositoryRoot, "CATALOG.md")];
23
- const queue = [path.join(repositoryRoot, "patterns")];
24
- while (queue.length > 0) {
25
- const current = queue.pop();
26
- for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
27
- const target = path.join(current, entry.name);
28
- if (entry.isDirectory()) queue.push(target);
29
- else if (entry.isFile()) files.push(target);
30
- }
31
- }
32
- const hash = crypto.createHash("sha256");
33
- for (const file of files.sort()) hash.update(path.relative(repositoryRoot, file)).update(fs.readFileSync(file));
34
- return hash.digest("hex");
35
- }
36
-
37
- const rendererPath = path.resolve(repositoryRoot, renderer);
38
- if (!fs.existsSync(rendererPath)) failures.push({ code: "renderer_missing", message: "renderer helper does not exist", path: renderer });
39
- else {
40
- const source = fs.readFileSync(rendererPath, "utf8");
41
- const imports = [...source.matchAll(/^import\s+[^;]+?\s+from\s+["']([^"']+)["'];?$/gm)].map((match) => match[1]);
42
- if (imports.length !== 1 || imports[0] !== "../../scripts/pattern-data.mjs") failures.push({ code: "renderer_import_boundary", message: "renderer may import only scripts/pattern-data.mjs", path: renderer });
43
- if (/generate-patterns|build-reference-artifacts/.test(source)) failures.push({ code: "renderer_generator_dependency", message: "renderer must not import or call generators", path: renderer });
44
- if (/node:(?:fs|child_process)|\b(?:writeFile|appendFile|rmSync|renameSync|spawnSync|execSync)\b/.test(source)) failures.push({ code: "renderer_side_effect_capability", message: "renderer must not hold filesystem or process mutation capability", path: renderer });
45
- if (failures.length === 0) {
46
- const before = hashProtectedSources();
47
- await import(`${pathToFileURL(rendererPath).href}?purity=${Date.now()}`);
48
- const after = hashProtectedSources();
49
- if (after !== before) failures.push({ code: "renderer_protected_source_mutation", message: "renderer import changed patterns or CATALOG.md", path: renderer });
50
- }
51
- }
52
-
53
- const result = { failures, ok: failures.length === 0, warnings: [] };
54
- if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
55
- else if (!result.ok) process.stderr.write(`${failures.map((failure) => `${failure.code}: ${failure.message}`).join("\n")}\n`);
56
- if (!result.ok) process.exitCode = 1;
@@ -1,146 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { createHash } from "node:crypto";
3
- import path from "node:path";
4
-
5
- const root = process.cwd();
6
- const trackedInventorySha256 = "b1438e6e8d438bab3eff2ff02f5a10e9374fdfb89b0581ffb936a42920fa2e05";
7
- const sourceInventorySha256 = "8291b471251053a8921651eb7c791d13a4794984b73670a51cdb1116deb49657";
8
- const snapshotTimestamp = "2026-07-14T12:59:16Z";
9
- const expectedCounts = new Map([
10
- ["meta-forks-mirrors", 356],
11
- ["meta-archived-historical", 32],
12
- ["game-ui-primary", 9],
13
- ["graphics-rendering", 38],
14
- ["ecs-dots", 22],
15
- ["networking", 21],
16
- ["xr-ar", 25],
17
- ["ml-ai", 23],
18
- ["robotics", 6],
19
- ["2d", 7],
20
- ["animation", 10],
21
- ["audio", 3],
22
- ["physics", 1],
23
- ["editor-tooling", 29],
24
- ["ci-infra", 2],
25
- ["language-runtime", 3],
26
- ["services-cloud", 3],
27
- ["platform-mobile", 9],
28
- ["samples-templates", 30],
29
- ["other-mixed-or-insufficient-metadata", 175],
30
- ]);
31
-
32
- function parseArguments(arguments_) {
33
- const options = {
34
- inventory: path.join(root, "game-ui", "unity", "data", "unity-technologies-public-repositories.json"),
35
- map: path.join(root, "game-ui", "unity", "repository-map.md"),
36
- wiki: path.join(root, "game-ui", "unity", "org-wiki.md"),
37
- };
38
- for (let index = 0; index < arguments_.length; index += 1) {
39
- const argument = arguments_[index];
40
- if (argument === "--json") continue;
41
- if (argument === "--inventory" || argument === "--map" || argument === "--wiki") {
42
- const value = arguments_[index + 1];
43
- if (!value) throw new Error(`${argument} requires a path`);
44
- options[argument.slice(2)] = path.resolve(value);
45
- index += 1;
46
- continue;
47
- }
48
- throw new Error(`unknown argument: ${argument}`);
49
- }
50
- return options;
51
- }
52
-
53
- function requireCondition(condition, message) {
54
- if (!condition) throw new Error(message);
55
- }
56
-
57
- function validateInventory(inventory) {
58
- requireCondition(Array.isArray(inventory), "inventory must be a JSON array");
59
- requireCondition(inventory.length === 804, `expected 804 repositories, got ${inventory.length}`);
60
- const ids = new Set();
61
- const names = new Set();
62
- const counts = new Map([...expectedCounts.keys()].map((cluster) => [cluster, 0]));
63
- let archivedCount = 0;
64
- let forkCount = 0;
65
- let templateCount = 0;
66
-
67
- for (const repository of inventory) {
68
- requireCondition(repository && typeof repository === "object", "repository row must be an object");
69
- requireCondition(Number.isSafeInteger(repository.id), "repository id must be an integer");
70
- requireCondition(typeof repository.full_name === "string", "repository full_name must be a string");
71
- requireCondition(!ids.has(repository.id) && !names.has(repository.full_name), `duplicate repository identity: ${repository.full_name}`);
72
- requireCondition(repository.full_name !== "tasharen/ngui", "NGUI must remain external to the Unity-Technologies inventory");
73
- requireCondition(repository.full_name.startsWith("Unity-Technologies/"), `repository outside Unity-Technologies: ${repository.full_name}`);
74
- requireCondition(repository.html_url === `https://github.com/${repository.full_name}`, `repository URL mismatch: ${repository.full_name}`);
75
- requireCondition(repository.capture_timestamp === snapshotTimestamp, `snapshot timestamp mismatch: ${repository.full_name}`);
76
- requireCondition(expectedCounts.has(repository.primary_cluster), `invalid primary_cluster: ${repository.primary_cluster}`);
77
- requireCondition(typeof repository.ui_relevance === "string" && repository.ui_relevance.length > 0, `UI relevance missing: ${repository.full_name}`);
78
- requireCondition(typeof repository.ui_disposition_reason === "string" && repository.ui_disposition_reason.length > 0, `UI disposition reason missing: ${repository.full_name}`);
79
- requireCondition(Array.isArray(repository.representative_ui_paths), `representative UI paths must be an array: ${repository.full_name}`);
80
- ids.add(repository.id);
81
- names.add(repository.full_name);
82
- counts.set(repository.primary_cluster, counts.get(repository.primary_cluster) + 1);
83
- if (repository.archived) archivedCount += 1;
84
- if (repository.fork) forkCount += 1;
85
- if (repository.is_template) templateCount += 1;
86
- }
87
-
88
- for (const [cluster, expected] of expectedCounts) {
89
- requireCondition(counts.get(cluster) === expected, `cluster count mismatch for ${cluster}: expected ${expected}, got ${counts.get(cluster)}`);
90
- }
91
- requireCondition(archivedCount === 59, `archived repository count mismatch: expected 59, got ${archivedCount}`);
92
- requireCondition(forkCount === 356, `fork repository count mismatch: expected 356, got ${forkCount}`);
93
- requireCondition(templateCount === 4, `template repository count mismatch: expected 4, got ${templateCount}`);
94
- return { archivedCount, counts: Object.fromEntries(counts), forkCount, templateCount };
95
- }
96
-
97
- function validatePublication(content, label) {
98
- requireCondition(content.includes("804"), `${label} must state the 804-row boundary`);
99
- const sourceHashes = [...content.matchAll(/(?:pre-projection|before tracked projection)[^`\n]*SHA-256(?: was)? `([0-9a-f]{64})`/g)].map((match) => match[1]);
100
- const trackedHashes = [...content.matchAll(/tracked JSON[^`\n]*SHA-256(?: is)? `([0-9a-f]{64})`/g)].map((match) => match[1]);
101
- requireCondition(sourceHashes.length > 0 && sourceHashes.every((hash) => hash === sourceInventorySha256), `${label} source inventory hash drift`);
102
- requireCondition(trackedHashes.length > 0 && trackedHashes.every((hash) => hash === trackedInventorySha256), `${label} tracked inventory hash drift`);
103
- requireCondition(content.includes(snapshotTimestamp), `${label} snapshot timestamp drift`);
104
- requireCondition(content.includes("59 archived") && content.includes("356 forks") && content.includes("4 templates"), `${label} lifecycle totals drift`);
105
- }
106
-
107
- function validateWiki(wiki) {
108
- requireCondition(wiki.includes("**804**"), "wiki must state the 804-row boundary");
109
- requireCondition(wiki.includes("Independent research — not affiliated with or endorsed by Unity Technologies"), "wiki must state the non-affiliation boundary");
110
- requireCondition(wiki.includes("tasharen/ngui") && wiki.includes("outside the Unity-Technologies 804-repository inventory"), "wiki must keep NGUI external");
111
- requireCondition(wiki.includes("data/unity-technologies-public-repositories.json"), "wiki must link the tracked inventory");
112
- requireCondition(!wiki.includes(".omo/"), "tracked wiki must not depend on .omo research paths");
113
- requireCondition(!/(?:\/Users\/|\/home\/|[A-Za-z]:\\Users\\)/.test(wiki), "absolute home path found in wiki");
114
- for (const [cluster, count] of expectedCounts) {
115
- requireCondition(wiki.includes(`| \`${cluster}\` | ${count} |`), `wiki cluster table drift: ${cluster}`);
116
- }
117
- validatePublication(wiki, "wiki");
118
- }
119
-
120
- function validateMap(map) {
121
- requireCondition(map.includes("data/unity-technologies-public-repositories.json"), "repository map must link the tracked inventory");
122
- requireCondition(map.includes("804 classified rows"), "repository map must state the 804-row boundary");
123
- validatePublication(map, "repository map");
124
- }
125
-
126
- async function main() {
127
- try {
128
- const options = parseArguments(process.argv.slice(2));
129
- const [inventoryText, map, wiki] = await Promise.all([
130
- readFile(options.inventory, "utf8"),
131
- readFile(options.map, "utf8"),
132
- readFile(options.wiki, "utf8"),
133
- ]);
134
- const stats = validateInventory(JSON.parse(inventoryText));
135
- const inventorySha256 = createHash("sha256").update(inventoryText).digest("hex");
136
- requireCondition(inventorySha256 === trackedInventorySha256, `tracked inventory SHA-256 mismatch: expected ${trackedInventorySha256}, got ${inventorySha256}`);
137
- validateMap(map);
138
- validateWiki(wiki);
139
- console.log(JSON.stringify({ ok: true, repositories: 804, uniqueIds: 804, uniqueNames: 804, inventorySha256, clusterTotal: Object.values(stats.counts).reduce((sum, value) => sum + value, 0), ...stats }, null, 2));
140
- } catch (error) {
141
- console.error(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }));
142
- process.exitCode = 1;
143
- }
144
- }
145
-
146
- await main();