Files
qinglong/scripts/ql3-legacy-shadow-primary-gate.cjs

152 lines
4.3 KiB
JavaScript

#!/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;
}
}