stylegallery 0.1.0
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.
- package/AGENTS.md +156 -0
- package/CATALOG.md +69 -0
- package/DOMAINS.md +65 -0
- package/GOVERNANCE.md +222 -0
- package/GUIDE.md +59 -0
- package/README.md +313 -0
- package/consumer-reference/adapters/style-dictionary.config.mjs +36 -0
- package/consumer-reference/agent-native/README.md +157 -0
- package/consumer-reference/agent-native/registry.json +640 -0
- package/consumer-reference/agent-native/schema/agent-native.schema.json +848 -0
- package/consumer-reference/agent-native/schema/epistemic.schema.json +14 -0
- package/consumer-reference/agent-native/schema/execution.schema.json +12 -0
- package/consumer-reference/agent-native/schema/identity.schema.json +10 -0
- package/consumer-reference/agent-native/schema/learning.schema.json +12 -0
- package/consumer-reference/agent-native/schema/manifest.schema.json +6 -0
- package/consumer-reference/agent-native/schema/operation.schema.json +12 -0
- package/consumer-reference/agent-native/schema/protocol-binding.schema.json +9 -0
- package/consumer-reference/agent-native/schema/retrieval.schema.json +10 -0
- package/consumer-reference/baselines/calibration.json +260 -0
- package/consumer-reference/baselines/manifest.json +21 -0
- package/consumer-reference/contract.md +83 -0
- package/consumer-reference/fixtures/consumer-conformance/e2e-fixture.mjs +282 -0
- package/consumer-reference/fixtures/consumer-conformance/manifest.json +102 -0
- package/consumer-reference/fixtures/consumer-conformance/runtime-proof.mjs +32 -0
- package/consumer-reference/fixtures/consumer-conformance/valid-migration-result.json +18 -0
- package/consumer-reference/fixtures/consumer-conformance/valid-migration.json +142 -0
- package/consumer-reference/fixtures/invalid-stable-ended-support.json +19 -0
- package/consumer-reference/fixtures/promotion/invalid-blocking-evidence.json +26 -0
- package/consumer-reference/fixtures/promotion/invalid-count-only.json +29 -0
- package/consumer-reference/fixtures/promotion/invalid-missing-owner-migration.json +25 -0
- package/consumer-reference/fixtures/promotion/invalid-normative-bypass-no-regression.json +25 -0
- package/consumer-reference/fixtures/promotion/invalid-related-counted-independent.json +45 -0
- package/consumer-reference/fixtures/promotion/invalid-stable-ended-support.json +26 -0
- package/consumer-reference/fixtures/promotion/manifest.json +15 -0
- package/consumer-reference/fixtures/promotion/valid-deferred-example.json +67 -0
- package/consumer-reference/fixtures/promotion/valid-normative-bypass.json +67 -0
- package/consumer-reference/fixtures/token-portability/invalid-cycle.json +7 -0
- package/consumer-reference/fixtures/token-portability/invalid-dangling-alias.json +6 -0
- package/consumer-reference/fixtures/token-portability/invalid-extends.json +4 -0
- package/consumer-reference/fixtures/token-portability/invalid-json-pointer.json +7 -0
- package/consumer-reference/fixtures/token-portability/invalid-resolver.json +9 -0
- package/consumer-reference/fixtures/token-portability/invalid-type-mismatch.json +10 -0
- package/consumer-reference/fixtures/token-portability/invalid-unknown-reserved.json +6 -0
- package/consumer-reference/fixtures/token-portability/invalid-untested-type.json +6 -0
- package/consumer-reference/fixtures/token-portability/valid-border.json +14 -0
- package/consumer-reference/fixtures/token-portability/valid-reference.json +32 -0
- package/consumer-reference/fixtures/token-portability/valid-whole-alias.json +7 -0
- package/consumer-reference/fixtures/valid-deprecated.json +21 -0
- package/consumer-reference/fixtures/valid-experimental.json +19 -0
- package/consumer-reference/generated/manifest.json +40 -0
- package/consumer-reference/generated/tokens.css +12 -0
- package/consumer-reference/index.md +32 -0
- package/consumer-reference/policies/shared-experimental.json +37 -0
- package/consumer-reference/schema/ax-evidence.schema.json +30 -0
- package/consumer-reference/schema/baseline-manifest.schema.json +55 -0
- package/consumer-reference/schema/calibration-record.schema.json +190 -0
- package/consumer-reference/schema/capture-session.schema.json +161 -0
- package/consumer-reference/schema/component-state.schema.json +150 -0
- package/consumer-reference/schema/consumer-conformance-record.schema.json +365 -0
- package/consumer-reference/schema/dom-evidence.schema.json +38 -0
- package/consumer-reference/schema/evidence-record.schema.json +81 -0
- package/consumer-reference/schema/fixture-manifest.schema.json +32 -0
- package/consumer-reference/schema/item.schema.json +188 -0
- package/consumer-reference/schema/page-evidence-manifest.schema.json +216 -0
- package/consumer-reference/schema/page-evidence-session.schema.json +119 -0
- package/consumer-reference/schema/portable-tokens.schema.json +104 -0
- package/consumer-reference/schema/promotion-rfc.schema.json +219 -0
- package/consumer-reference/schema/runtime-evidence-manifest.schema.json +33 -0
- package/consumer-reference/schema/visual-evidence.schema.json +31 -0
- package/design-engineering/consumer-migration-readiness.md +124 -0
- package/design-engineering/index.md +28 -0
- package/design-engineering/interface-craft.md +107 -0
- package/design-engineering/reference-profiles/external-adaptation/index.md +12 -0
- package/design-engineering/reference-profiles/governed-local/editorial/components/button.component.json +125 -0
- package/design-engineering/reference-profiles/governed-local/editorial/evidence/button.evidence.json +3504 -0
- package/design-engineering/reference-profiles/governed-local/editorial/fixtures/button.fixture.json +65 -0
- package/design-engineering/reference-profiles/governed-local/editorial/generated/evidence-coverage.md +23 -0
- package/design-engineering/reference-profiles/governed-local/editorial/generated/keyboard-matrix.md +19 -0
- package/design-engineering/reference-profiles/governed-local/editorial/generated/state-matrix.md +21 -0
- package/design-engineering/reference-profiles/governed-local/editorial/local-foundations.json +12 -0
- package/design-engineering/reference-profiles/governed-local/editorial/profile.json +43 -0
- package/design-engineering/reference-profiles/governed-local/editorial/states/button.states.json +195 -0
- package/design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json +17 -0
- package/design-engineering/reference-profiles/governed-local/index.md +26 -0
- package/design-engineering/reference-profiles/governed-local/terminal/components/button.component.json +125 -0
- package/design-engineering/reference-profiles/governed-local/terminal/evidence/button.evidence.json +3504 -0
- package/design-engineering/reference-profiles/governed-local/terminal/fixtures/button.fixture.json +65 -0
- package/design-engineering/reference-profiles/governed-local/terminal/generated/evidence-coverage.md +23 -0
- package/design-engineering/reference-profiles/governed-local/terminal/generated/keyboard-matrix.md +19 -0
- package/design-engineering/reference-profiles/governed-local/terminal/generated/state-matrix.md +21 -0
- package/design-engineering/reference-profiles/governed-local/terminal/local-foundations.json +12 -0
- package/design-engineering/reference-profiles/governed-local/terminal/profile.json +43 -0
- package/design-engineering/reference-profiles/governed-local/terminal/states/button.states.json +195 -0
- package/design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json +17 -0
- package/design-engineering/reference-profiles/index.md +19 -0
- package/game-ui/classification.md +82 -0
- package/game-ui/index.md +35 -0
- package/game-ui/reference-record.md +62 -0
- package/game-ui/screen-hierarchy.md +107 -0
- package/game-ui/unity/architecture.md +166 -0
- package/game-ui/unity/cli-loop.md +84 -0
- package/game-ui/unity/data/unity-technologies-public-repositories.json +26654 -0
- package/game-ui/unity/org-wiki.md +146 -0
- package/game-ui/unity/repository-map.md +102 -0
- package/game-ui/unity/ui-systems.md +85 -0
- package/guides/decision-tree.md +149 -0
- package/guides/documentation-mode-taxonomy.md +157 -0
- package/guides/layout-brief.md +220 -0
- package/guides/vocabulary.md +148 -0
- package/guides/webpage-generation-workflow.md +185 -0
- package/index.md +36 -0
- package/layout/index.md +26 -0
- package/motion/index.md +24 -0
- package/motion/practice-reference.md +145 -0
- package/motion/review-workflow.md +86 -0
- package/motion/vocabulary.md +82 -0
- package/package.json +78 -0
- package/patterns/centering/center.md +87 -0
- package/patterns/centering/index.md +6 -0
- package/patterns/centering/super-center.md +89 -0
- package/patterns/containment/box.md +87 -0
- package/patterns/containment/clamped-card.md +86 -0
- package/patterns/containment/content-limiter.md +87 -0
- package/patterns/containment/fluid-styles.md +86 -0
- package/patterns/containment/index.md +8 -0
- package/patterns/grid-repetition/card-grid.md +88 -0
- package/patterns/grid-repetition/columns.md +87 -0
- package/patterns/grid-repetition/dense-grid.md +88 -0
- package/patterns/grid-repetition/grid-wrapper.md +89 -0
- package/patterns/grid-repetition/index.md +12 -0
- package/patterns/grid-repetition/masonry-approx.md +87 -0
- package/patterns/grid-repetition/page-grid.md +90 -0
- package/patterns/grid-repetition/ram-grid.md +88 -0
- package/patterns/grid-repetition/twelve-span-grid.md +92 -0
- package/patterns/in-line-grouping/badge-list.md +94 -0
- package/patterns/in-line-grouping/breadcrumb.md +89 -0
- package/patterns/in-line-grouping/cluster.md +89 -0
- package/patterns/in-line-grouping/deconstructed-pancake.md +92 -0
- package/patterns/in-line-grouping/index.md +13 -0
- package/patterns/in-line-grouping/pagination.md +89 -0
- package/patterns/in-line-grouping/reel.md +90 -0
- package/patterns/in-line-grouping/split-nav.md +97 -0
- package/patterns/in-line-grouping/tab-strip.md +88 -0
- package/patterns/in-line-grouping/wrap-row.md +89 -0
- package/patterns/index.md +13 -0
- package/patterns/media-fit/frame.md +95 -0
- package/patterns/media-fit/icon-frame.md +87 -0
- package/patterns/media-fit/index.md +6 -0
- package/patterns/overlay-exception/imposter.md +95 -0
- package/patterns/overlay-exception/index.md +6 -0
- package/patterns/overlay-exception/overlay-stack.md +89 -0
- package/patterns/split-sidebar/index.md +12 -0
- package/patterns/split-sidebar/list-detail.md +95 -0
- package/patterns/split-sidebar/main-with-rail.md +95 -0
- package/patterns/split-sidebar/media-object.md +102 -0
- package/patterns/split-sidebar/sidebar.md +101 -0
- package/patterns/split-sidebar/split-screen.md +92 -0
- package/patterns/split-sidebar/sticky-aside.md +97 -0
- package/patterns/split-sidebar/supporting-pane.md +95 -0
- package/patterns/split-sidebar/switcher.md +93 -0
- package/patterns/stacking/feed.md +88 -0
- package/patterns/stacking/index.md +8 -0
- package/patterns/stacking/line-up.md +95 -0
- package/patterns/stacking/stack.md +87 -0
- package/patterns/stacking/step-nav.md +88 -0
- package/patterns/viewport-shell/cover.md +90 -0
- package/patterns/viewport-shell/fixed-sidenav-shell.md +97 -0
- package/patterns/viewport-shell/holy-grail.md +112 -0
- package/patterns/viewport-shell/index.md +11 -0
- package/patterns/viewport-shell/panel-layout.md +95 -0
- package/patterns/viewport-shell/scroll-body-shell.md +93 -0
- package/patterns/viewport-shell/sticky-footer.md +92 -0
- package/patterns/viewport-shell/sticky-header.md +86 -0
- package/platform-guides/apple-interaction.md +89 -0
- package/platform-guides/index.md +26 -0
- package/quality/claim-records/index.md +3 -0
- package/quality/claim-records/samples.md +92 -0
- package/quality/claim-records/stylegallery-multidomain-scope.md +42 -0
- package/quality/claims.md +77 -0
- package/quality/evidence/accessibility.md +58 -0
- package/quality/evidence/consumer-migration.md +42 -0
- package/quality/evidence/design-rationale.md +34 -0
- package/quality/evidence/executable-evidence.md +79 -0
- package/quality/evidence/families.md +56 -0
- package/quality/evidence/hci-evaluation.md +36 -0
- package/quality/evidence/index.md +16 -0
- package/quality/evidence/pattern-contract-audit.md +96 -0
- package/quality/evidence/search-metadata-polyhierarchy.md +86 -0
- package/quality/evidence/sources.md +43 -0
- package/quality/evidence/tokens.md +33 -0
- package/quality/evidence/visual-qa.md +41 -0
- package/quality/gates/accessibility-evidence.md +60 -0
- package/quality/gates/consumer-migration-evidence.md +53 -0
- package/quality/gates/design-claim.md +56 -0
- package/quality/gates/harmony-evaluation.md +78 -0
- package/quality/gates/index.md +13 -0
- package/quality/gates/layout.md +52 -0
- package/quality/gates/rationale.md +53 -0
- package/quality/gates/visual-evidence.md +47 -0
- package/quality/index.md +55 -0
- package/quality/principles.md +58 -0
- package/recipes/article-page.md +52 -0
- package/recipes/command-surface.md +55 -0
- package/recipes/dashboard.md +56 -0
- package/recipes/form-flow.md +55 -0
- package/recipes/homepage.md +89 -0
- package/recipes/index.md +13 -0
- package/recipes/list-detail.md +55 -0
- package/recipes/primitive-to-recipe-matrix.md +83 -0
- package/recipes/saas-settings.md +52 -0
- package/scripts/agent-native/a2a-projection.mjs +248 -0
- package/scripts/agent-native/agui-projection.mjs +178 -0
- package/scripts/agent-native/canonical-json.mjs +109 -0
- package/scripts/agent-native/cli-adapter.mjs +68 -0
- package/scripts/agent-native/events.mjs +113 -0
- package/scripts/agent-native/execution.mjs +227 -0
- package/scripts/agent-native/fixture.mjs +159 -0
- package/scripts/agent-native/identity.mjs +184 -0
- package/scripts/agent-native/immutable-store.mjs +102 -0
- package/scripts/agent-native/knowledge.mjs +168 -0
- package/scripts/agent-native/learning-records.mjs +244 -0
- package/scripts/agent-native/learning.mjs +247 -0
- package/scripts/agent-native/mcp-adapter.mjs +173 -0
- package/scripts/agent-native/queries.mjs +173 -0
- package/scripts/agent-native/registry.mjs +154 -0
- package/scripts/agent-native/retrieval.mjs +171 -0
- package/scripts/agent-native/self-description.mjs +160 -0
- package/scripts/artifact-metadata.mjs +20 -0
- package/scripts/audit-evidence-freshness.mjs +308 -0
- package/scripts/baseline-contract.mjs +189 -0
- package/scripts/baseline-schema-parity.mjs +40 -0
- package/scripts/build-reference-artifacts.mjs +127 -0
- package/scripts/calibration-raw-contract.mjs +172 -0
- package/scripts/capture-session-contract.mjs +187 -0
- package/scripts/component-state-artifact-fixture.mjs +44 -0
- package/scripts/component-state-contract.mjs +99 -0
- package/scripts/component-state-semantics.mjs +128 -0
- package/scripts/component-state-workflow-contract.mjs +67 -0
- package/scripts/component-state-workflow-negative-cases.mjs +64 -0
- package/scripts/consumer-conformance-contract.mjs +198 -0
- package/scripts/consumer-evidence-governance-contract.mjs +210 -0
- package/scripts/consumer-reference-case-runner.mjs +93 -0
- package/scripts/consumer-reference-schema.mjs +155 -0
- package/scripts/create-component-state-session.mjs +100 -0
- package/scripts/create-page-evidence-session.mjs +139 -0
- package/scripts/domain-document-boundaries.mjs +40 -0
- package/scripts/domain-validation-checks.mjs +141 -0
- package/scripts/evidence-artifact-contract.mjs +191 -0
- package/scripts/finalize-component-state-evidence.mjs +165 -0
- package/scripts/finalize-page-evidence.mjs +197 -0
- package/scripts/generate-consumer-reference-evidence.mjs +154 -0
- package/scripts/generate-patterns.mjs +218 -0
- package/scripts/governance-matrix-contract.mjs +73 -0
- package/scripts/governance-matrix-negative-cases.mjs +114 -0
- package/scripts/governance-policy-contract.mjs +49 -0
- package/scripts/governance-test-fixture.mjs +321 -0
- package/scripts/governance-workflow-negative-cases.mjs +241 -0
- package/scripts/governed-profile-registry.mjs +125 -0
- package/scripts/json-schema-formats.mjs +31 -0
- package/scripts/markdown-structure.mjs +45 -0
- package/scripts/page-artifact-metadata.mjs +124 -0
- package/scripts/page-evidence-contract.mjs +297 -0
- package/scripts/page-evidence-fixture.mjs +271 -0
- package/scripts/pattern-data.mjs +325 -0
- package/scripts/profile-record-contract.mjs +140 -0
- package/scripts/promotion-attestation-contract.mjs +91 -0
- package/scripts/promotion-boundary-test-contract.mjs +92 -0
- package/scripts/promotion-fixture-inventory.mjs +109 -0
- package/scripts/promotion-governance-contract.mjs +28 -0
- package/scripts/reference-artifact-path-contract.mjs +28 -0
- package/scripts/reference-profile-contract.mjs +114 -0
- package/scripts/reference-token-contract.mjs +156 -0
- package/scripts/run-consumer-page-evidence-ci.mjs +170 -0
- package/scripts/sg-mcp.mjs +16 -0
- package/scripts/sg.mjs +27 -0
- package/scripts/strict-json.mjs +72 -0
- package/scripts/summarize-sentinel-calibration.mjs +98 -0
- package/scripts/test-agent-adapter-conformance.mjs +250 -0
- package/scripts/test-agent-conformance-receipt.mjs +106 -0
- package/scripts/test-agent-events.mjs +81 -0
- package/scripts/test-agent-execution-effects.mjs +114 -0
- package/scripts/test-agent-execution-fixtures.mjs +140 -0
- package/scripts/test-agent-execution-kernel.mjs +39 -0
- package/scripts/test-agent-execution-learning.mjs +130 -0
- package/scripts/test-agent-execution-lifecycle.mjs +143 -0
- package/scripts/test-agent-execution-safety.mjs +158 -0
- package/scripts/test-agent-execution-views.mjs +117 -0
- package/scripts/test-agent-identity-integrity-cases.mjs +160 -0
- package/scripts/test-agent-knowledge-kernel.mjs +183 -0
- package/scripts/test-agent-learning.mjs +246 -0
- package/scripts/test-agent-mcp.mjs +246 -0
- package/scripts/test-agent-native-schemas.mjs +215 -0
- package/scripts/test-agent-projections.mjs +249 -0
- package/scripts/test-agent-registry-mcp-integrity.mjs +215 -0
- package/scripts/test-agent-registry-mutations.mjs +110 -0
- package/scripts/test-audit-evidence-freshness.mjs +177 -0
- package/scripts/test-component-state-sentinel.mjs +35 -0
- package/scripts/test-component-state-source-contract.mjs +83 -0
- package/scripts/test-consumer-conformance-sentinel.mjs +165 -0
- package/scripts/test-consumer-reference-sentinel.mjs +31 -0
- package/scripts/test-generate-consumer-reference-evidence.mjs +124 -0
- package/scripts/test-json-cli-output.mjs +151 -0
- package/scripts/test-json-schema-formats.mjs +40 -0
- package/scripts/test-reference-adapters.mjs +199 -0
- package/scripts/test-renderer-purity.mjs +34 -0
- package/scripts/test-sg-cli.mjs +129 -0
- package/scripts/test-summarize-sentinel-calibration.mjs +226 -0
- package/scripts/test-validate-baseline-manifest.mjs +234 -0
- package/scripts/test-validate-component-state-artifacts.mjs +243 -0
- package/scripts/test-validate-component-state.mjs +106 -0
- package/scripts/test-validate-consumer-conformance.mjs +427 -0
- package/scripts/test-validate-consumer-reference-cli.mjs +69 -0
- package/scripts/test-validate-consumer-reference.mjs +263 -0
- package/scripts/test-validate-domains.mjs +357 -0
- package/scripts/test-validate-governance.mjs +292 -0
- package/scripts/test-validate-ia.mjs +262 -0
- package/scripts/test-validate-okf.mjs +73 -0
- package/scripts/test-validate-page-evidence.mjs +302 -0
- package/scripts/test-validate-patterns.mjs +157 -0
- package/scripts/test-validate-promotion-rfc.mjs +250 -0
- package/scripts/test-validate-reference-profiles.mjs +170 -0
- package/scripts/test-validate-unity-org-wiki.mjs +57 -0
- package/scripts/test-validate-unity-source-contracts.mjs +67 -0
- package/scripts/test-validate-vocabulary.mjs +174 -0
- package/scripts/test-validate-webpage-workflow.mjs +293 -0
- package/scripts/validate-baseline-manifest.mjs +44 -0
- package/scripts/validate-catalog.mjs +65 -0
- package/scripts/validate-component-state.mjs +141 -0
- package/scripts/validate-consumer-conformance.mjs +477 -0
- package/scripts/validate-consumer-reference.mjs +288 -0
- package/scripts/validate-domains.mjs +237 -0
- package/scripts/validate-governance.mjs +248 -0
- package/scripts/validate-ia.mjs +136 -0
- package/scripts/validate-links.mjs +66 -0
- package/scripts/validate-okf.mjs +104 -0
- package/scripts/validate-page-evidence.mjs +191 -0
- package/scripts/validate-patterns.mjs +246 -0
- package/scripts/validate-promotion-rfc.mjs +224 -0
- package/scripts/validate-reference-artifacts.mjs +125 -0
- package/scripts/validate-renderer-purity.mjs +56 -0
- package/scripts/validate-unity-org-wiki.mjs +146 -0
- package/scripts/validate-unity-source-contracts.mjs +107 -0
- package/scripts/validate-vocabulary.mjs +115 -0
- package/scripts/validate-webpage-workflow.mjs +254 -0
- package/scripts/visual-expectation-contract.mjs +20 -0
- package/scripts/workflow-action-contract.mjs +163 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { copyTreeNoSymlinks, testPromotionBoundaries } from "./promotion-boundary-test-contract.mjs";
|
|
8
|
+
|
|
9
|
+
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const fixtureRoot = path.join(repositoryRoot, "consumer-reference/fixtures/promotion");
|
|
11
|
+
const validator = path.join(repositoryRoot, "scripts/validate-promotion-rfc.mjs");
|
|
12
|
+
|
|
13
|
+
function readJson(relative) {
|
|
14
|
+
return JSON.parse(fs.readFileSync(path.join(fixtureRoot, relative), "utf8"));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function runArguments(args) {
|
|
18
|
+
const child = spawnSync(process.execPath, [validator, ...args], { cwd: repositoryRoot, encoding: "utf8" });
|
|
19
|
+
let report = { failures: [], ok: false, parse_error: true };
|
|
20
|
+
try {
|
|
21
|
+
report = JSON.parse(child.stdout);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
24
|
+
report = { failures: [], ok: false, parse_error: true };
|
|
25
|
+
}
|
|
26
|
+
return { report, status: child.status };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function run(file, profileRepositoryRoot) {
|
|
30
|
+
const args = ["--file", file, "--json"];
|
|
31
|
+
if (profileRepositoryRoot) args.push("--profile-repository-root", profileRepositoryRoot);
|
|
32
|
+
return runArguments(args);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function runPolicy(file) {
|
|
36
|
+
return runArguments(["--policy", file, "--json"]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeMutation(name, source, mutate) {
|
|
40
|
+
const value = structuredClone(readJson(source));
|
|
41
|
+
mutate(value);
|
|
42
|
+
const file = path.join(tempRoot, `${name}.json`);
|
|
43
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
44
|
+
return file;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writePolicyMutation(root, name, mutate) {
|
|
48
|
+
const value = JSON.parse(fs.readFileSync(path.join(repositoryRoot, "consumer-reference/policies/shared-experimental.json"), "utf8"));
|
|
49
|
+
mutate(value);
|
|
50
|
+
const file = path.join(root, `${name}.json`);
|
|
51
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
52
|
+
return file;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function copyProfileRepository(name) {
|
|
56
|
+
const root = path.join(tempRoot, name);
|
|
57
|
+
const source = path.join(repositoryRoot, "design-engineering/reference-profiles/governed-local");
|
|
58
|
+
const target = path.join(root, "design-engineering/reference-profiles/governed-local");
|
|
59
|
+
copyTreeNoSymlinks(source, target);
|
|
60
|
+
return root;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function editProfileJson(root, relative, mutate) {
|
|
64
|
+
const file = path.join(root, relative);
|
|
65
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
66
|
+
mutate(value);
|
|
67
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fixtureManifest = JSON.parse(fs.readFileSync(path.join(fixtureRoot, "manifest.json"), "utf8"));
|
|
71
|
+
const invalidCases = fixtureManifest.invalid_records.map((entry) => [entry.file.replace(/\.json$/, ""), entry.file, entry.expected_code]);
|
|
72
|
+
|
|
73
|
+
const tempRoot = fs.mkdtempSync(path.join(repositoryRoot, ".tmp-pr6-promotion-"));
|
|
74
|
+
const repositoryTempRoot = fs.mkdtempSync(path.join(repositoryRoot, ".tmp-pr6-profile-"));
|
|
75
|
+
const results = [];
|
|
76
|
+
try {
|
|
77
|
+
for (const [name, relative, expected] of invalidCases) {
|
|
78
|
+
const child = run(path.join(fixtureRoot, relative));
|
|
79
|
+
const codes = child.report.failures.map((failure) => failure.code);
|
|
80
|
+
results.push({ actual: { codes, scaffold: child.report.scaffold === true, status: child.status }, expected, name, ok: child.status > 0 && codes.includes(expected) && child.report.scaffold !== true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const editorial = writeMutation("editorial_counted", "valid-deferred-example.json", (value) => {
|
|
84
|
+
value.attestations = [{ attested_by: "Synthetic owner", context_id: "editorial", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "stylegallery", profile_record: "design-engineering/reference-profiles/governed-local/editorial/profile.json", relationship: "independent" }];
|
|
85
|
+
});
|
|
86
|
+
const terminal = writeMutation("terminal_counted", "valid-deferred-example.json", (value) => {
|
|
87
|
+
value.attestations = [{ attested_by: "Synthetic owner", context_id: "terminal", consumer_id: "terminal-reference-profile", count_toward_gate: true, organization_id: "stylegallery", profile_record: "design-engineering/reference-profiles/governed-local/terminal/profile.json", relationship: "independent" }];
|
|
88
|
+
});
|
|
89
|
+
const booleanReview = writeMutation("boolean_review", "valid-deferred-example.json", (value) => { value.review_independence = false; });
|
|
90
|
+
const fakeIndependence = writeMutation("fake_independence", "valid-deferred-example.json", (value) => {
|
|
91
|
+
value.attestations = [
|
|
92
|
+
{ attested_by: "Synthetic owner", context_id: "one", consumer_id: "consumer-one", count_toward_gate: true, organization_id: "same-org", relationship: "independent" },
|
|
93
|
+
{ attested_by: "Synthetic owner", context_id: "two", consumer_id: "consumer-two", count_toward_gate: true, organization_id: "same-org", relationship: "independent" }
|
|
94
|
+
];
|
|
95
|
+
});
|
|
96
|
+
const duplicateIdentity = writeMutation("duplicate_identity", "valid-deferred-example.json", (value) => {
|
|
97
|
+
const attestation = { attested_by: "Synthetic owner", context_id: "same", consumer_id: "same-consumer", count_toward_gate: true, organization_id: "same-org", relationship: "independent" };
|
|
98
|
+
value.attestations = [attestation, structuredClone(attestation)];
|
|
99
|
+
});
|
|
100
|
+
const duplicateConsumer = writeMutation("duplicate_consumer", "valid-deferred-example.json", (value) => {
|
|
101
|
+
value.attestations = [
|
|
102
|
+
{ attested_by: "Synthetic owner A", context_id: "one", consumer_id: "same-consumer", count_toward_gate: true, organization_id: "organization-a", relationship: "independent" },
|
|
103
|
+
{ attested_by: "Synthetic owner B", context_id: "two", consumer_id: "same-consumer", count_toward_gate: true, organization_id: "organization-b", relationship: "independent" }
|
|
104
|
+
];
|
|
105
|
+
});
|
|
106
|
+
const unknownConsumers = writeMutation("unknown_consumers", "valid-deferred-example.json", (value) => {
|
|
107
|
+
value.attestations = [
|
|
108
|
+
{ attested_by: "Synthetic owner A", context_id: "one", consumer_id: "unknown-one", count_toward_gate: true, organization_id: "organization-a", relationship: "independent" },
|
|
109
|
+
{ attested_by: "Synthetic owner B", context_id: "two", consumer_id: "unknown-two", count_toward_gate: true, organization_id: "organization-b", relationship: "independent" }
|
|
110
|
+
];
|
|
111
|
+
});
|
|
112
|
+
const forgedOne = path.join(repositoryTempRoot, "forged-one.json");
|
|
113
|
+
const forgedTwo = path.join(repositoryTempRoot, "forged-two.json");
|
|
114
|
+
fs.writeFileSync(forgedOne, '{"fixture_independence":"independent","id":"forged-one"}\n');
|
|
115
|
+
fs.writeFileSync(forgedTwo, '{"fixture_independence":"independent","id":"forged-two"}\n');
|
|
116
|
+
const forgedProfiles = writeMutation("forged_profiles", "valid-deferred-example.json", (value) => {
|
|
117
|
+
value.attestations = [
|
|
118
|
+
{ attested_by: "Synthetic owner A", context_id: "one", consumer_id: "forged-one", count_toward_gate: true, organization_id: "organization-a", profile_record: path.relative(repositoryRoot, forgedOne), relationship: "independent" },
|
|
119
|
+
{ attested_by: "Synthetic owner B", context_id: "two", consumer_id: "forged-two", count_toward_gate: true, organization_id: "organization-b", profile_record: path.relative(repositoryRoot, forgedTwo), relationship: "independent" }
|
|
120
|
+
];
|
|
121
|
+
});
|
|
122
|
+
const copiedProfileRoot = path.join(repositoryTempRoot, "copied");
|
|
123
|
+
copyTreeNoSymlinks(path.join(repositoryRoot, "design-engineering/reference-profiles/governed-local/editorial"), copiedProfileRoot);
|
|
124
|
+
const copiedUnregistered = writeMutation("copied_unregistered", "valid-deferred-example.json", (value) => {
|
|
125
|
+
value.attestations = [{ attested_by: "Synthetic owner", context_id: "copied", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "copied-org", profile_record: path.relative(repositoryRoot, path.join(copiedProfileRoot, "profile.json")), relationship: "independent" }];
|
|
126
|
+
});
|
|
127
|
+
const invalidRegisteredRoot = copyProfileRepository("invalid-registered-root");
|
|
128
|
+
editProfileJson(invalidRegisteredRoot, "design-engineering/reference-profiles/governed-local/editorial/profile.json", (value) => { delete value.owner; });
|
|
129
|
+
const invalidRegistered = writeMutation("invalid_registered", "valid-deferred-example.json", (value) => {
|
|
130
|
+
value.attestations = [{ attested_by: "Synthetic owner", context_id: "invalid", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "invalid-org", profile_record: "design-engineering/reference-profiles/governed-local/editorial/profile.json", relationship: "independent" }];
|
|
131
|
+
});
|
|
132
|
+
const duplicateRegistryRoot = copyProfileRepository("duplicate-registry-root");
|
|
133
|
+
editProfileJson(duplicateRegistryRoot, "design-engineering/reference-profiles/governed-local/terminal/profile.json", (value) => { value.id = "editorial-reference-profile"; });
|
|
134
|
+
const duplicateRegistryIdentity = writeMutation("duplicate_registry_identity", "valid-deferred-example.json", (value) => {
|
|
135
|
+
value.attestations = [{ attested_by: "Synthetic owner", context_id: "duplicate", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "duplicate-org", profile_record: "design-engineering/reference-profiles/governed-local/editorial/profile.json", relationship: "independent" }];
|
|
136
|
+
});
|
|
137
|
+
const silentRelabel = writeMutation("silent_relabel", "valid-deferred-example.json", (value) => {
|
|
138
|
+
value.transition = { from: "stable", to: "shared_experimental" };
|
|
139
|
+
});
|
|
140
|
+
const pathEscape = writeMutation("path_escape", "valid-deferred-example.json", (value) => {
|
|
141
|
+
value.attestations = [{ attested_by: "Synthetic owner", context_id: "escape", consumer_id: "escape", count_toward_gate: true, organization_id: "escape", profile_record: "../outside.json", relationship: "independent" }];
|
|
142
|
+
});
|
|
143
|
+
const profileMismatch = writeMutation("profile_mismatch", "valid-deferred-example.json", (value) => {
|
|
144
|
+
value.attestations = [{ attested_by: "Synthetic owner", context_id: "editorial", consumer_id: "different-profile", count_toward_gate: true, organization_id: "stylegallery", profile_record: "design-engineering/reference-profiles/governed-local/editorial/profile.json", relationship: "independent" }];
|
|
145
|
+
});
|
|
146
|
+
const profileLink = path.join(repositoryTempRoot, "profile.json");
|
|
147
|
+
fs.symlinkSync(path.join(repositoryRoot, "design-engineering/reference-profiles/governed-local/editorial/profile.json"), profileLink);
|
|
148
|
+
const profileSymlink = writeMutation("profile_symlink", "valid-deferred-example.json", (value) => {
|
|
149
|
+
value.attestations = [{ attested_by: "Synthetic owner", context_id: "editorial", consumer_id: "editorial-reference-profile", count_toward_gate: true, organization_id: "stylegallery", profile_record: path.relative(repositoryRoot, profileLink), relationship: "independent" }];
|
|
150
|
+
});
|
|
151
|
+
const schemaExtra = writeMutation("schema_extra", "valid-deferred-example.json", (value) => { value.unexpected_promotion = true; });
|
|
152
|
+
const acceptedDecision = writeMutation("accepted_decision", "valid-deferred-example.json", (value) => { value.decision = "accepted"; });
|
|
153
|
+
const aestheticPromotion = writeMutation("aesthetic_promotion", "valid-deferred-example.json", (value) => { value.scope.aesthetic = true; });
|
|
154
|
+
const breakingWithoutMigration = writeMutation("breaking_without_migration", "valid-deferred-example.json", (value) => { value.compatibility.breaking = true; value.migration.required = false; });
|
|
155
|
+
const normativeWithoutRollback = writeMutation("normative_without_rollback", "valid-normative-bypass.json", (value) => { delete value.rollback; });
|
|
156
|
+
const normativeEndedSupport = writeMutation("normative_ended_support", "valid-normative-bypass.json", (value) => {
|
|
157
|
+
value.support = { capacity: "No active support capacity", status: "ended" };
|
|
158
|
+
});
|
|
159
|
+
const normativeMissingSupport = writeMutation("normative_missing_support", "valid-normative-bypass.json", (value) => { delete value.support; });
|
|
160
|
+
const normativeMissingCapacity = writeMutation("normative_missing_capacity", "valid-normative-bypass.json", (value) => { delete value.support.capacity; });
|
|
161
|
+
const weakCapacityCases = ["0", "Not available", "Absent", "Lapsed", "Negative capacity"].map((capacity, index) => [
|
|
162
|
+
`weak_capacity_${index + 1}`,
|
|
163
|
+
writeMutation(`weak_capacity_${index + 1}`, "valid-deferred-example.json", (value) => { value.support.capacity = capacity; }),
|
|
164
|
+
"promotion_support_required",
|
|
165
|
+
]);
|
|
166
|
+
const stableWithoutCount = writeMutation("stable_without_count", "valid-deferred-example.json", (value) => {
|
|
167
|
+
value.transition = { from: "shared_experimental", to: "stable" };
|
|
168
|
+
value.stable_scope_decision = true;
|
|
169
|
+
value.provenance.debt_resolved = true;
|
|
170
|
+
});
|
|
171
|
+
const stableWithoutEvidence = writeMutation("stable_without_evidence", "valid-deferred-example.json", (value) => {
|
|
172
|
+
value.transition = { from: "shared_experimental", to: "stable" };
|
|
173
|
+
value.stable_scope_decision = true;
|
|
174
|
+
value.provenance.debt_resolved = true;
|
|
175
|
+
value.evidence = [{ id: "regression", kind: "regression", reference: "synthetic-fixture:regression", status: "failed" }];
|
|
176
|
+
});
|
|
177
|
+
const duplicateKey = path.join(tempRoot, "duplicate_key.json");
|
|
178
|
+
fs.writeFileSync(duplicateKey, fs.readFileSync(path.join(fixtureRoot, "valid-deferred-example.json"), "utf8").replace('"decision": "deferred",', '"decision": "deferred",\n "decision": "rejected",'));
|
|
179
|
+
for (const [name, file, expected, profileRepositoryRoot] of [
|
|
180
|
+
["editorial_counted", editorial, "promotion_related_consumer_counted"],
|
|
181
|
+
["terminal_counted", terminal, "promotion_related_consumer_counted"],
|
|
182
|
+
["boolean_review", booleanReview, "review_independence_boolean"],
|
|
183
|
+
["fake_independence", fakeIndependence, "promotion_independence_unproven"],
|
|
184
|
+
["duplicate_identity", duplicateIdentity, "promotion_attestation_duplicate"],
|
|
185
|
+
["duplicate_consumer", duplicateConsumer, "promotion_attestation_duplicate"],
|
|
186
|
+
["unknown_consumers", unknownConsumers, "promotion_independence_unproven"],
|
|
187
|
+
["forged_profiles", forgedProfiles, "promotion_independence_unproven"],
|
|
188
|
+
["copied_unregistered", copiedUnregistered, "promotion_independence_unproven"],
|
|
189
|
+
["invalid_registered", invalidRegistered, "promotion_independence_unproven", invalidRegisteredRoot],
|
|
190
|
+
["duplicate_registry_identity", duplicateRegistryIdentity, "promotion_independence_unproven", duplicateRegistryRoot],
|
|
191
|
+
["silent_relabel", silentRelabel, "promotion_stable_relabel_forbidden"],
|
|
192
|
+
["path_escape", pathEscape, "promotion_profile_path_invalid"],
|
|
193
|
+
["profile_mismatch", profileMismatch, "promotion_profile_identity_mismatch"],
|
|
194
|
+
["profile_symlink", profileSymlink, "promotion_profile_path_symlink"],
|
|
195
|
+
["schema_extra", schemaExtra, "promotion_schema_invalid"],
|
|
196
|
+
["accepted_decision", acceptedDecision, "promotion_schema_invalid"],
|
|
197
|
+
["aesthetic_promotion", aestheticPromotion, "promotion_schema_invalid"],
|
|
198
|
+
["breaking_without_migration", breakingWithoutMigration, "promotion_migration_required"],
|
|
199
|
+
["normative_without_rollback", normativeWithoutRollback, "promotion_schema_invalid"],
|
|
200
|
+
["normative_ended_support", normativeEndedSupport, "promotion_support_required"],
|
|
201
|
+
["normative_missing_support", normativeMissingSupport, "promotion_support_required"],
|
|
202
|
+
["normative_missing_capacity", normativeMissingCapacity, "promotion_support_required"],
|
|
203
|
+
["duplicate_key", duplicateKey, "promotion_json_invalid"],
|
|
204
|
+
["stable_without_evidence", stableWithoutEvidence, "promotion_stable_evidence_required"],
|
|
205
|
+
...weakCapacityCases,
|
|
206
|
+
]) {
|
|
207
|
+
const child = run(file, profileRepositoryRoot);
|
|
208
|
+
const codes = child.report.failures.map((failure) => failure.code);
|
|
209
|
+
results.push({ actual: { codes, scaffold: child.report.scaffold === true, status: child.status }, expected, name, ok: child.status > 0 && codes.includes(expected) && child.report.scaffold !== true });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (const relative of ["valid-deferred-example.json", "valid-normative-bypass.json"]) {
|
|
213
|
+
const child = run(path.join(fixtureRoot, relative));
|
|
214
|
+
results.push({ actual: { scaffold: child.report.scaffold === true, status: child.status }, expected: "ok:true", name: relative, ok: child.status === 0 && child.report.ok === true && child.report.scaffold !== true });
|
|
215
|
+
}
|
|
216
|
+
const stable = run(stableWithoutCount);
|
|
217
|
+
results.push({ actual: { scaffold: stable.report.scaffold === true, status: stable.status }, expected: "stable_without_numeric_threshold", name: "stable_without_count", ok: stable.status === 0 && stable.report.ok === true && stable.report.scaffold !== true });
|
|
218
|
+
const canonicalPolicy = runPolicy(path.join(repositoryRoot, "consumer-reference/policies/shared-experimental.json"));
|
|
219
|
+
results.push({ actual: { status: canonicalPolicy.status }, expected: "canonical_policy_valid", name: "canonical_policy", ok: canonicalPolicy.status === 0 && canonicalPolicy.report.ok === true });
|
|
220
|
+
results.push(...testPromotionBoundaries({ fixtureRoot, repositoryRoot, runArguments, tempRoot }));
|
|
221
|
+
for (const [name, file, expected] of [
|
|
222
|
+
["stable_numeric_count", writePolicyMutation(tempRoot, "stable_numeric_count", (value) => { value.gateway.stable_numeric_threshold = 2; }), "promotion_stable_numeric_threshold_forbidden"],
|
|
223
|
+
["bypass_overreach", writePolicyMutation(tempRoot, "bypass_overreach", (value) => { value.normative_bypass.waives.push("regression_proof"); }), "promotion_normative_bypass_overreach"],
|
|
224
|
+
["fake_promotion_claim", writePolicyMutation(tempRoot, "fake_promotion_claim", (value) => { value.claims.promotion_occurred = true; }), "promotion_claim_forbidden"],
|
|
225
|
+
["policy_unknown_field", writePolicyMutation(tempRoot, "policy_unknown_field", (value) => { value.accepted = true; }), "promotion_policy_shape_invalid"],
|
|
226
|
+
["policy_nested_unknown", writePolicyMutation(tempRoot, "policy_nested_unknown", (value) => { value.gateway.stable_by_count = true; }), "promotion_policy_shape_invalid"],
|
|
227
|
+
["policy_promotes_palette", writePolicyMutation(tempRoot, "policy_promotes_palette", (value) => { value.boundaries.promoted_content.push("palette"); }), "promotion_aesthetic_boundary_required"],
|
|
228
|
+
["policy_promotes_alias", writePolicyMutation(tempRoot, "policy_promotes_alias", (value) => { value.boundaries.promoted_content.push("brand_identity"); }), "promotion_aesthetic_boundary_required"],
|
|
229
|
+
["policy_promoted_non_string", writePolicyMutation(tempRoot, "policy_promoted_non_string", (value) => { value.boundaries.promoted_content = [42]; }), "promotion_aesthetic_boundary_required"],
|
|
230
|
+
["policy_normative_aesthetic", writePolicyMutation(tempRoot, "policy_normative_aesthetic", (value) => { value.normative_bypass.categories = ["aesthetic"]; }), "promotion_normative_bypass_overreach"],
|
|
231
|
+
["policy_lifecycle_incomplete", writePolicyMutation(tempRoot, "policy_lifecycle_incomplete", (value) => { value.lifecycle.failure_actions = ["deprecate_with_migration"]; }), "promotion_lifecycle_invalid"],
|
|
232
|
+
["policy_excluded_non_string", writePolicyMutation(tempRoot, "policy_excluded_non_string", (value) => { value.boundaries.excluded_content.push(42); }), "promotion_aesthetic_boundary_required"],
|
|
233
|
+
["policy_retains_non_string", writePolicyMutation(tempRoot, "policy_retains_non_string", (value) => { value.normative_bypass.retains.push(42); }), "promotion_normative_bypass_overreach"],
|
|
234
|
+
["policy_verified_owner", writePolicyMutation(tempRoot, "policy_verified_owner", (value) => { value.owner.enforcement = "verified"; }), "promotion_policy_owner_invalid"],
|
|
235
|
+
["policy_promoted_status", writePolicyMutation(tempRoot, "policy_promoted_status", (value) => { value.status = "promoted"; }), "promotion_claim_forbidden"],
|
|
236
|
+
["policy_generated_mode", writePolicyMutation(tempRoot, "policy_generated_mode", (value) => { value.artifact_mode = "generated"; }), "promotion_policy_identity_invalid"],
|
|
237
|
+
]) {
|
|
238
|
+
const child = runPolicy(file);
|
|
239
|
+
const codes = child.report.failures.map((failure) => failure.code);
|
|
240
|
+
results.push({ actual: { codes, status: child.status }, expected, name, ok: child.status > 0 && codes.includes(expected) });
|
|
241
|
+
}
|
|
242
|
+
} finally {
|
|
243
|
+
fs.rmSync(tempRoot, { force: true, recursive: true });
|
|
244
|
+
fs.rmSync(repositoryTempRoot, { force: true, recursive: true });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const failures = results.filter((result) => !result.ok).map((result) => `missing_semantic:${result.name}:${result.expected}`);
|
|
248
|
+
const report = { failures, ok: failures.length === 0, results };
|
|
249
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
250
|
+
if (!report.ok) process.exitCode = 1;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const validator = path.join(repositoryRoot, "scripts", "validate-consumer-reference.mjs");
|
|
11
|
+
const profilePath = "design-engineering/reference-profiles/governed-local/editorial/profile.json";
|
|
12
|
+
const terminalProfilePath = "design-engineering/reference-profiles/governed-local/terminal/profile.json";
|
|
13
|
+
const aliasProfilePath = "design-engineering/reference-profiles/governed-local/alias/profile.json";
|
|
14
|
+
const layoutSha = "775430bbaf4ee208a642220f440f6926d79c90a3";
|
|
15
|
+
|
|
16
|
+
const baseProfile = {
|
|
17
|
+
artifact_mode: "governed_local",
|
|
18
|
+
component_records: ["components/button.component.json"],
|
|
19
|
+
default: false,
|
|
20
|
+
environment_assumptions: {
|
|
21
|
+
reset: { body_margin: "0", box_sizing: "border-box", figure_margin: "0" },
|
|
22
|
+
user_agent_styles: "Preserve browser defaults except for the declared reset.",
|
|
23
|
+
},
|
|
24
|
+
example_only: true,
|
|
25
|
+
evidence_records: ["evidence/button.evidence.json"],
|
|
26
|
+
fixture_records: ["fixtures/button.fixture.json"],
|
|
27
|
+
fixture_independence: "related",
|
|
28
|
+
handoff: { record: profilePath, status: "declared" },
|
|
29
|
+
generated_records: ["generated/evidence-coverage.md", "generated/keyboard-matrix.md", "generated/state-matrix.md"],
|
|
30
|
+
id: "editorial-reference-profile",
|
|
31
|
+
layout_source_sha: layoutSha,
|
|
32
|
+
local_foundations: "local-foundations.json",
|
|
33
|
+
maturity: "experimental",
|
|
34
|
+
owner: { enforcement: "placeholder", name: "Design Engineering owner" },
|
|
35
|
+
profile_kind: "governed_local",
|
|
36
|
+
related_fixture_set_id: "layout-identity-adversarial-pair",
|
|
37
|
+
review_independence: "single_account",
|
|
38
|
+
schema_version: "1.0",
|
|
39
|
+
selection: { method: "profile_path", required: true },
|
|
40
|
+
state_records: ["states/button.states.json"],
|
|
41
|
+
support: { status: "active" },
|
|
42
|
+
tokens: "tokens.dtcg.json",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const tokens = {
|
|
46
|
+
color: {
|
|
47
|
+
$type: "color",
|
|
48
|
+
accent: { $value: { alpha: 1, colorSpace: "srgb", components: [0.55, 0.18, 0.1] } },
|
|
49
|
+
},
|
|
50
|
+
space: { $type: "dimension", page: { $value: { unit: "rem", value: 1.5 } } },
|
|
51
|
+
};
|
|
52
|
+
const terminalTokens = {
|
|
53
|
+
color: {
|
|
54
|
+
$type: "color",
|
|
55
|
+
accent: { $value: { alpha: 1, colorSpace: "srgb", components: [0.1, 0.9, 0.5] } },
|
|
56
|
+
},
|
|
57
|
+
space: { $type: "dimension", page: { $value: { unit: "rem", value: 1 } } },
|
|
58
|
+
};
|
|
59
|
+
const editorialFoundations = { bindings: { accent: "{color.accent}" }, identity: "Warm editorial", profile_id: "editorial-reference-profile" };
|
|
60
|
+
const terminalFoundations = { bindings: { accent: "{color.accent}" }, identity: "Dark terminal", profile_id: "terminal-reference-profile" };
|
|
61
|
+
|
|
62
|
+
const cases = [
|
|
63
|
+
{ name: "valid_governed_local_profile" },
|
|
64
|
+
{ expect: "profile_artifact_mode_governed_local", name: "local_as_external", mutate: (item) => { item.artifact_mode = "external_consumer"; } },
|
|
65
|
+
{ expect: "profile_default_forbidden", name: "implicit_default", mutate: (item) => { item.default = true; } },
|
|
66
|
+
{ expect: "profile_layout_source_sha_required", name: "missing_layout_sha", mutate: (item) => { delete item.layout_source_sha; } },
|
|
67
|
+
{ expect: "profile_ua_assumption_required", name: "missing_ua_assumption", mutate: (item) => { delete item.environment_assumptions.user_agent_styles; } },
|
|
68
|
+
{ expect: "profile_reset_assumptions_required", name: "missing_reset_assumptions", mutate: (item) => { delete item.environment_assumptions.reset; } },
|
|
69
|
+
{ expect: "profile_explicit_selection_required", name: "implicit_selection", mutate: (item) => { item.selection.required = false; } },
|
|
70
|
+
{ expect: "fixture_independence_related", name: "independence_independent", mutate: (item) => { item.fixture_independence = "independent"; } },
|
|
71
|
+
{ expect: "reverse_import", name: "reverse_profile_import", patternData: "export const related_fixture_set_id = 'layout-identity-adversarial-pair';\n" },
|
|
72
|
+
{ expect: "external_adaptation_record_forbidden", externalRecord: true, name: "durable_external_adopter" },
|
|
73
|
+
{ equalTokens: true, expect: "profile_tokens_not_distinct", name: "single_profile_equal_pair_tokens" },
|
|
74
|
+
{ equalFoundations: true, expect: "profile_foundations_not_distinct", name: "single_profile_equal_pair_foundations" },
|
|
75
|
+
{ equalIdentity: true, expect: "profile_identity_not_distinct", name: "single_profile_equal_pair_identity" },
|
|
76
|
+
{ expect: "profile_related_set_mismatch", name: "single_profile_related_set_mismatch", mutateTerminal: (item) => { item.related_fixture_set_id = "unrelated-terminal-set"; } },
|
|
77
|
+
{ expect: "profile_tokens_symlink_escape", linkedArtifact: "tokens", name: "linked_outside_tokens" },
|
|
78
|
+
{ expect: "profile_foundations_symlink_escape", linkedArtifact: "foundations", name: "linked_outside_foundations" },
|
|
79
|
+
{ expect: "profile_environment_property_unknown", name: "unknown_environment_field", mutate: (item) => { item.environment_assumptions.viewport = "wide"; } },
|
|
80
|
+
{ expect: "profile_reset_property_unknown", name: "unknown_reset_field", mutate: (item) => { item.environment_assumptions.reset.list_margin = "0"; } },
|
|
81
|
+
{ expect: "profile_selection_property_unknown", name: "unknown_selection_field", mutate: (item) => { item.selection.fallback = "editorial"; } },
|
|
82
|
+
{ expect: "item_property_unknown", name: "unknown_profile_top_level", mutate: (item) => { item.profile_fallback = true; } },
|
|
83
|
+
{
|
|
84
|
+
equalTokens: true,
|
|
85
|
+
expect: "profile_tokens_not_distinct",
|
|
86
|
+
name: "normalized_equivalent_selector_loads_pair",
|
|
87
|
+
selector: "design-engineering/reference-profiles/governed-local/editorial/../editorial/profile.json",
|
|
88
|
+
},
|
|
89
|
+
{ absoluteSelector: true, expect: "item_unresolved", name: "absolute_profile_selector_rejected" },
|
|
90
|
+
{ expect: "item_unresolved", name: "scheme_profile_selector_rejected", selector: "https://example.com/editorial/profile.json" },
|
|
91
|
+
{ expect: "item_unresolved", name: "network_profile_selector_rejected", selector: "//server/editorial/profile.json" },
|
|
92
|
+
{ expect: "item_unresolved", name: "escaping_profile_selector_rejected", selector: "../editorial/profile.json" },
|
|
93
|
+
{ aliasSelector: true, expect: "item_redirect", name: "symlink_alias_profile_selector_rejected" },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
function writeFixture(testCase) {
|
|
97
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), `stylegallery-reference-profile-${testCase.name}-`));
|
|
98
|
+
const profile = structuredClone(baseProfile);
|
|
99
|
+
const terminalProfile = structuredClone(baseProfile);
|
|
100
|
+
terminalProfile.handoff.record = terminalProfilePath;
|
|
101
|
+
terminalProfile.id = "terminal-reference-profile";
|
|
102
|
+
testCase.mutate?.(profile);
|
|
103
|
+
testCase.mutateTerminal?.(terminalProfile);
|
|
104
|
+
const terminalFoundation = structuredClone(terminalFoundations);
|
|
105
|
+
if (testCase.equalIdentity) terminalFoundation.identity = editorialFoundations.identity;
|
|
106
|
+
const files = {
|
|
107
|
+
"CATALOG.md": "# Catalog\n",
|
|
108
|
+
[profilePath]: `${JSON.stringify(profile, null, 2)}\n`,
|
|
109
|
+
"design-engineering/reference-profiles/governed-local/editorial/local-foundations.json": `${JSON.stringify(editorialFoundations)}\n`,
|
|
110
|
+
"design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json": `${JSON.stringify(tokens, null, 2)}\n`,
|
|
111
|
+
[terminalProfilePath]: `${JSON.stringify(terminalProfile, null, 2)}\n`,
|
|
112
|
+
"design-engineering/reference-profiles/governed-local/terminal/local-foundations.json": `${JSON.stringify(testCase.equalFoundations ? editorialFoundations : terminalFoundation)}\n`,
|
|
113
|
+
"design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json": `${JSON.stringify(testCase.equalTokens ? tokens : terminalTokens, null, 2)}\n`,
|
|
114
|
+
"layout/index.md": "# Layout\n",
|
|
115
|
+
"patterns/index.md": "# Patterns\n",
|
|
116
|
+
"quality/handoff.md": "Implementation handoff:\nConsumer reference: not_applicable\nConsumer reference reason: This fixture has no consumer-specific reference record.\n",
|
|
117
|
+
"scripts/pattern-data.mjs": testCase.patternData ?? "export const patterns = [];\n",
|
|
118
|
+
};
|
|
119
|
+
if (testCase.externalRecord) files["design-engineering/reference-profiles/external-adaptation/adopter.json"] = "{}\n";
|
|
120
|
+
for (const [relative, content] of Object.entries(files)) {
|
|
121
|
+
const target = path.join(root, relative);
|
|
122
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
123
|
+
fs.writeFileSync(target, content);
|
|
124
|
+
}
|
|
125
|
+
let externalRoot;
|
|
126
|
+
if (testCase.linkedArtifact) {
|
|
127
|
+
externalRoot = fs.mkdtempSync(path.join(os.tmpdir(), "stylegallery-reference-profile-outside-"));
|
|
128
|
+
const filename = testCase.linkedArtifact === "tokens" ? "tokens.dtcg.json" : "local-foundations.json";
|
|
129
|
+
const target = path.join(root, path.posix.dirname(profilePath), filename);
|
|
130
|
+
const external = path.join(externalRoot, filename);
|
|
131
|
+
fs.writeFileSync(external, files[path.posix.join(path.posix.dirname(profilePath), filename)]);
|
|
132
|
+
fs.rmSync(target);
|
|
133
|
+
fs.symlinkSync(external, target);
|
|
134
|
+
}
|
|
135
|
+
let selector = testCase.selector ?? profilePath;
|
|
136
|
+
if (testCase.absoluteSelector) selector = path.join(root, profilePath);
|
|
137
|
+
if (testCase.aliasSelector) {
|
|
138
|
+
const alias = path.join(root, aliasProfilePath);
|
|
139
|
+
fs.mkdirSync(path.dirname(alias), { recursive: true });
|
|
140
|
+
fs.symlinkSync(path.join(root, profilePath), alias);
|
|
141
|
+
selector = aliasProfilePath;
|
|
142
|
+
}
|
|
143
|
+
return { externalRoot, root, selector };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function runCase(testCase) {
|
|
147
|
+
const fixture = writeFixture(testCase);
|
|
148
|
+
try {
|
|
149
|
+
const child = spawnSync(process.execPath, [validator, "--profile", fixture.selector, "--json"], { cwd: fixture.root, encoding: "utf8" });
|
|
150
|
+
const output = JSON.parse(child.stdout);
|
|
151
|
+
const codes = Array.isArray(output.failures) ? output.failures.map((failure) => failure.code) : [];
|
|
152
|
+
const accepted = child.status === 0 && output.ok === true && output.scaffold !== true;
|
|
153
|
+
const rejected = child.status !== 0 && output.ok === false && codes.includes(testCase.expect);
|
|
154
|
+
return {
|
|
155
|
+
actual: { codes, ok: output.ok, status: child.status },
|
|
156
|
+
expected: testCase.expect ?? "ok:true and exit:0",
|
|
157
|
+
name: testCase.name,
|
|
158
|
+
ok: testCase.expect ? rejected : accepted,
|
|
159
|
+
};
|
|
160
|
+
} finally {
|
|
161
|
+
fs.rmSync(fixture.root, { force: true, recursive: true });
|
|
162
|
+
if (fixture.externalRoot) fs.rmSync(fixture.externalRoot, { force: true, recursive: true });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const results = cases.map(runCase);
|
|
167
|
+
const failures = results.filter((result) => !result.ok).map((result) => `missing_semantic:${result.name}:${result.expected}`);
|
|
168
|
+
const report = { failures, ok: failures.length === 0, results };
|
|
169
|
+
console.log(JSON.stringify(report, null, 2));
|
|
170
|
+
process.exitCode = report.ok ? 0 : 1;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(import.meta.dirname, "..");
|
|
8
|
+
const validator = path.join(root, "scripts", "validate-unity-org-wiki.mjs");
|
|
9
|
+
|
|
10
|
+
function run(cwd) {
|
|
11
|
+
return spawnSync(process.execPath, [validator, "--json"], { cwd, encoding: "utf8" });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
const inventoryPath = path.join(root, "game-ui", "unity", "data", "unity-technologies-public-repositories.json");
|
|
16
|
+
const mapPath = path.join(root, "game-ui", "unity", "repository-map.md");
|
|
17
|
+
const wikiPath = path.join(root, "game-ui", "unity", "org-wiki.md");
|
|
18
|
+
const inventoryText = await readFile(inventoryPath, "utf8");
|
|
19
|
+
const inventory = JSON.parse(inventoryText);
|
|
20
|
+
const map = await readFile(mapPath, "utf8");
|
|
21
|
+
const wiki = await readFile(wikiPath, "utf8");
|
|
22
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "unity-org-wiki-validator-"));
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await writeFile(path.join(tempRoot, "inventory.json"), inventoryText);
|
|
26
|
+
await writeFile(path.join(tempRoot, "map.md"), map);
|
|
27
|
+
await writeFile(path.join(tempRoot, "wiki.md"), wiki);
|
|
28
|
+
const fixtures = [
|
|
29
|
+
["wrong count", inventory.slice(1), map, wiki, "expected 804 repositories"],
|
|
30
|
+
["duplicate repository", [...inventory.slice(0, -1), inventory[0]], map, wiki, "duplicate repository identity"],
|
|
31
|
+
["NGUI inside org", [...inventory.slice(0, -1), { ...inventory.at(-1), id: 999999999, full_name: "tasharen/ngui", html_url: "https://github.com/tasharen/ngui" }], map, wiki, "NGUI must remain external"],
|
|
32
|
+
["invalid cluster", [{ ...inventory[0], primary_cluster: "unverified" }, ...inventory.slice(1)], map, wiki, "invalid primary_cluster"],
|
|
33
|
+
["absolute path", inventory, map, `${wiki}\n/Users/example/private\n`, "absolute home path"],
|
|
34
|
+
["wiki data drift", inventory, map, wiki.replaceAll("**804**", "**803**"), "wiki must state the 804-row boundary"],
|
|
35
|
+
["repository map count drift", inventory, map.replace("804 classified rows", "803 classified rows"), wiki, "repository map must state the 804-row boundary"],
|
|
36
|
+
["repository map hash drift", inventory, map.replaceAll("b1438e6e8d438bab3eff2ff02f5a10e9374fdfb89b0581ffb936a42920fa2e05", "0000000000000000000000000000000000000000000000000000000000000000"), wiki, "repository map tracked inventory hash drift"],
|
|
37
|
+
["tracked inventory hash drift", [{ ...inventory[0], stargazers_count: inventory[0].stargazers_count + 1 }, ...inventory.slice(1)], map, wiki, "tracked inventory SHA-256 mismatch"],
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const [name, candidateInventory, candidateMap, candidateWiki, expected] of fixtures) {
|
|
41
|
+
await writeFile(path.join(tempRoot, "inventory.json"), candidateInventory === inventory ? inventoryText : JSON.stringify(candidateInventory));
|
|
42
|
+
await writeFile(path.join(tempRoot, "map.md"), candidateMap);
|
|
43
|
+
await writeFile(path.join(tempRoot, "wiki.md"), candidateWiki);
|
|
44
|
+
const result = spawnSync(process.execPath, [validator, "--json", "--inventory", path.join(tempRoot, "inventory.json"), "--map", path.join(tempRoot, "map.md"), "--wiki", path.join(tempRoot, "wiki.md")], { cwd: root, encoding: "utf8" });
|
|
45
|
+
assert.notEqual(result.status, 0, `${name} fixture unexpectedly passed`);
|
|
46
|
+
assert.match(`${result.stdout}${result.stderr}`, new RegExp(expected.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), `${name} fixture failed for the wrong reason`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = run(root);
|
|
50
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
51
|
+
console.log("Unity org wiki validator fixtures passed: 9 negative, 1 positive.");
|
|
52
|
+
} finally {
|
|
53
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await main();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(import.meta.dirname, "..");
|
|
10
|
+
const validator = path.join(root, "scripts", "validate-unity-source-contracts.mjs");
|
|
11
|
+
const documents = [
|
|
12
|
+
"game-ui/unity/architecture.md",
|
|
13
|
+
"game-ui/unity/cli-loop.md",
|
|
14
|
+
"game-ui/unity/org-wiki.md",
|
|
15
|
+
"game-ui/unity/repository-map.md",
|
|
16
|
+
"game-ui/unity/ui-systems.md",
|
|
17
|
+
];
|
|
18
|
+
const originals = Object.fromEntries(documents.map((relative) => [relative, fs.readFileSync(path.join(root, relative), "utf8")]));
|
|
19
|
+
const fixtures = [
|
|
20
|
+
{
|
|
21
|
+
name: "missing CLI license attribution",
|
|
22
|
+
relative: "game-ui/unity/cli-loop.md",
|
|
23
|
+
mutate: (content) => content.replace(/- Source license:.*\n/, ""),
|
|
24
|
+
expected: "game-ui/unity/cli-loop.md: missing pinned source license",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "mutable CLI body citation",
|
|
28
|
+
relative: "game-ui/unity/cli-loop.md",
|
|
29
|
+
mutate: (content) => content.replace("https://github.com/hatayama/unity-cli-loop/blob/61a0fe6d7da0aa9d0bcbc6d95944dd069c483ff0/README.md", "https://github.com/hatayama/unity-cli-loop/blob/main/README.md"),
|
|
30
|
+
expected: "game-ui/unity/cli-loop.md: GitHub body citation must use a non-zero 40-character revision: hatayama/unity-cli-loop",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "zeroed uGUI body revisions",
|
|
34
|
+
relative: "game-ui/unity/ui-systems.md",
|
|
35
|
+
mutate: (content) => content.replaceAll("9edb4420267b6652090ece4c28c38bd98746a68e", "0000000000000000000000000000000000000000"),
|
|
36
|
+
expected: "game-ui/unity/ui-systems.md: GitHub body citation must use a non-zero 40-character revision: Unity-Technologies/uGUI",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "invalid reviewed date",
|
|
40
|
+
relative: "game-ui/unity/architecture.md",
|
|
41
|
+
mutate: (content) => content.replace("reviewed_on: 2026-07-14", "reviewed_on: never"),
|
|
42
|
+
expected: "game-ui/unity/architecture.md: reviewed_on must be an ISO calendar date",
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function writeFixture(fixture) {
|
|
47
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "unity-source-contracts-"));
|
|
48
|
+
for (const [relative, content] of Object.entries(originals)) {
|
|
49
|
+
const target = path.join(directory, relative);
|
|
50
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
51
|
+
fs.writeFileSync(target, relative === fixture.relative ? fixture.mutate(content) : content);
|
|
52
|
+
}
|
|
53
|
+
return directory;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const fixture of fixtures) {
|
|
57
|
+
const directory = writeFixture(fixture);
|
|
58
|
+
const result = spawnSync(process.execPath, [validator, "--json", "--root", directory], { encoding: "utf8" });
|
|
59
|
+
fs.rmSync(directory, { force: true, recursive: true });
|
|
60
|
+
assert.notEqual(result.status, 0, `${fixture.name} unexpectedly passed`);
|
|
61
|
+
const output = JSON.parse(result.stdout);
|
|
62
|
+
assert(output.failures.includes(fixture.expected), `${fixture.name} failed for the wrong reason: ${result.stdout}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const positive = spawnSync(process.execPath, [validator, "--json"], { cwd: root, encoding: "utf8" });
|
|
66
|
+
assert.equal(positive.status, 0, positive.stderr || positive.stdout);
|
|
67
|
+
console.log("Unity source contract fixtures passed: 4 negative, 1 positive.");
|