Files
ThothII/harness/.pi/extensions/gate/core/artifact-contracts.js
T

137 lines
4.3 KiB
JavaScript

// artifact-contracts.js -- PURE soft-validators for the v2 gate payloads (WS2).
//
// No I/O, no TypeBox: hand-rolled, permissive on extra fields. Each validator
// returns { ok: boolean, errors: string[] } with actionable messages so the model
// can self-correct (the gate returns them as a textResult, never a hard TypeBox
// failure -- models loop when validation fails BEFORE execute()).
//
// A payload without `schema_version === 2` is NOT an error: it returns
// { ok: true, legacy: true } and the caller proceeds on the legacy path.
"use strict";
function isNonEmptyString(v) {
return typeof v === "string" && v.length > 0;
}
function isArray(v) {
return Array.isArray(v);
}
const VALID_CHECK_STATUS = new Set(["ok", "warn", "fail"]);
// Contract A -- cte_plan v2. Validates the model-owned skeleton; catalog-derived
// fields (index, table/filter descriptions) are filled later by enrich, so they
// are NOT required here.
function validateCtePlanV2(data) {
if (!data || typeof data !== "object" || data.schema_version !== 2) {
return { ok: true, legacy: true };
}
const errors = [];
if (!isArray(data.ctes) || data.ctes.length === 0) {
errors.push("ctes deve essere un array non vuoto.");
return { ok: false, errors };
}
const seen = [];
data.ctes.forEach((c, i) => {
if (!c || typeof c !== "object") {
errors.push(`ctes[${i}] deve essere un oggetto.`);
return;
}
if (!isNonEmptyString(c.name)) {
errors.push(`ctes[${i}].name mancante o vuoto.`);
}
if (isArray(c.depends_on)) {
for (const dep of c.depends_on) {
if (!seen.includes(dep)) {
errors.push(
`ctes[${i}].depends_on contiene '${dep}' che non è un CTE precedente.`,
);
}
}
}
if (isNonEmptyString(c.name)) seen.push(c.name);
});
return { ok: errors.length === 0, errors };
}
// Contract B -- cte_result THIN (what the model sends). The gate builds the full
// payload from `tht cte info`; the model only supplies {purpose?, rationale?, note?}.
// Everything is optional, so a thin v2 artifact is valid as long as it declares v2.
function validateCteResultThin(data) {
if (!data || typeof data !== "object" || data.schema_version !== 2) {
return { ok: true, legacy: true };
}
const errors = [];
for (const field of ["purpose", "rationale", "note"]) {
if (data[field] !== undefined && typeof data[field] !== "string") {
errors.push(`${field} deve essere una stringa se presente.`);
}
}
return { ok: errors.length === 0, errors };
}
// Contract C -- phase summary v2.
function validatePhaseSummaryV2(data) {
if (!data || typeof data !== "object" || data.schema_version !== 2) {
return { ok: true, legacy: true };
}
const errors = [];
if (!isNonEmptyString(data.summary)) {
errors.push("summary mancante o vuoto.");
}
if (data.checks !== undefined) {
if (!isArray(data.checks)) {
errors.push("checks deve essere un array se presente.");
} else {
data.checks.forEach((c, i) => {
if (!c || typeof c !== "object" || !isNonEmptyString(c.label)) {
errors.push(`checks[${i}].label mancante o vuoto.`);
}
if (!c || !VALID_CHECK_STATUS.has(c.status)) {
errors.push(`checks[${i}].status deve essere uno tra ok|warn|fail.`);
}
});
}
}
if (data.sections !== undefined) {
if (!isArray(data.sections)) {
errors.push("sections deve essere un array se presente.");
} else {
data.sections.forEach((s, i) => {
if (!s || typeof s !== "object") {
errors.push(`sections[${i}] deve essere un oggetto.`);
return;
}
if (!isNonEmptyString(s.title)) {
errors.push(`sections[${i}].title mancante o vuoto.`);
}
if (s.items !== undefined && !isArray(s.items)) {
errors.push(`sections[${i}].items deve essere un array se presente.`);
}
});
}
}
if (data.tables !== undefined && !isArray(data.tables)) {
errors.push("tables deve essere un array se presente.");
}
if (data.open_questions !== undefined) {
if (!isArray(data.open_questions)) {
errors.push("open_questions deve essere un array di stringhe se presente.");
} else {
data.open_questions.forEach((question, i) => {
if (typeof question !== "string") {
errors.push(`open_questions[${i}] deve essere una stringa.`);
}
});
}
}
return { ok: errors.length === 0, errors };
}
module.exports = {
validateCtePlanV2,
validateCteResultThin,
validatePhaseSummaryV2,
};