stylegallery 0.1.0 → 0.1.2

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 +29 -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 +57 -0
  14. package/package.json +183 -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,156 +0,0 @@
1
- const allowedTypes = new Set(["border", "color", "dimension", "duration"]);
2
- const aliasPattern = /^\{([a-z0-9]+(?:[.-][a-z0-9]+)*)\}$/;
3
- const tokenNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4
- const unsafePathSegments = new Set(["__proto__", "constructor", "prototype"]);
5
-
6
- function isObject(value) {
7
- return value !== null && typeof value === "object" && !Array.isArray(value);
8
- }
9
-
10
- function finding(code, tokenPath, message) {
11
- return { code, message, path: tokenPath || "<root>" };
12
- }
13
-
14
- function validatePathSegment(segment, tokenPath) {
15
- if (unsafePathSegments.has(segment)) return [finding("token_path_segment_unsafe", tokenPath, `path segment ${segment} is unsafe for the pinned adapter`)];
16
- if (!tokenNamePattern.test(segment)) return [finding("token_name_invalid", tokenPath, "token names must use lowercase kebab-case")];
17
- return [];
18
- }
19
-
20
- function exactKeys(value, allowed, tokenPath) {
21
- return Object.keys(value)
22
- .filter((key) => !allowed.has(key))
23
- .map((key) => finding("token_value_property_unknown", tokenPath, `unsupported value property ${key}`));
24
- }
25
-
26
- function validateDimension(value, tokenPath) {
27
- if (!isObject(value)) return [finding("token_value_invalid", tokenPath, "dimension must be an object")];
28
- const failures = exactKeys(value, new Set(["unit", "value"]), tokenPath);
29
- if (typeof value.value !== "number" || !Number.isFinite(value.value)) failures.push(finding("token_value_invalid", tokenPath, "dimension value must be finite"));
30
- if (!["px", "rem"].includes(value.unit)) failures.push(finding("token_unit_unsupported", tokenPath, "dimension unit must be px or rem"));
31
- return failures;
32
- }
33
-
34
- function validateDuration(value, tokenPath) {
35
- if (!isObject(value)) return [finding("token_value_invalid", tokenPath, "duration must be an object")];
36
- const failures = exactKeys(value, new Set(["unit", "value"]), tokenPath);
37
- if (typeof value.value !== "number" || !Number.isFinite(value.value) || value.value < 0) failures.push(finding("token_value_invalid", tokenPath, "duration value must be non-negative and finite"));
38
- if (!["ms", "s"].includes(value.unit)) failures.push(finding("token_unit_unsupported", tokenPath, "duration unit must be ms or s"));
39
- return failures;
40
- }
41
-
42
- function validateColor(value, tokenPath) {
43
- if (!isObject(value)) return [finding("token_value_invalid", tokenPath, "color must be an object")];
44
- const failures = exactKeys(value, new Set(["alpha", "colorSpace", "components"]), tokenPath);
45
- const validComponents = Array.isArray(value.components) && value.components.length === 3 && value.components.every((component) => typeof component === "number" && component >= 0 && component <= 1);
46
- if (value.colorSpace !== "srgb" || !validComponents || (value.alpha !== undefined && (typeof value.alpha !== "number" || value.alpha < 0 || value.alpha > 1))) {
47
- failures.push(finding("token_value_invalid", tokenPath, "color must use bounded sRGB components and optional alpha"));
48
- }
49
- return failures;
50
- }
51
-
52
- function parseReference(value, expectedType, tokenPath) {
53
- const match = typeof value === "string" ? aliasPattern.exec(value) : null;
54
- if (!match) return { failures: [finding("alias_whole_required", tokenPath, "references must be whole-token curly aliases")], references: [] };
55
- return { failures: [], references: [{ expectedType, source: tokenPath, target: match[1] }] };
56
- }
57
-
58
- function validateBorder(value, tokenPath) {
59
- if (!isObject(value)) return { failures: [finding("token_value_invalid", tokenPath, "border must be an object")], references: [] };
60
- const failures = exactKeys(value, new Set(["color", "style", "width"]), tokenPath);
61
- const references = [];
62
- if (value.style !== "solid") failures.push(finding("token_value_invalid", tokenPath, "only the tested solid border style is supported"));
63
- if (typeof value.color === "string") {
64
- const result = parseReference(value.color, "color", tokenPath);
65
- failures.push(...result.failures);
66
- references.push(...result.references);
67
- } else failures.push(...validateColor(value.color, tokenPath));
68
- if (typeof value.width === "string") {
69
- const result = parseReference(value.width, "dimension", tokenPath);
70
- failures.push(...result.failures);
71
- references.push(...result.references);
72
- } else failures.push(...validateDimension(value.width, tokenPath));
73
- return { failures, references };
74
- }
75
-
76
- function validateDirectValue(token) {
77
- if (typeof token.value === "string") return parseReference(token.value, token.type, token.path);
78
- if (token.type === "border") return validateBorder(token.value, token.path);
79
- if (token.type === "color") return { failures: validateColor(token.value, token.path), references: [] };
80
- if (token.type === "dimension") return { failures: validateDimension(token.value, token.path), references: [] };
81
- return { failures: validateDuration(token.value, token.path), references: [] };
82
- }
83
-
84
- export function validatePortableTokens(document) {
85
- const failures = [];
86
- const references = [];
87
- const tokens = [];
88
- if (!isObject(document)) return { failures: [finding("token_document_object_required", "", "token document must be an object")], tokens };
89
-
90
- function walk(node, segments, inheritedType) {
91
- if (!isObject(node)) {
92
- failures.push(finding("token_node_object_required", segments.join("."), "groups and tokens must be objects"));
93
- return;
94
- }
95
- const tokenPath = segments.join(".");
96
- if (Object.hasOwn(node, "$description") && typeof node.$description !== "string") failures.push(finding("token_description_invalid", tokenPath, "$description must be a string"));
97
- const serialized = JSON.stringify(node);
98
- if (/"(?:resolver|modifiers?|themes?)"\s*:/.test(serialized)) failures.push(finding("token_resolver_forbidden", tokenPath, "resolvers, modifiers, and themes are outside the portable subset"));
99
- const reserved = Object.keys(node).filter((key) => key.startsWith("$"));
100
- if (reserved.includes("$extends")) failures.push(finding("token_extends_forbidden", tokenPath, "$extends is outside the portable subset"));
101
- if (reserved.includes("$ref")) failures.push(finding("token_json_pointer_forbidden", tokenPath, "JSON Pointer references are outside the portable subset"));
102
- for (const key of reserved.filter((key) => !["$description", "$type", "$value", "$extends", "$ref"].includes(key))) {
103
- failures.push(finding("token_unknown_reserved", tokenPath, `unsupported reserved field ${key}`));
104
- }
105
- const declaredType = node.$type;
106
- if (declaredType !== undefined && !allowedTypes.has(declaredType)) failures.push(finding("token_type_unsupported", tokenPath, `unsupported token type ${String(declaredType)}`));
107
- const effectiveType = allowedTypes.has(declaredType) ? declaredType : inheritedType;
108
- if (Object.hasOwn(node, "$value")) {
109
- const allowed = new Set(["$description", "$type", "$value"]);
110
- for (const key of Object.keys(node).filter((key) => !allowed.has(key) && !key.startsWith("$"))) failures.push(finding("token_property_unknown", tokenPath, `token contains child property ${key}`));
111
- if (!effectiveType) failures.push(finding("token_type_required", tokenPath, "token requires an allowed explicit or inherited type"));
112
- else tokens.push({ path: tokenPath, type: effectiveType, value: node.$value });
113
- return;
114
- }
115
- const children = Object.entries(node).filter(([key]) => !key.startsWith("$"));
116
- if (children.length === 0) failures.push(finding("token_group_empty", tokenPath, "group must contain at least one token"));
117
- for (const [key, child] of children) {
118
- failures.push(...validatePathSegment(key, [...segments, key].join(".")));
119
- walk(child, [...segments, key], effectiveType);
120
- }
121
- }
122
-
123
- for (const [key, node] of Object.entries(document)) {
124
- if (key.startsWith("$")) failures.push(finding("token_unknown_reserved", "", `root field ${key} is unsupported`));
125
- else {
126
- failures.push(...validatePathSegment(key, key));
127
- walk(node, [key], undefined);
128
- }
129
- }
130
- for (const token of tokens) {
131
- const result = validateDirectValue(token);
132
- failures.push(...result.failures);
133
- references.push(...result.references);
134
- }
135
- const byPath = new Map(tokens.map((token) => [token.path, token]));
136
- for (const reference of references) {
137
- const target = byPath.get(reference.target);
138
- if (!target) failures.push(finding("alias_dangling", reference.source, `alias target ${reference.target} does not exist`));
139
- else if (target.type !== reference.expectedType) failures.push(finding("alias_type_mismatch", reference.source, `alias target ${reference.target} has type ${target.type}`));
140
- }
141
- const edges = new Map(references.filter((reference) => byPath.has(reference.target)).map((reference) => [reference.source, reference.target]));
142
- for (const start of edges.keys()) {
143
- const seen = new Set();
144
- let cursor = start;
145
- while (edges.has(cursor)) {
146
- if (seen.has(cursor)) {
147
- failures.push(finding("alias_cycle", start, "alias graph contains a cycle"));
148
- break;
149
- }
150
- seen.add(cursor);
151
- cursor = edges.get(cursor);
152
- }
153
- }
154
- const uniqueFailures = [...new Map(failures.map((item) => [`${item.code}:${item.path}:${item.message}`, item])).values()];
155
- return { failures: uniqueFailures, tokens: tokens.sort((left, right) => left.path.localeCompare(right.path)) };
156
- }
@@ -1,170 +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 { canonicalSourceManifest } from "./page-evidence-contract.mjs";
9
-
10
- const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11
- const workspaceRoot = process.cwd();
12
- const temporaryRoot = path.join(workspaceRoot, ".tmp");
13
- const syntheticBranch = "main";
14
- const syntheticRepository = "example/stylegallery-page-evidence-ci";
15
- const sourcePaths = [
16
- "playwright.config.mjs",
17
- "tests/consumer-conformance.spec.mjs",
18
- "tests/fixtures/consumer-conformance-scenarios.mjs",
19
- "tests/helpers/render-consumer-conformance.mjs",
20
- ];
21
-
22
- function trustedTemporaryRoot() {
23
- const resolvedWorkspace = path.resolve(workspaceRoot);
24
- if (fs.realpathSync(resolvedWorkspace) !== resolvedWorkspace) throw new Error("workspace root must not be a filesystem redirect");
25
- if (!fs.existsSync(temporaryRoot)) fs.mkdirSync(temporaryRoot);
26
- const stat = fs.lstatSync(temporaryRoot);
27
- if (!stat.isDirectory() || stat.isSymbolicLink() || fs.realpathSync(temporaryRoot) !== temporaryRoot) throw new Error(".tmp must be a real directory inside the workspace");
28
- return temporaryRoot;
29
- }
30
-
31
- function containedPath({ base, candidate, label }) {
32
- if (base === temporaryRoot) trustedTemporaryRoot();
33
- const resolved = path.resolve(workspaceRoot, candidate);
34
- const relative = path.relative(base, resolved);
35
- if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(label + " must be inside " + base);
36
- return resolved;
37
- }
38
-
39
- function run({ args, command, cwd }) {
40
- const child = spawnSync(command, args, { cwd, encoding: "utf8" });
41
- if (child.status !== 0) throw new Error(command + " " + args.join(" ") + " failed (" + child.status + ")\n" + child.stdout + child.stderr);
42
- return child.stdout.trim();
43
- }
44
-
45
- function writeJson(file, value) {
46
- fs.mkdirSync(path.dirname(file), { recursive: true });
47
- fs.writeFileSync(file, JSON.stringify(value, null, 2) + "\n", { flag: "wx" });
48
- }
49
-
50
- function parseArguments() {
51
- const command = process.argv[2];
52
- const values = {};
53
- for (let index = 3; index < process.argv.length; index += 2) {
54
- const name = process.argv[index];
55
- const value = process.argv[index + 1];
56
- if (!name?.startsWith("--") || !value || value.startsWith("--")) throw new Error("invalid argument pair at " + (name ?? "<missing>"));
57
- values[name.slice(2)] = value;
58
- }
59
- if (!["prepare", "verify-session", "package", "cleanup"].includes(command)) throw new Error("first argument must be prepare, verify-session, package, or cleanup");
60
- return { command, values };
61
- }
62
-
63
- function prepare(values) {
64
- const consumerRoot = containedPath({ base: temporaryRoot, candidate: values["consumer-root"], label: "--consumer-root" });
65
- const recordFile = containedPath({ base: consumerRoot, candidate: values.record, label: "--record" });
66
- const artifactRoot = containedPath({ base: consumerRoot, candidate: values["artifact-root"], label: "--artifact-root" });
67
- if (fs.existsSync(consumerRoot)) throw new Error("--consumer-root must not already exist");
68
- if (!fs.statSync(path.join(repositoryRoot, "node_modules")).isDirectory()) throw new Error("node_modules must exist before preparing the consumer");
69
-
70
- fs.mkdirSync(consumerRoot, { recursive: true });
71
- run({ command: "git", args: ["init", "-b", syntheticBranch], cwd: consumerRoot });
72
- run({ command: "git", args: ["config", "user.email", "page-evidence-ci@example.invalid"], cwd: consumerRoot });
73
- run({ command: "git", args: ["config", "user.name", "StyleGallery page-evidence CI"], cwd: consumerRoot });
74
- run({ command: "git", args: ["remote", "add", "origin", `https://github.com/${syntheticRepository}.git`], cwd: consumerRoot });
75
-
76
- for (const reference of sourcePaths) {
77
- const destination = path.join(consumerRoot, reference);
78
- fs.mkdirSync(path.dirname(destination), { recursive: true });
79
- fs.copyFileSync(path.join(repositoryRoot, reference), destination, fs.constants.COPYFILE_EXCL);
80
- }
81
- fs.symlinkSync(path.join(repositoryRoot, "node_modules"), path.join(consumerRoot, "node_modules"), "dir");
82
- run({ command: "git", args: ["add", ...sourcePaths], cwd: consumerRoot });
83
- run({ command: "git", args: ["commit", "-m", "test: prepare consumer page evidence"], cwd: consumerRoot });
84
-
85
- const revision = run({ command: "git", args: ["rev-parse", "HEAD"], cwd: consumerRoot });
86
- const sourceFailures = [];
87
- const source = canonicalSourceManifest(consumerRoot, sourcePaths, sourceFailures);
88
- if (!source || sourceFailures.length > 0) throw new Error("source manifest failed: " + JSON.stringify(sourceFailures));
89
- const runId = process.env.GITHUB_RUN_ID ? "github-" + process.env.GITHUB_RUN_ID : "local-" + revision.slice(0, 12);
90
- const sessionId = "page-evidence-" + crypto.randomUUID();
91
- const scenarioId = "responsive-layout";
92
- const reference = (file) => path.relative(consumerRoot, file).split(path.sep).join("/");
93
-
94
- writeJson(recordFile, {
95
- claim_boundary: "Synthetic CI evidence for the copied consumer-conformance probe; not product certification.",
96
- consumer: {
97
- relevant_sources: sourcePaths,
98
- repository: syntheticRepository,
99
- revision,
100
- },
101
- id: "stylegallery-consumer-page-evidence-ci",
102
- page_evidence: {
103
- manifest: reference(path.join(artifactRoot, "page-evidence-manifest.json")),
104
- status: "applicable",
105
- },
106
- record_kind: "consumer_migration_conformance",
107
- scenarios: [{
108
- assertions: ["The selected responsive layout case passes its browser assertions."],
109
- argv: ["npx", "playwright", "test", "tests/consumer-conformance.spec.mjs", "--project=chromium", "--grep", "state-w1024-focus"],
110
- evidence_method: "browser",
111
- exit_code: 0,
112
- id: scenarioId,
113
- observable_actions: ["Render and capture state-w1024-focus in Chromium."],
114
- result_artifact: reference(path.join(artifactRoot, "runner", scenarioId + ".json")),
115
- run_id: runId,
116
- session_id: sessionId,
117
- source_digest: source.sha256,
118
- }],
119
- schema_version: "1.0",
120
- });
121
- return { artifactRoot, consumerRoot, recordFile, revision, sourceSha256: source.sha256 };
122
- }
123
-
124
- function verifySession(values) {
125
- const consumerRoot = containedPath({ base: temporaryRoot, candidate: values["consumer-root"], label: "--consumer-root" });
126
- const artifactRoot = containedPath({ base: consumerRoot, candidate: values["artifact-root"], label: "--artifact-root" });
127
- const receiptFile = path.join(artifactRoot, "page-evidence-session.json");
128
- const receipt = JSON.parse(fs.readFileSync(receiptFile, "utf8"));
129
- const expected = {
130
- branch: syntheticBranch,
131
- repository: syntheticRepository,
132
- revision: run({ command: "git", args: ["rev-parse", "HEAD"], cwd: consumerRoot }),
133
- };
134
- for (const [field, value] of Object.entries(expected)) {
135
- if (receipt[field] !== value) throw new Error(`page-evidence receipt ${field} must be ${value}, received ${receipt[field]}`);
136
- }
137
- return { ...expected, receiptFile };
138
- }
139
-
140
- function packagePacket(values) {
141
- const consumerRoot = containedPath({ base: temporaryRoot, candidate: values["consumer-root"], label: "--consumer-root" });
142
- const artifactRoot = containedPath({ base: consumerRoot, candidate: values["artifact-root"], label: "--artifact-root" });
143
- const output = containedPath({ base: temporaryRoot, candidate: values.output, label: "--output" });
144
- const outputRelativeToConsumer = path.relative(consumerRoot, output);
145
- if (!outputRelativeToConsumer.startsWith("..") && !path.isAbsolute(outputRelativeToConsumer)) throw new Error("--output must be outside --consumer-root");
146
- if (!fs.statSync(artifactRoot).isDirectory()) throw new Error("--artifact-root must be a directory");
147
- if (fs.existsSync(output)) throw new Error("--output must not already exist");
148
- fs.mkdirSync(path.dirname(output), { recursive: true });
149
- fs.cpSync(artifactRoot, output, { errorOnExist: true, force: false, recursive: true });
150
- return { output };
151
- }
152
-
153
- function cleanup(values) {
154
- const consumerRoot = containedPath({ base: temporaryRoot, candidate: values["consumer-root"], label: "--consumer-root" });
155
- if (fs.existsSync(consumerRoot)) {
156
- const stat = fs.lstatSync(consumerRoot);
157
- if (!stat.isDirectory() || stat.isSymbolicLink() || fs.realpathSync(consumerRoot) !== consumerRoot) throw new Error("--consumer-root must be a real directory before cleanup");
158
- }
159
- fs.rmSync(consumerRoot, { force: true, recursive: true });
160
- return { consumerRoot };
161
- }
162
-
163
- try {
164
- const { command, values } = parseArguments();
165
- const result = command === "prepare" ? prepare(values) : command === "verify-session" ? verifySession(values) : command === "package" ? packagePacket(values) : cleanup(values);
166
- process.stdout.write(JSON.stringify({ command, ok: true, ...result }, null, 2) + "\n");
167
- } catch (error) {
168
- process.stderr.write((error instanceof Error ? error.message : String(error)) + "\n");
169
- process.exitCode = 1;
170
- }
@@ -1,98 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import { BASELINE_ENVIRONMENT, BASELINE_REFERENCE, finding, sha256, validateCalibration } from "./baseline-contract.mjs";
6
- import { parseCalibrationRun } from "./calibration-raw-contract.mjs";
7
-
8
- const options = { artifactName: "", checkoutSha: "", executionRepository: "", headSha: "", input: "", json: false, output: "", repository: "", runAttempt: "", runId: "", sha: "", workflow: "" };
9
- const valueOptions = new Map([
10
- ["--artifact-name", "artifactName"], ["--checkout-sha", "checkoutSha"], ["--execution-repository", "executionRepository"], ["--head-sha", "headSha"],
11
- ["--input", "input"], ["--output", "output"], ["--repository", "repository"],
12
- ["--run-attempt", "runAttempt"], ["--run-id", "runId"], ["--sha", "sha"], ["--workflow", "workflow"],
13
- ]);
14
- const failures = [];
15
- for (let index = 2; index < process.argv.length; index += 1) {
16
- const argument = process.argv[index];
17
- if (argument === "--json") options.json = true;
18
- else if (valueOptions.has(argument)) {
19
- const value = process.argv[index + 1];
20
- if (!value) failures.push(finding("argument_value_required", "<cli>", `${argument} requires a value`));
21
- else {
22
- options[valueOptions.get(argument)] = value;
23
- index += 1;
24
- }
25
- } else failures.push(finding("argument_unknown", "<cli>", `unsupported argument ${argument}`));
26
- }
27
-
28
- if (!options.input || !options.output) failures.push(finding("calibration_path_required", "<cli>", "--input and --output are required"));
29
- const inputIsDirectory = Boolean(options.input && fs.existsSync(options.input) && !fs.lstatSync(options.input).isSymbolicLink() && fs.lstatSync(options.input).isDirectory());
30
- if (options.input && fs.existsSync(options.input) && !inputIsDirectory) failures.push(finding("calibration_input_invalid", options.input, "input must be a regular raw calibration directory, not a file or symlink"));
31
- const expectedOutput = options.input ? path.join(path.resolve(options.input), "calibration.json") : "";
32
- if (options.output && path.resolve(options.output) !== expectedOutput) failures.push(finding("calibration_output_route", options.output, "output must be calibration.json directly inside the raw input directory"));
33
- if (inputIsDirectory && options.output && path.resolve(options.output) === expectedOutput && fs.existsSync(options.output)) {
34
- if (fs.lstatSync(options.output).isDirectory()) failures.push(finding("calibration_output_invalid", options.output, "output path must not be a directory"));
35
- else fs.rmSync(options.output);
36
- }
37
- const runs = [];
38
- if (options.input && fs.existsSync(options.input)) {
39
- if (inputIsDirectory) {
40
- const directories = fs.readdirSync(options.input, { withFileTypes: true });
41
- const expectedDirectories = Array.from({ length: 20 }, (_, index) => `run-${String(index + 1).padStart(2, "0")}`);
42
- for (const directory of directories.filter((entry) => !entry.isDirectory() || !expectedDirectories.includes(entry.name))) {
43
- failures.push(finding("calibration_run_directory_unknown", path.join(options.input, directory.name), "raw input may contain only run-01 through run-20 directories"));
44
- }
45
- for (let run = 1; run <= 20; run += 1) {
46
- const name = expectedDirectories[run - 1];
47
- const root = path.join(options.input, name);
48
- if (!fs.existsSync(root)) failures.push(finding("calibration_run_directory_missing", root, `${name} is required`));
49
- else if (fs.lstatSync(root).isSymbolicLink() || !fs.lstatSync(root).isDirectory() || !fs.realpathSync(root).startsWith(`${fs.realpathSync(options.input)}${path.sep}`)) failures.push(finding("calibration_run_directory_invalid", root, `${name} must be a contained regular directory, not a file or symlink`));
50
- else {
51
- const parsed = parseCalibrationRun(root, run, failures);
52
- if (parsed) runs.push(parsed);
53
- }
54
- }
55
- }
56
- } else if (options.input) failures.push(finding("calibration_input_missing", options.input, "input artifact directory does not exist"));
57
-
58
- const rawEvidence = failures.length === 0 ? sha256(Array.from({ length: 20 }, (_, index) => path.join(options.input, `run-${String(index + 1).padStart(2, "0")}`))
59
- .flatMap((root) => fs.readdirSync(root).sort().map((name) => {
60
- const file = path.join(root, name);
61
- return `${path.relative(options.input, file)}:${sha256(fs.readFileSync(file))}`;
62
- })).join("\n")) : "";
63
-
64
- const record = {
65
- baseline_owner_approval: "pending",
66
- committed_ci: {
67
- artifact_name: options.artifactName,
68
- checkout_sha: options.checkoutSha,
69
- execution_repository: options.executionRepository,
70
- external_verification: null,
71
- head_sha: options.headSha,
72
- raw_evidence_sha256: rawEvidence,
73
- repository: options.repository,
74
- run_attempt: options.runAttempt,
75
- run_id: options.runId,
76
- sha: options.sha,
77
- workflow: options.workflow,
78
- },
79
- environment: BASELINE_ENVIRONMENT,
80
- required_runs: 20,
81
- reference: BASELINE_REFERENCE,
82
- runs,
83
- schema_version: "1.0",
84
- status: "awaiting_external_verification",
85
- };
86
- failures.push(...validateCalibration(record, options.output || "<output>"));
87
- if (failures.length === 0) {
88
- const temporaryOutput = `${options.output}.tmp`;
89
- fs.writeFileSync(temporaryOutput, `${JSON.stringify(record, null, 2)}\n`);
90
- fs.renameSync(temporaryOutput, options.output);
91
- }
92
- const diffs = runs.map((run) => run.screenshot_diff_pixels).sort((left, right) => left - right);
93
- const stats = diffs.length === 20 ? { max: diffs[19], mean: diffs.reduce((sum, value) => sum + value, 0) / 20, min: diffs[0], p95: diffs[18] } : null;
94
- const uniqueFailures = [...new Map(failures.map((failure) => [`${failure.code}:${failure.path}:${failure.message}`, failure])).values()];
95
- const result = { failures: uniqueFailures, ok: uniqueFailures.length === 0, runs: runs.length, stats, status: uniqueFailures.length === 0 ? "awaiting_external_verification" : "incomplete", warnings: [] };
96
- if (options.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
97
- else if (!result.ok) process.stderr.write(`${result.failures.map((failure) => `${failure.code}: ${failure.message}`).join("\n")}\n`);
98
- if (!result.ok) process.exitCode = 1;