diff --git a/.repository-projection.json b/.repository-projection.json index ce1ac73..da7a7ca 100644 --- a/.repository-projection.json +++ b/.repository-projection.json @@ -3,11 +3,11 @@ "projection": "examples", "projectionSchemaVersion": 1, "sourceRepository": "dx-corp/mono", - "sourceSha": "0d871a9ef1b80fe4c6322c4691030b8a10228159", + "sourceSha": "bd4d62e09588aadc36dddb9553846a5d043dcb66", "destinationRepository": "dx-corp/examples", - "priorProjectedBase": "b862c77771834c18a08546086a89c88200c22cb5", + "priorProjectedBase": "326c00f9f6995ba52b6f57828fdd0969ee8513e7", "definitionDigest": "ec2a926dbb322ffc1c2329c0b1f56b2b8bc0528c918473ebbcea0ef8234ff2b5", "toolDigest": "bcbcbfc0201d00c537cd53da8b0f8316bc454be3", - "contentDigest": "a829427722e59440620a73e54c56d75208ff3885aa91ea7234ae48c41571df98", + "contentDigest": "210e2dcfd4b62c17450522b54de7de6f07f8efa15daec8203a0e33d6eac3af50", "publicationEligible": true } diff --git a/scripts/distribution-validation.mjs b/scripts/distribution-validation.mjs index f6736e3..4005326 100644 --- a/scripts/distribution-validation.mjs +++ b/scripts/distribution-validation.mjs @@ -6,7 +6,7 @@ import { dirname, join, relative, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { parseArgs } from "node:util"; -const NAMES = new Set(["endpoint", "private-runner", "private-deployment", "api", "examples", "plugins", "capobara", "borg", "ts-rs"]); +const NAMES = new Set(["endpoint", "private-runner", "private-deployment", "api", "examples", "plugins", "capobara", "borg", "ts-rs", "corpus"]); const SHA = /^[0-9a-f]{40}$/; const HEX = /^[0-9a-f]{64}$/; // `toolDigest` is the one provenance field with two legitimate shapes. Node @@ -394,6 +394,32 @@ async function validateBorg(root, files) { requireValue(member?.name === "borg", "borg standalone workspace member is not the crate"); } +// `corpus` is the Mono-owned Corpus service source published back to +// dx-corp/corpus. The tree must stay a standalone Cargo workspace with its own +// lockfile, migrations and container build, and must not carry Mono-only +// surfaces. Destination-owned `.github/**` is legitimately present. The destination's own CI and image publisher own compile proof. +const CORPUS_MEMBERS = ["corpus-agent", "corpus-core", "corpus-scanner", "corpus-server", "corpusctl"]; + +async function validateCorpus(root, files) { + for (const required of ["Cargo.toml", "Cargo.lock", "Dockerfile", "LICENSE", "README.md", "rustfmt.toml"]) { + requireValue(files.includes(required), `corpus is missing ${required}`); + } + requireValue(files.some(path => /^migrations\/[^/]+\.sql$/.test(path)), "corpus is missing its migrations"); + for (const path of files) { + requireValue(!/(?:^|\/)hawk\.toml$/.test(path), `corpus contains a Mono-only path: ${path}`); + } + for (const manifest of files.filter(path => /(^|\/)Cargo\.toml$/.test(path))) { + const text = await readFile(join(root, manifest), "utf8"); + requireValue(!/\bpath\s*=\s*"\.\.\/\.\.\/\.\./.test(text), `corpus ${manifest} reaches outside the projected tree`); + } + const metadata = JSON.parse(run("cargo", ["metadata", "--locked", "--format-version", "1", "--no-deps"], root)); + const members = metadata.workspace_members + .map(id => metadata.packages.find(pkg => pkg.id === id)?.name) + .sort(); + requireValue(JSON.stringify(members) === JSON.stringify(CORPUS_MEMBERS), + `corpus standalone workspace members changed: ${members.join(", ")}`); +} + // The projected macro is standalone, without a committed Cargo.lock. Resolve // its manifest and check the applied repair here; destination CI owns compile // proof, which alone does not prove deterministic macro expansion. @@ -437,7 +463,7 @@ export async function validateDistribution({ name, target }) { await validateProvenance(name, root); const validators = { endpoint: validateEndpoint, "private-runner": validateRunner, "private-deployment": validateDeployment, - api: validateApi, examples: validateExamples, plugins: validatePlugins, capobara: validateCapobara, borg: validateBorg, "ts-rs": validateTsRs, + api: validateApi, examples: validateExamples, plugins: validatePlugins, capobara: validateCapobara, borg: validateBorg, "ts-rs": validateTsRs, corpus: validateCorpus, }; await validators[name](root, files); return { name, target: root, files: files.length, valid: true };