Files
qinglong/test/back/runtimeRolloutManifestLoader.test.cjs
T

242 lines
7.8 KiB
JavaScript

require('ts-node/register/transpile-only');
const assert = require('node:assert/strict');
const crypto = require('node:crypto');
const fs = require('node:fs/promises');
const os = require('node:os');
const path = require('node:path');
const { afterEach, test } = require('node:test');
const {
loadRuntimeRolloutManifest,
} = require('../../back/runtime/adapters/fs/runtimeRolloutManifestLoader');
const {
createLegacyShadowPrimaryGateReceipt,
} = require('../../back/runtime/domain/legacyShadowPrimaryGate');
const NOW = 1_750_000_000_000;
const directories = [];
async function fixturePath(name = 'qinglong3-rollout.json') {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'ql3-rollout-'));
directories.push(directory);
return path.join(directory, name);
}
function gateReceipt() {
const start = NOW - 20_000;
const end = NOW - 10_000;
const startup = {
schema: 'qinglong/legacy-shadow-startup-difference-report@v1',
profile: 'edge',
assessment: 'converged',
configuredOriginCount: 1,
coverage: { remaining: false },
byOrigin: [{ origin: 'manual' }],
};
const capture = {
schema: 'qinglong/legacy-shadow-capture-evidence@v1',
profile: 'edge',
startup,
capture: {
schema: 'qinglong/legacy-shadow-capture-report@v1',
profile: 'edge',
assessment: 'captured',
configuredOriginCount: 1,
window: {
basis: 'process_local_legacy_admission',
startInclusiveMs: start,
endExclusiveMs: end,
},
totals: { admitted: 8, captured: 8, failed: 0, pending: 0 },
byOrigin: [{ origin: 'manual' }],
capturePermille: 1_000,
},
qualification: { passed: true },
};
const terminal = {
schema: 'qinglong/legacy-shadow-terminal-difference-report@v1',
profile: 'edge',
observedAtMs: NOW - 3_000,
window: { startInclusiveMs: start, endExclusiveMs: end, closed: true },
coverage: {
direction: 'shadow_to_legacy',
cohort: 'legacy_owned_shadow_runs',
legacyWithoutShadow: 'not_measured',
},
assessment: 'matched',
scanned: 8,
remaining: false,
evidenceComplete: true,
counts: { matched: 8 },
byOrigin: [{ origin: 'manual', scanned: 8 }],
terminalAgreementPermille: 1_000,
fullyComparablePermille: 1_000,
};
const resource = {
schemaVersion: 1,
fixture: 'qinglong/legacy-shadow-resource-rollback-evidence@v1',
profile: 'edge',
workload: { mode: 'full', runtime: 'compiled_backend' },
rollback: {
performed: true,
legacyContinued: true,
shadowWritesStopped: true,
databaseIntegrity: 'ok',
},
qualification: { passed: true, violations: [] },
};
return createLegacyShadowPrimaryGateReceipt({
profile: 'edge',
generatedAtMs: NOW - 2_000,
capture,
terminal,
resource,
});
}
async function writeGateReceipt(sourcePath, value = gateReceipt()) {
const bytes = Buffer.from(`${JSON.stringify(value)}\n`);
await fs.writeFile(
path.join(path.dirname(sourcePath), 'primary-gate.json'),
bytes,
{ mode: 0o600 },
);
return crypto.createHash('sha256').update(bytes).digest('hex');
}
function enabledManifest(receiptSha256) {
return {
schemaVersion: 2,
revision: 'manual-primary-canary-1',
enabled: true,
approvedBy: 'operator:admin',
approvedAtMs: NOW - 1_000,
expiresAtMs: NOW + 60_000,
rollbackPlanRef: 'docs/runbooks/disable-primary.md',
primaryGate: {
schema: 'qinglong/legacy-shadow-primary-gate-reference@v1',
origin: 'manual',
receiptFile: 'primary-gate.json',
receiptSha256,
},
rollout: {
defaultMode: 'off',
origins: { manual: 'primary' },
allowLegacyFallbackBeforeStart: false,
},
gates: {
durableCancellation: 'passed',
startupReconciliation: 'passed',
atomicLegacyProjection: 'passed',
rollbackDrill: 'passed',
edgeBudget: 'passed',
},
};
}
afterEach(async () => {
await Promise.all(
directories
.splice(0)
.map((directory) => fs.rm(directory, { recursive: true, force: true })),
);
});
test('fails closed when the rollout file is absent', async () => {
const sourcePath = await fixturePath();
const result = await loadRuntimeRolloutManifest(sourcePath, {
clock: { now: () => NOW },
});
assert.equal(result.status, 'missing');
assert.equal(result.policy.modeFor('manual'), 'off');
assert.deepEqual(result.audit, {
event: 'runtime.rollout_config_evaluated',
evaluatedAtMs: NOW,
sourcePath,
status: 'missing',
reasonCode: 'FILE_MISSING',
});
});
test('loads an approved manifest and audits only bounded metadata', async () => {
const sourcePath = await fixturePath();
const receiptSha256 = await writeGateReceipt(sourcePath);
const raw = JSON.stringify(enabledManifest(receiptSha256));
await fs.writeFile(sourcePath, raw);
const result = await loadRuntimeRolloutManifest(sourcePath, {
clock: { now: () => NOW },
});
assert.equal(result.status, 'accepted');
assert.equal(result.policy.modeFor('manual'), 'primary');
assert.equal(result.audit.revision, 'manual-primary-canary-1');
assert.equal(result.primaryGateReceipt.assessment, 'eligible');
assert.match(result.audit.sourceSha256, /^[a-f0-9]{64}$/);
assert.doesNotMatch(JSON.stringify(result.audit), /operator:admin|rollback/);
});
test('rejects a missing, tampered or ineligible Primary gate receipt', async () => {
const missingPath = await fixturePath('missing-gate-manifest.json');
await fs.writeFile(
missingPath,
JSON.stringify(enabledManifest('a'.repeat(64))),
);
const missing = await loadRuntimeRolloutManifest(missingPath, {
clock: { now: () => NOW },
});
assert.equal(missing.status, 'rejected');
assert.equal(missing.audit.reasonCode, 'PRIMARY_GATE_READ_FAILED');
const tamperedPath = await fixturePath('tampered-gate-manifest.json');
const tampered = gateReceipt();
tampered.sources.terminal.counts.matched = 7;
const tamperedDigest = await writeGateReceipt(tamperedPath, tampered);
await fs.writeFile(
tamperedPath,
JSON.stringify(enabledManifest(tamperedDigest)),
);
const tamperedResult = await loadRuntimeRolloutManifest(tamperedPath, {
clock: { now: () => NOW },
});
assert.equal(tamperedResult.status, 'rejected');
assert.equal(tamperedResult.audit.reasonCode, 'PRIMARY_GATE_INVALID');
const invalidPath = await fixturePath('invalid-gate-manifest.json');
const ineligible = {
...gateReceipt(),
assessment: 'ineligible',
violations: ['terminal_not_matched'],
};
const digest = await writeGateReceipt(invalidPath, ineligible);
await fs.writeFile(invalidPath, JSON.stringify(enabledManifest(digest)));
const invalid = await loadRuntimeRolloutManifest(invalidPath, {
clock: { now: () => NOW },
});
assert.equal(invalid.status, 'rejected');
assert.equal(invalid.audit.reasonCode, 'PRIMARY_GATE_INVALID');
});
test('rejects malformed and oversized files without exposing their contents', async () => {
const malformedPath = await fixturePath('malformed.json');
await fs.writeFile(malformedPath, '{"approvedBy":"secret"');
const malformed = await loadRuntimeRolloutManifest(malformedPath, {
clock: { now: () => NOW },
});
assert.equal(malformed.status, 'rejected');
assert.equal(malformed.audit.reasonCode, 'INVALID_JSON');
assert.equal(malformed.policy.modeFor('manual'), 'off');
assert.doesNotMatch(JSON.stringify(malformed.audit), /secret/);
const oversizedPath = await fixturePath('oversized.json');
await fs.writeFile(oversizedPath, 'x'.repeat(11));
const oversized = await loadRuntimeRolloutManifest(oversizedPath, {
clock: { now: () => NOW },
maxBytes: 10,
});
assert.equal(oversized.status, 'rejected');
assert.equal(oversized.audit.reasonCode, 'FILE_TOO_LARGE');
assert.equal(oversized.policy.modeFor('manual'), 'off');
});