/*
RFC-1281/RFC-1328: onboarding.intake.validate — deterministic INTAKE-01…15
validation of a werkstatt:intake/v1 hand-over bundle (INTAKE-CONTRACT +
AMD-005). Rule order is fixed: INTAKE-01 (manifest/hash integrity) and
INTAKE-02 (envelope schema, unknown major fails closed) run first;
INTAKE-03…15 run only when both produced no errors. All file access goes
through WorkspaceIO; hashing is node:crypto sha256.
- Do not vendor PBP entity shapes — INTAKE-03 delegates to pbpSchemaById (DNA-103).
- Do not write files — the command is read-only; the bundle is immutable on arrival.
- contractSnapshot is compared to the sha256 of the vendored export resolved via import.meta.resolve("@warpgogol/werkstatt-shared/onboarding/intake.schema.generated.yaml")
- Verbatim substring checks (INTAKE-05) run against text-typed evidence items only (text/*, application/json, message/rfc822); a binary-only source is an error,.
- RFC-1324: INTAKE-01 manifest coverage exempts the fixture-only composition/ tree (compose material, not intake payload).
- RFC-1328/AMD-005: INTAKE-12…15 — anchored internal-marker + assessment-vocabulary scan on published free text (name/statement/summary/description), locale-native statementByLocale coverage, WebP-only rasters, closed palette vocabulary; envelope-only fields stripped before the strict pbp parse (INTAKE-03).
- RFC-1281: created — validateIntakeBundle (INTAKE-01…11) + runIntakeValidate command + resolveIntakeExportSha256.
- RFC-1281: intake contract and onboarding.intake.validate
*/
import { createHash } from "node:crypto";
import { fileURLToPath } from "node:url";
import { isAbsolute, join, relative, resolve } from "node:path";
import { pbpSchemaById } from "@warpgogol/pbp/entities";
import { biomeAxesSchema, biomePaletteSchema } from "@warpgogol/werkstatt-shared/ontology";
import {
INTAKE_SCHEMA_ID,
intakeEnvelopeSchema,
type IntakeEnvelope,
} from "@warpgogol/werkstatt-shared/onboarding/intake";
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-shared/kernel";
import type { WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
export const INTAKE_RULE_IDS = [
"INTAKE-01",
"INTAKE-02",
"INTAKE-03",
"INTAKE-04",
"INTAKE-05",
"INTAKE-06",
"INTAKE-07",
"INTAKE-08",
"INTAKE-09",
"INTAKE-10",
"INTAKE-11",
"INTAKE-12",
"INTAKE-13",
"INTAKE-14",
"INTAKE-15",
] as const;
export type IntakeRuleId = (typeof INTAKE_RULE_IDS)[number];
/**
* AMD-005/RFC-1328: evidence-ledger id shape plus the internal-assessment
* vocabulary (evidence-source kinds, publishability gates) in free text.
* Anchored — bare words ("source", "gap") never match.
*/
const INTERNAL_MARKER_RE =
/\b(?:SRC|CLM|GAP|OPD|DEF|CHK|AST)-\d+|\b(?:internal_only|client-statement|after_proof)\b/;
/** Free-text fields INTAKE-12 scans on published entities (AMD-005). */
const PBP_FREE_TEXT_FIELDS = ["name", "statement", "summary", "description"] as const;
/**
* AMD-005/RFC-1328: `visual.palette` keys must come from the canonical biome
* palette vocabulary (`biomePaletteSchema.shape` keys — DNA-103 single source)
* plus `background`, which aliases into the site background upstream
* (`biome.from-intake` INTAKE_PALETTE_ALIASES).
*/
const CLOSED_PALETTE_KEYS: ReadonlySet = new Set([
...Object.keys(biomePaletteSchema.shape),
"background",
]);
export interface IntakeDiagnostic {
rule: IntakeRuleId;
severity: "error" | "warning";
message: string;
/** Bundle-relative file path, or JSON pointer into intake.json. */
path?: string;
}
export interface IntakeValidateResult {
command: "onboarding.intake.validate";
status: "pass" | "fail";
bundle: string;
intakeId: string | null;
systemId: string | null;
contractSnapshot: { expected: string; actual: string | null };
diagnostics: IntakeDiagnostic[];
}
export interface IntakeValidateOptions {
/** sha256:<64hex> of the validator's own intake.schema.generated.yaml. */
exportSha256: string;
/** Budget ceiling for the INTAKE-11 warning. Default 5 USD. */
costBudgetUsd?: number;
/** Display form of the bundle path for the result payload. */
bundleLabel?: string;
}
const EVIDENCE_SOURCE_SCHEMA = "pbp/evidence-source@1";
const CLAIM_SCHEMA = "pbp/claim@1";
const PBP_REF_RE = /^pbp\/([a-z][a-z0-9-]*)@(\d+):(.+)$/;
const SHA256_RE = /^sha256:[0-9a-f]{64}$/;
const ASSET_MEDIA_BY_EXT: Record = {
svg: "image/svg+xml",
png: "image/png",
jpg: "image/jpeg",
jpeg: "image/jpeg",
webp: "image/webp",
avif: "image/avif",
mp4: "video/mp4",
webm: "video/webm",
pdf: "application/pdf",
};
function sha256Of(bytes: Uint8Array): string {
return createHash("sha256").update(bytes).digest("hex");
}
function toPosix(p: string): string {
return p.replace(/\\/g, "/");
}
/** Resolve `pbp/@:` (PBP ref) or a bare `` to { schema, id }. */
function resolveEntityRef(ref: string): { schema: string | null; id: string } {
const m = PBP_REF_RE.exec(ref);
if (m) return { schema: `pbp/${m[1]}@${m[2]}`, id: m[3] };
return { schema: null, id: ref };
}
function isTextMediaType(mediaType: string | undefined): boolean {
return (
!!mediaType &&
(mediaType.startsWith("text/") ||
mediaType === "application/json" ||
mediaType === "message/rfc822")
);
}
/**
* sha256 of the vendored schema export as installed — resolved through the
* package export map so the validator always hashes the artifact a producer
* would vendor. Null when the file is absent (export not run / not vendored).
*/
export async function resolveIntakeExportSha256(
io: WorkspaceIO,
): Promise<{ sha256: string; path: string } | null> {
let url: string;
try {
url = import.meta
.resolve("@warpgogol/werkstatt-shared/onboarding/intake.schema.generated.yaml");
} catch {
return null;
}
const path = fileURLToPath(url);
if (!(await io.exists(path))) return null;
const bytes = await io.readFileBytes(path);
return { sha256: `sha256:${sha256Of(bytes)}`, path };
}
async function listBundleFiles(bundleDir: string, io: WorkspaceIO): Promise {
const entries = await io.glob("**/*", { cwd: bundleDir });
const files: string[] = [];
for (const rel of entries) {
const stat = await io.stat(join(bundleDir, rel));
if (stat.isFile) files.push(toPosix(rel));
}
return files.sort();
}
/**
* Validate one intake bundle directory. Pure over `io` — no ambient fs.
* INTAKE-03…11 run only when INTAKE-01 and INTAKE-02 produced no errors.
*/
export async function validateIntakeBundle(
bundleDir: string,
io: WorkspaceIO,
options: IntakeValidateOptions,
): Promise {
const diagnostics: IntakeDiagnostic[] = [];
const error = (rule: IntakeRuleId, message: string, path?: string): void => {
diagnostics.push({ rule, severity: "error", message, path });
};
const warn = (rule: IntakeRuleId, message: string, path?: string): void => {
diagnostics.push({ rule, severity: "warning", message, path });
};
const result: IntakeValidateResult = {
command: "onboarding.intake.validate",
status: "pass",
bundle: options.bundleLabel ?? bundleDir,
intakeId: null,
systemId: null,
contractSnapshot: { expected: options.exportSha256, actual: null },
diagnostics,
};
// ---------------------------------------------------------------------
// INTAKE-01 — MANIFEST.sha256 covers every file, hashes match, no strays
// ---------------------------------------------------------------------
let manifestClean = true;
if (!(await io.exists(bundleDir))) {
error("INTAKE-01", `bundle directory does not exist: ${bundleDir}`, bundleDir);
manifestClean = false;
} else {
const manifestPath = join(bundleDir, "MANIFEST.sha256");
if (!(await io.exists(manifestPath))) {
error("INTAKE-01", "MANIFEST.sha256 is missing from the bundle", "MANIFEST.sha256");
manifestClean = false;
} else {
const manifest = await io.readFile(manifestPath);
const listed = new Map();
for (const [lineNo, rawLine] of manifest.split("\n").entries()) {
const line = rawLine.trim();
if (line === "") continue;
const m = /^([0-9a-f]{64})\s+\*?(.+)$/.exec(line);
if (!m) {
error(
"INTAKE-01",
`MANIFEST.sha256 line ${lineNo + 1} is malformed: '${line}'`,
"MANIFEST.sha256",
);
manifestClean = false;
continue;
}
const rel = toPosix(m[2].trim());
if (listed.has(rel)) {
error("INTAKE-01", `MANIFEST.sha256 lists '${rel}' twice`, "MANIFEST.sha256");
manifestClean = false;
continue;
}
listed.set(rel, m[1]);
}
// composition/ is RFC-1324 fixture compose material, not intake payload —
// it is exempt from manifest coverage (a real operator bundle never carries it).
const diskFiles = (await listBundleFiles(bundleDir, io)).filter(
(f) => f !== "MANIFEST.sha256" && !f.startsWith("composition/"),
);
const diskSet = new Set(diskFiles);
for (const [rel, expectedSha] of listed) {
if (!diskSet.has(rel)) {
error("INTAKE-01", `MANIFEST.sha256 lists '${rel}' but the file is absent`, rel);
manifestClean = false;
continue;
}
const actual = sha256Of(await io.readFileBytes(join(bundleDir, rel)));
if (actual !== expectedSha) {
error(
"INTAKE-01",
`hash mismatch for '${rel}': manifest ${expectedSha}, actual ${actual}`,
rel,
);
manifestClean = false;
}
}
for (const rel of diskFiles) {
if (!listed.has(rel)) {
error("INTAKE-01", `file '${rel}' is not listed in MANIFEST.sha256`, rel);
manifestClean = false;
}
}
}
}
// ---------------------------------------------------------------------
// INTAKE-02 — intake.json parses and validates against werkstatt:intake/v1;
// an unknown schema major fails closed with a single diagnostic
// ---------------------------------------------------------------------
let envelope: IntakeEnvelope | null = null;
try {
const raw = await io.readFile(join(bundleDir, "intake.json"));
const parsed: unknown = JSON.parse(raw);
const schemaField =
parsed !== null && typeof parsed === "object"
? (parsed as Record).schema
: undefined;
if (
typeof schemaField === "string" &&
/^werkstatt:intake\/v\d+$/.test(schemaField) &&
schemaField !== INTAKE_SCHEMA_ID
) {
error(
"INTAKE-02",
`unsupported intake schema '${schemaField}' — validator supports '${INTAKE_SCHEMA_ID}' only (unknown major fails closed)`,
"/schema",
);
} else {
const parsed_ = intakeEnvelopeSchema.safeParse(parsed);
if (!parsed_.success) {
for (const issue of parsed_.error.issues) {
error("INTAKE-02", issue.message, `/${issue.path.map((p) => String(p)).join("/")}`);
}
} else {
envelope = parsed_.data;
}
}
} catch (cause) {
error(
"INTAKE-02",
`intake.json is missing or not valid JSON: ${(cause as Error).message}`,
"intake.json",
);
}
if (envelope !== null) {
result.intakeId = envelope.intakeId;
result.systemId = envelope.systemId;
result.contractSnapshot.actual = envelope.provenance.contractSnapshot;
}
// ---------------------------------------------------------------------
// INTAKE-03…11 — only when INTAKE-01 + INTAKE-02 produced no errors
// ---------------------------------------------------------------------
const hasErrors = (): boolean => diagnostics.some((d) => d.severity === "error");
if (manifestClean && envelope !== null && !hasErrors()) {
const env = envelope;
// --- shared lookup tables ------------------------------------------
const entitiesById = new Map }>();
const claimIds = new Set();
const gapIds = new Set(env.gaps.map((g) => g.id));
const assetIds = new Set(env.assets.map((a) => a.id));
// --- INTAKE-03: canonical PBP validation -----------------------------
for (const [i, entity] of env.pbp.entries()) {
if (entitiesById.has(entity.id)) {
error("INTAKE-03", `duplicate pbp entity id '${entity.id}'`, `/pbp/${i}/id`);
}
entitiesById.set(entity.id, { index: i, entity: entity as Record });
if (entity.schema === CLAIM_SCHEMA) claimIds.add(entity.id);
const canonical = pbpSchemaById[entity.schema];
if (!canonical) {
error(
"INTAKE-03",
`no canonical PBP schema registered for '${entity.schema}'`,
`/pbp/${i}/schema`,
);
continue;
}
// AMD-005: statementByLocale/localeNeutral are envelope-level fields —
// the frozen pbp/*@1 schemas never see them (strict parse).
const {
statementByLocale: _statementByLocale,
localeNeutral: _localeNeutral,
...canonicalEntity
} = entity as Record;
const r = canonical.safeParse(canonicalEntity);
if (!r.success) {
for (const issue of r.error.issues) {
error(
"INTAKE-03",
`entity '${entity.id}': ${issue.message}`,
`/pbp/${i}${issue.path.length > 0 ? `/${issue.path.map((p) => String(p)).join("/")}` : ""}`,
);
}
}
}
const checkPbpRef = (ref: string, path: string): void => {
const { id } = resolveEntityRef(ref);
if (!entitiesById.has(id)) {
error("INTAKE-04", `unresolved pbp entity ref '${ref}'`, path);
}
};
const checkGapRef = (ref: string, path: string): void => {
if (!gapIds.has(ref)) {
error("INTAKE-04", `unresolved gap ref '${ref}'`, path);
}
};
const checkEvidenceRef = (ref: string, path: string): void => {
const { schema, id } = resolveEntityRef(ref);
if (schema !== null && schema !== EVIDENCE_SOURCE_SCHEMA) {
error(
"INTAKE-04",
`evidence ref '${ref}' points at schema '${schema}', not '${EVIDENCE_SOURCE_SCHEMA}'`,
path,
);
return;
}
const target = entitiesById.get(id);
if (!target) {
error("INTAKE-04", `unresolved evidence-source ref '${ref}'`, path);
} else if ((target.entity as { schema?: string }).schema !== EVIDENCE_SOURCE_SCHEMA) {
error(
"INTAKE-04",
`ref '${ref}' resolves to '${(target.entity as { schema?: string }).schema}', not an evidence-source`,
path,
);
}
};
// --- INTAKE-04: referential integrity ---------------------------------
// annotation keys → pbp ids; annotation contents → gaps/evidence.
for (const [entityId, annotation] of Object.entries(env.annotations)) {
checkPbpRef(entityId, `/annotations/${entityId}`);
if (annotation.gapRef) checkGapRef(annotation.gapRef, `/annotations/${entityId}/gapRef`);
for (const [j, vb] of (annotation.verbatim ?? []).entries()) {
checkEvidenceRef(vb.sourceRef, `/annotations/${entityId}/verbatim/${j}/sourceRef`);
}
}
// gap.blocks → pbp ids; gap.evidenceRefs → evidence-source ids.
for (const [i, gap] of env.gaps.entries()) {
for (const [j, ref] of gap.blocks.entries()) {
checkPbpRef(ref, `/gaps/${i}/blocks/${j}`);
}
for (const [j, ref] of (gap.evidenceRefs ?? []).entries()) {
checkEvidenceRef(ref, `/gaps/${i}/evidenceRefs/${j}`);
}
}
// voice sourceRefs → evidence-source; claimRefs → claim ids.
for (const [i, item] of env.voice.verbatim.entries()) {
checkEvidenceRef(item.sourceRef, `/voice/verbatim/${i}/sourceRef`);
for (const [j, ref] of (item.claimRefs ?? []).entries()) {
if (!claimIds.has(ref)) {
error(
"INTAKE-04",
`unresolved claim ref '${ref}'`,
`/voice/verbatim/${i}/claimRefs/${j}`,
);
}
}
}
for (const [i, item] of env.voice.directives.entries()) {
checkEvidenceRef(item.sourceRef, `/voice/directives/${i}/sourceRef`);
}
// classification evidenceRefs → evidence-source ids.
for (const [i, locale] of env.classification.locales.entries()) {
for (const [j, ref] of (locale.evidenceRefs ?? []).entries()) {
checkEvidenceRef(ref, `/classification/locales/${i}/evidenceRefs/${j}`);
}
}
for (const [i, intent] of env.classification.pageSetIntent.entries()) {
for (const [j, ref] of intent.evidenceRefs.entries()) {
checkEvidenceRef(ref, `/classification/pageSetIntent/${i}/evidenceRefs/${j}`);
}
}
// claim evidenceRefs → evidence-source ids; asset refs → assets/gaps/evidence.
for (const [i, entity] of env.pbp.entries()) {
if (entity.schema !== CLAIM_SCHEMA) continue;
const refs = (entity as { evidenceRefs?: Record }).evidenceRefs;
for (const [key, entry] of Object.entries(refs ?? {})) {
if (entry && typeof entry.ref === "string") {
checkEvidenceRef(entry.ref, `/pbp/${i}/evidenceRefs/${key}/ref`);
}
}
}
for (const [i, asset] of env.assets.entries()) {
if (asset.rights.gapRef) checkGapRef(asset.rights.gapRef, `/assets/${i}/rights/gapRef`);
if (asset.rights.evidenceRef) {
checkEvidenceRef(asset.rights.evidenceRef, `/assets/${i}/rights/evidenceRef`);
}
}
for (const [i, ref] of (env.visual.moodAssetRefs ?? []).entries()) {
if (!assetIds.has(ref)) {
error("INTAKE-04", `unresolved mood asset ref '${ref}'`, `/visual/moodAssetRefs/${i}`);
}
}
// every evidence-source item sha256 → a sources/.* file exists.
for (const { entity } of entitiesById.values()) {
if ((entity as { schema?: string }).schema !== EVIDENCE_SOURCE_SCHEMA) continue;
const items = (
entity as {
items?: Record;
}
).items;
for (const [key, item] of Object.entries(items ?? {})) {
if (!item.sha256) continue;
const matches = await io.glob(`sources/${item.sha256}.*`, { cwd: bundleDir });
if (matches.length === 0) {
error(
"INTAKE-04",
`evidence item '${key}' of '${(entity as { id?: string }).id}' names sha256 ${item.sha256} but no sources/${item.sha256}.* exists`,
`/pbp/${(entity as { id?: string }).id}/items/${key}/sha256`,
);
}
}
}
// --- INTAKE-05: byte-exact verbatim quotes ----------------------------
interface VerbatimCheck {
text: string;
sourceRef: string;
path: string;
}
const verbatimChecks: VerbatimCheck[] = [];
for (const [entityId, annotation] of Object.entries(env.annotations)) {
for (const [j, vb] of (annotation.verbatim ?? []).entries()) {
verbatimChecks.push({
text: vb.text,
sourceRef: vb.sourceRef,
path: `/annotations/${entityId}/verbatim/${j}/text`,
});
}
}
for (const [i, item] of env.voice.verbatim.entries()) {
verbatimChecks.push({
text: item.text,
sourceRef: item.sourceRef,
path: `/voice/verbatim/${i}/text`,
});
}
for (const [i, item] of env.voice.directives.entries()) {
verbatimChecks.push({
text: item.text,
sourceRef: item.sourceRef,
path: `/voice/directives/${i}/text`,
});
}
for (const check of verbatimChecks) {
const { id } = resolveEntityRef(check.sourceRef);
const target = entitiesById.get(id);
// Unresolvable/wrong-type refs are already reported by INTAKE-04.
if (!target || (target.entity as { schema?: string }).schema !== EVIDENCE_SOURCE_SCHEMA) {
continue;
}
const items = (
target.entity as {
items?: Record;
}
).items;
const textItems = Object.values(items ?? {}).filter(
(item) => item.sha256 && isTextMediaType(item.mediaType),
);
if (textItems.length === 0) {
error(
"INTAKE-05",
`source '${check.sourceRef}' has no text-typed items — verbatim text cannot be substring-checked`,
check.path,
);
continue;
}
let sawFile = false;
let found = false;
for (const item of textItems) {
for (const rel of await io.glob(`sources/${item.sha256}.*`, { cwd: bundleDir })) {
sawFile = true;
const content = await io.readFile(join(bundleDir, rel));
if (content.includes(check.text)) {
found = true;
break;
}
}
if (found) break;
}
if (sawFile && !found) {
error(
"INTAKE-05",
`verbatim text is not a byte-exact substring of any text-typed item of '${check.sourceRef}'`,
check.path,
);
}
}
// --- INTAKE-06: every draft pbp entity is internal or names a gap -----
for (const [i, entity] of env.pbp.entries()) {
if (entity.status !== "draft") continue;
const annotation = env.annotations[entity.id];
if (!annotation || (annotation.internal !== true && !annotation.gapRef)) {
error(
"INTAKE-06",
`draft entity '${entity.id}' has no annotation with internal: true or a gapRef`,
`/pbp/${i}`,
);
}
}
// --- INTAKE-07: locales ----------------------------------------------
const localeTags = env.classification.locales.map((l) => l.tag);
if (new Set(localeTags).size !== localeTags.length) {
error("INTAKE-07", "duplicate BCP 47 locale tags", "/classification/locales");
}
const primaries = env.classification.locales.filter((l) => l.role === "primary");
if (primaries.length !== 1) {
error(
"INTAKE-07",
`expected exactly one primary locale, found ${primaries.length}`,
"/classification/locales",
);
}
const allowedLangs = new Set(localeTags.flatMap((tag) => [tag, tag.split("-")[0]]));
for (const [i, entity] of env.pbp.entries()) {
if (entity.schema !== CLAIM_SCHEMA || entity.status !== "published") continue;
const lang = (entity as { statementLang?: string }).statementLang;
if (lang && !allowedLangs.has(lang)) {
error(
"INTAKE-07",
`published claim '${entity.id}' statementLang '${lang}' is not among classification locales`,
`/pbp/${i}/statementLang`,
);
}
}
// --- INTAKE-08: visual axes via the live biomeAxesSchema --------------
const axesResult = biomeAxesSchema.safeParse(env.visual.axes);
if (!axesResult.success) {
for (const issue of axesResult.error.issues) {
error(
"INTAKE-08",
`visual.axes: ${issue.message}`,
`/visual/axes${issue.path.length > 0 ? `/${issue.path.map((p) => String(p)).join("/")}` : ""}`,
);
}
}
for (const [i, font] of env.visual.fonts.entries()) {
if (!font.scripts || font.scripts.length === 0) {
error(
"INTAKE-08",
`font '${font.family}' declares no ISO 15924 scripts`,
`/visual/fonts/${i}/scripts`,
);
}
}
// --- INTAKE-09: asset files -------------------------------------------
for (const [i, asset] of env.assets.entries()) {
const assetPath = `/assets/${i}`;
const fileAbs = join(bundleDir, asset.file);
if (!(await io.exists(fileAbs))) {
error("INTAKE-09", `asset file '${asset.file}' is missing`, `${assetPath}/file`);
continue;
}
const bytes = await io.readFileBytes(fileAbs);
const actualSha = `sha256:${sha256Of(bytes)}`;
if (actualSha !== asset.sha256) {
error(
"INTAKE-09",
`asset '${asset.id}' sha256 mismatch — declared ${asset.sha256}, actual ${actualSha}`,
`${assetPath}/sha256`,
);
}
const stem = asset.file.slice("assets/".length, asset.file.lastIndexOf("."));
if (SHA256_RE.test(asset.sha256) && asset.sha256 !== `sha256:${stem}`) {
error(
"INTAKE-09",
`asset '${asset.id}' sha256 does not equal its content-addressed name '${asset.file}'`,
`${assetPath}/file`,
);
}
const ext = asset.file.slice(asset.file.lastIndexOf(".") + 1);
const expectedMedia = ASSET_MEDIA_BY_EXT[ext];
if (expectedMedia && asset.mediaType !== expectedMedia) {
error(
"INTAKE-09",
`asset '${asset.id}' mediaType '${asset.mediaType}' does not match '.${ext}' (expected '${expectedMedia}')`,
`${assetPath}/mediaType`,
);
}
if (asset.origin === "generated" && !asset.generator) {
error(
"INTAKE-09",
`asset '${asset.id}' is origin 'generated' but carries no generator provenance`,
`${assetPath}/generator`,
);
}
if (asset.rights.state === "licensed-stock" && !asset.rights.licence) {
error(
"INTAKE-09",
`asset '${asset.id}' is 'licensed-stock' but declares no licence`,
`${assetPath}/rights/licence`,
);
}
}
// --- INTAKE-10: provenance completeness --------------------------------
if (env.provenance.contractSnapshot !== options.exportSha256) {
error(
"INTAKE-10",
`provenance.contractSnapshot ${env.provenance.contractSnapshot} does not match the validator's schema export ${options.exportSha256} — producer must re-vendor intake.schema.generated.yaml`,
"/provenance/contractSnapshot",
);
}
for (const [i, input] of env.provenance.inputs.entries()) {
// z.literal(false) makes clipped:true unreachable after INTAKE-02 —
// kept as a defensive belt for future envelope loosening.
if ((input as { clipped?: boolean }).clipped === true) {
error(
"INTAKE-10",
`provenance.inputs[${i}] '${input.path}' was clipped (P1 forbids)`,
`/provenance/inputs/${i}/clipped`,
);
}
}
if (env.provenance.quoteCheck.result !== "pass") {
error(
"INTAKE-10",
`provenance.quoteCheck.result is '${env.provenance.quoteCheck.result}', expected 'pass'`,
"/provenance/quoteCheck/result",
);
}
// --- INTAKE-11: cost report — warnings only, never blocks --------------
if (!(await io.exists(join(bundleDir, "cost-report.json")))) {
warn("INTAKE-11", "cost-report.json is missing from the bundle", "cost-report.json");
}
const budget = options.costBudgetUsd ?? 5;
if (env.provenance.cost.total > budget) {
warn(
"INTAKE-11",
`provenance.cost.total $${env.provenance.cost.total} exceeds budget $${budget}`,
"/provenance/cost/total",
);
}
// --- INTAKE-12: public-safe free text on published entities (AMD-005) --
// Anchored on the evidence-ledger id shape — bare words never match.
for (const [i, entity] of env.pbp.entries()) {
if (entity.status !== "published") continue;
for (const field of PBP_FREE_TEXT_FIELDS) {
const value = (entity as Record)[field];
if (typeof value === "string" && INTERNAL_MARKER_RE.test(value)) {
error(
"INTAKE-12",
`published entity '${entity.id}' ${field} contains an internal marker`,
`/pbp/${i}/${field}`,
);
}
}
}
// --- INTAKE-13: locale-native statements (AMD-005) ---------------------
// The root statement field is the primary-locale value; statementByLocale
// must cover the rest, unless the statement is locale-neutral.
const nonPrimaryLocaleTags = env.classification.locales
.filter((l) => l.role !== "primary")
.map((l) => l.tag);
for (const [i, entity] of env.pbp.entries()) {
if (entity.status !== "published" || entity.localeNeutral === true) continue;
const e = entity as Record;
const carriesStatement =
typeof e["statement"] === "string" || typeof e["summary"] === "string";
if (!carriesStatement) continue;
const byLocale = (e["statementByLocale"] ?? {}) as Record;
for (const tag of nonPrimaryLocaleTags) {
if (typeof byLocale[tag] !== "string" || byLocale[tag] === "") {
error(
"INTAKE-13",
`published entity '${entity.id}' has no statementByLocale value for site locale '${tag}' — localise the statement or mark localeNeutral`,
`/pbp/${i}/statementByLocale`,
);
}
}
}
// --- INTAKE-14: raster assets are WebP (AMD-005) -----------------------
for (const [i, asset] of env.assets.entries()) {
if (
asset.mediaType.startsWith("image/") &&
asset.mediaType !== "image/webp" &&
asset.mediaType !== "image/svg+xml"
) {
error(
"INTAKE-14",
`asset '${asset.id}' mediaType '${asset.mediaType}' is a raster type — the platform serves image/webp only`,
`/assets/${i}/mediaType`,
);
}
}
// --- INTAKE-15: closed palette vocabulary (AMD-005) --------------------
for (const key of Object.keys(env.visual.palette)) {
if (!CLOSED_PALETTE_KEYS.has(key)) {
error(
"INTAKE-15",
`visual.palette key '${key}' is outside the closed biome vocabulary — map it to a canonical key before emitting`,
`/visual/palette/${key}`,
);
}
}
}
result.status = diagnostics.some((d) => d.severity === "error") ? "fail" : "pass";
return result;
}
// ---------------------------------------------------------------------------
// Command handler
// ---------------------------------------------------------------------------
function flagString(input: KernelCommandInput, name: string): string | undefined {
const v = input.flags[name];
return typeof v === "string" ? v : undefined;
}
export async function runIntakeValidate(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const id = flagString(input, "id");
const bundle = flagString(input, "bundle");
if ((id === undefined) === (bundle === undefined)) {
return {
exitCode: 2,
summary: "[intake.validate] requires exactly one of --id or --bundle ",
};
}
const budgetRaw = flagString(input, "cost-budget-usd");
const costBudgetUsd = budgetRaw === undefined ? 5 : Number(budgetRaw);
if (!Number.isFinite(costBudgetUsd) || costBudgetUsd < 0) {
return {
exitCode: 2,
summary: `[intake.validate] --cost-budget-usd must be a non-negative number (got '${budgetRaw}')`,
};
}
const bundleDir =
bundle !== undefined
? isAbsolute(bundle)
? bundle
: resolve(context.workspaceRoot, bundle)
: join(context.workspaceRoot, "onboarding", id as string, ".input", "intake");
const rel = relative(context.workspaceRoot, bundleDir);
const bundleLabel = rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)) ? rel : bundleDir;
const exportInfo = await resolveIntakeExportSha256(context.io);
if (exportInfo === null) {
const result: IntakeValidateResult = {
command: "onboarding.intake.validate",
status: "fail",
bundle: bundleLabel,
intakeId: null,
systemId: null,
contractSnapshot: { expected: "", actual: null },
diagnostics: [
{
rule: "INTAKE-10",
severity: "error",
message:
"intake.schema.generated.yaml is absent from the installed @warpgogol/werkstatt-shared package — run `onboarding.intake.schema.export` first",
},
],
};
return {
data: result,
exitCode: 1,
summary:
"[intake.validate] schema export missing — run onboarding.intake.schema.export first",
nextSteps: [
{
action:
"Run onboarding.intake.schema.export to regenerate intake.schema.generated.yaml, then re-run this validation.",
kind: "required",
},
],
};
}
const result = await validateIntakeBundle(bundleDir, context.io, {
exportSha256: exportInfo.sha256,
costBudgetUsd,
bundleLabel,
});
const errors = result.diagnostics.filter((d) => d.severity === "error").length;
const warnings = result.diagnostics.filter((d) => d.severity === "warning").length;
return {
data: result,
exitCode: result.status === "pass" ? 0 : 1,
summary: `[intake.validate] ${result.systemId ?? bundleLabel}: ${result.status} (${errors} errors, ${warnings} warnings)`,
};
}