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