Defer promotion snapshots to campaign activation

This commit is contained in:
2026-10-06 00:16:43 -04:00
parent d6ff59b129
commit 9fd448339d
15 changed files with 179 additions and 56 deletions
+10 -27
View File
@@ -35,7 +35,7 @@ import {
type ScriptVersionRow
} from './scripts.js';
import { composeServerCfg } from './config-compose.js';
import { getServerScript, removeScript } from './inventory.js';
import { removeScript } from './inventory.js';
import { cancelStagedFiles, isFileApplied, queueFileChange, stagedFileChanges } from './staged-files.js';
import { emit } from '../pipeline/bus.js';
import { cancelProvisioningJob } from '../provisioning/cancellation.js';
@@ -976,7 +976,6 @@ async function promoteBetaInternal(betaId: number, reviewedToken?: string): Prom
const targets = reviewed.preview.files.find(file => file.id === script.id)!.servers;
for (const name of targets) {
const dest = destOfKind(name, script.kind);
let plan: Plan;
if (betaServerSet.has(name)) {
const bss = getBetaServerScript(betaId, name, script.id);
@@ -1001,21 +1000,9 @@ async function promoteBetaInternal(betaId: number, reviewedToken?: string): Prom
plan = { script, version, server: name, priorVersionId: null, priorSha: null, priorContent: null, hadFile: false };
}
} else {
// Non-beta matching-scope server: current on-disk file (if any)
// is the "before" state for a promote-then-rollback round trip.
const targetPath = join(dest, script.target_rel_path);
if (await exists(targetPath)) {
const priorContent = await readFile(targetPath);
const inv = getServerScript(name, script.target_rel_path);
plan = {
script, version, server: name,
priorVersionId: inv?.script_version_id ?? null,
priorSha: sha256Buffer(priorContent),
priorContent, hadFile: true
};
} else {
plan = { script, version, server: name, priorVersionId: null, priorSha: null, priorContent: null, hadFile: false };
}
// Campaign activation captures the actual before image just
// before replacing this file. Do not wait on remote reads here.
plan = { script, version, server: name, priorVersionId: null, priorSha: null, priorContent: null, hadFile: false };
}
plans.push(plan);
}
@@ -1068,12 +1055,6 @@ async function promoteBetaInternal(betaId: number, reviewedToken?: string): Prom
hadFile = true;
}
}
if (!betaServerSet.has(name) && await exists(targetPath)) {
// Capture an initial preview; activation records the actual before image.
priorContent = await readFile(targetPath);
priorSha = sha256Buffer(priorContent);
hadFile = true;
}
const requiresActivation = !betaServerSet.has(name) || !await exists(targetPath)
|| sha256Buffer(await readFile(targetPath)) !== composedSha;
cfgPlans.push({ server: name, priorContent, priorSha, composedContent: composedBuf, composedSha, hadFile, requiresActivation });
@@ -1114,8 +1095,8 @@ async function promoteBetaInternal(betaId: number, reviewedToken?: string): Prom
);
const insertDepFile = db().prepare(
`INSERT INTO deployment_files
(deployment_id, server, verb, rel_path, sha256_before, sha256_after, content_before, content_after)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
(deployment_id, server, verb, rel_path, sha256_before, sha256_after, content_before, content_after, snapshot_pending)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
);
const uniqueServers = Array.from(new Set([
@@ -1140,7 +1121,8 @@ async function promoteBetaInternal(betaId: number, reviewedToken?: string): Prom
p.priorSha,
p.version.sha256,
p.priorContent,
p.version.content
p.version.content,
betaServerSet.has(p.server) ? 0 : 1
);
if (plansToActivate.has(p)) {
queueFileChange({ sourceKind: 'campaign', sourceId: depId, server: p.server,
@@ -1159,7 +1141,8 @@ async function promoteBetaInternal(betaId: number, reviewedToken?: string): Prom
cp.priorSha,
cp.composedSha,
cp.priorContent,
cp.composedContent
cp.composedContent,
betaServerSet.has(cp.server) ? 0 : 1
);
if (cp.requiresActivation) {
queueFileChange({ sourceKind: 'campaign', sourceId: depId, server: cp.server,