mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): gate primary on shadow capture evidence
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
require('ts-node/register/transpile-only');
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { afterEach, test } = require('node:test');
|
||||
const {
|
||||
createLegacyShadowPrimaryGateReceipt,
|
||||
parseLegacyShadowPrimaryGateReceipt,
|
||||
} = require('../../back/runtime/domain/legacyShadowPrimaryGate');
|
||||
const {
|
||||
parseArguments,
|
||||
readEvidence,
|
||||
run,
|
||||
} = require('../../scripts/ql3-legacy-shadow-primary-gate.cjs');
|
||||
|
||||
const START = 1_750_400_000_000;
|
||||
const END = START + 60_000;
|
||||
const GENERATED = END + 6 * 60_000;
|
||||
const directories = [];
|
||||
|
||||
function captureEvidence(admitted = 8) {
|
||||
const outcomes = {
|
||||
completed: 0,
|
||||
cancelled: 0,
|
||||
abandoned: 0,
|
||||
markedLost: 0,
|
||||
repaired: 0,
|
||||
pending: 0,
|
||||
ambiguous: 0,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
};
|
||||
return {
|
||||
schema: 'qinglong/legacy-shadow-capture-evidence@v1',
|
||||
profile: 'edge',
|
||||
startup: {
|
||||
schema: 'qinglong/legacy-shadow-startup-difference-report@v1',
|
||||
profile: 'edge',
|
||||
assessment: 'converged',
|
||||
configuredOriginCount: 1,
|
||||
coverage: { remaining: false },
|
||||
outcomes,
|
||||
byOrigin: [{ origin: 'manual', scanned: 0, ...outcomes }],
|
||||
},
|
||||
capture: {
|
||||
schema: 'qinglong/legacy-shadow-capture-report@v1',
|
||||
profile: 'edge',
|
||||
assessment: 'captured',
|
||||
epoch: '019f75d2-5555-7555-8555-555555555555',
|
||||
window: {
|
||||
basis: 'process_local_legacy_admission',
|
||||
startInclusiveMs: START,
|
||||
endExclusiveMs: END,
|
||||
},
|
||||
configuredOriginCount: 1,
|
||||
totals: {
|
||||
admitted,
|
||||
captured: admitted,
|
||||
failed: 0,
|
||||
pending: 0,
|
||||
failures: { fact: 0, observer: 0, initialization: 0, accept: 0 },
|
||||
},
|
||||
byOrigin: [
|
||||
{
|
||||
origin: 'manual',
|
||||
admitted,
|
||||
captured: admitted,
|
||||
failed: 0,
|
||||
pending: 0,
|
||||
failures: {
|
||||
fact: 0,
|
||||
observer: 0,
|
||||
initialization: 0,
|
||||
accept: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
capturePermille: 1_000,
|
||||
},
|
||||
qualification: {
|
||||
passed: true,
|
||||
startupConverged: true,
|
||||
originCoverageExact: true,
|
||||
captureComplete: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function terminal(scanned = 8) {
|
||||
return {
|
||||
schema: 'qinglong/legacy-shadow-terminal-difference-report@v1',
|
||||
profile: 'edge',
|
||||
observedAtMs: GENERATED - 1,
|
||||
window: {
|
||||
basis: 'shadow_run_created_at',
|
||||
startInclusiveMs: START,
|
||||
endExclusiveMs: END,
|
||||
minimumSettlingAgeMs: 300_000,
|
||||
closed: true,
|
||||
},
|
||||
coverage: {
|
||||
direction: 'shadow_to_legacy',
|
||||
cohort: 'legacy_owned_shadow_runs',
|
||||
legacyWithoutShadow: 'not_measured',
|
||||
},
|
||||
scanned,
|
||||
remaining: false,
|
||||
evidenceComplete: true,
|
||||
assessment: 'matched',
|
||||
counts: { matched: scanned },
|
||||
byOrigin: [{ origin: 'manual', scanned, matched: scanned }],
|
||||
terminalAgreementPermille: 1_000,
|
||||
fullyComparablePermille: 1_000,
|
||||
};
|
||||
}
|
||||
|
||||
function resource() {
|
||||
return {
|
||||
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: [] },
|
||||
};
|
||||
}
|
||||
|
||||
function input(overrides = {}) {
|
||||
return {
|
||||
profile: 'edge',
|
||||
generatedAtMs: GENERATED,
|
||||
capture: captureEvidence(),
|
||||
terminal: terminal(),
|
||||
resource: resource(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const directory of directories.splice(0)) {
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('combines capture, startup, terminal and rollback evidence into eligibility', () => {
|
||||
const receipt = createLegacyShadowPrimaryGateReceipt(input());
|
||||
|
||||
assert.equal(receipt.assessment, 'eligible');
|
||||
assert.deepEqual(receipt.violations, []);
|
||||
assert.deepEqual(receipt.counts, {
|
||||
admitted: 8,
|
||||
captured: 8,
|
||||
terminalScanned: 8,
|
||||
terminalMatched: 8,
|
||||
});
|
||||
assert.deepEqual(parseLegacyShadowPrimaryGateReceipt(receipt), receipt);
|
||||
});
|
||||
|
||||
test('fails closed for an undersized cohort, terminal drift and audit-only rollback', () => {
|
||||
const capture = captureEvidence(7);
|
||||
const terminalReport = terminal(6);
|
||||
const rollback = resource();
|
||||
rollback.workload.mode = 'audit-only';
|
||||
const receipt = createLegacyShadowPrimaryGateReceipt(
|
||||
input({ capture, terminal: terminalReport, resource: rollback }),
|
||||
);
|
||||
|
||||
assert.equal(receipt.assessment, 'ineligible');
|
||||
assert.deepEqual(receipt.violations, [
|
||||
'capture_sample_budget_invalid',
|
||||
'terminal_not_matched',
|
||||
'resource_not_compiled_full_rollback',
|
||||
]);
|
||||
assert.throws(
|
||||
() =>
|
||||
parseLegacyShadowPrimaryGateReceipt({
|
||||
...receipt,
|
||||
assessment: 'eligible',
|
||||
}),
|
||||
/invalid/,
|
||||
);
|
||||
});
|
||||
|
||||
test('CLI reads no-follow bounded inputs and publishes a no-replace receipt', () => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-primary-gate-'));
|
||||
directories.push(directory);
|
||||
const paths = Object.fromEntries(
|
||||
['capture', 'terminal', 'resource', 'output'].map((name) => [
|
||||
name,
|
||||
path.join(directory, `${name}.json`),
|
||||
]),
|
||||
);
|
||||
fs.writeFileSync(paths.capture, `${JSON.stringify(captureEvidence())}\n`, {
|
||||
mode: 0o600,
|
||||
});
|
||||
fs.writeFileSync(paths.terminal, `${JSON.stringify(terminal())}\n`, {
|
||||
mode: 0o600,
|
||||
});
|
||||
fs.writeFileSync(paths.resource, `${JSON.stringify(resource())}\n`, {
|
||||
mode: 0o600,
|
||||
});
|
||||
const options = parseArguments([
|
||||
'--profile=edge',
|
||||
`--capture=${paths.capture}`,
|
||||
`--terminal=${paths.terminal}`,
|
||||
`--resource=${paths.resource}`,
|
||||
`--output=${paths.output}`,
|
||||
`--generated-at-ms=${GENERATED}`,
|
||||
]);
|
||||
|
||||
const receipt = run(options);
|
||||
assert.equal(receipt.assessment, 'eligible');
|
||||
assert.equal(
|
||||
parseLegacyShadowPrimaryGateReceipt(readEvidence(paths.output).value)
|
||||
.assessment,
|
||||
'eligible',
|
||||
);
|
||||
assert.equal(fs.statSync(paths.output).mode & 0o777, 0o600);
|
||||
assert.throws(() => run(options), /EEXIST/);
|
||||
|
||||
const symlink = path.join(directory, 'capture-link.json');
|
||||
fs.symlinkSync(paths.capture, symlink);
|
||||
assert.throws(() => readEvidence(symlink), /ELOOP|symbolic/i);
|
||||
});
|
||||
Reference in New Issue
Block a user