Review execution semantic-major changes without storage migration planning

This commit is contained in:
Timothy J. Aveni
2026-09-21 01:38:15 -07:00
parent 01a3ec5ae2
commit 70344ca06a
3 changed files with 111 additions and 17 deletions
+53 -14
View File
@@ -367,6 +367,36 @@ export const planEvolution = (
...(next ? { candidate: next } : {}),
});
}
const semanticReview = assessSemanticReviews(baseline, candidate, options.reviews ?? [], checkerVersion, [
...nextRuntimes.values(),
]);
const reviews = semanticReview.reviews;
blockers.push(...semanticReview.blockers);
return {
schemaVersion: 1,
baselineDigest: baseline ? contentDigest(baseline) : null,
candidateDigest,
checkerVersion,
runtimeActions,
storageChanges,
migrationRequired: storageChanges.filter((entry) => entry.requiresMigration).map((entry) => entry.id),
reviews,
packageChecks: runtimeActions
.filter((entry) => entry.candidate && entry.action !== "keep")
.map((entry) => ({ groupId: entry.groupId, contractDigest: entry.candidate!.digest })),
blockers,
};
};
/** Review dependency contracts, without making any storage or process-placement decision. */
function assessSemanticReviews(
baseline: WorkspaceRevision | null,
candidate: WorkspaceRevision,
receipts: readonly EvolutionReview[],
checkerVersion: string,
consumers: readonly RuntimeContract[],
): Pick<EvolutionReport, "reviews" | "blockers"> {
const blockers: string[] = [];
const providers = (workspace: WorkspaceRevision) => [
...workspace.packageImports.map((entry) => ({
id: entry.packageId as string,
@@ -393,7 +423,7 @@ export const planEvolution = (
}
if (!old[0] || old[0].major === provider.major) continue;
if (provider.major < old[0].major) blockers.push(`Semantic major decreases for ${provider.id}`);
for (const consumer of nextRuntimes.values()) {
for (const consumer of consumers) {
if (consumer.packageId === provider.id || !consumer.reviewProviders.includes(provider.id)) continue;
const requirement = {
consumerId: consumer.groupId,
@@ -408,7 +438,7 @@ export const planEvolution = (
consumer: consumer.digest,
checkerVersion,
});
const accepted = (options.reviews ?? []).some(
const accepted = receipts.some(
(entry) =>
entry.requirementDigest === requirementDigest &&
["changed", "accepted-unchanged"].includes(entry.decision) &&
@@ -419,18 +449,27 @@ export const planEvolution = (
if (!accepted) blockers.push(`Semantic-major review required: ${consumer.groupId} consumes ${provider.id}`);
}
}
return { reviews, blockers };
}
export function reviewSemanticMajors(
baseline: WorkspaceRevision | null,
candidate: WorkspaceRevision,
receipts: readonly EvolutionReview[] = [],
) {
for (const workspace of baseline ? [baseline, candidate] : [candidate]) {
const issues = validateWorkspaceRevision(workspace);
if (issues.length)
throw new Error("Invalid semantic-review workspace: " + issues.map((issue) => issue.message).join("; "));
}
if (baseline && baseline.workspaceId !== candidate.workspaceId)
throw new Error("Cannot review a different workspace");
const checkerVersion = "quixos-semantic-review-v1";
const report = assessSemanticReviews(baseline, candidate, receipts, checkerVersion, runtimeContracts(candidate));
return {
schemaVersion: 1,
baselineDigest: baseline ? contentDigest(baseline) : null,
candidateDigest,
checkerVersion,
runtimeActions,
storageChanges,
migrationRequired: storageChanges.filter((entry) => entry.requiresMigration).map((entry) => entry.id),
reviews,
packageChecks: runtimeActions
.filter((entry) => entry.candidate && entry.action !== "keep")
.map((entry) => ({ groupId: entry.groupId, contractDigest: entry.candidate!.digest })),
blockers,
baselineDigest: baseline ? contentDigest(baseline) : null,
candidateDigest: contentDigest(candidate),
...report,
};
};
}