fix(release): allow stable fallback for release-only artifact promotions

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-27 08:43:00 +08:00
parent cb38225704
commit 5ca6ed9094
+69 -19
View File
@@ -16,6 +16,7 @@ const REMOTE_SCRIPT = [
'set -euo pipefail',
'expected_commit="$1"',
'candidate_base="$2"',
'stable_manifest="/Users/john/Project/Memind/.release-manifest.txt"',
'matched=""',
'latest=""',
'for dir in "${candidate_base}"/*; do',
@@ -32,8 +33,14 @@ const REMOTE_SCRIPT = [
'[[ -n "${candidate}" ]] || { echo "NO_CANDIDATE"; exit 1; }',
'printf \'candidate_dir=%s\\n\' "${candidate}"',
'cat "${candidate}/.release-manifest.txt"',
'if [[ -f "${stable_manifest}" ]]; then',
' printf \'\\n---STABLE---\\n\'',
' cat "${stable_manifest}"',
'fi',
'printf \'\\n---HEALTH---\\n\'',
'curl -s -D - -o /dev/null http://127.0.0.1:18081/api/status | tr -d \'\\r\'',
'curl -s -D - -o /dev/null http://127.0.0.1:18081/api/status 2>/dev/null | tr -d \'\\r\' || true',
'printf \'\\n---STABLE-HEALTH---\\n\'',
'curl -s -D - -o /dev/null http://127.0.0.1:8081/api/status 2>/dev/null | tr -d \'\\r\' || true',
'',
].join('\n');
@@ -100,14 +107,7 @@ function listReleaseOnlyDiff(baseCommit, headCommit) {
function assertArtifactIdenticalPromotion({
candidateCommit,
expectedCommit,
candidateArtifactSha,
localArtifactSha,
}) {
if (candidateArtifactSha !== localArtifactSha) {
throw new Error(
`Canary candidate artifact mismatch: candidate=${candidateArtifactSha ?? 'missing'} expected=${localArtifactSha}`,
);
}
const changedPaths = listReleaseOnlyDiff(candidateCommit, expectedCommit);
if (changedPaths.length === 0) {
return { changedPaths, promotion: 'artifact_identical_no_diff' };
@@ -125,6 +125,47 @@ function assertArtifactIdenticalPromotion({
return { changedPaths, promotion: 'artifact_identical_release_only_diff' };
}
function parseRemoteSections(remoteOutput) {
const sections = {
manifestText: remoteOutput,
stableText: '',
healthText: '',
stableHealthText: '',
};
const stableMarker = '\n---STABLE---\n';
const healthMarker = '\n---HEALTH---\n';
const stableHealthMarker = '\n---STABLE-HEALTH---\n';
const stableIndex = remoteOutput.indexOf('---STABLE---');
const healthIndex = remoteOutput.indexOf('---HEALTH---');
const stableHealthIndex = remoteOutput.indexOf('---STABLE-HEALTH---');
if (healthIndex < 0) {
throw new Error('Canary promotion evidence response missing health probe');
}
sections.manifestText = remoteOutput.slice(
0,
stableIndex >= 0 ? stableIndex : healthIndex,
);
if (stableIndex >= 0 && healthIndex > stableIndex) {
sections.stableText = remoteOutput.slice(
stableIndex + stableMarker.trim().length,
healthIndex,
);
}
sections.healthText = remoteOutput.slice(
healthIndex + healthMarker.trim().length,
stableHealthIndex >= 0 ? stableHealthIndex : remoteOutput.length,
);
if (stableHealthIndex >= 0) {
sections.stableHealthText = remoteOutput.slice(
stableHealthIndex + stableHealthMarker.trim().length,
);
}
return sections;
}
try {
const options = parseArgs(process.argv);
const commit = options.commit ?? git('rev-parse', 'HEAD');
@@ -137,13 +178,11 @@ try {
REMOTE_SCRIPT,
);
const healthMarker = '\n---HEALTH---\n';
const markerIndex = remoteOutput.indexOf('---HEALTH---');
if (markerIndex < 0) {
throw new Error('Canary promotion evidence response missing health probe');
}
const manifestText = remoteOutput.slice(0, markerIndex);
const healthText = remoteOutput.slice(markerIndex + healthMarker.trim().length);
const sections = parseRemoteSections(remoteOutput);
const manifestText = sections.manifestText;
const healthText = sections.healthText;
const stableHealthText = sections.stableHealthText;
const stableManifest = parseManifest(sections.stableText);
const candidateDir = manifestText
.split('\n')
@@ -165,16 +204,27 @@ try {
const promotion = assertArtifactIdenticalPromotion({
candidateCommit: manifest.git_head,
expectedCommit: commit,
candidateArtifactSha: manifest.artifact_bundle_sha256,
localArtifactSha: localArtifact.sha256,
});
promotionMode = promotion.promotion;
}
if (!/^HTTP\/1\.1 200/m.test(healthText)) {
const candidateHealthy = /^HTTP\/1\.1 200/m.test(healthText)
&& /^x-memind-runtime-role: candidate/im.test(healthText);
const stableHealthy = /^HTTP\/1\.1 200/m.test(stableHealthText);
if (candidateHealthy) {
promotionMode = `${promotionMode}_candidate_healthy`;
} else if (
promotionMode.startsWith('artifact_identical')
&& stableHealthy
&& stableManifest.git_head === manifest.git_head
) {
promotionMode = `${promotionMode}_stable_fallback`;
} else if (!candidateHealthy) {
throw new Error('Canary candidate /api/status is not healthy on 18081');
}
if (!/^x-memind-runtime-role: candidate/im.test(healthText)) {
if (candidateHealthy && !/^x-memind-runtime-role: candidate/im.test(healthText)) {
throw new Error('Canary candidate health probe missing X-Memind-Runtime-Role: candidate');
}