mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-23 03:18:09 +08:00
feat(ql3): gate primary on shadow capture evidence
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
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');
|
||||
@@ -8,6 +9,9 @@ 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 = [];
|
||||
@@ -18,15 +22,103 @@ async function fixturePath(name = 'qinglong3-rollout.json') {
|
||||
return path.join(directory, name);
|
||||
}
|
||||
|
||||
function enabledManifest() {
|
||||
return {
|
||||
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' },
|
||||
@@ -69,7 +161,8 @@ test('fails closed when the rollout file is absent', async () => {
|
||||
|
||||
test('loads an approved manifest and audits only bounded metadata', async () => {
|
||||
const sourcePath = await fixturePath();
|
||||
const raw = JSON.stringify(enabledManifest());
|
||||
const receiptSha256 = await writeGateReceipt(sourcePath);
|
||||
const raw = JSON.stringify(enabledManifest(receiptSha256));
|
||||
await fs.writeFile(sourcePath, raw);
|
||||
|
||||
const result = await loadRuntimeRolloutManifest(sourcePath, {
|
||||
@@ -79,10 +172,52 @@ test('loads an approved manifest and audits only bounded metadata', async () =>
|
||||
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"');
|
||||
|
||||
Reference in New Issue
Block a user