feat(ql3): prove shadow resource rollback

This commit is contained in:
whyour
2026-08-19 02:36:06 +08:00
parent b739d92347
commit c60a86d1bf
12 changed files with 1564 additions and 89 deletions
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const ROOT = path.resolve(__dirname, '..');
const STATIC_ROOT = path.join(ROOT, 'static');
const TARGET = path.join(STATIC_ROOT, 'build');
class QingLong3BackendBuildError extends Error {
constructor(message) {
super(`QingLong backend build failed: ${message}`);
this.name = 'QingLong3BackendBuildError';
}
}
function assertWithinStatic(target, label) {
const relative = path.relative(STATIC_ROOT, target);
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
throw new QingLong3BackendBuildError(`${label} escaped static root`);
}
}
function listFiles(directory) {
const files = [];
const pending = [directory];
while (pending.length > 0) {
const current = pending.pop();
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const entryPath = path.join(current, entry.name);
if (entry.isDirectory()) pending.push(entryPath);
else if (entry.isFile()) files.push(entryPath);
else {
throw new QingLong3BackendBuildError(
'staged output contains a non-regular entry',
);
}
}
}
return files;
}
function sourceMapPlan(compiledBackend) {
return listFiles(compiledBackend)
.filter((filePath) => filePath.endsWith('.js.map'))
.map((sourcePath) => {
const relative = path.relative(compiledBackend, sourcePath);
const destinationPath = path.join(TARGET, relative);
const sourceMap = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
if (!Array.isArray(sourceMap.sources)) {
throw new QingLong3BackendBuildError(
`source map ${relative} has no sources array`,
);
}
return Object.freeze({
sourcePath,
destinationPath,
sourceMap,
absoluteSources: sourceMap.sources.map((source) => {
if (typeof source !== 'string' || path.isAbsolute(source)) {
throw new QingLong3BackendBuildError(
`source map ${relative} contains an invalid source`,
);
}
return path.resolve(path.dirname(sourcePath), source);
}),
});
});
}
function rewriteSourceMaps(plans) {
for (const plan of plans) {
plan.sourceMap.sources = plan.absoluteSources.map((absoluteSource) =>
path
.relative(path.dirname(plan.destinationPath), absoluteSource)
.split(path.sep)
.join('/'),
);
fs.writeFileSync(plan.sourcePath, JSON.stringify(plan.sourceMap));
}
}
function publishCompiledBackend(compiledBackend, stageRoot) {
for (const required of [
'app.js',
'token.js',
'runtime/adapters/local-process/localProcessExecutor.js',
]) {
if (!fs.statSync(path.join(compiledBackend, required)).isFile()) {
throw new QingLong3BackendBuildError(
`compiled backend is missing ${required}`,
);
}
}
const maps = sourceMapPlan(compiledBackend);
rewriteSourceMaps(maps);
const backup = path.join(
STATIC_ROOT,
`.build-backup-${process.pid}-${Date.now()}`,
);
assertWithinStatic(TARGET, 'target');
assertWithinStatic(backup, 'backup');
let previousMoved = false;
try {
if (fs.existsSync(TARGET)) {
fs.renameSync(TARGET, backup);
previousMoved = true;
}
fs.renameSync(compiledBackend, TARGET);
} catch (error) {
if (!fs.existsSync(TARGET) && previousMoved && fs.existsSync(backup)) {
fs.renameSync(backup, TARGET);
}
throw error;
}
if (previousMoved) fs.rmSync(backup, { recursive: true, force: true });
fs.rmSync(stageRoot, { recursive: true, force: true });
}
function main() {
fs.mkdirSync(STATIC_ROOT, { recursive: true });
const stageRoot = fs.mkdtempSync(path.join(STATIC_ROOT, '.back-build-'));
assertWithinStatic(stageRoot, 'stage');
const output = path.join(stageRoot, 'output');
try {
const tsc = require.resolve('typescript/bin/tsc');
const result = spawnSync(
process.execPath,
[tsc, '-p', 'back/tsconfig.json', '--outDir', output],
{
cwd: ROOT,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
},
);
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
if (result.error) throw result.error;
if (result.status !== 0) {
throw new QingLong3BackendBuildError(
`TypeScript compiler exited with ${result.status}`,
);
}
const compiledBackend = path.join(output, 'back');
if (!fs.existsSync(compiledBackend)) {
throw new QingLong3BackendBuildError(
'TypeScript output did not contain the backend subtree',
);
}
publishCompiledBackend(compiledBackend, stageRoot);
} catch (error) {
fs.rmSync(stageRoot, { recursive: true, force: true });
throw error;
}
}
module.exports = {
QingLong3BackendBuildError,
assertWithinStatic,
listFiles,
publishCompiledBackend,
rewriteSourceMaps,
sourceMapPlan,
};
if (require.main === module) {
try {
main();
} catch (error) {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
}
}
@@ -0,0 +1,776 @@
#!/usr/bin/env node
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { performance } = require('node:perf_hooks');
const sqlite3 = require('sqlite3');
const { DataTypes, QueryTypes, Sequelize } = require('sequelize');
const REPOSITORY_ROOT = path.resolve(__dirname, '..');
process.env.QL_DIR ??= REPOSITORY_ROOT;
const BUILD_ROOT = path.join(REPOSITORY_ROOT, 'static/build');
const BUILD_AVAILABLE = fs.existsSync(
path.join(
BUILD_ROOT,
'runtime/application/legacyShadowTerminalDifferenceAuditor.js',
),
);
if (!BUILD_AVAILABLE) require('ts-node/register/transpile-only');
const fromRuntime = (relativePath) =>
require(path.join(
BUILD_AVAILABLE ? BUILD_ROOT : path.join(REPOSITORY_ROOT, 'back'),
relativePath,
));
const MIB = 1024 * 1024;
const BASE_TIME_MS = 1_750_200_000_000;
const MAX_CHILD_OUTPUT_BYTES = 64 * 1024;
const PROFILES = Object.freeze({
edge: Object.freeze({ candidates: 8, pageSize: 8, maxPages: 1 }),
standalone: Object.freeze({ candidates: 128, pageSize: 32, maxPages: 4 }),
});
const MEASURES = Object.freeze([
'real_sequelize_sqlite_shadow_terminal_audit',
'profile_maximum_closed_window_candidate_count',
'bounded_candidate_and_evidence_query_count',
'read_only_database_storage_stability',
'process_restart_shadow_enabled_to_off',
'real_legacy_child_execution_before_and_after_restart',
'off_path_zero_fact_factory_and_shadow_write',
]);
const EXCLUSIONS = Object.freeze([
'legacy_to_shadow_capture_rate',
'physical_router_or_flash_wear',
'power_loss_survival',
'production_task_content_or_identity',
'primary_execution_eligibility',
'cluster_or_postgresql_runtime',
]);
class QingLong3LegacyShadowResourceRollbackError extends Error {
constructor(message) {
super(
`QingLong 3.0 Legacy Shadow resource/rollback gate failed: ${message}`,
);
this.name = 'QingLong3LegacyShadowResourceRollbackError';
}
}
function integerArgument(name, value, minimum, maximum) {
if (!/^\d+$/.test(value)) {
throw new QingLong3LegacyShadowResourceRollbackError(
`${name} must be an integer`,
);
}
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
throw new QingLong3LegacyShadowResourceRollbackError(
`${name} must be between ${minimum} and ${maximum}`,
);
}
return parsed;
}
function parseArguments(argv) {
const options = {
profile: 'edge',
mode: 'full',
samples: 8,
maxAuditP95Ms: 2_000,
maxRssDeltaBytes: 64 * MIB,
requireCompiled: false,
json: false,
};
for (const argument of argv) {
if (argument === '--' || argument === '--json') {
if (argument === '--json') options.json = true;
} else if (argument === '--require-compiled') {
options.requireCompiled = true;
} else if (argument.startsWith('--profile=')) {
const value = argument.slice('--profile='.length);
if (!(value in PROFILES)) {
throw new QingLong3LegacyShadowResourceRollbackError(
'--profile must be edge or standalone',
);
}
options.profile = value;
} else if (argument.startsWith('--mode=')) {
const value = argument.slice('--mode='.length);
if (value !== 'audit-only' && value !== 'full') {
throw new QingLong3LegacyShadowResourceRollbackError(
'--mode must be audit-only or full',
);
}
options.mode = value;
} else if (argument.startsWith('--samples=')) {
options.samples = integerArgument(
'--samples',
argument.slice('--samples='.length),
1,
32,
);
} else if (argument.startsWith('--max-audit-p95-ms=')) {
options.maxAuditP95Ms = integerArgument(
'--max-audit-p95-ms',
argument.slice('--max-audit-p95-ms='.length),
1,
60_000,
);
} else if (argument.startsWith('--max-rss-delta-mb=')) {
options.maxRssDeltaBytes =
integerArgument(
'--max-rss-delta-mb',
argument.slice('--max-rss-delta-mb='.length),
1,
512,
) * MIB;
} else {
throw new QingLong3LegacyShadowResourceRollbackError(
`unsupported argument ${argument}`,
);
}
}
return Object.freeze(options);
}
function nextIdentifier(sequence) {
return `019f7500-0000-7000-8000-${String(sequence).padStart(12, '0')}`;
}
async function createFixture(databasePath, profile) {
const { defineSchemaMigrationModel } = fromRuntime('data/schemaMigration');
const { runSchemaMigration } = fromRuntime('migrations/0002-run-schema');
const { runningInstanceRunReferenceMigration } = fromRuntime(
'migrations/0003-running-instance-run-reference',
);
const { runCancellationRequestMigration } = fromRuntime(
'migrations/0004-run-cancellation-request',
);
const { runAttemptDeadlineMigration } = fromRuntime(
'migrations/0006-run-attempt-deadline',
);
const { runMigrations } = fromRuntime('migrations/runner');
const { createLegacyLogArtifactId } = fromRuntime(
'runtime/compatibility/legacyTaskRevision',
);
const { LegacySequelizeRunRepository } = fromRuntime(
'runtime/adapters/legacy-sequelize/runRepository',
);
const { LegacyShadowRunWriter } = fromRuntime(
'runtime/application/legacyShadowRunWriter',
);
const database = new Sequelize({
dialect: 'sqlite',
storage: databasePath,
logging: false,
});
try {
await database.getQueryInterface().createTable('RunningInstances', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
cron_id: { type: DataTypes.INTEGER, allowNull: false },
pid: { type: DataTypes.INTEGER, allowNull: true },
log_path: { type: DataTypes.STRING, allowNull: true },
started_at: { type: DataTypes.INTEGER, allowNull: false },
finished_at: { type: DataTypes.INTEGER, allowNull: true },
status: { type: DataTypes.INTEGER, allowNull: false },
exit_code: { type: DataTypes.INTEGER, allowNull: true },
});
await runMigrations({
database,
migrationModel: defineSchemaMigrationModel(database),
migrations: [
runSchemaMigration,
runningInstanceRunReferenceMigration,
runCancellationRequestMigration,
runAttemptDeadlineMigration,
],
logger: { info() {} },
});
let identifierSequence = 10_000;
const repository = new LegacySequelizeRunRepository(database);
const writer = new LegacyShadowRunWriter(repository, () => {
identifierSequence += 1;
return nextIdentifier(identifierSequence);
});
const rows = [];
for (let index = 0; index < PROFILES[profile].candidates; index += 1) {
const acceptedAtMs = BASE_TIME_MS + index * 2_000;
const legacyCronId = index + 1;
const pid = 20_000 + index;
const logPath = `resource/${index}.log`;
const reference = await writer.accept({
origin: 'manual',
projectId: 'default',
taskId: `legacy-resource:${index}`,
taskRevision: `sha256:${String(index).padStart(64, '0')}`,
legacyCronId,
triggerType: 'manual',
acceptedAtMs,
});
await writer.spawned(reference, {
atMs: acceptedAtMs + 100,
pid,
logArtifactId: createLegacyLogArtifactId(logPath),
});
await writer.running(reference, acceptedAtMs + 200);
await writer.exited(reference, {
atMs: acceptedAtMs + 500,
exitCode: 0,
});
rows.push({
cron_id: legacyCronId,
run_id: reference.runId,
attempt_id: reference.attemptId,
pid,
log_path: logPath,
started_at: Math.floor((acceptedAtMs + 100) / 1_000),
finished_at: Math.floor((acceptedAtMs + 500) / 1_000),
status: 1,
exit_code: 0,
});
}
await database.getQueryInterface().bulkInsert('RunningInstances', rows);
} finally {
await database.close();
}
}
function fileStorage(databasePath) {
const files = [
databasePath,
`${databasePath}-wal`,
`${databasePath}-shm`,
`${databasePath}-journal`,
];
let logicalBytes = 0;
let allocatedBytes = 0;
let fileCount = 0;
for (const filePath of files) {
try {
const stat = fs.statSync(filePath);
logicalBytes += stat.size;
allocatedBytes += stat.blocks * 512;
fileCount += 1;
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}
}
return Object.freeze({ logicalBytes, allocatedBytes, fileCount });
}
function percentile(values, fraction) {
const sorted = [...values].sort((left, right) => left - right);
return sorted[Math.max(0, Math.ceil(sorted.length * fraction) - 1)];
}
async function auditChild(options) {
const { createLegacyLogArtifactId } = fromRuntime(
'runtime/compatibility/legacyTaskRevision',
);
const { LegacySequelizeShadowTerminalDifferenceSource } = fromRuntime(
'runtime/adapters/legacy-sequelize/legacyShadowTerminalDifferenceSource',
);
const { LegacyShadowTerminalDifferenceAuditor } = fromRuntime(
'runtime/application/legacyShadowTerminalDifferenceAuditor',
);
const databasePath = process.env.QL3_SHADOW_DRILL_DATABASE;
if (!path.isAbsolute(databasePath ?? '')) {
throw new QingLong3LegacyShadowResourceRollbackError(
'internal audit database is invalid',
);
}
let queryCount = 0;
const database = new Sequelize({
dialect: 'sqlite',
storage: databasePath,
logging() {
queryCount += 1;
},
dialectOptions: { mode: sqlite3.OPEN_READONLY },
pool: { max: 1, min: 0, idle: 1_000, acquire: 5_000 },
});
const rssBeforeBytes = process.memoryUsage().rss;
const durationsMs = [];
let pages = 0;
let scanned = 0;
try {
const source = new LegacySequelizeShadowTerminalDifferenceSource(
database,
createLegacyLogArtifactId,
);
const auditor = new LegacyShadowTerminalDifferenceAuditor(source);
const profile = PROFILES[options.profile];
const windowEndMs = BASE_TIME_MS + (profile.candidates - 1) * 2_000 + 1_000;
for (let sample = 0; sample < options.samples; sample += 1) {
const startedAt = performance.now();
const report = await auditor.run({
profile: options.profile,
origins: ['manual'],
windowStartMs: BASE_TIME_MS - 1,
windowEndMs,
observedAtMs: windowEndMs + 5 * 60_000,
});
durationsMs.push(performance.now() - startedAt);
if (
report.assessment !== 'matched' ||
report.scanned !== profile.candidates ||
report.remaining ||
!report.evidenceComplete ||
report.terminalAgreementPermille !== 1_000
) {
throw new QingLong3LegacyShadowResourceRollbackError(
'audit child did not reproduce the closed matched cohort',
);
}
pages += report.pages;
scanned += report.scanned;
}
} finally {
await database.close();
}
const rssAfterBytes = process.memoryUsage().rss;
return Object.freeze({
samples: options.samples,
pages,
scanned,
queryCount,
expectedQueryCount: pages * 2,
durationP50Ms: Number(percentile(durationsMs, 0.5).toFixed(3)),
durationP95Ms: Number(percentile(durationsMs, 0.95).toFixed(3)),
durationMaxMs: Number(Math.max(...durationsMs).toFixed(3)),
rssBeforeBytes,
rssAfterBytes,
rssDeltaBytes: Math.max(0, rssAfterBytes - rssBeforeBytes),
peakProcessRssBytes: process.resourceUsage().maxRSS * 1024,
});
}
function logger() {
return { info() {}, warn() {}, error() {} };
}
async function runSummary(database) {
const [row] = await database.query(
`SELECT
COUNT(*) AS count,
SUM(CASE WHEN status = 'succeeded' THEN 1 ELSE 0 END) AS succeeded
FROM Runs
WHERE execution_owner = 'legacy'`,
{ type: QueryTypes.SELECT },
);
return Object.freeze({
count: Number(row.count),
succeeded: Number(row.succeeded ?? 0),
});
}
async function rollbackChild(mode, expectedBefore) {
const databasePath = process.env.QL3_SHADOW_DRILL_DATABASE;
if (!path.isAbsolute(databasePath ?? '')) {
throw new QingLong3LegacyShadowResourceRollbackError(
'internal rollback database is invalid',
);
}
const taskLimit = fromRuntime('shared/pLimit').default;
await taskLimit.setCustomLimit();
const ScheduleService = fromRuntime('services/schedule').default;
const bridge = fromRuntime('runtime/compatibility/legacyExecutionBridge');
const { sequelize } = fromRuntime('data');
let shortCircuitFactCalls = 0;
try {
const configuredOrigins = bridge.configuredLegacyShadowOrigins();
if (mode === 'off') {
const observation = bridge.observeLegacyExecution('system', () => {
shortCircuitFactCalls += 1;
throw new Error('off path must not construct a Shadow fact');
});
if (observation !== undefined || shortCircuitFactCalls !== 0) {
throw new QingLong3LegacyShadowResourceRollbackError(
'off path did not short-circuit before Shadow fact construction',
);
}
}
const service = new ScheduleService(logger());
const command = `${JSON.stringify(process.execPath)} -e "process.exit(0)"`;
const result = await service.runTask(
command,
{},
{
id: 'opaque-resource-rollback',
name: 'resource rollback fixture',
schedule: '0 * * * *',
runOrigin: 'system',
},
);
const expectedAfter = expectedBefore + (mode === 'enabled' ? 1 : 0);
const deadline = Date.now() + 10_000;
let observed = await runSummary(sequelize);
while (
(observed.count !== expectedAfter ||
observed.succeeded !== expectedAfter) &&
Date.now() < deadline
) {
await new Promise((resolve) => setTimeout(resolve, 10));
observed = await runSummary(sequelize);
}
const loadedModules = Object.keys(require.cache);
const defaultObserverLoaded = loadedModules.some((filePath) =>
/legacyShadowRunObserver\.(?:ts|js)$/.test(filePath),
);
const repositoryLoaded = loadedModules.some((filePath) =>
/legacy-sequelize\/runRepository\.(?:ts|js)$/.test(filePath),
);
if (
result?.code !== 0 ||
observed.count !== expectedAfter ||
observed.succeeded !== expectedAfter
) {
throw new QingLong3LegacyShadowResourceRollbackError(
`${mode} restart did not preserve the expected Legacy result`,
);
}
return Object.freeze({
mode,
configuredOrigins,
legacyExitCode: result.code,
runCountBefore: expectedBefore,
runCountAfter: observed.count,
succeededRunCountAfter: observed.succeeded,
runDelta: observed.count - expectedBefore,
shortCircuitFactCalls,
defaultObserverLoaded,
repositoryLoaded,
peakProcessRssBytes: process.resourceUsage().maxRSS * 1024,
});
} finally {
await sequelize.close();
}
}
function runInternalChild(args, env) {
const result = spawnSync(process.execPath, [__filename, ...args], {
cwd: path.resolve(__dirname, '..'),
env: { ...process.env, ...env },
encoding: 'utf8',
timeout: 30_000,
maxBuffer: MAX_CHILD_OUTPUT_BYTES,
});
if (result.error) throw result.error;
if (result.status !== 0) {
throw new QingLong3LegacyShadowResourceRollbackError(
`internal child failed: ${(result.stderr || result.stdout).trim()}`,
);
}
try {
const lines = result.stdout
.trim()
.split('\n')
.map((line) => line.trim())
.filter(Boolean);
for (const line of lines.reverse()) {
try {
return JSON.parse(line);
} catch {
// Some legacy dependencies emit bounded diagnostics on stdout.
}
}
throw new Error('no JSON record was emitted');
} catch (error) {
throw new QingLong3LegacyShadowResourceRollbackError(
`internal child returned invalid JSON: ${error.message}`,
);
}
}
async function inspectDatabase(databasePath) {
const database = new Sequelize({
dialect: 'sqlite',
storage: databasePath,
logging: false,
dialectOptions: { mode: sqlite3.OPEN_READONLY },
pool: { max: 1, min: 0 },
});
try {
const [integrity] = await database.query('PRAGMA integrity_check', {
type: QueryTypes.SELECT,
});
return Object.freeze({
integrity: integrity.integrity_check,
runCount: (await runSummary(database)).count,
});
} finally {
await database.close();
}
}
async function runEvidence(options) {
const directory = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-legacy-shadow-resource-'),
);
const dataDirectory = path.join(directory, 'data');
const databaseDirectory = path.join(dataDirectory, 'db');
const databasePath = path.join(databaseDirectory, 'database.sqlite');
const previousDataDirectory = process.env.QL_DATA_DIR;
fs.mkdirSync(databaseDirectory, { recursive: true });
process.env.QL_DATA_DIR = dataDirectory;
try {
await createFixture(databasePath, options.profile);
const beforeAudit = fileStorage(databasePath);
const childEnvironment = {
QL_DATA_DIR: dataDirectory,
QL3_SHADOW_DRILL_DATABASE: databasePath,
};
let audit;
if (options.mode === 'audit-only') {
const previousDatabase = process.env.QL3_SHADOW_DRILL_DATABASE;
process.env.QL3_SHADOW_DRILL_DATABASE = databasePath;
try {
audit = await auditChild(options);
} finally {
if (previousDatabase === undefined) {
delete process.env.QL3_SHADOW_DRILL_DATABASE;
} else {
process.env.QL3_SHADOW_DRILL_DATABASE = previousDatabase;
}
}
} else {
audit = runInternalChild(
[
'--internal-child=audit',
`--profile=${options.profile}`,
`--samples=${options.samples}`,
`--max-audit-p95-ms=${options.maxAuditP95Ms}`,
`--max-rss-delta-mb=${options.maxRssDeltaBytes / MIB}`,
],
{ ...childEnvironment, QL3_SHADOW_ORIGINS: '' },
);
}
const afterAudit = fileStorage(databasePath);
const fixtureRunCount = PROFILES[options.profile].candidates;
const enabled =
options.mode === 'full'
? runInternalChild(
[
'--internal-child=rollback-enabled',
`--expected-before=${fixtureRunCount}`,
],
{ ...childEnvironment, QL3_SHADOW_ORIGINS: 'system' },
)
: undefined;
const off =
options.mode === 'full'
? runInternalChild(
[
'--internal-child=rollback-off',
`--expected-before=${fixtureRunCount + 1}`,
],
{ ...childEnvironment, QL3_SHADOW_ORIGINS: '' },
)
: undefined;
const finalDatabase = await inspectDatabase(databasePath);
const violations = [];
if (audit.queryCount !== audit.expectedQueryCount) {
violations.push('audit query count exceeded the two-query page contract');
}
if (audit.durationP95Ms > options.maxAuditP95Ms) {
violations.push('audit p95 exceeded its configured resource budget');
}
if (audit.rssDeltaBytes > options.maxRssDeltaBytes) {
violations.push(
'audit RSS delta exceeded its configured resource budget',
);
}
if (
beforeAudit.logicalBytes !== afterAudit.logicalBytes ||
beforeAudit.allocatedBytes !== afterAudit.allocatedBytes ||
beforeAudit.fileCount !== afterAudit.fileCount
) {
violations.push('read-only audit changed SQLite storage');
}
if (
options.mode === 'full' &&
(enabled.runDelta !== 1 ||
enabled.legacyExitCode !== 0 ||
!enabled.defaultObserverLoaded ||
!enabled.repositoryLoaded)
) {
violations.push(
'enabled restart did not produce one Shadow terminal Run',
);
}
if (
options.mode === 'full' &&
(off.runDelta !== 0 ||
off.legacyExitCode !== 0 ||
off.configuredOrigins.length !== 0 ||
off.shortCircuitFactCalls !== 0 ||
off.defaultObserverLoaded ||
off.repositoryLoaded)
) {
violations.push('off restart retained Shadow initialization or writes');
}
if (
finalDatabase.integrity !== 'ok' ||
finalDatabase.runCount !==
fixtureRunCount + (options.mode === 'full' ? 1 : 0)
) {
violations.push('rollback drill database did not converge safely');
}
return Object.freeze({
schemaVersion: 1,
fixture: 'qinglong/legacy-shadow-resource-rollback-evidence@v1',
profile: options.profile,
workload: Object.freeze({
mode: options.mode,
runtime: BUILD_AVAILABLE ? 'compiled_backend' : 'typescript_fallback',
candidates: PROFILES[options.profile].candidates,
pageSize: PROFILES[options.profile].pageSize,
maxPages: PROFILES[options.profile].maxPages,
samples: options.samples,
}),
audit: Object.freeze({
...audit,
databaseStorageBefore: beforeAudit,
databaseStorageAfter: afterAudit,
readOnlyStorageStable:
beforeAudit.logicalBytes === afterAudit.logicalBytes &&
beforeAudit.allocatedBytes === afterAudit.allocatedBytes &&
beforeAudit.fileCount === afterAudit.fileCount,
}),
rollback:
options.mode === 'full'
? Object.freeze({
performed: true,
mechanism: 'process_restart_environment_disable',
enabled: Object.freeze({
configuredOrigins: enabled.configuredOrigins,
legacyExitCode: enabled.legacyExitCode,
runDelta: enabled.runDelta,
defaultObserverLoaded: enabled.defaultObserverLoaded,
repositoryLoaded: enabled.repositoryLoaded,
peakProcessRssBytes: enabled.peakProcessRssBytes,
}),
off: Object.freeze({
configuredOrigins: off.configuredOrigins,
legacyExitCode: off.legacyExitCode,
runDelta: off.runDelta,
shortCircuitFactCalls: off.shortCircuitFactCalls,
defaultObserverLoaded: off.defaultObserverLoaded,
repositoryLoaded: off.repositoryLoaded,
peakProcessRssBytes: off.peakProcessRssBytes,
}),
finalRunCount: finalDatabase.runCount,
databaseIntegrity: finalDatabase.integrity,
legacyContinued:
enabled.legacyExitCode === 0 && off.legacyExitCode === 0,
shadowWritesStopped: enabled.runDelta === 1 && off.runDelta === 0,
physicalPowerLossProven: false,
})
: Object.freeze({
performed: false,
reason: 'separate_release_gate',
finalRunCount: finalDatabase.runCount,
databaseIntegrity: finalDatabase.integrity,
}),
qualification: Object.freeze({
passed: violations.length === 0,
measures: options.mode === 'full' ? MEASURES : MEASURES.slice(0, 4),
doesNotProve:
options.mode === 'full'
? EXCLUSIONS
: [...EXCLUSIONS, 'shadow_off_process_restart_rollback'],
violations,
}),
});
} finally {
if (previousDataDirectory === undefined) {
delete process.env.QL_DATA_DIR;
} else {
process.env.QL_DATA_DIR = previousDataDirectory;
}
fs.rmSync(directory, { recursive: true, force: true });
}
}
async function main() {
const internalChild = process.argv.find((argument) =>
argument.startsWith('--internal-child='),
);
if (internalChild) {
const mode = internalChild.slice('--internal-child='.length);
const profile =
process.argv
.find((argument) => argument.startsWith('--profile='))
?.slice('--profile='.length) ?? 'edge';
const samples = Number(
process.argv
.find((argument) => argument.startsWith('--samples='))
?.slice('--samples='.length) ?? 1,
);
let report;
if (mode === 'audit') {
report = await auditChild({ profile, samples });
} else if (mode === 'rollback-enabled' || mode === 'rollback-off') {
const expectedBefore = Number(
process.argv
.find((argument) => argument.startsWith('--expected-before='))
?.slice('--expected-before='.length),
);
if (!Number.isSafeInteger(expectedBefore) || expectedBefore < 0) {
throw new QingLong3LegacyShadowResourceRollbackError(
'internal expected Run count is invalid',
);
}
report = await rollbackChild(
mode === 'rollback-enabled' ? 'enabled' : 'off',
expectedBefore,
);
} else {
throw new QingLong3LegacyShadowResourceRollbackError(
'internal child mode is invalid',
);
}
process.stdout.write(`${JSON.stringify(report)}\n`);
return;
}
const options = parseArguments(process.argv.slice(2));
if (Number(process.versions.node.split('.')[0]) < 24) {
throw new QingLong3LegacyShadowResourceRollbackError(
'Node.js 24 or newer is required',
);
}
if (options.requireCompiled && !BUILD_AVAILABLE) {
throw new QingLong3LegacyShadowResourceRollbackError(
'compiled backend is required for resource evidence',
);
}
const report = await runEvidence(options);
process.stdout.write(
`${JSON.stringify(report, null, options.json ? 0 : 2)}\n`,
);
if (!report.qualification.passed) process.exitCode = 1;
}
module.exports = {
EXCLUSIONS,
MEASURES,
PROFILES,
QingLong3LegacyShadowResourceRollbackError,
parseArguments,
percentile,
runEvidence,
};
if (require.main === module) {
main().catch((error) => {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
});
}
+121 -44
View File
@@ -22,6 +22,9 @@ const RESOURCE_TIERS = Object.freeze({
sqliteMaxTransactionP95Ms: 500,
sqliteMaxBatchStallMs: 5_000,
sqliteMaxRssDeltaMb: 32,
shadowAuditSamples: 8,
shadowAuditMaxP95Ms: 1_500,
shadowAuditMaxRssDeltaMb: 32,
workflowLockSamples: 16,
workflowMaxLockP95Ms: 500,
workflowMaxProcessRssMb: 96,
@@ -41,6 +44,9 @@ const RESOURCE_TIERS = Object.freeze({
sqliteMaxTransactionP95Ms: 250,
sqliteMaxBatchStallMs: 2_500,
sqliteMaxRssDeltaMb: 64,
shadowAuditSamples: 8,
shadowAuditMaxP95Ms: 1_000,
shadowAuditMaxRssDeltaMb: 64,
workflowLockSamples: 32,
workflowMaxLockP95Ms: 250,
workflowMaxProcessRssMb: 160,
@@ -164,6 +170,33 @@ function parseMountOptions(raw) {
return mounts;
}
function mountOptionsForPath(mounts, targetPath) {
let selectedPath;
let selectedOptions;
for (const [mountPath, options] of mounts) {
const containsTarget =
mountPath === targetPath ||
(mountPath === '/' && targetPath.startsWith('/')) ||
(mountPath !== '/' && targetPath.startsWith(`${mountPath}/`));
if (
containsTarget &&
(selectedPath === undefined || mountPath.length > selectedPath.length)
) {
selectedPath = mountPath;
selectedOptions = options;
}
}
return selectedOptions;
}
function isPathTreeReadOnly(mounts, targetPath) {
if (!mountOptionsForPath(mounts, targetPath)?.includes('ro')) return false;
return [...mounts].every(
([mountPath, options]) =>
!mountPath.startsWith(`${targetPath}/`) || !options.includes('rw'),
);
}
function readLinuxEnvelope(root = '/') {
const read = (relativePath) =>
fs.readFileSync(path.join(root, relativePath), 'utf8');
@@ -219,12 +252,13 @@ function validateEnvelope(tierName, envelope, identity) {
violations.push(`architecture ${identity.architecture} is unsupported`);
}
if (identity.uid === 0) violations.push('resource workload must be non-root');
for (const mountPath of ['/', '/workspace']) {
if (!envelope.mounts.get(mountPath)?.includes('ro')) {
violations.push(`${mountPath} must be mounted read-only`);
}
if (!mountOptionsForPath(envelope.mounts, '/')?.includes('ro')) {
violations.push('/ must be mounted read-only');
}
if (!envelope.mounts.get('/tmp')?.includes('rw')) {
if (!isPathTreeReadOnly(envelope.mounts, '/workspace')) {
violations.push('/workspace must be mounted read-only');
}
if (!mountOptionsForPath(envelope.mounts, '/tmp')?.includes('rw')) {
violations.push('/tmp must be a writable bounded tmpfs');
}
return Object.freeze(violations);
@@ -319,7 +353,7 @@ function createWorkloadPlans(root, tierName) {
durableOutputBytes: PROMPT_RESOURCE_OUTPUT_BYTES,
providerCalls: 2,
keyLoads: 1,
keyResolutions: 1,
keyResolutions: 2,
exactReplay: true,
contentFree: true,
maxLogicalWriteAmplificationPermille: 3_000,
@@ -346,40 +380,44 @@ function createWorkloadPlans(root, tierName) {
`--max-lock-p95-ms=${tier.workflowMaxLockP95Ms}`,
]),
}),
Object.freeze({
name: 'local-workflow-admission-crash-recovery',
format: 'node_test',
nodeArgs: Object.freeze([
path.join(
root,
'packages/ql3-local-sqlite/test/pluginPackageWorkflowAdmissionCrashMatrix.test.cjs',
),
]),
contract: Object.freeze({
profiles: Object.freeze(['edge', 'standalone']),
crashPointsPerProfile: 8,
scenarios: 16,
mechanism: 'process_sigkill_then_database_reopen',
physicalPowerLossProven: false,
}),
}),
Object.freeze({
name: 'local-workflow-control-crash-recovery',
format: 'node_test',
nodeArgs: Object.freeze([
path.join(
root,
'packages/ql3-local-sqlite/test/pluginPackageWorkflowTaskControlCrashMatrix.test.cjs',
),
]),
contract: Object.freeze({
profiles: Object.freeze(['edge', 'standalone']),
crashPointsPerProfile: 8,
scenarios: 16,
conclusiveStopObserved: true,
physicalPowerLossProven: false,
}),
}),
...(tierName === 'edge-release-ci'
? [
Object.freeze({
name: 'local-workflow-admission-crash-recovery',
format: 'node_test',
nodeArgs: Object.freeze([
path.join(
root,
'packages/ql3-local-sqlite/test/pluginPackageWorkflowAdmissionCrashMatrix.test.cjs',
),
]),
contract: Object.freeze({
profiles: Object.freeze(['edge', 'standalone']),
crashPointsPerProfile: 8,
scenarios: 16,
mechanism: 'process_sigkill_then_database_reopen',
physicalPowerLossProven: false,
}),
}),
Object.freeze({
name: 'local-workflow-control-crash-recovery',
format: 'node_test',
nodeArgs: Object.freeze([
path.join(
root,
'packages/ql3-local-sqlite/test/pluginPackageWorkflowTaskControlCrashMatrix.test.cjs',
),
]),
contract: Object.freeze({
profiles: Object.freeze(['edge', 'standalone']),
crashPointsPerProfile: 8,
scenarios: 16,
conclusiveStopObserved: true,
physicalPowerLossProven: false,
}),
}),
]
: []),
...(tierName === 'edge-release-ci'
? [
Object.freeze({
@@ -437,6 +475,42 @@ function createWorkloadPlans(root, tierName) {
`--max-database-growth-bytes=${4 * MIB}`,
]),
}),
Object.freeze({
name: 'legacy-shadow-terminal-edge',
script: path.join(
root,
'scripts/ql3-legacy-shadow-resource-rollback.cjs',
),
args: Object.freeze([
'--json',
'--require-compiled',
'--profile=edge',
`--mode=${tierName === 'edge-release-ci' ? 'full' : 'audit-only'}`,
`--samples=${tier.shadowAuditSamples}`,
`--max-audit-p95-ms=${tier.shadowAuditMaxP95Ms}`,
`--max-rss-delta-mb=${tier.shadowAuditMaxRssDeltaMb}`,
]),
}),
...(tierName === 'edge-release-ci'
? [
Object.freeze({
name: 'legacy-shadow-terminal-standalone',
script: path.join(
root,
'scripts/ql3-legacy-shadow-resource-rollback.cjs',
),
args: Object.freeze([
'--json',
'--require-compiled',
'--profile=standalone',
'--mode=audit-only',
`--samples=${tier.shadowAuditSamples}`,
`--max-audit-p95-ms=${tier.shadowAuditMaxP95Ms}`,
`--max-rss-delta-mb=${tier.shadowAuditMaxRssDeltaMb}`,
]),
}),
]
: []),
]);
}
return Object.freeze([
@@ -656,10 +730,11 @@ function main() {
pidsMax: after.pidsMax,
noNewPrivileges: after.noNewPrivileges,
seccompMode: after.seccompMode,
rootReadOnly: after.mounts.get('/')?.includes('ro') ?? false,
workspaceReadOnly:
after.mounts.get('/workspace')?.includes('ro') ?? false,
tmpWritable: after.mounts.get('/tmp')?.includes('rw') ?? false,
rootReadOnly:
mountOptionsForPath(after.mounts, '/')?.includes('ro') ?? false,
workspaceReadOnly: isPathTreeReadOnly(after.mounts, '/workspace'),
tmpWritable:
mountOptionsForPath(after.mounts, '/tmp')?.includes('rw') ?? false,
memoryEventsBefore: before.memoryEvents,
memoryEventsAfter: after.memoryEvents,
},
@@ -679,6 +754,8 @@ module.exports = {
RESOURCE_TIERS,
QingLong3LinuxResourceGateError,
createWorkloadPlans,
isPathTreeReadOnly,
mountOptionsForPath,
parseArguments,
parseCpuMax,
parseKeyValueFile,