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,308 +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 { addDateTimeFormat, isRfc3339DateTime } from "./json-schema-formats.mjs";
8
- import { parseStrictJson } from "./strict-json.mjs";
9
- import { validateCalibration } from "./baseline-contract.mjs";
10
-
11
- const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
- const calibrationSchemaPath = path.join(repositoryRoot, "consumer-reference", "schema", "calibration-record.schema.json");
13
- const defaultPageSchemaPath = path.join(repositoryRoot, "consumer-reference", "schema", "page-evidence-manifest.schema.json");
14
- const PAGE_RECORD_KIND = "page_evidence_manifest";
15
- // This is an advisory warning window only. It never changes an explicit expiry or adds a TTL.
16
- const DEFAULT_EXPIRING_WINDOW_DAYS = 7;
17
-
18
- function failure(code, sourcePath, message, details = undefined) {
19
- return details === undefined ? { code, message, path: sourcePath } : { code, message, path: sourcePath, details };
20
- }
21
-
22
- function warning(code, sourcePath, status, thresholdDate, asOf, message) {
23
- return { asOf, code, message, source_path: sourcePath, status, threshold_date: thresholdDate };
24
- }
25
-
26
- function isRecord(value) {
27
- return value !== null && typeof value === "object" && !Array.isArray(value);
28
- }
29
-
30
- function parseDateTime(value) {
31
- return isRfc3339DateTime(value) ? Date.parse(value) : NaN;
32
- }
33
-
34
- function isInside(base, target) {
35
- const relative = path.relative(path.resolve(base), path.resolve(target));
36
- return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
37
- }
38
-
39
- function displayPath(file, root) {
40
- const absolute = path.resolve(file);
41
- if (isInside(root, absolute)) return path.relative(root, absolute).split(path.sep).join("/") || ".";
42
- return absolute;
43
- }
44
-
45
- function readRecord(file, root, failures) {
46
- const sourcePath = displayPath(file, root);
47
- let stat;
48
- try {
49
- stat = fs.lstatSync(file);
50
- } catch (error) {
51
- failures.push(failure("freshness_record_unreadable", sourcePath, `record cannot be read: ${error instanceof Error ? error.message : String(error)}`));
52
- return undefined;
53
- }
54
- if (!stat.isFile() || stat.isSymbolicLink()) {
55
- failures.push(failure("freshness_record_unreadable", sourcePath, "record must be a regular non-symlink file"));
56
- return undefined;
57
- }
58
- if (stat.size > 1024 * 1024) {
59
- failures.push(failure("freshness_record_unreadable", sourcePath, "record must not exceed 1 MiB"));
60
- return undefined;
61
- }
62
- try {
63
- return { sourcePath, value: parseStrictJson(fs.readFileSync(file, "utf8")) };
64
- } catch (error) {
65
- failures.push(failure("freshness_record_invalid", sourcePath, error instanceof Error ? error.message : String(error)));
66
- return undefined;
67
- }
68
- }
69
-
70
- function readSchema(schemaPath, sourcePath, failures) {
71
- let stat;
72
- try {
73
- stat = fs.lstatSync(schemaPath);
74
- } catch (error) {
75
- failures.push(failure("freshness_schema_missing", sourcePath, `schema cannot be read: ${error instanceof Error ? error.message : String(error)}`));
76
- return undefined;
77
- }
78
- if (!stat.isFile() || stat.isSymbolicLink()) {
79
- failures.push(failure("freshness_schema_invalid", sourcePath, "schema must be a regular non-symlink file"));
80
- return undefined;
81
- }
82
- try {
83
- return parseStrictJson(fs.readFileSync(schemaPath, "utf8"));
84
- } catch (error) {
85
- failures.push(failure("freshness_schema_invalid", sourcePath, error instanceof Error ? error.message : String(error)));
86
- return undefined;
87
- }
88
- }
89
-
90
- function compileSchema(schema, schemaPath, sourcePath, failures, { strict = true } = {}) {
91
- if (!isRecord(schema)) {
92
- failures.push(failure("freshness_schema_invalid", sourcePath, "schema must be a JSON object"));
93
- return undefined;
94
- }
95
- try {
96
- const ajv = new Ajv2020({ allErrors: true, strict });
97
- addDateTimeFormat(ajv);
98
- const schemaDirectory = path.dirname(defaultPageSchemaPath);
99
- if (path.resolve(schemaPath) === path.resolve(defaultPageSchemaPath) && fs.existsSync(schemaDirectory)) {
100
- for (const entry of fs.readdirSync(schemaDirectory, { withFileTypes: true }).filter((item) => item.isFile() && item.name.endsWith(".schema.json"))) {
101
- const dependencyPath = path.join(schemaDirectory, entry.name);
102
- if (path.resolve(dependencyPath) === path.resolve(schemaPath)) continue;
103
- try {
104
- const dependency = parseStrictJson(fs.readFileSync(dependencyPath, "utf8"));
105
- if (dependency?.$id) ajv.addSchema(dependency, dependency.$id);
106
- } catch {
107
- // Only schemas reachable from the selected page schema affect this compile.
108
- }
109
- }
110
- }
111
- if (schema.$id) ajv.addSchema(schema, schema.$id);
112
- else ajv.addSchema(schema, schemaPath);
113
- return ajv.getSchema(schema.$id ?? schemaPath) ?? ajv.compile(schema);
114
- } catch (error) {
115
- failures.push(failure("freshness_schema_invalid", sourcePath, error instanceof Error ? error.message : String(error)));
116
- return undefined;
117
- }
118
- }
119
-
120
- function schemaFailures(validate, value, sourcePath) {
121
- if (validate(value)) return [];
122
- return (validate.errors ?? []).map((error) => failure(
123
- "freshness_record_invalid",
124
- sourcePath,
125
- `${error.instancePath || "/"} ${error.message}`,
126
- ));
127
- }
128
-
129
- function validateCalibrationRecord(value, sourcePath, failures) {
130
- const schema = readSchema(calibrationSchemaPath, sourcePath, failures);
131
- const validate = schema === undefined ? undefined : compileSchema(schema, calibrationSchemaPath, sourcePath, failures, { strict: false });
132
- if (validate) failures.push(...schemaFailures(validate, value, sourcePath));
133
- for (const finding of validateCalibration(value, sourcePath)) {
134
- failures.push(failure("freshness_record_invalid", sourcePath, finding.message, { code: finding.code }));
135
- }
136
- if (!isRecord(value)) return undefined;
137
- const external = value.committed_ci?.external_verification;
138
- if (external === null || external === undefined) return { thresholdDate: null, verifiedAt: null };
139
- if (!isRecord(external) || !isRecord(external.artifact)) {
140
- failures.push(failure("freshness_record_invalid", sourcePath, "calibration external verification must contain artifact metadata"));
141
- return undefined;
142
- }
143
- const thresholdDate = external.artifact.expires_at;
144
- const verifiedAt = external.verified_at;
145
- if (!Number.isFinite(parseDateTime(thresholdDate)) || !Number.isFinite(parseDateTime(verifiedAt))) {
146
- failures.push(failure("freshness_record_invalid", sourcePath, "calibration expiry and verification dates must be RFC 3339 date-time values"));
147
- return undefined;
148
- }
149
- return { thresholdDate, verifiedAt };
150
- }
151
-
152
- function validatePageRecord(value, sourcePath, pageSchemaPath, failures) {
153
- const schema = readSchema(pageSchemaPath, sourcePath, failures);
154
- if (schema === undefined) return undefined;
155
- const validate = compileSchema(schema, pageSchemaPath, sourcePath, failures, { strict: true });
156
- if (validate) failures.push(...schemaFailures(validate, value, sourcePath));
157
- if (!isRecord(value) || value.record_kind !== PAGE_RECORD_KIND) return undefined;
158
- const thresholdDate = value.review_by;
159
- if (!Number.isFinite(parseDateTime(thresholdDate))) {
160
- failures.push(failure("freshness_record_invalid", sourcePath, "page evidence review_by must be an RFC 3339 date-time value"));
161
- return undefined;
162
- }
163
- return { thresholdDate, verifiedAt: null };
164
- }
165
-
166
- function classify(entry, asOfMs, windowMs) {
167
- if (entry.thresholdDate === null) return "fresh";
168
- const thresholdMs = Date.parse(entry.thresholdDate);
169
- if (entry.kind === PAGE_RECORD_KIND) {
170
- if (asOfMs >= thresholdMs) return "review_due";
171
- return thresholdMs - asOfMs <= windowMs ? "expiring" : "fresh";
172
- }
173
- if (asOfMs >= thresholdMs) return "expired";
174
- return thresholdMs - asOfMs <= windowMs ? "expiring" : "fresh";
175
- }
176
-
177
- function isBlockingStatus(status) {
178
- return status === "expired" || status === "review_due";
179
- }
180
-
181
- function auditOne({ file, asOf, asOfMs, mode, root, pageSchemaPath, expiringWindowMs }) {
182
- const failures = [];
183
- const sourcePath = displayPath(file, root);
184
- const loaded = readRecord(file, root, failures);
185
- if (!loaded) return { failures, warnings: [], record: null };
186
- const { value } = loaded;
187
- if (!isRecord(value)) {
188
- failures.push(failure("freshness_record_invalid", sourcePath, "record must be a JSON object"));
189
- return { failures, warnings: [], record: null };
190
- }
191
- let kind;
192
- let dates;
193
- if (value.record_kind === PAGE_RECORD_KIND) {
194
- kind = PAGE_RECORD_KIND;
195
- dates = validatePageRecord(value, sourcePath, pageSchemaPath, failures);
196
- } else if (value.committed_ci !== undefined || value.required_runs !== undefined || value.runs !== undefined) {
197
- kind = "calibration_record";
198
- dates = validateCalibrationRecord(value, sourcePath, failures);
199
- } else {
200
- failures.push(failure("freshness_record_invalid", sourcePath, "record_kind is not a supported evidence record"));
201
- return { failures, warnings: [], record: null };
202
- }
203
- if (dates === undefined || failures.length > 0) return { failures, warnings: [], record: null };
204
- const entry = { asOf, kind, source_path: sourcePath, threshold_date: dates.thresholdDate, verified_at: dates.verifiedAt };
205
- const status = classify({ kind, thresholdDate: dates.thresholdDate }, asOfMs, expiringWindowMs);
206
- const result = { ...entry, status };
207
- const warnings = [];
208
- if (status !== "fresh") {
209
- const code = status === "expiring" ? "evidence_expiring" : status === "expired" ? "evidence_expired" : "evidence_review_due";
210
- const message = status === "expiring"
211
- ? `evidence expiry is within the advisory window (${dates.thresholdDate})`
212
- : status === "expired"
213
- ? `evidence expired at ${dates.thresholdDate}`
214
- : `evidence review is due at ${dates.thresholdDate}`;
215
- warnings.push(warning(code, sourcePath, status, dates.thresholdDate, asOf, message));
216
- if (mode === "blocking" && isBlockingStatus(status)) failures.push(failure(code, sourcePath, message, { asOf, threshold_date: dates.thresholdDate }));
217
- }
218
- return { failures, record: result, warnings };
219
- }
220
-
221
- export function parseArguments(argv = process.argv.slice(2)) {
222
- const options = { asOf: null, expiringWindowDays: DEFAULT_EXPIRING_WINDOW_DAYS, json: false, mode: "advisory", pageSchema: defaultPageSchemaPath, records: [] };
223
- const failures = [];
224
- for (let index = 0; index < argv.length; index += 1) {
225
- const argument = argv[index];
226
- if (argument === "--json") {
227
- options.json = true;
228
- continue;
229
- }
230
- if (["--record", "--as-of", "--mode", "--page-schema", "--expiring-within-days"].includes(argument)) {
231
- const value = argv[index + 1];
232
- if (!value || value.startsWith("--")) {
233
- failures.push(failure("argument_value_required", "<cli>", `${argument} requires a value`));
234
- continue;
235
- }
236
- index += 1;
237
- if (argument === "--record") options.records.push(value);
238
- else if (argument === "--as-of") options.asOf = value;
239
- else if (argument === "--mode") options.mode = value;
240
- else if (argument === "--page-schema") options.pageSchema = path.resolve(process.cwd(), value);
241
- else options.expiringWindowDays = Number(value);
242
- continue;
243
- }
244
- failures.push(failure("argument_unknown", "<cli>", `unsupported argument ${argument}`));
245
- }
246
- if (options.records.length === 0) failures.push(failure("record_required", "<cli>", "at least one --record is required"));
247
- if (options.asOf === null) failures.push(failure("as_of_required", "<cli>", "--as-of is required for deterministic freshness auditing"));
248
- else if (!Number.isFinite(parseDateTime(options.asOf))) failures.push(failure("as_of_invalid", "<cli>", "--as-of must be an RFC 3339 date-time value"));
249
- if (!["advisory", "blocking"].includes(options.mode)) failures.push(failure("mode_invalid", "<cli>", "--mode must be advisory or blocking"));
250
- if (!Number.isFinite(options.expiringWindowDays) || options.expiringWindowDays < 0 || options.expiringWindowDays > 3650) failures.push(failure("expiring_window_invalid", "<cli>", "--expiring-within-days must be a number from 0 through 3650"));
251
- return { failures, options };
252
- }
253
-
254
- export function auditEvidenceFreshness({ records, asOf, mode = "advisory", root = repositoryRoot, pageSchema = defaultPageSchemaPath, expiringWindowDays = DEFAULT_EXPIRING_WINDOW_DAYS }) {
255
- const asOfMs = parseDateTime(asOf);
256
- const failures = [];
257
- const warnings = [];
258
- const entries = [];
259
- if (!Number.isFinite(asOfMs)) failures.push(failure("as_of_invalid", "<cli>", "asOf must be an RFC 3339 date-time value"));
260
- for (const record of records) {
261
- const file = path.resolve(root, record);
262
- const result = auditOne({ asOf, asOfMs, expiringWindowMs: expiringWindowDays * 24 * 60 * 60 * 1000, file, mode, pageSchemaPath: pageSchema, root });
263
- failures.push(...result.failures);
264
- warnings.push(...result.warnings);
265
- if (result.record) entries.push(result.record);
266
- }
267
- const uniqueFailures = [...new Map(failures.map((item) => [`${item.code}:${item.path}:${item.message}`, item])).values()];
268
- const uniqueWarnings = [...new Map(warnings.map((item) => [`${item.code}:${item.source_path}:${item.threshold_date}`, item])).values()];
269
- return {
270
- asOf,
271
- expiring_window_days: expiringWindowDays,
272
- failures: uniqueFailures,
273
- mode,
274
- ok: uniqueFailures.length === 0,
275
- records: entries,
276
- warnings: uniqueWarnings,
277
- };
278
- }
279
-
280
- export function runCli(argv = process.argv.slice(2), root = repositoryRoot) {
281
- const parsed = parseArguments(argv);
282
- if (parsed.failures.length > 0) return { asOf: parsed.options.asOf, failures: parsed.failures, mode: parsed.options.mode, ok: false, records: [], warnings: [] };
283
- return auditEvidenceFreshness({
284
- asOf: parsed.options.asOf,
285
- expiringWindowDays: parsed.options.expiringWindowDays,
286
- mode: parsed.options.mode,
287
- pageSchema: parsed.options.pageSchema,
288
- records: parsed.options.records,
289
- root,
290
- });
291
- }
292
-
293
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
294
- try {
295
- const parsed = parseArguments();
296
- const report = parsed.failures.length > 0
297
- ? { asOf: parsed.options.asOf, failures: parsed.failures, mode: parsed.options.mode, ok: false, records: [], warnings: [] }
298
- : runCli();
299
- if (parsed.options.json || parsed.failures.length > 0) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
300
- else if (report.ok) process.stdout.write(`ok: audited ${report.records.length} evidence record(s)\n`);
301
- else process.stderr.write(`${report.failures.map((item) => `${item.code}: ${item.message}`).join("\n")}\n`);
302
- process.exitCode = report.ok ? 0 : 1;
303
- } catch (error) {
304
- const report = { failures: [failure("freshness_tool_error", "<tool>", error instanceof Error ? error.message : String(error))], ok: false, records: [], warnings: [] };
305
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
306
- process.exitCode = 1;
307
- }
308
- }
@@ -1,189 +0,0 @@
1
- import crypto from "node:crypto";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { isDeepStrictEqual } from "node:util";
5
- import { parseStrictJson } from "./strict-json.mjs";
6
-
7
- export const BASELINE_ENVIRONMENT = {
8
- architecture: "amd64",
9
- browser_revision: "1228",
10
- browser_version: "149.0.7827.55",
11
- container_image: "mcr.microsoft.com/playwright:v1.61.0-noble@sha256:57b65fdc9ceabe0ef613124c7bbe2babcf9362c4d85e382fe3b03604e84b428a",
12
- node: "22",
13
- os: "Ubuntu 24.04",
14
- platform: "linux/amd64",
15
- playwright: "1.61.0",
16
- viewport: { height: 768, width: 1024 },
17
- };
18
-
19
- export const BASELINE_REFERENCE = {
20
- baseline: { path: "tests/snapshots/consumer-reference-card-grid.png", sha256: "5528358e957a6115793155e501f62716f7db31dc1c86856d9e1234868d672837" },
21
- source: { path: "tests/helpers/render-consumer-reference.mjs", sha256: "88802a948909d5e40470be6b5481766ce2de498e59c053ac68af370b46e72ca9" },
22
- };
23
-
24
- export const BASELINE_METADATA_SHA256 = sha256(JSON.stringify({ environment: BASELINE_ENVIRONMENT, reference: BASELINE_REFERENCE }));
25
- export const CALIBRATION_CANONICAL_REPOSITORY = "changeroa/StyleGallery";
26
- export const CALIBRATION_EXECUTION_REPOSITORIES = Object.freeze(["ark-jo/StyleGallery", CALIBRATION_CANONICAL_REPOSITORY]);
27
- export const CALIBRATION_EXTERNAL_VERIFICATION = {
28
- artifact: {
29
- api_digest: "sha256:3f11a517b447e1b5a1da17d9ee66ba2dd947fc8a0a482c447556ef00652e6074",
30
- expires_at: "2026-07-27T15:01:36Z",
31
- id: "8283099324",
32
- name: "chromium-sentinel-calibration-29260372260-18229be570766d3b42f5600955120bfcba690b76",
33
- size_in_bytes: 70310,
34
- },
35
- repository_relationship: {
36
- execution_is_fork: true,
37
- parent: CALIBRATION_CANONICAL_REPOSITORY,
38
- source: CALIBRATION_CANONICAL_REPOSITORY,
39
- },
40
- source: "https://github.com/ark-jo/StyleGallery/actions/runs/29260372260",
41
- verified_at: "2026-07-13T15:04:41Z",
42
- };
43
-
44
- export function finding(code, file, message) {
45
- return { code, message, path: file };
46
- }
47
-
48
- export function sha256(value) {
49
- return crypto.createHash("sha256").update(value).digest("hex");
50
- }
51
-
52
- export function readJson(file, failures) {
53
- if (!fs.existsSync(file)) {
54
- failures.push(finding("baseline_file_missing", file, "required baseline record is missing"));
55
- return undefined;
56
- }
57
- try {
58
- const stat = fs.lstatSync(file);
59
- if (stat.isSymbolicLink() || !stat.isFile() || stat.size > 1024 * 1024) throw new Error("JSON input must be a regular file no larger than 1 MiB");
60
- return parseStrictJson(fs.readFileSync(file, "utf8"));
61
- } catch (error) {
62
- failures.push(finding("baseline_json_invalid", file, error instanceof Error ? error.message : String(error)));
63
- return undefined;
64
- }
65
- }
66
-
67
- function sameValue(left, right) {
68
- return isDeepStrictEqual(left, right);
69
- }
70
-
71
- function isRecord(value) {
72
- return value !== null && typeof value === "object" && !Array.isArray(value);
73
- }
74
-
75
- function rejectUnknown(value, allowed, code, file, failures) {
76
- if (!isRecord(value)) return;
77
- for (const key of Object.keys(value).filter((key) => !allowed.has(key))) {
78
- failures.push(finding(code, file, `unsupported property ${key}`));
79
- }
80
- }
81
-
82
- function isHash(value) {
83
- return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
84
- }
85
-
86
- export function validateEnvironment(environment, file) {
87
- return sameValue(environment, BASELINE_ENVIRONMENT)
88
- ? []
89
- : [finding("calibration_environment_mismatch", file, "environment must match the immutable linux/amd64 calibration image")];
90
- }
91
-
92
- export function validateCalibration(record, file) {
93
- const failures = [];
94
- if (!isRecord(record)) return [finding("calibration_record_invalid", file, "calibration record must be an object")];
95
- const allowed = new Set(["baseline_owner_approval", "committed_ci", "environment", "reference", "required_runs", "runs", "schema_version", "status"]);
96
- rejectUnknown(record, allowed, "calibration_property_unknown", file, failures);
97
- if (record.schema_version !== "1.0") failures.push(finding("calibration_schema_version", file, "schema_version must be 1.0"));
98
- if (record.baseline_owner_approval !== "pending") failures.push(finding("baseline_owner_approval_invalid", file, "baseline owner approval must remain pending"));
99
- if (record.required_runs !== 20) failures.push(finding("calibration_required_runs", file, "required_runs must equal 20"));
100
- failures.push(...validateEnvironment(record.environment, file));
101
- if (!sameValue(record.reference, BASELINE_REFERENCE)) failures.push(finding("calibration_reference_mismatch", file, "source and baseline hashes must match the proposed reference"));
102
- if (!Array.isArray(record.runs)) return [...failures, finding("calibration_runs_invalid", file, "runs must be an array")];
103
- if (record.status === "awaiting_committed_ci") {
104
- if (record.runs.length !== 0 || record.committed_ci !== null) failures.push(finding("calibration_pending_evidence_forbidden", file, "pending calibration cannot claim runs or committed CI"));
105
- return failures;
106
- }
107
- if (!["awaiting_external_verification", "completed"].includes(record.status)) return [...failures, finding("calibration_status_invalid", file, "status must be awaiting_committed_ci, awaiting_external_verification, or completed")];
108
- if (!isRecord(record.committed_ci)) failures.push(finding("calibration_committed_ci_missing", file, "calibrated record requires committed CI metadata"));
109
- else {
110
- rejectUnknown(record.committed_ci, new Set(["artifact_name", "checkout_sha", "execution_repository", "external_verification", "head_sha", "raw_evidence_sha256", "repository", "run_attempt", "run_id", "sha", "workflow"]), "calibration_committed_ci_property_unknown", file, failures);
111
- const expectedArtifact = `chromium-sentinel-calibration-${record.committed_ci.run_id}-${record.committed_ci.sha}`;
112
- if (!/^[0-9]+$/.test(record.committed_ci.run_id ?? "")
113
- || !/^[0-9]+$/.test(record.committed_ci.run_attempt ?? "")
114
- || !/^[0-9a-f]{40}$/.test(record.committed_ci.sha ?? "")
115
- || !/^[0-9a-f]{40}$/.test(record.committed_ci.head_sha ?? "")
116
- || record.committed_ci.checkout_sha !== record.committed_ci.sha
117
- || !CALIBRATION_EXECUTION_REPOSITORIES.includes(record.committed_ci.execution_repository)
118
- || !/^[0-9a-f]{64}$/.test(record.committed_ci.raw_evidence_sha256 ?? "")
119
- || record.committed_ci.repository !== CALIBRATION_CANONICAL_REPOSITORY
120
- || record.committed_ci.workflow !== ".github/workflows/validate.yml"
121
- || record.committed_ci.artifact_name !== expectedArtifact) {
122
- failures.push(finding("calibration_committed_ci_invalid", file, "canonical and execution repositories, workflow, run, attempt, SHAs, raw evidence, and artifact name must have the canonical identity"));
123
- }
124
- if (record.status === "awaiting_external_verification" && record.committed_ci.external_verification !== null) {
125
- failures.push(finding("calibration_external_verification_preclaim", file, "awaiting calibration must not claim uploaded artifact verification"));
126
- }
127
- if (record.status === "completed") {
128
- if (!("external_verification" in record.committed_ci) || record.committed_ci.external_verification === null) {
129
- failures.push(finding("calibration_external_verification_missing", file, "completed calibration requires independently checked run and artifact metadata"));
130
- } else {
131
- const external = record.committed_ci.external_verification;
132
- const expectedSource = `https://github.com/${record.committed_ci.execution_repository}/actions/runs/${record.committed_ci.run_id}`;
133
- if (!sameValue(external, CALIBRATION_EXTERNAL_VERIFICATION)
134
- || external?.source !== expectedSource
135
- || external?.artifact?.name !== record.committed_ci.artifact_name
136
- || external?.artifact?.api_digest === `sha256:${record.committed_ci.raw_evidence_sha256}`) {
137
- failures.push(finding("calibration_external_verification_invalid", file, "external verification must equal the independently checked GitHub Actions run, fork relationship, and artifact API identity"));
138
- }
139
- }
140
- }
141
- }
142
- if (record.runs.length !== 20) failures.push(finding("calibration_run_count", file, "calibrated record requires exactly 20 runs"));
143
- const runAllowed = new Set(["architecture", "ax_sha256", "completed", "dom_sha256", "metadata_sha256", "png_sha256", "run", "screenshot_diff_pixels"]);
144
- for (const run of record.runs) {
145
- if (!isRecord(run)) {
146
- failures.push(finding("calibration_run_invalid", file, "every run must be an object"));
147
- continue;
148
- }
149
- rejectUnknown(run, runAllowed, "calibration_run_property_unknown", file, failures);
150
- if (!Number.isInteger(run.run) || run.run < 1 || run.run > 20) failures.push(finding("calibration_run_number_invalid", file, "run must be an integer from 1 through 20"));
151
- if (run.completed !== true) failures.push(finding("calibration_run_incomplete", file, "every calibration run must complete"));
152
- if (run.architecture !== "amd64") failures.push(finding("calibration_architecture_variance", file, "every run must record architecture amd64"));
153
- if (![run.ax_sha256, run.dom_sha256, run.metadata_sha256, run.png_sha256].every(isHash)) failures.push(finding("calibration_hash_invalid", file, "every artifact hash must be 64 lowercase hexadecimal characters"));
154
- if (run.png_sha256 !== BASELINE_REFERENCE.baseline.sha256) failures.push(finding("calibration_baseline_hash_mismatch", file, "every completed PNG must equal the proposed baseline hash"));
155
- if (run.metadata_sha256 !== BASELINE_METADATA_SHA256) failures.push(finding("calibration_metadata_hash_mismatch", file, "every completed metadata hash must equal the canonical environment and reference hash"));
156
- if (run.screenshot_diff_pixels !== 0) failures.push(finding("calibration_diff_nonzero", file, "every screenshot diff must equal zero"));
157
- }
158
- const runNumbers = record.runs.map((run) => run?.run);
159
- if (new Set(runNumbers).size !== runNumbers.length) failures.push(finding("calibration_run_duplicate", file, "calibration run numbers must be unique"));
160
- if (!sameValue([...runNumbers].sort((left, right) => left - right), Array.from({ length: 20 }, (_, index) => index + 1))) failures.push(finding("calibration_run_set", file, "calibration runs must be the complete range 1 through 20"));
161
- const architectures = new Set(record.runs.map((run) => run?.architecture));
162
- if (architectures.size !== 1 || !architectures.has("amd64")) failures.push(finding("calibration_architecture_variance", file, "every run must record architecture amd64"));
163
- if (["png_sha256", "dom_sha256", "ax_sha256"].some((field) => new Set(record.runs.map((run) => run?.[field])).size !== 1)) failures.push(finding("calibration_hash_variance", file, "PNG, DOM, and AX hashes must each be identical across runs"));
164
- if (new Set(record.runs.map((run) => run?.metadata_sha256)).size !== 1) failures.push(finding("calibration_metadata_variance", file, "stable environment metadata must be identical across runs"));
165
- return failures;
166
- }
167
-
168
- export function validateManifest(manifest, file, root) {
169
- const failures = [];
170
- if (!isRecord(manifest)) return [finding("baseline_manifest_invalid", file, "baseline manifest must be an object")];
171
- const allowed = new Set(["architecture", "baseline", "baseline_owner_approval", "browser_revision", "browser_version", "calibration", "container_image", "node", "os", "platform", "playwright", "schema_version", "source", "status", "viewport"]);
172
- rejectUnknown(manifest, allowed, "baseline_manifest_property_unknown", file, failures);
173
- if (manifest.schema_version !== "1.0" || manifest.status !== "proposed") failures.push(finding("baseline_manifest_state", file, "manifest must be proposed schema 1.0"));
174
- if (manifest.baseline_owner_approval !== "pending") failures.push(finding("baseline_owner_approval_invalid", file, "baseline owner approval must remain pending"));
175
- for (const [key, value] of Object.entries(BASELINE_ENVIRONMENT)) if (!sameValue(manifest[key], value)) failures.push(finding("baseline_environment_mismatch", file, `${key} differs from the immutable environment`));
176
- rejectUnknown(manifest.calibration, new Set(["record", "required_runs", "workflow_artifact_name"]), "baseline_calibration_property_unknown", file, failures);
177
- if (!isRecord(manifest.calibration) || manifest.calibration.required_runs !== 20 || manifest.calibration.record !== "consumer-reference/baselines/calibration.json") failures.push(finding("baseline_calibration_route", file, "manifest must route to the 20-run calibration record"));
178
- if (manifest.calibration?.workflow_artifact_name !== "chromium-sentinel-calibration-${run_id}-${sha}") failures.push(finding("baseline_artifact_name", file, "workflow artifact name template is immutable"));
179
- for (const [kind, entry] of [["source", manifest.source], ["baseline", manifest.baseline]]) {
180
- rejectUnknown(entry, new Set(["path", "sha256"]), `baseline_${kind}_property_unknown`, file, failures);
181
- if (!sameValue(entry, BASELINE_REFERENCE[kind])) failures.push(finding(`baseline_${kind}_reference_mismatch`, file, `${kind} identity must equal the proposed reference`));
182
- const target = typeof entry?.path === "string" ? path.resolve(root, entry.path) : "";
183
- if (!isRecord(entry) || !isHash(entry.sha256)) failures.push(finding(`baseline_${kind}_invalid`, file, `${kind} requires an exact path and 64-character hash`));
184
- if (!target || !target.startsWith(`${root}${path.sep}`) || !fs.existsSync(target)) failures.push(finding(`baseline_${kind}_missing`, file, `${kind} path must resolve inside the repository`));
185
- else if (fs.lstatSync(target).isSymbolicLink() || !fs.lstatSync(target).isFile() || !fs.realpathSync(target).startsWith(`${fs.realpathSync(root)}${path.sep}`)) failures.push(finding(`baseline_${kind}_file_invalid`, file, `${kind} must be a regular repository file, not a directory or symlink`));
186
- else if (entry.sha256 !== sha256(fs.readFileSync(target))) failures.push(finding(`baseline_${kind}_hash_mismatch`, file, `${kind} hash differs from repository bytes`));
187
- }
188
- return failures;
189
- }
@@ -1,40 +0,0 @@
1
- import { BASELINE_METADATA_SHA256, BASELINE_REFERENCE } from "./baseline-contract.mjs";
2
-
3
- const executionRepositories = ["ark-jo/StyleGallery", "changeroa/StyleGallery"];
4
-
5
- export function baselineSchemaParity(schema, canonical, completedEvidence) {
6
- const committedSchema = schema.properties.committed_ci.oneOf.find((entry) => entry.type === "object");
7
- const externalSchema = committedSchema.properties.external_verification?.oneOf?.find((entry) => entry.type === "object") ?? {};
8
- const artifactSchema = externalSchema.properties?.artifact ?? {};
9
- const relationshipSchema = externalSchema.properties?.repository_relationship ?? {};
10
- const runSchema = schema.properties.runs.items;
11
- const runContains = schema.allOf[0].else.properties.runs.allOf;
12
- return schema.additionalProperties === false
13
- && committedSchema.additionalProperties === false
14
- && schema.properties.environment.additionalProperties === false
15
- && Object.entries(canonical.environment).every(([key, value]) => key === "viewport"
16
- ? JSON.stringify(schema.properties.environment.properties.viewport.properties) === JSON.stringify({ width: { const: value.width }, height: { const: value.height } })
17
- : schema.properties.environment.properties[key].const === value)
18
- && schema.properties.reference.additionalProperties === false
19
- && schema.properties.reference.properties.source.additionalProperties === false
20
- && schema.properties.reference.properties.source.properties.sha256.const === BASELINE_REFERENCE.source.sha256
21
- && schema.properties.reference.properties.baseline.properties.sha256.const === BASELINE_REFERENCE.baseline.sha256
22
- && runSchema.additionalProperties === false
23
- && runSchema.properties.png_sha256.const === BASELINE_REFERENCE.baseline.sha256
24
- && runSchema.properties.metadata_sha256.const === BASELINE_METADATA_SHA256
25
- && committedSchema.properties.repository.const === "changeroa/StyleGallery"
26
- && JSON.stringify(committedSchema.properties.execution_repository?.enum ?? []) === JSON.stringify(executionRepositories)
27
- && externalSchema.additionalProperties === false
28
- && artifactSchema.additionalProperties === false
29
- && relationshipSchema.additionalProperties === false
30
- && artifactSchema.properties?.api_digest?.const === completedEvidence.artifactApiDigest
31
- && artifactSchema.properties?.id?.const === completedEvidence.artifactId
32
- && artifactSchema.properties?.name?.const === completedEvidence.artifactName
33
- && artifactSchema.properties?.size_in_bytes?.const === completedEvidence.artifactSize
34
- && artifactSchema.properties?.expires_at?.const === completedEvidence.artifactExpiresAt
35
- && externalSchema.properties?.source?.const === completedEvidence.source
36
- && externalSchema.properties?.verified_at?.const === completedEvidence.verifiedAt
37
- && JSON.stringify(runContains.map((entry) => entry.contains.properties.run.const).sort((left, right) => left - right)) === JSON.stringify(Array.from({ length: 20 }, (_, index) => index + 1))
38
- && runContains.every((entry) => entry.minContains === 1 && entry.maxContains === 1)
39
- && JSON.stringify([...committedSchema.required].sort()) === JSON.stringify(["artifact_name", "checkout_sha", "execution_repository", "external_verification", "head_sha", "raw_evidence_sha256", "repository", "run_attempt", "run_id", "sha", "workflow"].sort());
40
- }