feat(ql3): report legacy shadow startup differences

This commit is contained in:
whyour
2026-08-18 09:58:16 +08:00
parent 6cb9a075d7
commit d38f624262
9 changed files with 722 additions and 85 deletions
@@ -6,10 +6,40 @@ const path = require('node:path');
const { test } = require('node:test');
const {
bootstrapLegacyShadowStartupReconciliation,
createLegacyShadowStartupDifferenceReport,
} = require('../../back/runtime/adapters/legacy/bootstrapLegacyShadowStartupReconciliation');
function summary(overrides = {}) {
const OUTCOMES = [
'completed',
'cancelled',
'abandoned',
'markedLost',
'repaired',
'pending',
'ambiguous',
'skipped',
'failed',
];
function originSummary(origin, overrides = {}) {
return {
origin,
scanned: 0,
completed: 0,
cancelled: 0,
abandoned: 0,
markedLost: 0,
repaired: 0,
pending: 0,
ambiguous: 0,
skipped: 0,
failed: 0,
...overrides,
};
}
function summary(overrides = {}, origins = ['manual']) {
const value = {
pages: 1,
scanned: 0,
completed: 0,
@@ -25,6 +55,8 @@ function summary(overrides = {}) {
remaining: false,
...overrides,
};
value.byOrigin ??= origins.map((origin) => originSummary(origin));
return value;
}
test('disabled bootstrap remains fully lazy', async () => {
@@ -67,7 +99,7 @@ test('applies distinct one-shot edge and standalone budgets', async () => {
profile,
async execute(request) {
requests.push(request);
return summary();
return summary({}, request.origins);
},
audit() {},
});
@@ -87,6 +119,155 @@ test('applies distinct one-shot edge and standalone budgets', async () => {
);
});
test('emits one versioned origin-bounded difference report and metric batch', async () => {
const batches = [];
const result = await bootstrapLegacyShadowStartupReconciliation({
origins: ['manual', 'scheduled_system'],
profile: 'standalone',
async execute(request) {
return summary(
{
scanned: 4,
completed: 1,
markedLost: 1,
repaired: 1,
pending: 1,
byOrigin: [
originSummary('manual', {
scanned: 3,
completed: 1,
markedLost: 1,
repaired: 1,
}),
originSummary('scheduled_system', {
scanned: 1,
pending: 1,
}),
],
},
request.origins,
);
},
audit() {},
collect(batch) {
batches.push(batch);
},
});
assert.equal(result.state, 'incomplete');
assert.equal(
result.report.schema,
'qinglong/legacy-shadow-startup-difference-report@v1',
);
assert.equal(result.report.assessment, 'waiting_external_callback');
assert.equal(result.report.budget.maxCandidates, 128);
assert.deepEqual(
result.report.byOrigin.map(({ origin, scanned }) => ({ origin, scanned })),
[
{ origin: 'manual', scanned: 3 },
{ origin: 'scheduled_system', scanned: 1 },
],
);
assert.equal(batches.length, 1);
assert.equal(
batches[0].schema,
'qinglong/legacy-shadow-startup-metric-batch@v1',
);
assert.equal(batches[0].dimensions.assessment, 'waiting_external_callback');
assert.equal(batches[0].values.scanned, 4);
assert.equal(batches[0].values.pending, 1);
assert.deepEqual(
Object.keys(result.report.outcomes).sort(),
[...OUTCOMES].sort(),
);
});
test('turns inconsistent report input into a fail-open low-sensitivity audit', async () => {
let collected = false;
const result = await bootstrapLegacyShadowStartupReconciliation({
origins: ['manual'],
profile: 'edge',
async execute() {
return summary({ scanned: 1 });
},
audit() {},
collect() {
collected = true;
},
});
assert.deepEqual(result, { state: 'failed', errorType: 'RangeError' });
assert.equal(collected, false);
});
test('rejects non-Profile budgets and cursors outside page-limit reports', () => {
assert.throws(
() =>
createLegacyShadowStartupDifferenceReport(
{
origins: ['manual'],
profile: 'edge',
pageSize: 32,
maxPages: 1,
},
summary(),
),
RangeError,
);
assert.throws(
() =>
createLegacyShadowStartupDifferenceReport(
{ origins: ['manual'], profile: 'edge', pageSize: 8, maxPages: 1 },
summary({
nextCursor: { createdAtMs: 10, runId: 'must-not-escape' },
}),
),
RangeError,
);
});
test('classifies ambiguous, skipped, or failed comparisons as attention required', async () => {
const result = await bootstrapLegacyShadowStartupReconciliation({
origins: ['manual'],
profile: 'edge',
async execute(request) {
return summary(
{
scanned: 1,
ambiguous: 1,
byOrigin: [originSummary('manual', { scanned: 1, ambiguous: 1 })],
},
request.origins,
);
},
audit() {},
});
assert.equal(result.state, 'incomplete');
assert.equal(result.report.assessment, 'attention_required');
assert.equal(result.metrics.dimensions.assessment, 'attention_required');
});
test('keeps startup fail-open when metric collection fails', async () => {
const result = await bootstrapLegacyShadowStartupReconciliation({
origins: ['manual'],
profile: 'edge',
async execute(request) {
return summary({}, request.origins);
},
audit() {},
collect() {
throw new Error('collector transport secret');
},
});
assert.equal(result.state, 'reconciled');
assert.equal(
JSON.stringify(result).includes('collector transport secret'),
false,
);
});
test('fails open with a low-sensitivity error type', async () => {
const result = await bootstrapLegacyShadowStartupReconciliation({
origins: ['manual'],
@@ -121,6 +302,9 @@ test('redacts the resume cursor Run identity from startup audit output', async (
assert.equal(result.state, 'incomplete');
assert.equal(result.summary.resumeAvailable, true);
assert.equal('nextCursor' in result.summary, false);
assert.equal(result.report.assessment, 'incomplete');
assert.equal(result.metrics.values.resumeAvailable, 1);
assert.equal('nextCursor' in result.report.coverage, false);
assert.equal(records.join('').includes('run-secret-identity'), false);
});
@@ -281,6 +281,50 @@ test('keeps system-crond pending without terminal callback evidence', async () =
);
});
test('keeps a bounded per-origin outcome matrix for later ownership gates', async () => {
const { database, writer, reconciler } = await createStack();
await activeShadow(writer, { legacyCronId: 15, pid: 4311 });
await activeShadow(writer, {
origin: 'scheduled_system',
legacyCronId: 16,
pid: 4312,
});
await insertInstance(database, {
cron_id: 15,
pid: 4311,
status: 2,
finished_at: null,
});
await insertInstance(database, {
cron_id: 16,
pid: 4312,
status: 2,
finished_at: null,
});
const summary = await reconciler.reconcileBatch({
origins: ['manual', 'scheduled_system'],
});
assert.deepEqual(
summary.byOrigin.map(({ origin, scanned, markedLost, pending }) => ({
origin,
scanned,
markedLost,
pending,
})),
[
{ origin: 'manual', scanned: 1, markedLost: 1, pending: 0 },
{
origin: 'scheduled_system',
scanned: 1,
markedLost: 0,
pending: 1,
},
],
);
});
test('refuses ambiguous duplicate identity evidence instead of guessing a terminal result', async () => {
const { database, repository, writer, reconciler } = await createStack();
const logPath = 'cron/ambiguous.log';
@@ -349,6 +393,21 @@ test('supervisor preserves a stable cursor when its Profile budget is exhausted'
ambiguous: 0,
skipped: 0,
failed: 0,
byOrigin: [
{
origin: 'manual',
scanned: 1,
completed: 0,
cancelled: 0,
abandoned: 0,
markedLost: 1,
repaired: 0,
pending: 0,
ambiguous: 0,
skipped: 0,
failed: 0,
},
],
truncated: true,
nextCursor: { createdAtMs: 10, runId: 'run-10' },
};