mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 08:05:22 +08:00
242 lines
7.8 KiB
JavaScript
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');
|
|
});
|