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,139 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import crypto from "node:crypto";
4
- import fs from "node:fs";
5
- import path from "node:path";
6
- import { fileURLToPath } from "node:url";
7
- import {
8
- addSchemaFindings,
9
- canonicalSourceManifest,
10
- compilePageEvidenceSchemas,
11
- dirtyRelevantSources,
12
- finding,
13
- gitIdentity,
14
- metadata,
15
- normalizeReference,
16
- PAGE_EVIDENCE_RECEIPT,
17
- readJsonFile,
18
- resolveContained,
19
- } from "./page-evidence-contract.mjs";
20
-
21
- const styleGalleryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
22
- const options = { artifactRoot: undefined, json: false, record: undefined, root: undefined };
23
- const failures = [];
24
-
25
- for (let index = 2; index < process.argv.length; index += 1) {
26
- const argument = process.argv[index];
27
- if (argument === "--json") options.json = true;
28
- else if (["--artifact-root", "--record", "--root"].includes(argument)) {
29
- const value = process.argv[index + 1];
30
- if (!value || value.startsWith("--")) failures.push(finding("argument_value_required", `${argument} requires a value`, "<cli>"));
31
- else {
32
- if (argument === "--artifact-root") options.artifactRoot = path.resolve(process.cwd(), value);
33
- if (argument === "--record") options.record = value;
34
- if (argument === "--root") options.root = path.resolve(process.cwd(), value);
35
- index += 1;
36
- }
37
- } else failures.push(finding("argument_unknown", `unsupported argument ${argument}`, "<cli>"));
38
- }
39
-
40
- for (const [name, value] of [["--root", options.root], ["--record", options.record], ["--artifact-root", options.artifactRoot]]) {
41
- if (!value) failures.push(finding("argument_value_required", `${name} is required`, "<cli>"));
42
- }
43
-
44
- let schemas;
45
- try { schemas = compilePageEvidenceSchemas(path.join(styleGalleryRoot, "consumer-reference/schema")); }
46
- catch (error) { failures.push(finding("page_evidence_schema_invalid", error instanceof Error ? error.message : String(error), "consumer-reference/schema")); }
47
-
48
- let artifactReference;
49
- if (options.root && options.artifactRoot) {
50
- artifactReference = normalizeReference(options.root, options.artifactRoot);
51
- if (!artifactReference) failures.push(finding("page_evidence_artifact_escape", "artifact root must be inside the consumer repository", options.artifactRoot));
52
- else {
53
- const resolved = resolveContained({ allowMissing: true, expectedType: "directory", prefix: "page_evidence_artifact", reference: artifactReference, root: options.root }, failures);
54
- if (resolved && fs.existsSync(resolved.file) && fs.readdirSync(resolved.file).length > 0) failures.push(finding("page_evidence_session_replay", "artifact root must be empty before session start", artifactReference));
55
- }
56
- }
57
-
58
- let record;
59
- let recordReference;
60
- if (options.root && options.record) {
61
- recordReference = normalizeReference(options.root, options.record);
62
- if (!recordReference) failures.push(finding("page_evidence_conformance_escape", "conformance record must be inside the consumer repository", options.record));
63
- else record = readJsonFile({ prefix: "page_evidence_conformance", reference: recordReference, root: options.root }, failures);
64
- }
65
-
66
- const consumer = record?.value?.consumer;
67
- const sourcePaths = consumer?.relevant_sources;
68
- const browserScenarios = Array.isArray(record?.value?.scenarios)
69
- ? record.value.scenarios.filter((scenario) => scenario?.evidence_method === "browser")
70
- : [];
71
- if (!consumer || typeof consumer.repository !== "string" || !/^[a-f0-9]{40}$/.test(consumer.revision ?? "") || !Array.isArray(sourcePaths)) {
72
- failures.push(finding("page_evidence_conformance_invalid", "conformance record must declare consumer repository, revision, and relevant_sources", recordReference ?? "<conformance-record>"));
73
- }
74
- if (browserScenarios.length === 0) failures.push(finding("page_evidence_intent_missing", "conformance record must declare at least one browser scenario", recordReference ?? "<conformance-record>"));
75
-
76
- const scenarioIds = browserScenarios.map((scenario) => scenario.id);
77
- const sessionIds = new Set(browserScenarios.map((scenario) => scenario.session_id));
78
- const runIds = new Set(browserScenarios.map((scenario) => scenario.run_id));
79
- if (new Set(scenarioIds).size !== scenarioIds.length || scenarioIds.some((id) => typeof id !== "string")) failures.push(finding("page_evidence_intent_invalid", "browser scenario IDs must be unique strings", recordReference ?? "<conformance-record>"));
80
- if (sessionIds.size !== 1 || runIds.size !== 1 || [...sessionIds, ...runIds].some((identity) => typeof identity !== "string" || identity.length === 0)) {
81
- failures.push(finding("page_evidence_intent_identity_mismatch", "all page scenarios must use one nonempty session and run identity", recordReference ?? "<conformance-record>"));
82
- }
83
-
84
- const git = options.root ? gitIdentity(options.root, failures) : undefined;
85
- if (git && consumer) {
86
- if (git.revision !== consumer.revision || (process.env.GITHUB_SHA && process.env.GITHUB_SHA !== git.revision)) failures.push(finding("page_evidence_revision_mismatch", "conformance revision must equal checked-out Git HEAD", recordReference));
87
- if (git.repository && git.repository !== consumer.repository) failures.push(finding("page_evidence_repository_mismatch", "conformance repository differs from the Git remote identity", recordReference));
88
- }
89
-
90
- let source;
91
- if (options.root && Array.isArray(sourcePaths)) {
92
- source = canonicalSourceManifest(options.root, sourcePaths, failures);
93
- if (source) {
94
- const dirty = dirtyRelevantSources(options.root, sourcePaths);
95
- if (dirty.length > 0) failures.push(finding("page_evidence_source_dirty", `relevant sources must be clean tracked files: ${dirty.join(", ")}`, options.root));
96
- if (browserScenarios.some((scenario) => scenario.source_digest !== source.sha256)) failures.push(finding("page_evidence_source_mismatch", "browser scenario source_digest must equal the relevant-source aggregate", recordReference));
97
- }
98
- }
99
-
100
- const receipt = record && source && git && sessionIds.size === 1 && runIds.size === 1 ? {
101
- attempt: Number(process.env.GITHUB_RUN_ATTEMPT ?? 1),
102
- branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || git.branch,
103
- conformance_record: metadata(record.bytes, "application/json", recordReference),
104
- environment: {
105
- kind: "browser_evidence",
106
- node: process.version,
107
- platform: `${process.platform}/${process.arch}`,
108
- producer: "stylegallery_page_evidence",
109
- },
110
- intended_scenario_ids: scenarioIds,
111
- nonce: crypto.randomBytes(32).toString("hex"),
112
- record_kind: "page_evidence_session",
113
- repository: consumer.repository,
114
- revision: consumer.revision,
115
- run_id: [...runIds][0],
116
- schema_version: "1.0",
117
- session_id: [...sessionIds][0],
118
- source,
119
- started_at: new Date().toISOString(),
120
- } : undefined;
121
- if (receipt && schemas) addSchemaFindings(schemas.session, receipt, PAGE_EVIDENCE_RECEIPT, "page_evidence_session_schema_invalid", failures);
122
-
123
- const receiptFile = options.artifactRoot ? path.join(options.artifactRoot, PAGE_EVIDENCE_RECEIPT) : undefined;
124
- if (failures.length === 0 && receiptFile) {
125
- fs.mkdirSync(options.artifactRoot, { recursive: true });
126
- fs.writeFileSync(receiptFile, `${JSON.stringify(receipt, null, 2)}\n`, { flag: "wx" });
127
- }
128
- const result = {
129
- failures,
130
- intendedScenarios: receipt?.intended_scenario_ids.length ?? 0,
131
- ok: failures.length === 0,
132
- receipt: receiptFile,
133
- sessionId: receipt?.session_id,
134
- sourceSha256: receipt?.source.sha256,
135
- };
136
- if (options.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
137
- else if (result.ok) process.stdout.write(`created page evidence session ${result.sessionId}\n`);
138
- else process.stderr.write(`${failures.map((issue) => `${issue.code}: ${issue.path}: ${issue.message}`).join("\n")}\n`);
139
- if (!result.ok) process.exitCode = 1;
@@ -1,40 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { isOmoDependency, markdownLinkDestinations } from "./markdown-structure.mjs";
4
-
5
- const generatedReference = /^design-engineering\/reference-profiles\/governed-local\/(?:editorial|terminal)\/generated\/(?:state-matrix|keyboard-matrix|evidence-coverage)\.md$/;
6
- const ignoredDirectories = new Set([".git", ".omo", "node_modules"]);
7
-
8
- function walkMarkdown(dir) {
9
- if (!fs.existsSync(dir)) return [];
10
- return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
11
- if (ignoredDirectories.has(entry.name)) return [];
12
- const target = path.join(dir, entry.name);
13
- if (entry.isDirectory()) return walkMarkdown(target);
14
- return entry.isFile() && entry.name.endsWith(".md") ? [target] : [];
15
- });
16
- }
17
-
18
- function undeclaredDomainDocumentFailures(root, domains) {
19
- return domains.flatMap((domain) => {
20
- const declared = new Set([`${domain.slug}/index.md`, ...domain.leaves.map(([leaf]) => leaf), ...(domain.referenceDocuments ?? [])]);
21
- return walkMarkdown(path.join(root, domain.slug)).flatMap((absolute) => {
22
- const relative = path.relative(root, absolute);
23
- if (generatedReference.test(relative) || declared.has(relative)) return [];
24
- return [`${relative}: undeclared governed domain document`];
25
- });
26
- });
27
- }
28
-
29
- function omoDependencyFailures(root) {
30
- return walkMarkdown(root).flatMap((absolute) => {
31
- const relative = path.relative(root, absolute);
32
- const content = fs.readFileSync(absolute, "utf8");
33
- if (markdownLinkDestinations(content).some(isOmoDependency)) return [`${relative}: tracked document must not depend on .omo`];
34
- return [];
35
- });
36
- }
37
-
38
- export function collectDomainBoundaryFailures(root, domains) {
39
- return [...undeclaredDomainDocumentFailures(root, domains), ...omoDependencyFailures(root)];
40
- }
@@ -1,141 +0,0 @@
1
- import path from "node:path";
2
- import { isOmoDependency, markdownLinkDestinations, stripFencedCodeBlocks, structuralMarkdown } from "./markdown-structure.mjs";
3
-
4
- export function createDomainValidationChecks({
5
- domains,
6
- failures,
7
- read,
8
- referenceDocuments,
9
- repository,
10
- revision,
11
- revisionPattern,
12
- requiredLeafSections,
13
- sourceOverrides,
14
- }) {
15
- function parseFrontmatter(relative, content) {
16
- const match = content.match(/^---\n([\s\S]*?)\n---\n/);
17
- if (!match) {
18
- failures.push(`${relative}: missing frontmatter`);
19
- return {};
20
- }
21
- const metadata = {};
22
- for (const rawLine of match[1].split("\n")) {
23
- if (!rawLine.trim()) continue;
24
- const separator = rawLine.indexOf(":");
25
- if (separator === -1) {
26
- failures.push(`${relative}: malformed frontmatter line`);
27
- continue;
28
- }
29
- const key = rawLine.slice(0, separator).trim();
30
- const value = rawLine.slice(separator + 1).trim().replace(/^["']|["']$/g, "");
31
- if (Object.hasOwn(metadata, key)) failures.push(`${relative}: duplicate frontmatter key ${key}`);
32
- metadata[key] = value;
33
- }
34
- return metadata;
35
- }
36
-
37
- function checkIndex(domain) {
38
- const relative = `${domain.slug}/index.md`;
39
- const content = structuralMarkdown(read(relative));
40
- if (!content) return;
41
- if (!content.includes("## Scope Boundary")) failures.push(`${relative}: missing Scope Boundary section`);
42
- if (!/^In scope:\s*\S/m.test(content)) failures.push(`${relative}: missing In scope declaration`);
43
- if (!/^Out of scope:\s*\S/m.test(content)) failures.push(`${relative}: missing Out of scope declaration`);
44
- if (!/^Parent: \[[^\]]+\]\([^)]+\)/m.test(content)) failures.push(`${relative}: missing Parent navigation link`);
45
- if (!/^Next: \[[^\]]+\]\([^)]+\)/m.test(content)) failures.push(`${relative}: missing Next navigation link`);
46
- for (const leaf of domain.leaves) {
47
- const target = path.posix.relative(domain.slug, leaf.path);
48
- if (!content.match(new RegExp(`\\[[^\\]]+\\]\\(${target.replaceAll(".", "\\.")}\\)`))) {
49
- failures.push(`${relative}: missing leaf route ${target}`);
50
- }
51
- }
52
- if (domain.referenceDocuments?.length > 0 && !content.includes("[Reference Profiles](reference-profiles/index.md)")) {
53
- failures.push(`${relative}: missing reference profile route`);
54
- }
55
- }
56
-
57
- function checkLeaf(domain, leaf, titles) {
58
- const { path: relative, provenance, sourcePath: expectedSourcePath } = leaf;
59
- const content = read(relative);
60
- if (!content) return;
61
- const metadata = parseFrontmatter(relative, content);
62
- const requiredFields = ["type", "title", "description", "domain", "lifecycle"];
63
- if (provenance === "external") requiredFields.push("source_repository", "source_path", "source_revision");
64
- if (provenance === "local") requiredFields.push("provenance_kind");
65
- for (const field of requiredFields) {
66
- if (!metadata[field]) failures.push(`${relative}: missing ${field}`);
67
- }
68
- const knownDomain = domains.some((candidate) => candidate.slug === metadata.domain);
69
- if (metadata.domain && !knownDomain) failures.push(`${relative}: unknown domain ${metadata.domain}`);
70
- else if (metadata.domain && metadata.domain !== domain.slug) failures.push(`${relative}: domain ${metadata.domain} does not match ${domain.slug}`);
71
- if (metadata.lifecycle && metadata.lifecycle !== "experimental") {
72
- const kind = provenance === "local" ? "local leaf" : provenance === "external" ? "external adaptation" : "domain leaf";
73
- failures.push(`${relative}: ${kind} lifecycle must be experimental`);
74
- }
75
- if (provenance === "external") {
76
- const expectedSource = sourceOverrides[relative] ?? { repository, revision };
77
- if (metadata.source_repository && metadata.source_repository !== expectedSource.repository) failures.push(`${relative}: unexpected source_repository ${metadata.source_repository}`);
78
- if (metadata.source_path && metadata.source_path !== expectedSourcePath) failures.push(`${relative}: unexpected source_path ${metadata.source_path}`);
79
- if (metadata.source_revision && !revisionPattern.test(metadata.source_revision)) {
80
- failures.push(`${relative}: source_revision must be a full 40-character lowercase Git SHA`);
81
- } else if (metadata.source_revision && metadata.source_revision !== expectedSource.revision) {
82
- failures.push(`${relative}: unexpected source_revision ${metadata.source_revision}`);
83
- }
84
- } else if (provenance === "local") {
85
- if (metadata.provenance_kind && metadata.provenance_kind !== "local") failures.push(`${relative}: local leaf provenance_kind must be local`);
86
- for (const field of ["source_repository", "source_path", "source_revision"]) {
87
- if (Object.hasOwn(metadata, field)) failures.push(`${relative}: local leaf must not declare ${field}`);
88
- }
89
- } else {
90
- for (const field of ["source_repository", "source_path", "source_revision"]) {
91
- if (Object.hasOwn(metadata, field)) failures.push(`${relative}: locally authored leaf must omit ${field}`);
92
- }
93
- }
94
- if (metadata.title) {
95
- if (titles.has(metadata.title)) failures.push(`${relative}: duplicate title ${metadata.title}`);
96
- titles.add(metadata.title);
97
- }
98
- const body = structuralMarkdown(content);
99
- for (const section of requiredLeafSections) {
100
- if (!body.includes(`## ${section}`)) failures.push(`${relative}: missing ${section} section`);
101
- }
102
- if (!/^Parent: \[[^\]]+\]\([^)]+\)/m.test(body)) failures.push(`${relative}: missing Parent navigation link`);
103
- if (!/^Next: \[[^\]]+\]\([^)]+\)/m.test(body)) failures.push(`${relative}: missing Next navigation link`);
104
- if (markdownLinkDestinations(content).some(isOmoDependency)) failures.push(`${relative}: tracked document must not depend on .omo`);
105
- if (metadata.lifecycle === "experimental" && /canonical universal policy/i.test(body)) {
106
- failures.push(`${relative}: experimental document claims canonical authority`);
107
- }
108
- }
109
-
110
- function checkReferenceDocuments() {
111
- const index = stripFencedCodeBlocks(read(referenceDocuments[0]));
112
- if (!/Domain classification:\s*design-engineering\./i.test(index)) {
113
- failures.push(`${referenceDocuments[0]}: reference profiles must remain in the Design Engineering domain`);
114
- }
115
- if (!index.includes("[Governed Local Profiles](governed-local/index.md)")) {
116
- failures.push(`${referenceDocuments[0]}: missing governed-local route`);
117
- }
118
- const external = stripFencedCodeBlocks(read(referenceDocuments[2]));
119
- if (!external.includes("Synthetic validator coverage only") || !external.includes("no durable adopter record")) {
120
- failures.push(`${referenceDocuments[1]}: external adaptation must remain documentation-only`);
121
- }
122
- }
123
-
124
- function checkPromotionBoundary() {
125
- const relative = "DOMAINS.md";
126
- const content = stripFencedCodeBlocks(read(relative));
127
- const required = [
128
- "### Consumer Reference Promotion",
129
- "applies only to consumer-local → shared-experimental invariant eligibility",
130
- "Editorial and terminal are related examples in one fixture set",
131
- "Shared stable has no numeric adoption threshold",
132
- "Normative correctness may waive adoption count only",
133
- "never silently relabeled experimental",
134
- "Promotion records are JSON-only",
135
- "zero adopter attestations",
136
- ];
137
- for (const clause of required) if (!content.includes(clause)) failures.push(`${relative}: missing promotion boundary ${clause}`);
138
- }
139
-
140
- return { checkIndex, checkLeaf, checkReferenceDocuments, checkPromotionBoundary };
141
- }
@@ -1,191 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { artifactMetadata } from "./artifact-metadata.mjs";
4
- import { sameJson, withinSession } from "./capture-session-contract.mjs";
5
- import { parseStrictJson } from "./strict-json.mjs";
6
- import { visualExpectationFor } from "./visual-expectation-contract.mjs";
7
-
8
- function finding(code, file, message) {
9
- return { code, message, path: file };
10
- }
11
-
12
- function isInside(base, target) {
13
- const relative = path.relative(base, target);
14
- return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
15
- }
16
-
17
- function isSafeArtifactPath(reference) {
18
- return typeof reference === "string"
19
- && reference.length > 0
20
- && !path.posix.isAbsolute(reference)
21
- && !path.win32.isAbsolute(reference)
22
- && !reference.includes("\\")
23
- && !reference.includes("?")
24
- && !reference.includes("#")
25
- && !/^[A-Za-z][A-Za-z\d+.-]*:/.test(reference)
26
- && path.posix.normalize(reference) === reference
27
- && !reference.split("/").some((segment) => segment === "." || segment === "..");
28
- }
29
-
30
- function validateIntegrity(pass, bytes, evidenceFile, failures) {
31
- const actual = artifactMetadata(bytes, pass.artifact.media_type);
32
- if (actual.sha256 !== pass.artifact.sha256 || actual.byte_length !== pass.artifact.byte_length) failures.push(finding("evidence_artifact_integrity", evidenceFile, `${pass.id} hash or byte length does not match`));
33
- if (pass.channel === "visual") {
34
- if (!actual.width || !actual.height) failures.push(finding("evidence_png_invalid", evidenceFile, `${pass.id} is not a valid PNG with IHDR dimensions`));
35
- else if (actual.width !== pass.artifact.width || actual.height !== pass.artifact.height) failures.push(finding("evidence_png_dimensions_mismatch", evidenceFile, `${pass.id} PNG dimensions do not match`));
36
- }
37
- return actual;
38
- }
39
-
40
- function observedDomValue(document, key) {
41
- if (key === "active") return String(document.active);
42
- if (key === "role") return document.role;
43
- return document.attributes?.[key];
44
- }
45
-
46
- function validateDom(document, pass, scenario, fixture, profileId, evidenceFile, failures) {
47
- if (document.schema_version !== "1.0" || document.channel !== "dom" || document.profile_id !== profileId || document.scenario_id !== pass.scenario_id || document.semantic_mode !== scenario.semantic_mode) {
48
- failures.push(finding("evidence_dom_identity_mismatch", evidenceFile, `${pass.id} DOM identity does not match its pass`));
49
- }
50
- if (document.activation_key !== fixture.activation_key) failures.push(finding("evidence_runtime_identity_mismatch", evidenceFile, `${pass.id} activation key differs from the canonical fixture`));
51
- for (const [key, expected] of Object.entries(scenario.expected.dom)) if (observedDomValue(document, key) !== expected) failures.push(finding("evidence_dom_content_mismatch", evidenceFile, `${pass.id} DOM ${key} differs from canonical state`));
52
- const visual = JSON.stringify([...(document.visual_states ?? [])].sort());
53
- if (visual !== JSON.stringify([...scenario.expected.visual].sort())) failures.push(finding("evidence_dom_content_mismatch", evidenceFile, `${pass.id} visual states differ from canonical state`));
54
- const expectedActivations = scenario.expected.activation === "allowed" ? 1 : 0;
55
- if (document.activation_count !== expectedActivations) failures.push(finding("evidence_dom_content_mismatch", evidenceFile, `${pass.id} activation result differs from canonical state`));
56
- }
57
-
58
- function validateAx(document, pass, scenario, fixture, profileId, evidenceFile, failures) {
59
- if (document.schema_version !== "1.0" || document.channel !== "ax" || document.profile_id !== profileId || document.scenario_id !== pass.scenario_id || document.semantic_mode !== scenario.semantic_mode || document.name !== fixture.label) {
60
- failures.push(finding("evidence_ax_identity_mismatch", evidenceFile, `${pass.id} AX identity does not match its pass`));
61
- }
62
- if (document.role !== scenario.expected.ax.role) failures.push(finding("evidence_ax_content_mismatch", evidenceFile, `${pass.id} AX role differs from canonical state`));
63
- for (const [key, expected] of Object.entries(scenario.expected.ax)) {
64
- const actual = key === "role" ? document.role : document.properties?.[key];
65
- if (actual !== expected) failures.push(finding("evidence_ax_content_mismatch", evidenceFile, `${pass.id} AX ${key} differs from canonical state`));
66
- }
67
- }
68
-
69
- function validateVisual(document, pass, scenario, profileId, actual, visualEnvironments, evidenceFile, failures) {
70
- if (document.schema_version !== "1.0" || document.channel !== "visual" || document.profile_id !== profileId || document.scenario_id !== pass.scenario_id || document.semantic_mode !== scenario.semantic_mode) {
71
- failures.push(finding("evidence_visual_identity_mismatch", evidenceFile, `${pass.id} visual identity does not match its pass`));
72
- }
73
- if (document.source_sha256 !== pass.session?.source?.sha256) failures.push(finding("capture_source_mismatch", evidenceFile, `${pass.id} visual source digest differs from its capture session`));
74
- const expectedImage = { path: path.posix.basename(pass.artifact.path), ...actual };
75
- if (!sameJson(document.image, expectedImage)) failures.push(finding("evidence_visual_image_mismatch", evidenceFile, `${pass.id} visual metadata does not match its PNG`));
76
- try {
77
- const expectation = visualExpectationFor(scenario, pass.environment, visualEnvironments);
78
- if (actual.sha256 !== expectation.sha256 || actual.width !== expectation.width || actual.height !== expectation.height) failures.push(finding("evidence_visual_expectation_mismatch", evidenceFile, `${pass.id} PNG differs from its pre-capture approved visual expectation`));
79
- } catch (error) {
80
- failures.push(finding("evidence_visual_expectation_invalid", evidenceFile, error instanceof Error ? error.message : String(error)));
81
- }
82
- }
83
-
84
- function parseArtifactJson(bytes, pass, scenario, fixture, profileId, evidenceFile, failures, schemas, session) {
85
- let document;
86
- try {
87
- document = parseStrictJson(bytes.toString("utf8"));
88
- } catch (error) {
89
- failures.push(finding("evidence_json_invalid", evidenceFile, `${pass.id} is not structured JSON: ${error instanceof Error ? error.message : String(error)}`));
90
- return undefined;
91
- }
92
- const validate = pass.channel === "dom" ? schemas.dom : pass.channel === "ax" ? schemas.ax : undefined;
93
- if (validate && !validate(document)) {
94
- const code = pass.channel === "dom" ? "evidence_dom_schema_invalid" : "evidence_ax_schema_invalid";
95
- for (const error of validate.errors ?? []) failures.push(finding(code, evidenceFile, `${pass.id} ${error.instancePath || "/"} ${error.message}`));
96
- return undefined;
97
- }
98
- if (!sameJson(document.capture_session, pass.session)) failures.push(finding("capture_session_mismatch", evidenceFile, `${pass.id} artifact session differs from its pass`));
99
- if (document.captured_at !== pass.recorded_at) failures.push(finding("evidence_recorded_at_mismatch", evidenceFile, `${pass.id} recorded_at is not derived from artifact content`));
100
- if (session && !withinSession(document.captured_at, session.started_at, session.completed_at)) failures.push(finding("capture_session_time_outside", evidenceFile, `${pass.id} was not captured within its session`));
101
- if (pass.channel === "dom") validateDom(document, pass, scenario, fixture, profileId, evidenceFile, failures);
102
- else if (pass.channel === "ax") validateAx(document, pass, scenario, fixture, profileId, evidenceFile, failures);
103
- else failures.push(finding("evidence_channel_content_unknown", evidenceFile, `${pass.id} has no structured content contract`));
104
- return document;
105
- }
106
-
107
- function readArtifact(pass, artifactRoot, evidenceFile, failures) {
108
- const reference = pass.artifact?.path;
109
- if (!isSafeArtifactPath(reference)) {
110
- failures.push(finding("evidence_artifact_escape", evidenceFile, `${pass.id} artifact path is unsafe`));
111
- return undefined;
112
- }
113
- const root = path.resolve(artifactRoot);
114
- const target = path.resolve(root, reference);
115
- if (!isInside(root, target) || !fs.existsSync(target)) {
116
- failures.push(finding("evidence_artifact_missing", evidenceFile, `${pass.id} artifact is missing`));
117
- return undefined;
118
- }
119
- const metadata = fs.lstatSync(target);
120
- if (metadata.isSymbolicLink()) {
121
- failures.push(finding("evidence_artifact_symlink", evidenceFile, `${pass.id} artifact must not be a symlink`));
122
- return undefined;
123
- }
124
- if (!metadata.isFile()) {
125
- failures.push(finding("evidence_artifact_type_invalid", evidenceFile, `${pass.id} artifact must be a regular file`));
126
- return undefined;
127
- }
128
- const realRoot = fs.realpathSync(root);
129
- const realTarget = fs.realpathSync(target);
130
- if (!isInside(realRoot, realTarget) || realTarget !== path.join(realRoot, reference)) {
131
- failures.push(finding("evidence_artifact_redirect", evidenceFile, `${pass.id} artifact resolves through a redirect`));
132
- return undefined;
133
- }
134
- return fs.readFileSync(realTarget);
135
- }
136
-
137
- function parseVisualMetadata(pass, artifactRoot, scenario, profileId, actual, visualEnvironments, evidenceFile, failures, schemas, session) {
138
- const reference = pass.artifact.path.replace(/\.png$/, ".visual.json");
139
- const bytes = readArtifact({ artifact: { path: reference }, id: `${pass.id}-metadata` }, artifactRoot, evidenceFile, failures);
140
- if (!bytes) return undefined;
141
- let document;
142
- try {
143
- document = parseStrictJson(bytes.toString("utf8"));
144
- } catch (error) {
145
- failures.push(finding("evidence_visual_json_invalid", evidenceFile, `${pass.id} visual metadata is not structured JSON: ${error instanceof Error ? error.message : String(error)}`));
146
- return undefined;
147
- }
148
- if (!schemas.visual(document)) {
149
- for (const error of schemas.visual.errors ?? []) failures.push(finding("evidence_visual_schema_invalid", evidenceFile, `${pass.id} ${error.instancePath || "/"} ${error.message}`));
150
- return undefined;
151
- }
152
- if (!sameJson(document.capture_session, pass.session)) failures.push(finding("capture_session_mismatch", evidenceFile, `${pass.id} visual metadata session differs from its pass`));
153
- if (document.captured_at !== pass.recorded_at) failures.push(finding("evidence_recorded_at_mismatch", evidenceFile, `${pass.id} recorded_at is not derived from visual metadata`));
154
- if (session && !withinSession(document.captured_at, session.started_at, session.completed_at)) failures.push(finding("capture_session_time_outside", evidenceFile, `${pass.id} visual metadata was not captured within its session`));
155
- validateVisual(document, pass, scenario, profileId, actual, visualEnvironments, evidenceFile, failures);
156
- return document;
157
- }
158
-
159
- export function validateEvidenceArtifacts({ artifactRoot, evidence, evidenceFile, fixture, profileId, schemas, session, states }) {
160
- const failures = [];
161
- const scenarios = new Map(states.scenarios.map((scenario) => [scenario.id, scenario]));
162
- const fixtures = new Map(fixture.scenarios.map((scenario) => [scenario.id, scenario]));
163
- const capturedTimes = new Map();
164
- for (const pass of evidence.passes ?? []) {
165
- const scenario = scenarios.get(pass.scenario_id);
166
- const runtimeFixture = fixtures.get(pass.scenario_id);
167
- if (!scenario || !runtimeFixture || !pass.artifact?.media_type) continue;
168
- const bytes = readArtifact(pass, artifactRoot, evidenceFile, failures);
169
- if (!bytes) continue;
170
- const actual = validateIntegrity(pass, bytes, evidenceFile, failures);
171
- if (pass.artifact.media_type === "application/json") {
172
- const document = parseArtifactJson(bytes, pass, scenario, runtimeFixture, profileId, evidenceFile, failures, schemas, session);
173
- if (document?.captured_at) {
174
- if (!capturedTimes.has(pass.scenario_id)) capturedTimes.set(pass.scenario_id, new Set());
175
- capturedTimes.get(pass.scenario_id).add(document.captured_at);
176
- }
177
- } else if (pass.channel === "visual") {
178
- const document = parseVisualMetadata(pass, artifactRoot, scenario, profileId, actual, states.visual_environments, evidenceFile, failures, schemas, session);
179
- if (document?.captured_at) {
180
- if (!capturedTimes.has(pass.scenario_id)) capturedTimes.set(pass.scenario_id, new Set());
181
- capturedTimes.get(pass.scenario_id).add(document.captured_at);
182
- }
183
- }
184
- }
185
- for (const [scenarioId, times] of capturedTimes) {
186
- if (times.size !== 1) failures.push(finding("capture_session_time_mismatch", evidenceFile, `${scenarioId} visual, DOM, and AX captured_at values differ`));
187
- const capturedAt = [...times][0];
188
- for (const pass of evidence.passes.filter((entry) => entry.scenario_id === scenarioId)) if (pass.recorded_at !== capturedAt) failures.push(finding("evidence_recorded_at_mismatch", evidenceFile, `${pass.id} recorded_at is not derived from its scenario artifacts`));
189
- }
190
- return failures;
191
- }