test(ql3): qualify failed upgrades on edge devices

This commit is contained in:
whyour
2026-08-14 14:53:44 +08:00
parent 1c75ac0f45
commit 51145a8028
10 changed files with 1156 additions and 71 deletions
+24 -23
View File
@@ -117,6 +117,7 @@ test('builds tier-specific workload plans without shell commands', () => {
'local-workflow-sqlite-lock',
'local-workflow-admission-crash-recovery',
'local-workflow-control-crash-recovery',
'plugin-package-failed-upgrade',
],
);
assert.match(edge[0].script, /ql3-edge-benchmark\.cjs$/);
@@ -135,10 +136,7 @@ test('builds tier-specific workload plans without shell commands', () => {
),
true,
);
const edgeRelease = createWorkloadPlans(
'/workspace',
'edge-release-ci',
);
const edgeRelease = createWorkloadPlans('/workspace', 'edge-release-ci');
assert.deepEqual(
edgeRelease.map(({ name }) => name),
[
@@ -152,6 +150,7 @@ test('builds tier-specific workload plans without shell commands', () => {
'local-workflow-control-crash-recovery',
'local-ai-prompt-model-invocation-crash-recovery',
'local-ai-prompt-outer-transaction-crash-recovery',
'plugin-package-failed-upgrade',
],
);
assert.equal(
@@ -185,17 +184,11 @@ test('builds tier-specific workload plans without shell commands', () => {
assert.equal(edgeRelease[3].contract.exactReplay, true);
assert.equal(edgeRelease[3].contract.contentFree, true);
assert.equal(edgeRelease[3].contract.durableOutputBytes, 512 * 1024);
assert.equal(
edgeRelease[3].contract.maxWalWriteAmplificationPermille,
0,
);
assert.equal(edgeRelease[3].contract.maxWalWriteAmplificationPermille, 0);
assert.equal(edgeRelease[4].contract.profile, 'standalone');
assert.equal(edgeRelease[4].contract.journalMode, 'wal');
assert.equal(edgeRelease[4].contract.requireWalGrowth, true);
assert.match(
edge[3].script,
/ql3-local-workflow-resource-benchmark\.cjs$/,
);
assert.match(edge[3].script, /ql3-local-workflow-resource-benchmark\.cjs$/);
assert.ok(edge[3].args.includes('--lock-samples=16'));
assert.ok(edge[3].args.includes('--max-lock-p95-ms=500'));
assert.equal(edge[4].contract.scenarios, 16);
@@ -221,6 +214,12 @@ test('builds tier-specific workload plans without shell commands', () => {
true,
);
assert.equal(edgeRelease[9].contract.physicalPowerLossProven, false);
assert.match(
edge.at(-1).script,
/ql3-plugin-package-recovery-edge-benchmark\.cjs$/,
);
assert.ok(edge.at(-1).args.includes('--max-rss-delta-mb=64'));
assert.ok(edgeRelease.at(-1).args.includes('--max-rss-delta-mb=96'));
const cluster = createWorkloadPlans('/workspace', 'cluster-control-ci');
assert.deepEqual(
cluster.map(({ name }) => name),
@@ -248,7 +247,9 @@ test('fails closed when durable Prompt resource evidence drifts', () => {
const [edgePlan, standalonePlan] = createWorkloadPlans(
'/workspace',
'edge-release-ci',
).filter(({ contract }) => contract?.kind === 'durable_prompt_output_resource');
).filter(
({ contract }) => contract?.kind === 'durable_prompt_output_resource',
);
const evidence = {
profile: 'edge',
journalMode: 'delete',
@@ -268,7 +269,9 @@ test('fails closed when durable Prompt resource evidence drifts', () => {
physicalPowerLossProven: false,
};
const output = (value) =>
`tests 1\npass 1\nfail 0\nskipped 0\nQL3_RESOURCE_EVIDENCE=${JSON.stringify(value)}\n`;
`tests 1\npass 1\nfail 0\nskipped 0\nQL3_RESOURCE_EVIDENCE=${JSON.stringify(
value,
)}\n`;
assert.equal(
parseNodeTestReport(output(evidence), edgePlan).evidence.profile,
'edge',
@@ -301,12 +304,8 @@ test('fails closed when durable Prompt resource evidence drifts', () => {
});
test('fails closed when authenticated Workflow cancellation evidence drifts', () => {
const plan = createWorkloadPlans(
'/workspace',
'edge-release-ci',
).find(
({ contract }) =>
contract?.kind === 'local_workflow_product_lifecycle',
const plan = createWorkloadPlans('/workspace', 'edge-release-ci').find(
({ contract }) => contract?.kind === 'local_workflow_product_lifecycle',
);
assert.ok(plan);
const completionEvidence = {
@@ -332,10 +331,12 @@ test('fails closed when authenticated Workflow cancellation evidence drifts', ()
physicalPowerLossProven: false,
};
const output = (value) =>
`tests 2\npass 2\nfail 0\nskipped 0\nQL3_RESOURCE_EVIDENCE=${JSON.stringify(completionEvidence)}\nQL3_RESOURCE_EVIDENCE=${JSON.stringify(value)}\n`;
`tests 2\npass 2\nfail 0\nskipped 0\nQL3_RESOURCE_EVIDENCE=${JSON.stringify(
completionEvidence,
)}\nQL3_RESOURCE_EVIDENCE=${JSON.stringify(value)}\n`;
assert.equal(
parseNodeTestReport(output(cancellationEvidence), plan)
.evidenceRecords[1].processExited,
parseNodeTestReport(output(cancellationEvidence), plan).evidenceRecords[1]
.processExited,
true,
);
assert.throws(
+45 -2
View File
@@ -24,6 +24,9 @@ const {
validateObservedPlatform,
writeNoReplace,
} = require('../../scripts/ql3-physical-edge-evidence.cjs');
const {
runBenchmark: runPluginPackageRecoveryEdgeBenchmark,
} = require('../../scripts/ql3-plugin-package-recovery-edge-benchmark.cjs');
const {
buildReport: buildAdoptionScaleReport,
normalizeManifest: normalizeAdoptionScaleManifest,
@@ -168,15 +171,49 @@ test('fails closed on architecture, memory, filesystem and virtualization drift'
);
});
test('produces digest-bound candidate evidence but never supported status', () => {
test('produces digest-bound candidate evidence but never supported status', async () => {
const pluginPackageRecovery = await runPluginPackageRecoveryEdgeBenchmark({
maxDatabaseGrowthBytes: 4 * 1024 * 1024,
maxDurationMs: 10_000,
maxRssDeltaBytes: 96 * 1024 * 1024,
});
const workloads = [
{
name: 'edge-executor',
report: {
schemaVersion: 1,
profile: 'edge',
gates: { passed: true, violations: [] },
},
},
{
name: 'node-sqlite-on-device-storage',
report: {
journalMode: 'delete',
synchronous: 'full',
integrityCheck: 'ok',
databaseBytes: 16_384,
rssDeltaMb: 1,
},
},
{
name: 'plugin-package-failed-upgrade',
report: pluginPackageRecovery,
},
];
const report = buildEvidenceReport({
manifest: manifest(),
observed: observed(),
workloads: [{ name: 'edge-executor', report: { gates: { passed: true } } }],
workloads,
generatedAt: '2026-07-22T00:00:00.000Z',
});
assert.equal(report.supported, false);
assert.equal(report.qualification.physicalCandidate, true);
assert.ok(
report.qualification.collectedEvidence.includes(
'plugin_package_failed_upgrade_retains_active_generation',
),
);
assert.equal(report.sha256.length, 64);
const changed = buildEvidenceReport({
manifest: manifest({ deviceId: 'router-a2' }),
@@ -184,6 +221,12 @@ test('produces digest-bound candidate evidence but never supported status', () =
workloads: [],
generatedAt: '2026-07-22T00:00:00.000Z',
});
assert.equal(changed.qualification.physicalCandidate, false);
assert.ok(
changed.qualification.remainingRequiredEvidence.includes(
'plugin_package_failed_upgrade_retains_active_generation',
),
);
assert.notEqual(changed.sha256, report.sha256);
});
@@ -0,0 +1,49 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
parseArguments,
runBenchmark,
validateReport,
} = require('../../scripts/ql3-plugin-package-recovery-edge-benchmark.cjs');
test('parses bounded failed-upgrade benchmark thresholds', () => {
assert.deepEqual(
parseArguments([
'--json',
'--max-duration-ms=5000',
'--max-rss-delta-mb=64',
'--max-database-growth-bytes=2097152',
]),
{
json: true,
maxDatabaseGrowthBytes: 2 * 1024 * 1024,
maxDurationMs: 5_000,
maxRssDeltaBytes: 64 * 1024 * 1024,
},
);
assert.throws(
() => parseArguments(['--max-rss-delta-mb=0']),
/positive number/,
);
assert.throws(() => parseArguments(['--unknown=1']), /unsupported argument/);
});
test('uses real SQLite recovery to reject an invalid upgrade before publication', async () => {
const report = await runBenchmark({
maxDatabaseGrowthBytes: 4 * 1024 * 1024,
maxDurationMs: 10_000,
maxRssDeltaBytes: 96 * 1024 * 1024,
});
assert.deepEqual(validateReport(report), []);
assert.equal(report.durable.state, 'failed');
assert.equal(report.durable.activeLockDigestPreserved, true);
assert.equal(report.database.materializedCandidateRevisions, 0);
assert.equal(report.calls.publisherPublish, 0);
assert.equal(report.gates.passed, true);
assert.deepEqual(validateReport({ ...report, supported: true }), [
'report identity is invalid',
]);
});