Files
qinglong/scripts/ql3-manual-primary-canary.cjs
T

1092 lines
35 KiB
JavaScript

#!/usr/bin/env node
require('ts-node/register/transpile-only');
const crypto = require('node:crypto');
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const {
createLegacyShadowPrimaryGateReceipt,
legacyShadowPrimaryEvidenceSha256,
parseLegacyShadowPrimaryGateReceipt,
} = require('../back/runtime/domain/legacyShadowPrimaryGate');
const {
createManualPrimaryCanaryDisabledManifest,
createManualPrimaryCanaryEnabledManifest,
createManualPrimaryCanaryPlan,
createManualPrimaryCanaryQualification,
manualPrimaryCanarySha256,
parseManualPrimaryCanaryPlan,
parseManualPrimaryCanaryQualification,
} = require('../back/runtime/domain/manualPrimaryCanaryCeremony');
const {
parseRuntimeRolloutManifest,
} = require('../back/runtime/domain/runtimeRolloutManifest');
const MAX_PRIVATE_JSON_BYTES = 1024 * 1024;
const MODES = new Set([
'prepare',
'observe',
'resource',
'qualify',
'approve',
'status',
'rollback',
]);
const ROLLBACK_REASONS = new Set([
'operator_request',
'runtime_failure',
'gate_rejected',
'approval_expired',
]);
class ManualPrimaryCanaryError extends Error {
constructor(code, message = code) {
super(message);
this.name = 'ManualPrimaryCanaryError';
this.code = code;
}
}
function integer(name, value, minimum, maximum) {
if (!/^\d+$/u.test(value)) {
throw new ManualPrimaryCanaryError(
'argument_invalid',
`${name} must be an integer`,
);
}
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
throw new ManualPrimaryCanaryError(
'argument_invalid',
`${name} must be between ${minimum} and ${maximum}`,
);
}
return parsed;
}
function parseArguments(argv) {
const options = {};
for (const argument of argv) {
if (argument === '--') continue;
if (argument.startsWith('--mode=')) {
options.mode = argument.slice('--mode='.length);
} else if (argument.startsWith('--root=')) {
const value = argument.slice('--root='.length);
if (!value) throw new ManualPrimaryCanaryError('argument_invalid');
options.root = path.resolve(value);
} else if (argument.startsWith('--session=')) {
options.sessionId = argument.slice('--session='.length);
} else if (argument.startsWith('--profile=')) {
options.profile = argument.slice('--profile='.length);
} else if (argument.startsWith('--admissions=')) {
options.admissions = integer(
'--admissions',
argument.slice('--admissions='.length),
1,
128,
);
} else if (argument.startsWith('--database=')) {
const value = argument.slice('--database='.length);
if (!value) throw new ManualPrimaryCanaryError('argument_invalid');
options.database = path.resolve(value);
} else if (argument.startsWith('--approved-by=')) {
options.approvedBy = argument.slice('--approved-by='.length);
} else if (argument.startsWith('--approval-ms=')) {
options.approvalMs = integer(
'--approval-ms',
argument.slice('--approval-ms='.length),
60_000,
24 * 60 * 60 * 1_000,
);
} else if (argument.startsWith('--operator=')) {
options.operator = argument.slice('--operator='.length);
} else if (argument.startsWith('--reason=')) {
options.reason = argument.slice('--reason='.length);
} else {
throw new ManualPrimaryCanaryError(
'argument_invalid',
`Unsupported argument: ${argument}`,
);
}
}
if (!MODES.has(options.mode)) {
throw new ManualPrimaryCanaryError('argument_invalid', '--mode is invalid');
}
if (!options.root || !path.isAbsolute(options.root)) {
throw new ManualPrimaryCanaryError(
'argument_invalid',
'--root must be an absolute path',
);
}
if (!options.sessionId) {
throw new ManualPrimaryCanaryError(
'argument_invalid',
'--session is required',
);
}
if (options.mode === 'prepare') {
if (options.profile !== 'edge' && options.profile !== 'standalone') {
throw new ManualPrimaryCanaryError(
'argument_invalid',
'--profile must be edge or standalone',
);
}
if (options.admissions === undefined) {
options.admissions = options.profile === 'edge' ? 8 : 32;
}
}
if (options.mode === 'observe' && !options.database) {
throw new ManualPrimaryCanaryError(
'argument_invalid',
'--database is required for observe',
);
}
if (options.mode === 'approve') {
if (!options.approvedBy) {
throw new ManualPrimaryCanaryError(
'argument_invalid',
'--approved-by is required for approve',
);
}
options.approvalMs ??= 60 * 60 * 1_000;
}
if (options.mode === 'rollback') {
if (
!options.operator ||
options.operator.trim() !== options.operator ||
options.operator.length < 3 ||
options.operator.length > 128 ||
/[\u0000-\u001f\u007f]/u.test(options.operator) ||
!ROLLBACK_REASONS.has(options.reason)
) {
throw new ManualPrimaryCanaryError(
'argument_invalid',
'--operator and a supported --reason are required for rollback',
);
}
}
return options;
}
function assertRoot(root) {
if (!path.isAbsolute(root)) {
throw new ManualPrimaryCanaryError('root_unsafe');
}
const stat = fs.lstatSync(root);
if (
!stat.isDirectory() ||
stat.isSymbolicLink() ||
(typeof process.getuid === 'function' && stat.uid !== process.getuid()) ||
(stat.mode & 0o022) !== 0
) {
throw new ManualPrimaryCanaryError(
'root_unsafe',
'Canary root must be an owner-controlled real directory',
);
}
}
function filePath(root, fileName) {
if (path.basename(fileName) !== fileName || fileName.includes('..')) {
throw new ManualPrimaryCanaryError('file_name_invalid');
}
return path.join(root, fileName);
}
function readPrivateJson(sourcePath, maximumBytes = MAX_PRIVATE_JSON_BYTES) {
let descriptor;
try {
descriptor = fs.openSync(
sourcePath,
fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0),
);
const stat = fs.fstatSync(descriptor);
if (
!stat.isFile() ||
(stat.mode & 0o077) !== 0 ||
(typeof process.getuid === 'function' && stat.uid !== process.getuid()) ||
stat.size < 2 ||
stat.size > maximumBytes
) {
throw new ManualPrimaryCanaryError('private_file_unsafe');
}
const bytes = Buffer.alloc(stat.size);
let offset = 0;
while (offset < bytes.length) {
const count = fs.readSync(
descriptor,
bytes,
offset,
bytes.length - offset,
offset,
);
if (count === 0)
throw new ManualPrimaryCanaryError('private_file_stalled');
offset += count;
}
return {
bytes,
sha256: manualPrimaryCanarySha256(bytes),
value: JSON.parse(bytes.toString('utf8')),
};
} catch (error) {
if (error instanceof ManualPrimaryCanaryError) throw error;
throw new ManualPrimaryCanaryError(
'private_file_unavailable',
error instanceof Error ? error.message : String(error),
);
} finally {
if (descriptor !== undefined) fs.closeSync(descriptor);
}
}
function serialized(value) {
return Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
}
function syncDirectory(directory) {
const descriptor = fs.openSync(directory, fs.constants.O_RDONLY);
try {
fs.fsyncSync(descriptor);
} finally {
fs.closeSync(descriptor);
}
}
function publishNoReplace(targetPath, value) {
const bytes = Buffer.isBuffer(value) ? value : serialized(value);
const directory = path.dirname(targetPath);
const temporaryPath = path.join(
directory,
`.ql3-canary-${process.pid}-${crypto.randomBytes(8).toString('hex')}.tmp`,
);
let descriptor;
try {
descriptor = fs.openSync(temporaryPath, 'wx', 0o600);
fs.writeFileSync(descriptor, bytes);
fs.fsyncSync(descriptor);
fs.closeSync(descriptor);
descriptor = undefined;
fs.linkSync(temporaryPath, targetPath);
syncDirectory(directory);
} finally {
if (descriptor !== undefined) fs.closeSync(descriptor);
try {
fs.unlinkSync(temporaryPath);
} catch (error) {
if (error?.code !== 'ENOENT') throw error;
}
}
return { bytes, sha256: manualPrimaryCanarySha256(bytes), state: 'created' };
}
function publishOrVerify(targetPath, value) {
const expected = Buffer.isBuffer(value) ? value : serialized(value);
try {
return publishNoReplace(targetPath, expected);
} catch (error) {
if (error?.code !== 'EEXIST') throw error;
const existing = readPrivateJson(targetPath);
if (!existing.bytes.equals(expected)) {
throw new ManualPrimaryCanaryError('publication_conflict');
}
return { ...existing, state: 'existing' };
}
}
function readPlan(root, sessionId) {
const candidate = createManualPrimaryCanaryPlan({
sessionId,
profile: 'edge',
createdAtMs: 0,
admissionTarget: 8,
currentRollout: { state: 'absent' },
});
const planRead = readPrivateJson(
filePath(root, candidate.files.plan),
64 * 1024,
);
const plan = parseManualPrimaryCanaryPlan(planRead.value);
if (plan.sessionId !== sessionId) {
throw new ManualPrimaryCanaryError('plan_session_mismatch');
}
return { plan, read: planRead };
}
function currentRollout(root, now) {
const target = filePath(root, 'qinglong3-rollout.json');
let read;
try {
read = readPrivateJson(target, 64 * 1024);
} catch (error) {
if (error.code === 'private_file_unavailable' && !fs.existsSync(target)) {
return { state: 'absent', target };
}
throw error;
}
const decision = parseRuntimeRolloutManifest(read.value, now);
return {
state: decision.manifest.enabled ? 'enabled' : 'disabled',
target,
read,
manifest: decision.manifest,
};
}
function assertRolloutExpectation(plan, current) {
if (plan.currentRollout.state !== current.state) {
throw new ManualPrimaryCanaryError('rollout_changed');
}
if (
current.state === 'disabled' &&
plan.currentRollout.sha256 !== current.read.sha256
) {
throw new ManualPrimaryCanaryError('rollout_changed');
}
}
function replaceRollout(targetPath, expected, nextBytes) {
const directory = path.dirname(targetPath);
const temporaryPath = path.join(
directory,
`.ql3-rollout-${process.pid}-${crypto.randomBytes(8).toString('hex')}.tmp`,
);
let descriptor;
try {
descriptor = fs.openSync(temporaryPath, 'wx', 0o600);
fs.writeFileSync(descriptor, nextBytes);
fs.fsyncSync(descriptor);
fs.closeSync(descriptor);
descriptor = undefined;
const current = fs.existsSync(targetPath)
? readPrivateJson(targetPath, 64 * 1024)
: undefined;
if (
(expected === undefined && current !== undefined) ||
(expected !== undefined && current?.sha256 !== expected)
) {
throw new ManualPrimaryCanaryError('rollout_changed');
}
if (current === undefined) {
fs.linkSync(temporaryPath, targetPath);
fs.unlinkSync(temporaryPath);
} else {
fs.renameSync(temporaryPath, targetPath);
}
syncDirectory(directory);
} finally {
if (descriptor !== undefined) fs.closeSync(descriptor);
try {
fs.unlinkSync(temporaryPath);
} catch (error) {
if (error?.code !== 'ENOENT') throw error;
}
}
}
function validateCapture(plan, value, now) {
const evidence = value && typeof value === 'object' ? value : {};
const capture = evidence.capture ?? {};
const totals = capture.totals ?? {};
if (
evidence.schema !== 'qinglong/legacy-shadow-capture-evidence@v1' ||
evidence.profile !== plan.profile ||
evidence.qualification?.passed !== true ||
capture.profile !== plan.profile ||
capture.assessment !== 'captured' ||
capture.capturePermille !== 1_000 ||
capture.window?.basis !== 'process_local_legacy_admission' ||
!Number.isSafeInteger(capture.window?.startInclusiveMs) ||
!Number.isSafeInteger(capture.window?.endExclusiveMs) ||
capture.window.startInclusiveMs >= capture.window.endExclusiveMs ||
capture.configuredOriginCount !== 1 ||
capture.byOrigin?.length !== 1 ||
capture.byOrigin[0]?.origin !== 'manual' ||
capture.byOrigin[0]?.admitted !== plan.admissionTarget ||
capture.byOrigin[0]?.captured !== plan.admissionTarget ||
capture.byOrigin[0]?.failed !== 0 ||
capture.byOrigin[0]?.pending !== 0 ||
totals.admitted !== plan.admissionTarget ||
totals.captured !== plan.admissionTarget ||
totals.failed !== 0 ||
totals.pending !== 0 ||
capture.window?.startInclusiveMs < plan.createdAtMs ||
capture.window?.endExclusiveMs + plan.minimumSettlingAgeMs > now
) {
throw new ManualPrimaryCanaryError('capture_not_ready');
}
return capture.window;
}
function runJsonChild(script, arguments_, timeoutMs, dependencies) {
const result = (dependencies.spawnSync ?? spawnSync)(
process.execPath,
[script, ...arguments_],
{
cwd: dependencies.workspaceRoot,
encoding: 'utf8',
timeout: timeoutMs,
maxBuffer: MAX_PRIVATE_JSON_BYTES,
env: process.env,
},
);
if (result.error || result.signal || result.status !== 0) {
throw new ManualPrimaryCanaryError('evidence_child_failed');
}
if (
typeof result.stdout !== 'string' ||
Buffer.byteLength(result.stdout) > MAX_PRIVATE_JSON_BYTES
) {
throw new ManualPrimaryCanaryError('evidence_child_output_invalid');
}
try {
return JSON.parse(result.stdout);
} catch {
throw new ManualPrimaryCanaryError('evidence_child_output_invalid');
}
}
function prepare(options, dependencies) {
assertRoot(options.root);
const now = dependencies.clock.now();
const current = currentRollout(options.root, now);
if (current.state === 'enabled') {
throw new ManualPrimaryCanaryError('primary_already_enabled');
}
const expectedFiles = createManualPrimaryCanaryPlan({
sessionId: options.sessionId,
profile: options.profile,
createdAtMs: 0,
admissionTarget: options.admissions,
currentRollout: { state: 'absent' },
}).files;
const existingPlanPath = filePath(options.root, expectedFiles.plan);
if (existsRegular(existingPlanPath)) {
const existing = parseManualPrimaryCanaryPlan(
readPrivateJson(existingPlanPath, 64 * 1024).value,
);
if (
existing.profile !== options.profile ||
existing.admissionTarget !== options.admissions
) {
throw new ManualPrimaryCanaryError('publication_conflict');
}
assertRolloutExpectation(existing, current);
return {
state: 'prepared',
publication: 'existing',
sessionId: existing.sessionId,
profile: existing.profile,
admissionTarget: existing.admissionTarget,
environment: {
QL_DEPLOYMENT_PROFILE: existing.profile,
QL3_SHADOW_ORIGINS: 'manual',
QL3_SHADOW_CAPTURE_EVIDENCE_FILE: existing.files.capture,
},
automaticActivation: false,
};
}
const plan = createManualPrimaryCanaryPlan({
sessionId: options.sessionId,
profile: options.profile,
createdAtMs: now,
admissionTarget: options.admissions,
currentRollout:
current.state === 'absent'
? { state: 'absent' }
: { state: 'disabled', sha256: current.read.sha256 },
});
const publication = publishOrVerify(
filePath(options.root, plan.files.plan),
plan,
);
return {
state: 'prepared',
publication: publication.state,
sessionId: plan.sessionId,
profile: plan.profile,
admissionTarget: plan.admissionTarget,
environment: {
QL_DEPLOYMENT_PROFILE: plan.profile,
QL3_SHADOW_ORIGINS: 'manual',
QL3_SHADOW_CAPTURE_EVIDENCE_FILE: plan.files.capture,
},
automaticActivation: false,
};
}
function observe(options, dependencies) {
assertRoot(options.root);
const { plan } = readPlan(options.root, options.sessionId);
const now = dependencies.clock.now();
const capture = readPrivateJson(filePath(options.root, plan.files.capture));
const window = validateCapture(plan, capture.value, now);
const terminalPath = filePath(options.root, plan.files.terminal);
if (existsRegular(terminalPath)) {
const terminal = readPrivateJson(terminalPath).value;
if (
terminal?.schema !==
'qinglong/legacy-shadow-terminal-difference-report@v1' ||
terminal.profile !== plan.profile ||
terminal.assessment !== 'matched' ||
terminal.window?.startInclusiveMs !== window.startInclusiveMs ||
terminal.window?.endExclusiveMs !== window.endExclusiveMs ||
terminal.scanned !== plan.admissionTarget
) {
throw new ManualPrimaryCanaryError('publication_conflict');
}
return {
state: 'terminal_observed',
publication: 'existing',
sessionId: plan.sessionId,
profile: plan.profile,
assessment: terminal.assessment,
scanned: terminal.scanned,
};
}
const databaseStat = fs.lstatSync(options.database);
if (
!databaseStat.isFile() ||
databaseStat.isSymbolicLink() ||
(typeof process.getuid === 'function' &&
databaseStat.uid !== process.getuid()) ||
(databaseStat.mode & 0o022) !== 0
) {
throw new ManualPrimaryCanaryError('database_unsafe');
}
const terminal = runJsonChild(
path.join(
dependencies.workspaceRoot,
'scripts/ql3-legacy-shadow-terminal-audit.cjs',
),
[
`--database=${options.database}`,
`--profile=${plan.profile}`,
'--origin=manual',
`--window-start-ms=${window.startInclusiveMs}`,
`--window-end-ms=${window.endExclusiveMs}`,
`--observed-at-ms=${now}`,
`--minimum-settling-age-ms=${plan.minimumSettlingAgeMs}`,
'--json',
'--fail-on-difference',
],
30_000,
dependencies,
);
const publication = publishOrVerify(terminalPath, terminal);
return {
state: 'terminal_observed',
publication: publication.state,
sessionId: plan.sessionId,
profile: plan.profile,
assessment: terminal.assessment,
scanned: terminal.scanned,
};
}
function resource(options, dependencies) {
assertRoot(options.root);
const { plan } = readPlan(options.root, options.sessionId);
const resourcePath = filePath(options.root, plan.files.resource);
if (existsRegular(resourcePath)) {
const existing = readPrivateJson(resourcePath).value;
if (
existing?.fixture !==
'qinglong/legacy-shadow-resource-rollback-evidence@v1' ||
existing.profile !== plan.profile ||
existing.workload?.mode !== 'full' ||
existing.workload?.runtime !== 'compiled_backend' ||
existing.qualification?.passed !== true ||
existing.rollback?.performed !== true ||
existing.rollback?.legacyContinued !== true ||
existing.rollback?.shadowWritesStopped !== true ||
existing.rollback?.databaseIntegrity !== 'ok'
) {
throw new ManualPrimaryCanaryError('publication_conflict');
}
return {
state: 'resource_proven',
publication: 'existing',
sessionId: plan.sessionId,
profile: plan.profile,
qualified: true,
};
}
const report = runJsonChild(
path.join(
dependencies.workspaceRoot,
'scripts/ql3-legacy-shadow-resource-rollback.cjs',
),
[
`--profile=${plan.profile}`,
'--mode=full',
'--samples=8',
'--require-compiled',
'--json',
],
plan.profile === 'edge' ? 120_000 : 180_000,
dependencies,
);
const publication = publishOrVerify(resourcePath, report);
return {
state: 'resource_proven',
publication: publication.state,
sessionId: plan.sessionId,
profile: plan.profile,
qualified: report.qualification?.passed === true,
};
}
function qualify(options, dependencies) {
assertRoot(options.root);
const { plan, read: planRead } = readPlan(options.root, options.sessionId);
const gatePath = filePath(options.root, plan.files.primaryGate);
const qualificationPath = filePath(options.root, plan.files.qualification);
if (existsRegular(gatePath) && existsRegular(qualificationPath)) {
const { qualification } = verifyQualification(options.root, plan, planRead);
return {
state: 'qualified',
publication: 'existing',
sessionId: plan.sessionId,
profile: plan.profile,
admitted: qualification.counts.admitted,
automaticActivation: false,
};
}
const capture = readPrivateJson(filePath(options.root, plan.files.capture));
const terminal = readPrivateJson(filePath(options.root, plan.files.terminal));
const resourceRead = readPrivateJson(
filePath(options.root, plan.files.resource),
);
const now = dependencies.clock.now();
validateCapture(plan, capture.value, now);
const gate = existsRegular(gatePath)
? parseLegacyShadowPrimaryGateReceipt(
readPrivateJson(gatePath, 64 * 1024).value,
)
: createLegacyShadowPrimaryGateReceipt({
profile: plan.profile,
generatedAtMs: now,
capture: capture.value,
terminal: terminal.value,
resource: resourceRead.value,
});
if (gate.assessment !== 'eligible') {
throw new ManualPrimaryCanaryError(
'primary_gate_ineligible',
gate.violations.join(','),
);
}
if (
legacyShadowPrimaryEvidenceSha256(capture.value) !==
gate.evidence.captureSha256 ||
legacyShadowPrimaryEvidenceSha256(terminal.value) !==
gate.evidence.terminalSha256 ||
legacyShadowPrimaryEvidenceSha256(resourceRead.value) !==
gate.evidence.resourceSha256
) {
throw new ManualPrimaryCanaryError('primary_gate_source_drift');
}
const gatePublication = publishOrVerify(gatePath, gate);
const qualification = createManualPrimaryCanaryQualification({
plan,
planSha256: planRead.sha256,
primaryGate: gate,
primaryGateFileSha256: gatePublication.sha256,
sourceFileSha256: {
capture: capture.sha256,
terminal: terminal.sha256,
resource: resourceRead.sha256,
},
qualifiedAtMs: now,
});
const qualificationPublication = publishOrVerify(
qualificationPath,
qualification,
);
return {
state: 'qualified',
publication: qualificationPublication.state,
sessionId: plan.sessionId,
profile: plan.profile,
admitted: qualification.counts.admitted,
automaticActivation: false,
};
}
function verifyQualification(root, plan, planRead) {
const gateRead = readPrivateJson(
filePath(root, plan.files.primaryGate),
64 * 1024,
);
const gate = parseLegacyShadowPrimaryGateReceipt(gateRead.value);
const qualificationRead = readPrivateJson(
filePath(root, plan.files.qualification),
64 * 1024,
);
const qualification = parseManualPrimaryCanaryQualification(
qualificationRead.value,
);
const capture = readPrivateJson(filePath(root, plan.files.capture));
const terminal = readPrivateJson(filePath(root, plan.files.terminal));
const resource = readPrivateJson(filePath(root, plan.files.resource));
if (
qualification.sessionId !== plan.sessionId ||
qualification.profile !== plan.profile ||
qualification.planSha256 !== planRead.sha256 ||
qualification.primaryGateFileSha256 !== gateRead.sha256 ||
qualification.sourceFileSha256.capture !== capture.sha256 ||
qualification.sourceFileSha256.terminal !== terminal.sha256 ||
qualification.sourceFileSha256.resource !== resource.sha256 ||
qualification.sourceCanonicalSha256.capture !==
gate.evidence.captureSha256 ||
qualification.sourceCanonicalSha256.terminal !==
gate.evidence.terminalSha256 ||
qualification.sourceCanonicalSha256.resource !==
gate.evidence.resourceSha256 ||
qualification.window.startInclusiveMs !== gate.window.startInclusiveMs ||
qualification.window.endExclusiveMs !== gate.window.endExclusiveMs ||
qualification.counts.admitted !== gate.counts.admitted ||
qualification.counts.captured !== gate.counts.captured ||
qualification.counts.terminalScanned !== gate.counts.terminalScanned ||
qualification.counts.terminalMatched !== gate.counts.terminalMatched ||
gate.counts.admitted !== plan.admissionTarget ||
legacyShadowPrimaryEvidenceSha256(capture.value) !==
qualification.sourceCanonicalSha256.capture ||
legacyShadowPrimaryEvidenceSha256(terminal.value) !==
qualification.sourceCanonicalSha256.terminal ||
legacyShadowPrimaryEvidenceSha256(resource.value) !==
qualification.sourceCanonicalSha256.resource ||
gate.assessment !== 'eligible'
) {
throw new ManualPrimaryCanaryError('qualification_drift');
}
return { gate, gateRead, qualification, qualificationRead };
}
function approve(options, dependencies) {
assertRoot(options.root);
const { plan, read: planRead } = readPlan(options.root, options.sessionId);
const { qualification } = verifyQualification(options.root, plan, planRead);
const now = dependencies.clock.now();
const current = currentRollout(options.root, now);
if (
current.state === 'enabled' &&
current.manifest.revision === `manual-primary-${plan.sessionId}`
) {
if (
current.manifest.primaryGate.receiptSha256 !==
qualification.primaryGateFileSha256 ||
current.manifest.primaryGate.receiptFile !== plan.files.primaryGate ||
current.manifest.rollout.origins.manual !== 'primary' ||
current.manifest.approvedBy !== options.approvedBy ||
current.manifest.expiresAtMs - current.manifest.approvedAtMs !==
options.approvalMs
) {
throw new ManualPrimaryCanaryError('active_rollout_drift');
}
const selection = {
schema: 'qinglong/manual-primary-canary-selection@v1',
schemaVersion: 1,
sessionId: plan.sessionId,
profile: plan.profile,
selectedAtMs: current.manifest.approvedAtMs,
expiresAtMs: current.manifest.expiresAtMs,
manifestSha256: current.read.sha256,
priorRollout: plan.currentRollout,
};
const publication = publishOrVerify(
filePath(options.root, plan.files.selection),
selection,
);
return {
state: 'activation_approved',
publication: publication.state,
sessionId: plan.sessionId,
expiresAtMs: current.manifest.expiresAtMs,
requiresRestart: true,
};
}
assertRolloutExpectation(plan, current);
const manifest = createManualPrimaryCanaryEnabledManifest({
plan,
qualification,
approvedBy: options.approvedBy,
approvedAtMs: now,
approvalMs: options.approvalMs,
});
const manifestBytes = serialized(manifest);
if (current.state === 'disabled') {
publishOrVerify(
filePath(options.root, plan.files.previousRollout),
current.read.bytes,
);
}
replaceRollout(
current.target,
current.state === 'disabled' ? current.read.sha256 : undefined,
manifestBytes,
);
const manifestSha256 = manualPrimaryCanarySha256(manifestBytes);
const selection = {
schema: 'qinglong/manual-primary-canary-selection@v1',
schemaVersion: 1,
sessionId: plan.sessionId,
profile: plan.profile,
selectedAtMs: now,
expiresAtMs: manifest.expiresAtMs,
manifestSha256,
priorRollout: plan.currentRollout,
};
const selectionPublication = publishOrVerify(
filePath(options.root, plan.files.selection),
selection,
);
return {
state: 'activation_approved',
publication: selectionPublication.state,
sessionId: plan.sessionId,
expiresAtMs: manifest.expiresAtMs,
requiresRestart: true,
};
}
function rollback(options, dependencies) {
assertRoot(options.root);
const { plan } = readPlan(options.root, options.sessionId);
const now = dependencies.clock.now();
const target = filePath(options.root, plan.files.rollout);
const currentRead = readPrivateJson(target, 64 * 1024);
const raw = currentRead.value;
const evaluationTime =
raw?.enabled === true && Number.isSafeInteger(raw.approvedAtMs)
? raw.approvedAtMs
: now;
const decision = parseRuntimeRolloutManifest(raw, evaluationTime);
const disabled = createManualPrimaryCanaryDisabledManifest(plan.sessionId);
const disabledBytes = serialized(disabled);
const disabledSha256 = manualPrimaryCanarySha256(disabledBytes);
if (decision.manifest.enabled === false) {
if (currentRead.sha256 !== disabledSha256) {
throw new ManualPrimaryCanaryError('different_disabled_rollout');
}
} else if (
decision.manifest.revision !== `manual-primary-${plan.sessionId}`
) {
throw new ManualPrimaryCanaryError('rollout_session_mismatch');
}
const intentPath = filePath(options.root, plan.files.rollbackIntent);
let intent;
let intentSha256;
if (existsRegular(intentPath)) {
const intentRead = readPrivateJson(intentPath, 64 * 1024);
intent = intentRead.value;
intentSha256 = intentRead.sha256;
if (
intent?.schema !== 'qinglong/manual-primary-canary-rollback-intent@v1' ||
intent.sessionId !== plan.sessionId ||
intent.profile !== plan.profile ||
intent.operator !== options.operator ||
intent.reason !== options.reason ||
intent.disabledManifestSha256 !== disabledSha256 ||
(decision.manifest.enabled === true &&
intent.enabledManifestSha256 !== currentRead.sha256)
) {
throw new ManualPrimaryCanaryError('publication_conflict');
}
} else {
if (decision.manifest.enabled === false) {
throw new ManualPrimaryCanaryError('rollback_intent_missing');
}
intent = {
schema: 'qinglong/manual-primary-canary-rollback-intent@v1',
schemaVersion: 1,
sessionId: plan.sessionId,
profile: plan.profile,
createdAtMs: now,
operator: options.operator,
reason: options.reason,
enabledManifestSha256: currentRead.sha256,
disabledManifestSha256: disabledSha256,
};
intentSha256 = publishNoReplace(intentPath, intent).sha256;
}
if (decision.manifest.enabled === true) {
replaceRollout(target, currentRead.sha256, disabledBytes);
}
const completedPath = filePath(options.root, plan.files.rollbackComplete);
let publication;
if (existsRegular(completedPath)) {
const completed = readPrivateJson(completedPath, 64 * 1024).value;
if (
completed?.schema !==
'qinglong/manual-primary-canary-rollback-complete@v1' ||
completed.sessionId !== plan.sessionId ||
completed.profile !== plan.profile ||
completed.intentSha256 !== intentSha256 ||
completed.disabledManifestSha256 !== disabledSha256
) {
throw new ManualPrimaryCanaryError('publication_conflict');
}
publication = { state: 'existing' };
} else {
publication = publishNoReplace(completedPath, {
schema: 'qinglong/manual-primary-canary-rollback-complete@v1',
schemaVersion: 1,
sessionId: plan.sessionId,
profile: plan.profile,
completedAtMs: now,
intentSha256,
disabledManifestSha256: disabledSha256,
});
}
return {
state: 'rolled_back',
publication: publication.state,
sessionId: plan.sessionId,
profile: plan.profile,
};
}
function existsRegular(target) {
try {
const stat = fs.lstatSync(target);
return stat.isFile() && !stat.isSymbolicLink();
} catch (error) {
if (error?.code === 'ENOENT') return false;
throw error;
}
}
function status(options, dependencies) {
assertRoot(options.root);
const { plan, read: planRead } = readPlan(options.root, options.sessionId);
const present = Object.fromEntries(
[
'capture',
'terminal',
'resource',
'primaryGate',
'qualification',
'selection',
'rollbackIntent',
'rollbackComplete',
].map((name) => [
name,
existsRegular(filePath(options.root, plan.files[name])),
]),
);
let stage = 'prepared';
if (present.capture) stage = 'captured';
if (present.terminal) stage = 'terminal_observed';
if (present.resource) stage = 'resource_proven';
if (present.primaryGate || present.qualification) {
if (!present.primaryGate || !present.qualification) {
throw new ManualPrimaryCanaryError('qualification_incomplete');
}
verifyQualification(options.root, plan, planRead);
stage = 'qualified';
}
const rolloutTarget = filePath(options.root, plan.files.rollout);
let rolloutMode = 'off';
let approvalExpired = false;
if (existsRegular(rolloutTarget)) {
const read = readPrivateJson(rolloutTarget, 64 * 1024);
const raw = read.value;
const now = dependencies.clock.now();
approvalExpired =
raw?.enabled === true &&
Number.isSafeInteger(raw.expiresAtMs) &&
raw.expiresAtMs <= now;
const evaluatedAt = approvalExpired ? raw.approvedAtMs : now;
const decision = parseRuntimeRolloutManifest(raw, evaluatedAt);
if (decision.manifest.enabled && !approvalExpired) {
if (decision.manifest.revision !== `manual-primary-${plan.sessionId}`) {
throw new ManualPrimaryCanaryError('rollout_session_mismatch');
}
rolloutMode = 'primary_selected';
stage = 'activation_approved';
} else if (approvalExpired) {
stage = 'approval_expired';
}
}
if (present.rollbackComplete) {
const disabled = createManualPrimaryCanaryDisabledManifest(plan.sessionId);
const read = readPrivateJson(rolloutTarget, 64 * 1024);
if (read.sha256 !== manualPrimaryCanarySha256(serialized(disabled))) {
throw new ManualPrimaryCanaryError('rollback_not_effective');
}
rolloutMode = 'off';
stage = 'rolled_back';
}
return {
state: stage,
sessionId: plan.sessionId,
profile: plan.profile,
admissionTarget: plan.admissionTarget,
rolloutMode,
approvalExpired,
runtimeActivationObserved: false,
artifacts: present,
};
}
function run(options, overrides = {}) {
const dependencies = {
clock: overrides.clock ?? { now: () => Date.now() },
spawnSync: overrides.spawnSync,
workspaceRoot: overrides.workspaceRoot ?? path.resolve(__dirname, '..'),
};
switch (options.mode) {
case 'prepare':
return prepare(options, dependencies);
case 'observe':
return observe(options, dependencies);
case 'resource':
return resource(options, dependencies);
case 'qualify':
return qualify(options, dependencies);
case 'approve':
return approve(options, dependencies);
case 'rollback':
return rollback(options, dependencies);
case 'status':
return status(options, dependencies);
default:
throw new ManualPrimaryCanaryError('mode_invalid');
}
}
function main() {
const result = run(parseArguments(process.argv.slice(2)));
process.stdout.write(`${JSON.stringify(result)}\n`);
}
module.exports = {
MAX_PRIVATE_JSON_BYTES,
ManualPrimaryCanaryError,
parseArguments,
publishNoReplace,
publishOrVerify,
readPrivateJson,
run,
serialized,
};
if (require.main === module) {
try {
main();
} catch (error) {
const code =
error instanceof ManualPrimaryCanaryError
? error.code
: error instanceof Error
? error.name
: 'unknown';
process.stderr.write(`${code}\n`);
process.exitCode = 1;
}
}