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

416 lines
14 KiB
JavaScript

#!/usr/bin/env node
require('ts-node/register/transpile-only');
const fs = require('node:fs');
const path = require('node:path');
const {
legacyShadowPrimaryEvidenceSha256,
parseLegacyShadowPrimaryGateReceipt,
} = require('../back/runtime/domain/legacyShadowPrimaryGate');
const {
createManualPrimaryCanaryDisabledManifest,
manualPrimaryCanaryFileSet,
manualPrimaryCanarySha256,
parseManualPrimaryCanaryPlan,
parseManualPrimaryCanaryQualification,
} = require('../back/runtime/domain/manualPrimaryCanaryCeremony');
const {
parseRuntimeRolloutManifest,
} = require('../back/runtime/domain/runtimeRolloutManifest');
const {
MANUAL_PRIMARY_RUNTIME_RECEIPT_FILE,
MAX_MANUAL_PRIMARY_RUNTIME_RECEIPT_BYTES,
parseManualPrimaryRuntimeReceipt,
} = require('../back/runtime/domain/manualPrimaryRuntimeReceipt');
const {
readPrivateJson,
serialized,
} = require('./ql3-manual-primary-canary.cjs');
const REQUIREMENTS = new Set([
'prepared',
'qualified',
'selected',
'active',
'off',
'rolled-back',
]);
const ROLLBACK_REASONS = new Set([
'operator_request',
'runtime_failure',
'gate_rejected',
'approval_expired',
]);
function hasExactKeys(value, expected) {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return false;
}
const actual = Object.keys(value).sort();
const wanted = [...expected].sort();
return (
actual.length === wanted.length &&
actual.every((key, index) => key === wanted[index])
);
}
function parseArguments(argv) {
const options = { require: 'prepared' };
for (const argument of argv) {
if (argument === '--') continue;
if (argument.startsWith('--root=')) {
const value = argument.slice('--root='.length);
if (!value) throw new TypeError('--root must not be empty');
options.root = path.resolve(value);
} else if (argument.startsWith('--session=')) {
options.sessionId = argument.slice('--session='.length);
} else if (argument.startsWith('--require=')) {
options.require = argument.slice('--require='.length);
} else {
throw new TypeError(`Unsupported argument: ${argument}`);
}
}
if (!options.root || !path.isAbsolute(options.root)) {
throw new TypeError('--root must be absolute');
}
if (!options.sessionId) throw new TypeError('--session is required');
if (!REQUIREMENTS.has(options.require)) {
throw new TypeError('--require is invalid');
}
return options;
}
function regularFile(target) {
try {
const stat = fs.lstatSync(target);
return stat.isFile() && !stat.isSymbolicLink();
} catch (error) {
if (error?.code === 'ENOENT') return false;
throw error;
}
}
function inspectRuntimeProcess(identity) {
if (identity.kind !== 'linux-proc' || process.platform !== 'linux') {
return 'unsupported';
}
let bootId;
try {
bootId = fs.readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim();
} catch (error) {
if (error?.code === 'ENOENT') return 'unsupported';
throw error;
}
if (bootId !== identity.bootId) return 'identity_mismatch';
let stat;
try {
stat = fs.readFileSync(`/proc/${identity.pid}/stat`, 'utf8');
} catch (error) {
if (error?.code === 'ENOENT' || error?.code === 'ESRCH') return 'exited';
throw error;
}
const close = stat.lastIndexOf(')');
const fields =
close < 1
? []
: stat
.slice(close + 1)
.trim()
.split(/\s+/u);
if (
fields.length < 20 ||
Number(fields[2]) !== identity.processGroupId ||
fields[19] !== identity.startTimeTicks
) {
return 'identity_mismatch';
}
return ['Z', 'X', 'x'].includes(fields[0]) ? 'exited' : 'running';
}
function run(options, dependencies = {}) {
const rootStat = fs.lstatSync(options.root);
if (
!rootStat.isDirectory() ||
rootStat.isSymbolicLink() ||
(rootStat.mode & 0o022) !== 0 ||
(typeof process.getuid === 'function' && rootStat.uid !== process.getuid())
) {
throw new TypeError('Canary root is unsafe');
}
const files = manualPrimaryCanaryFileSet(options.sessionId);
const resolve = (name) => path.join(options.root, files[name]);
const planRead = readPrivateJson(resolve('plan'), 64 * 1024);
const plan = parseManualPrimaryCanaryPlan(planRead.value);
if (plan.sessionId !== options.sessionId) {
throw new TypeError('Canary plan session mismatch');
}
const gatePresent = regularFile(resolve('primaryGate'));
const qualificationPresent = regularFile(resolve('qualification'));
if (gatePresent !== qualificationPresent) {
throw new TypeError('Canary qualification is partial');
}
let eligible = false;
if (gatePresent) {
const gateRead = readPrivateJson(resolve('primaryGate'), 64 * 1024);
const gate = parseLegacyShadowPrimaryGateReceipt(gateRead.value);
const qualification = parseManualPrimaryCanaryQualification(
readPrivateJson(resolve('qualification'), 64 * 1024).value,
);
const sources = {
capture: readPrivateJson(resolve('capture')),
terminal: readPrivateJson(resolve('terminal')),
resource: readPrivateJson(resolve('resource')),
};
if (
gate.assessment !== 'eligible' ||
gate.profile !== plan.profile ||
gate.origin !== 'manual' ||
gate.counts.admitted !== plan.admissionTarget ||
qualification.sessionId !== plan.sessionId ||
qualification.profile !== plan.profile ||
qualification.planSha256 !== planRead.sha256 ||
qualification.primaryGateFileSha256 !== gateRead.sha256 ||
qualification.sourceFileSha256.capture !== sources.capture.sha256 ||
qualification.sourceFileSha256.terminal !== sources.terminal.sha256 ||
qualification.sourceFileSha256.resource !== sources.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 ||
legacyShadowPrimaryEvidenceSha256(sources.capture.value) !==
qualification.sourceCanonicalSha256.capture ||
legacyShadowPrimaryEvidenceSha256(sources.terminal.value) !==
qualification.sourceCanonicalSha256.terminal ||
legacyShadowPrimaryEvidenceSha256(sources.resource.value) !==
qualification.sourceCanonicalSha256.resource
) {
throw new TypeError('Canary qualification drifted');
}
eligible = true;
}
const rolloutPath = resolve('rollout');
let rolloutMode = 'off';
let approvalExpired = false;
let rolloutSha256;
if (regularFile(rolloutPath)) {
const rolloutRead = readPrivateJson(rolloutPath, 64 * 1024);
const raw = rolloutRead.value;
const now = Date.now();
approvalExpired =
raw?.enabled === true &&
Number.isSafeInteger(raw.expiresAtMs) &&
raw.expiresAtMs <= now;
const evaluatedAt = approvalExpired ? raw.approvedAtMs : now;
const decision = parseRuntimeRolloutManifest(raw, evaluatedAt);
rolloutSha256 = rolloutRead.sha256;
if (decision.manifest.enabled && !approvalExpired) {
if (
decision.manifest.revision !== `manual-primary-${plan.sessionId}` ||
!eligible ||
decision.manifest.primaryGate.receiptSha256 !==
readPrivateJson(resolve('primaryGate'), 64 * 1024).sha256
) {
throw new TypeError('Active rollout is not bound to the canary');
}
if (!regularFile(resolve('selection'))) {
throw new TypeError('Selected rollout has no selection receipt');
}
const selection = readPrivateJson(resolve('selection'), 64 * 1024).value;
if (
!hasExactKeys(selection, [
'schema',
'schemaVersion',
'sessionId',
'profile',
'selectedAtMs',
'expiresAtMs',
'manifestSha256',
'priorRollout',
]) ||
selection?.schema !== 'qinglong/manual-primary-canary-selection@v1' ||
selection.schemaVersion !== 1 ||
selection.sessionId !== plan.sessionId ||
selection.profile !== plan.profile ||
selection.selectedAtMs !== decision.manifest.approvedAtMs ||
selection.manifestSha256 !== rolloutRead.sha256 ||
selection.expiresAtMs !== decision.manifest.expiresAtMs ||
JSON.stringify(selection.priorRollout) !==
JSON.stringify(plan.currentRollout)
) {
throw new TypeError('Selection receipt drifted');
}
rolloutMode = 'primary_selected';
}
}
const rolledBack = regularFile(resolve('rollbackComplete'));
if (rolledBack) {
const expectedDisabled = serialized(
createManualPrimaryCanaryDisabledManifest(plan.sessionId),
);
if (
!regularFile(rolloutPath) ||
readPrivateJson(rolloutPath, 64 * 1024).sha256 !==
manualPrimaryCanarySha256(expectedDisabled)
) {
throw new TypeError('Rollback completion is not effective');
}
const intentRead = readPrivateJson(resolve('rollbackIntent'), 64 * 1024);
const intent = intentRead.value;
const completed = readPrivateJson(
resolve('rollbackComplete'),
64 * 1024,
).value;
const disabledSha256 = manualPrimaryCanarySha256(expectedDisabled);
if (
!hasExactKeys(intent, [
'schema',
'schemaVersion',
'sessionId',
'profile',
'createdAtMs',
'operator',
'reason',
'enabledManifestSha256',
'disabledManifestSha256',
]) ||
intent?.schema !== 'qinglong/manual-primary-canary-rollback-intent@v1' ||
intent.schemaVersion !== 1 ||
intent.sessionId !== plan.sessionId ||
intent.profile !== plan.profile ||
typeof intent.operator !== 'string' ||
intent.operator.length < 3 ||
intent.operator.length > 128 ||
/[\u0000-\u001f\u007f]/u.test(intent.operator) ||
!ROLLBACK_REASONS.has(intent.reason) ||
!Number.isSafeInteger(intent.createdAtMs) ||
!/^[a-f0-9]{64}$/u.test(intent.enabledManifestSha256) ||
intent.disabledManifestSha256 !== disabledSha256 ||
!hasExactKeys(completed, [
'schema',
'schemaVersion',
'sessionId',
'profile',
'completedAtMs',
'intentSha256',
'disabledManifestSha256',
]) ||
completed?.schema !==
'qinglong/manual-primary-canary-rollback-complete@v1' ||
completed.schemaVersion !== 1 ||
completed.sessionId !== plan.sessionId ||
completed.profile !== plan.profile ||
!Number.isSafeInteger(completed.completedAtMs) ||
completed.completedAtMs < intent.createdAtMs ||
completed.intentSha256 !== intentRead.sha256 ||
completed.disabledManifestSha256 !== disabledSha256
) {
throw new TypeError('Rollback receipt chain drifted');
}
rolloutMode = 'off';
}
let runtimeActivationObserved = false;
let runtimeActivationCurrent = false;
let runtimeReceiptState = 'missing';
let runtimeProcessState = 'missing';
const runtimeReceiptPath = path.join(
options.root,
MANUAL_PRIMARY_RUNTIME_RECEIPT_FILE,
);
if (regularFile(runtimeReceiptPath)) {
const receipt = parseManualPrimaryRuntimeReceipt(
readPrivateJson(
runtimeReceiptPath,
MAX_MANUAL_PRIMARY_RUNTIME_RECEIPT_BYTES,
).value,
);
runtimeReceiptState = receipt.state;
const selectionPath = resolve('selection');
if (regularFile(selectionPath)) {
const selection = readPrivateJson(selectionPath, 64 * 1024).value;
runtimeActivationObserved =
receipt.revision === `manual-primary-${plan.sessionId}` &&
receipt.profile === plan.profile &&
selection?.schema === 'qinglong/manual-primary-canary-selection@v1' &&
selection.sessionId === plan.sessionId &&
selection.profile === plan.profile &&
selection.manifestSha256 === receipt.rolloutSourceSha256;
}
if (receipt.state === 'active') {
runtimeProcessState = (
dependencies.inspectRuntimeProcess ?? inspectRuntimeProcess
)(receipt.process);
runtimeActivationCurrent = runtimeProcessState === 'running';
}
}
const compatible =
options.require === 'prepared' ||
(options.require === 'qualified' && eligible) ||
(options.require === 'selected' && rolloutMode === 'primary_selected') ||
(options.require === 'active' &&
rolloutMode === 'primary_selected' &&
runtimeActivationObserved &&
runtimeActivationCurrent) ||
(options.require === 'off' &&
rolloutMode === 'off' &&
!runtimeActivationCurrent) ||
(options.require === 'rolled-back' &&
rolloutMode === 'off' &&
rolledBack &&
!runtimeActivationCurrent);
const report = {
schema: 'qinglong/manual-primary-canary-audit@v1',
schemaVersion: 1,
sessionId: plan.sessionId,
profile: plan.profile,
admissionTarget: plan.admissionTarget,
eligible,
rolloutMode,
approvalExpired,
runtimeActivationObserved,
runtimeActivationCurrent,
runtimeReceiptState,
runtimeProcessState,
rolledBack,
planSha256: planRead.sha256,
...(rolloutSha256 === undefined ? {} : { rolloutSha256 }),
requirement: options.require,
compatible,
};
if (!compatible) {
const error = new Error(
'Manual Primary canary requirement is not satisfied',
);
error.report = report;
throw error;
}
return report;
}
function main() {
process.stdout.write(
`${JSON.stringify(run(parseArguments(process.argv.slice(2))))}\n`,
);
}
module.exports = { parseArguments, run };
if (require.main === module) {
try {
main();
} catch (error) {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
}
}