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,151 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('ts-node/register/transpile-only');
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const {
|
||||
createLegacyShadowPrimaryGateReceipt,
|
||||
} = require('../back/runtime/domain/legacyShadowPrimaryGate');
|
||||
|
||||
const MAX_EVIDENCE_BYTES = 1024 * 1024;
|
||||
|
||||
function parseArguments(argv) {
|
||||
const options = { profile: 'edge' };
|
||||
for (const argument of argv) {
|
||||
if (argument === '--') continue;
|
||||
if (argument.startsWith('--profile=')) {
|
||||
options.profile = argument.slice('--profile='.length);
|
||||
} else if (argument.startsWith('--capture=')) {
|
||||
options.capturePath = path.resolve(argument.slice('--capture='.length));
|
||||
} else if (argument.startsWith('--terminal=')) {
|
||||
options.terminalPath = path.resolve(argument.slice('--terminal='.length));
|
||||
} else if (argument.startsWith('--resource=')) {
|
||||
options.resourcePath = path.resolve(argument.slice('--resource='.length));
|
||||
} else if (argument.startsWith('--output=')) {
|
||||
options.outputPath = path.resolve(argument.slice('--output='.length));
|
||||
} else if (argument.startsWith('--generated-at-ms=')) {
|
||||
const raw = argument.slice('--generated-at-ms='.length);
|
||||
if (!/^\d+$/u.test(raw)) {
|
||||
throw new TypeError('--generated-at-ms must be an integer');
|
||||
}
|
||||
options.generatedAtMs = Number(raw);
|
||||
} else {
|
||||
throw new TypeError(`Unsupported argument: ${argument}`);
|
||||
}
|
||||
}
|
||||
if (options.profile !== 'edge' && options.profile !== 'standalone') {
|
||||
throw new TypeError('--profile must be edge or standalone');
|
||||
}
|
||||
for (const name of [
|
||||
'capturePath',
|
||||
'terminalPath',
|
||||
'resourcePath',
|
||||
'outputPath',
|
||||
]) {
|
||||
if (!options[name] || !path.isAbsolute(options[name])) {
|
||||
throw new TypeError(`--${name.replace('Path', '')} is required`);
|
||||
}
|
||||
}
|
||||
if (
|
||||
options.generatedAtMs !== undefined &&
|
||||
(!Number.isSafeInteger(options.generatedAtMs) || options.generatedAtMs < 0)
|
||||
) {
|
||||
throw new TypeError('--generated-at-ms is invalid');
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function readEvidence(sourcePath) {
|
||||
const descriptor = fs.openSync(
|
||||
sourcePath,
|
||||
fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0),
|
||||
);
|
||||
try {
|
||||
const stat = fs.fstatSync(descriptor);
|
||||
if (
|
||||
!stat.isFile() ||
|
||||
(stat.mode & 0o077) !== 0 ||
|
||||
stat.size < 2 ||
|
||||
stat.size > MAX_EVIDENCE_BYTES
|
||||
) {
|
||||
throw new TypeError('Primary gate evidence file shape is invalid');
|
||||
}
|
||||
const bytes = Buffer.alloc(stat.size);
|
||||
let offset = 0;
|
||||
while (offset < bytes.length) {
|
||||
const count = fs.readSync(
|
||||
descriptor,
|
||||
bytes,
|
||||
offset,
|
||||
bytes.length - offset,
|
||||
offset,
|
||||
);
|
||||
if (count === 0) throw new Error('Primary gate evidence read stalled');
|
||||
offset += count;
|
||||
}
|
||||
return {
|
||||
value: JSON.parse(bytes.toString('utf8')),
|
||||
};
|
||||
} finally {
|
||||
fs.closeSync(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
function writeReceipt(outputPath, receipt) {
|
||||
const descriptor = fs.openSync(outputPath, 'wx', 0o600);
|
||||
try {
|
||||
fs.writeFileSync(descriptor, `${JSON.stringify(receipt)}\n`, 'utf8');
|
||||
fs.fsyncSync(descriptor);
|
||||
} finally {
|
||||
fs.closeSync(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
function run(options) {
|
||||
const capture = readEvidence(options.capturePath);
|
||||
const terminal = readEvidence(options.terminalPath);
|
||||
const resource = readEvidence(options.resourcePath);
|
||||
const receipt = createLegacyShadowPrimaryGateReceipt({
|
||||
profile: options.profile,
|
||||
generatedAtMs: options.generatedAtMs ?? Date.now(),
|
||||
capture: capture.value,
|
||||
terminal: terminal.value,
|
||||
resource: resource.value,
|
||||
});
|
||||
if (receipt.assessment !== 'eligible') {
|
||||
const error = new Error(
|
||||
`Legacy Shadow Primary gate is ineligible: ${receipt.violations.join(
|
||||
',',
|
||||
)}`,
|
||||
);
|
||||
error.code = 'QL3_PRIMARY_GATE_INELIGIBLE';
|
||||
throw error;
|
||||
}
|
||||
writeReceipt(options.outputPath, receipt);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const receipt = run(parseArguments(process.argv.slice(2)));
|
||||
process.stdout.write(`${JSON.stringify(receipt)}\n`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MAX_EVIDENCE_BYTES,
|
||||
parseArguments,
|
||||
readEvidence,
|
||||
run,
|
||||
writeReceipt,
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
process.stderr.write(
|
||||
`${error instanceof Error ? error.message : String(error)}\n`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@@ -369,7 +369,7 @@ async function runSummary(database) {
|
||||
});
|
||||
}
|
||||
|
||||
async function rollbackChild(mode, expectedBefore) {
|
||||
async function rollbackChild(mode, expectedBefore, profile) {
|
||||
const databasePath = process.env.QL3_SHADOW_DRILL_DATABASE;
|
||||
if (!path.isAbsolute(databasePath ?? '')) {
|
||||
throw new QingLong3LegacyShadowResourceRollbackError(
|
||||
@@ -380,10 +380,17 @@ async function rollbackChild(mode, expectedBefore) {
|
||||
await taskLimit.setCustomLimit();
|
||||
const ScheduleService = fromRuntime('services/schedule').default;
|
||||
const bridge = fromRuntime('runtime/compatibility/legacyExecutionBridge');
|
||||
const { createLegacyShadowCaptureReport } = fromRuntime(
|
||||
'runtime/application/legacyShadowCaptureAuthority',
|
||||
);
|
||||
const { sequelize } = fromRuntime('data');
|
||||
let shortCircuitFactCalls = 0;
|
||||
try {
|
||||
const configuredOrigins = bridge.configuredLegacyShadowOrigins();
|
||||
const captureBefore =
|
||||
mode === 'enabled'
|
||||
? bridge.legacyShadowCaptureSnapshot(['system'])
|
||||
: undefined;
|
||||
if (mode === 'off') {
|
||||
const observation = bridge.observeLegacyExecution('system', () => {
|
||||
shortCircuitFactCalls += 1;
|
||||
@@ -434,6 +441,15 @@ async function rollbackChild(mode, expectedBefore) {
|
||||
`${mode} restart did not preserve the expected Legacy result`,
|
||||
);
|
||||
}
|
||||
const capture =
|
||||
mode === 'enabled'
|
||||
? createLegacyShadowCaptureReport(
|
||||
profile,
|
||||
['system'],
|
||||
captureBefore,
|
||||
bridge.legacyShadowCaptureSnapshot(['system']),
|
||||
)
|
||||
: undefined;
|
||||
return Object.freeze({
|
||||
mode,
|
||||
configuredOrigins,
|
||||
@@ -445,6 +461,7 @@ async function rollbackChild(mode, expectedBefore) {
|
||||
shortCircuitFactCalls,
|
||||
defaultObserverLoaded,
|
||||
repositoryLoaded,
|
||||
...(capture === undefined ? {} : { capture }),
|
||||
peakProcessRssBytes: process.resourceUsage().maxRSS * 1024,
|
||||
});
|
||||
} finally {
|
||||
@@ -558,6 +575,7 @@ async function runEvidence(options) {
|
||||
[
|
||||
'--internal-child=rollback-enabled',
|
||||
`--expected-before=${fixtureRunCount}`,
|
||||
`--profile=${options.profile}`,
|
||||
],
|
||||
{ ...childEnvironment, QL3_SHADOW_ORIGINS: 'system' },
|
||||
)
|
||||
@@ -568,6 +586,7 @@ async function runEvidence(options) {
|
||||
[
|
||||
'--internal-child=rollback-off',
|
||||
`--expected-before=${fixtureRunCount + 1}`,
|
||||
`--profile=${options.profile}`,
|
||||
],
|
||||
{ ...childEnvironment, QL3_SHADOW_ORIGINS: '' },
|
||||
)
|
||||
@@ -653,6 +672,7 @@ async function runEvidence(options) {
|
||||
runDelta: enabled.runDelta,
|
||||
defaultObserverLoaded: enabled.defaultObserverLoaded,
|
||||
repositoryLoaded: enabled.repositoryLoaded,
|
||||
capture: enabled.capture,
|
||||
peakProcessRssBytes: enabled.peakProcessRssBytes,
|
||||
}),
|
||||
off: Object.freeze({
|
||||
@@ -729,6 +749,7 @@ async function main() {
|
||||
report = await rollbackChild(
|
||||
mode === 'rollback-enabled' ? 'enabled' : 'off',
|
||||
expectedBefore,
|
||||
profile,
|
||||
);
|
||||
} else {
|
||||
throw new QingLong3LegacyShadowResourceRollbackError(
|
||||
|
||||
Reference in New Issue
Block a user