fix(reference): verify station coverage and rollback inputs

This commit is contained in:
jb
2026-09-19 09:53:46 -04:00
parent 24660edb09
commit 7601365621
5 changed files with 69 additions and 8 deletions
+14 -5
View File
@@ -339,7 +339,7 @@ export class ReferenceCompiler {
if (!manifest.active || !manifest.previous) throw new Error('no previous reference artifact is available for rollback');
const previousPath = artifactDatabasePath(this.directory, manifest.previous.database);
if (!await exists(previousPath)) throw new Error('previous reference database is missing');
validateRollbackArtifact(previousPath, manifest.previous, this.profile, this.importerVersion, this.thresholds);
await validateRollbackArtifact(this.directory, previousPath, manifest.previous, this.profile, this.importerVersion, this.thresholds);
const next: ReferenceManifest = { ...manifest, active: manifest.previous, previous: manifest.active, lastAttempt: { startedAt: iso(this.now()), finishedAt: iso(this.now()), status: 'success', profile: manifest.previous.profile, version: manifest.previous.version } };
await atomicWriteJson(this.directory, MANIFEST_FILE, next);
return { activeVersion: next.active!.version, previousVersion: next.previous?.version };
@@ -347,19 +347,28 @@ export class ReferenceCompiler {
}
}
function validateRollbackArtifact(path: string, artifact: ActiveManifest, expectedProfile: BuildProfile, expectedImporterVersion: string, thresholds: ValidationThresholds): void {
if (artifact.profile !== expectedProfile || artifact.importerVersion !== expectedImporterVersion || artifact.stationCoverage !== thresholds.stationCoverage) throw new Error('previous reference artifact is incompatible with the current compiler/profile');
async function validateRollbackArtifact(directory: string, path: string, artifact: ActiveManifest, expectedProfile: BuildProfile, expectedImporterVersion: string, thresholds: ValidationThresholds): Promise<void> {
if (artifact.profile !== expectedProfile || artifact.importerVersion !== expectedImporterVersion || artifact.stationCoverage !== thresholds.stationCoverage || basename(path) !== `${artifact.version}.sqlite`) throw new Error('previous reference artifact is incompatible with the current compiler/profile');
if (!artifact.sourceInputs || !artifact.counts) throw new Error('previous reference artifact is missing compatibility metadata');
let db: Database | undefined;
try {
for (const source of REFERENCE_SOURCES) {
const expected = artifact.sourceInputs[source];
if (!expected) throw new Error(`previous reference artifact is missing source input metadata for ${source}`);
await readSourceCache(directory, source, expected.cacheFile, cacheExpectation(source, expected, expectedImporterVersion, expectedProfile));
}
db = new Database(path, { readonly: true, strict: true });
const counts = validateCandidate(db, thresholds, artifact.profile);
for (const [table, expected] of Object.entries(artifact.counts)) if (counts[table] !== expected) throw new Error(`previous reference artifact count mismatch for ${table}: expected ${expected}, got ${counts[table]}`);
for (const key of Object.keys(counts)) {
if (!(key in artifact.counts)) throw new Error(`previous reference artifact is missing count metadata for ${key}`);
const expected = artifact.counts[key];
if (counts[key] !== expected) throw new Error(`previous reference artifact count mismatch for ${key}: expected ${expected}, got ${counts[key]}`);
}
const rows = db.query<{ source: string; fingerprint: string; local_sha256: string }, []>('SELECT source,fingerprint,local_sha256 FROM source_provenance').all();
const bySource = new Map(rows.map((row) => [row.source, row]));
for (const source of REFERENCE_SOURCES) {
const expected = artifact.sourceInputs[source]; const row = bySource.get(source);
if (!expected || !row || row.fingerprint !== expected.fingerprint || row.local_sha256 !== expected.localSha256) throw new Error(`previous reference artifact provenance mismatch for ${source}`);
if (!row || row.fingerprint !== expected.fingerprint || row.local_sha256 !== expected.localSha256) throw new Error(`previous reference artifact provenance mismatch for ${source}`);
}
} finally { try { db?.close(); } catch { /* best effort */ } }
}