feat: add domain core and verification harness

This commit is contained in:
QiuSW
2026-08-04 23:28:10 +08:00
parent a82af6939f
commit bad67fa5a8
38 changed files with 2054 additions and 43 deletions
+104
View File
@@ -0,0 +1,104 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import {
contentDigest,
kingWenEntries,
validateContentPackage,
} from "./content-contract.mjs";
import { repositoryRoot } from "./repository-files.mjs";
const schemaPath = path.join(repositoryRoot, "content", "schema", "hexagram-content.schema.json");
const schema = JSON.parse(await readFile(schemaPath, "utf8"));
if (schema.$schema !== "https://json-schema.org/draft/2020-12/schema") {
throw new Error("Content JSON Schema must use draft 2020-12");
}
const kotlinCatalogPath = path.join(
repositoryRoot,
"app",
"src",
"main",
"java",
"brainwave",
"domain",
"casting",
"HexagramCatalog.kt",
);
const kotlinCatalog = await readFile(kotlinCatalogPath, "utf8");
const kotlinPairPattern = /TrigramPair\(Trigram\.(\w+), Trigram\.(\w+)\) to (\d+)/gu;
const kotlinPairs = new Map(
[...kotlinCatalog.matchAll(kotlinPairPattern)].map((match) => [
`${match[1]}/${match[2]}`,
Number(match[3]),
]),
);
const contractEntries = kingWenEntries();
if (kotlinPairs.size !== 64 || contractEntries.some((entry) =>
kotlinPairs.get(`${entry.upperTrigram}/${entry.lowerTrigram}`) !== entry.kingWenNumber)) {
throw new Error("Kotlin King Wen catalog and content-contract lookup must match exactly");
}
function validFixture() {
return {
schemaVersion: 1,
contentVersion: "fixture-only-v1",
specialUsageTexts: { qian: false, kun: false },
sources: [{
id: "fixture-source",
title: "Automated test fixture",
edition: "not publishable",
license: "test data only",
url: "https://example.invalid/fixture",
}],
hexagrams: contractEntries.map((entry) => ({
...entry,
name: `fixture-${entry.kingWenNumber}`,
symbol: `fixture-symbol-${entry.kingWenNumber}`,
judgmentOriginal: "fixture original text",
judgmentPlain: "fixture plain text",
lineTextsBottomUp: Array.from({ length: 6 }, (_, index) => `fixture original line ${index + 1}`),
linePlainBottomUp: Array.from({ length: 6 }, (_, index) => `fixture plain line ${index + 1}`),
specialUsageText: null,
sourceRefs: ["fixture-source"],
})),
};
}
function clone(value) {
return structuredClone(value);
}
const fixture = validFixture();
const validErrors = validateContentPackage(fixture);
if (validErrors.length > 0) {
throw new Error(`Valid content fixture was rejected:\n${validErrors.join("\n")}`);
}
const digest = contentDigest(fixture);
if (!/^[a-f0-9]{64}$/u.test(digest) || digest !== contentDigest(clone(fixture))) {
throw new Error("Content digest must be a stable SHA-256 value");
}
const negativeCases = [
["unsupported schema", (value) => { value.schemaVersion = 2; }, "schemaVersion"],
["duplicate id", (value) => { value.hexagrams[1].kingWenNumber = 1; }, "unique"],
["wrong bottom-up pattern", (value) => { value.hexagrams[0].patternBottomUp[0] = "YIN"; }, "bottom-up"],
["five line texts", (value) => { value.hexagrams[0].lineTextsBottomUp.pop(); }, "exactly six"],
["unknown source", (value) => { value.hexagrams[0].sourceRefs = ["missing"]; }, "unknown source"],
["blank license", (value) => { value.sources[0].license = " "; }, "non-blank"],
["script content", (value) => { value.hexagrams[0].judgmentPlain = "<script>alert(1)</script>"; }, "script-like"],
["special declaration mismatch", (value) => { value.specialUsageTexts.qian = true; }, "specialUsageTexts.qian"],
];
for (const [name, mutate, expected] of negativeCases) {
const candidate = clone(fixture);
mutate(candidate);
const errors = validateContentPackage(candidate);
if (!errors.some((error) => error.includes(expected))) {
throw new Error(`${name} fixture did not fail with '${expected}':\n${errors.join("\n")}`);
}
}
console.log(
`Content contract verification passed (64 entries cross-checked with Kotlin, ${negativeCases.length} negative fixtures, digest ${digest.slice(0, 12)}…).`,
);