feat(ql3): gate console capacity evidence

This commit is contained in:
whyour
2026-08-20 10:04:21 +08:00
parent 0a5f1448f1
commit 3979707f6d
7 changed files with 2289 additions and 1 deletions
@@ -0,0 +1,402 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { test } = require('node:test');
const {
ASSERTION_SEQUENCE,
LIMITATIONS,
MEMORY_MAX_BYTES,
MINIMUM_MEMORY_HEADROOM_BYTES,
createArchitectureEvidence,
evidenceDigest,
mergeCrossArchitectureEvidence,
normalizeSource,
readJsonFile,
validateArchitectureEvidence,
validateObservation,
validateReleaseEvidence,
} = require('../../scripts/ql3-cluster-copilot-console-capacity-evidence.cjs');
const root = path.resolve(__dirname, '../..');
const scriptPath = path.join(
root,
'scripts/ql3-cluster-copilot-console-capacity-evidence.cjs',
);
const scriptSource = fs.readFileSync(scriptPath, 'utf8');
function fixtureSource(overrides = {}) {
return {
repository: 'whyour/qinglong',
revision: 'a'.repeat(40),
workflow: 'QingLong 3.0 CI',
runId: '123456',
runAttempt: 1,
...overrides,
};
}
function memoryEvents(overrides = {}) {
return {
low: 0,
high: 0,
max: 0,
oom: 0,
oomKill: 0,
oomGroupKill: 0,
...overrides,
};
}
function fixtureObservation(architecture, overrides = {}) {
const peak = 96 * 1024 * 1024;
return {
schemaVersion: 1,
observedAtMs: 1_700_000_000_000,
platform: 'linux',
architecture,
image: {
architecture: architecture === 'x64' ? 'amd64' : 'arm64',
id: `sha256:${architecture === 'x64' ? '1' : '2'}`.padEnd(
71,
architecture === 'x64' ? '1' : '2',
),
bytes: architecture === 'x64' ? 120_000_000 : 119_000_000,
user: '10001:10001',
},
runtime: { node: 'v24.18.0', uid: 10001, gid: 10001 },
envelope: {
memoryMaxBytes: MEMORY_MAX_BYTES,
memoryPeakBytes: peak,
memoryHeadroomBytes: MEMORY_MAX_BYTES - peak,
swapMaxBytes: 0,
cpuQuotaMicros: 25_000,
cpuPeriodMicros: 100_000,
pidsMax: 32,
pidsCurrent: 5,
noNewPrivileges: 1,
seccompMode: 2,
readOnlyRoot: true,
tmpfsBytes: 8 * 1024 * 1024,
publishedHostAddress: '127.0.0.1',
capabilityDrop: 'ALL',
memoryEventsBefore: memoryEvents(),
memoryEventsAfter: memoryEvents(),
},
assertionLifecycle: {
requestCount: 4,
sequence: [...ASSERTION_SEQUENCE],
tlsVersion: 'TLSv1.3',
mutualTls: true,
consoleRestarted: false,
mutation: false,
operation: 'run.cancellation.summary',
expiredConsoleStatus: 502,
expiredCode: 'assertion_expired',
},
...overrides,
};
}
function fixtureArchitecture(architecture, source = fixtureSource()) {
return createArchitectureEvidence({
source,
architecture,
observation: fixtureObservation(architecture),
});
}
function sourceArguments(source = fixtureSource()) {
return [
`--repository=${source.repository}`,
`--revision=${source.revision}`,
`--workflow=${source.workflow}`,
`--run-id=${source.runId}`,
`--run-attempt=${source.runAttempt}`,
];
}
function writeJson(filePath, value) {
fs.writeFileSync(filePath, `${JSON.stringify(value)}\n`, 'utf8');
}
function runCli(arguments_, env = {}) {
return spawnSync(process.execPath, [scriptPath, ...arguments_], {
encoding: 'utf8',
env: { ...process.env, ...env },
});
}
test('creates exact native reports and a source-bound cross-architecture release report', () => {
const source = fixtureSource();
const x64 = fixtureArchitecture('x64', source);
const arm64 = fixtureArchitecture('arm64', source);
const release = mergeCrossArchitectureEvidence({ source, x64, arm64 });
assert.equal(
x64.fixture,
'qinglong/cluster-console-capacity-architecture-evidence@v1',
);
assert.equal(
release.fixture,
'qinglong/cluster-console-capacity-cross-architecture-evidence@v1',
);
assert.deepEqual(
release.architectures.map(({ architecture }) => architecture),
['x64', 'arm64'],
);
assert.equal(release.gates.releaseEvidenceComplete, true);
assert.equal(release.gates.passed, true);
assert.deepEqual(release.assertionLifecycle.sequence, ASSERTION_SEQUENCE);
assert.deepEqual(release.limitations, LIMITATIONS);
assert.equal(release.releaseDigest.length, 64);
assert.notEqual(x64.bundleDigest, arm64.bundleDigest);
assert.equal(validateReleaseEvidence(release, source), release);
});
test('rejects memory pressure, OOM, swap, PID and widened envelope observations', () => {
const architecture = 'x64';
const base = fixtureObservation(architecture);
const cases = [
[
{
envelope: {
...base.envelope,
memoryPeakBytes: MEMORY_MAX_BYTES - MINIMUM_MEMORY_HEADROOM_BYTES + 1,
memoryHeadroomBytes: MINIMUM_MEMORY_HEADROOM_BYTES - 1,
},
},
/resource envelope drifted/,
],
[
{
envelope: {
...base.envelope,
memoryEventsAfter: memoryEvents({ oomKill: 1 }),
},
},
/memory event oomKill changed/,
],
[
{ envelope: { ...base.envelope, swapMaxBytes: 1024 } },
/resource envelope drifted/,
],
[
{ envelope: { ...base.envelope, pidsCurrent: 33 } },
/resource envelope drifted/,
],
[
{ envelope: { ...base.envelope, unexpected: true } },
/envelope fields are invalid/,
],
];
for (const [override, expected] of cases) {
assert.throws(
() =>
validateObservation(
fixtureObservation(architecture, override),
architecture,
),
expected,
);
}
});
test('rejects assertion lifecycle, native identity and mutation drift', () => {
const base = fixtureObservation('arm64');
for (const assertionLifecycle of [
{ ...base.assertionLifecycle, requestCount: 3 },
{
...base.assertionLifecycle,
sequence: ['initial_accepted', 'expired_rejected'],
},
{ ...base.assertionLifecycle, tlsVersion: 'TLSv1.2' },
{ ...base.assertionLifecycle, mutualTls: false },
{ ...base.assertionLifecycle, consoleRestarted: true },
{ ...base.assertionLifecycle, mutation: true },
{ ...base.assertionLifecycle, operation: 'run.cancellation.rearm' },
{ ...base.assertionLifecycle, expiredCode: 'assertion_invalid' },
]) {
assert.throws(
() =>
validateObservation(
fixtureObservation('arm64', { assertionLifecycle }),
'arm64',
),
/assertion lifecycle drifted/,
);
}
assert.throws(
() => validateObservation(fixtureObservation('arm64'), 'x64'),
/native identity is invalid/,
);
});
test('rejects tampering, cross-run mixing and duplicate image identity', () => {
const source = fixtureSource();
const x64 = fixtureArchitecture('x64', source);
const arm64 = fixtureArchitecture('arm64', source);
assert.throws(
() =>
validateArchitectureEvidence(
{ ...x64, bundleDigest: '0'.repeat(64) },
source,
'x64',
),
/digest or gates drifted/,
);
assert.throws(
() =>
mergeCrossArchitectureEvidence({
source,
x64,
arm64: fixtureArchitecture(
'arm64',
fixtureSource({ revision: 'b'.repeat(40) }),
),
}),
/belongs to another source/,
);
const duplicateObservation = fixtureObservation('arm64');
duplicateObservation.image.id = x64.observation.image.id;
const duplicate = createArchitectureEvidence({
source,
architecture: 'arm64',
observation: duplicateObservation,
});
assert.throws(
() => mergeCrossArchitectureEvidence({ source, x64, arm64: duplicate }),
/independently measured images/,
);
});
test('rejects coercible source fields and oversized canonical evidence', () => {
for (const source of [
fixtureSource({ repository: 123 }),
fixtureSource({ revision: 123 }),
fixtureSource({ runId: 123456 }),
]) {
assert.throws(
() => normalizeSource(source),
/source (repository|revision|runId)/,
);
}
assert.throws(
() => evidenceDigest(Array.from({ length: 100_000 }, () => null)),
/node budget exceeded/,
);
});
test('CLI merges, audits and refuses overwrite, symlink and source drift', (t) => {
const temporaryDirectory = fs.realpathSync(
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-console-capacity-evidence-')),
);
t.after(() =>
fs.rmSync(temporaryDirectory, { recursive: true, force: true }),
);
const source = fixtureSource();
const x64Path = path.join(temporaryDirectory, 'x64.json');
const arm64Path = path.join(temporaryDirectory, 'arm64.json');
const outputPath = path.join(temporaryDirectory, 'cross.json');
writeJson(x64Path, fixtureArchitecture('x64', source));
writeJson(arm64Path, fixtureArchitecture('arm64', source));
const merged = runCli([
'--mode=merge',
...sourceArguments(source),
`--x64=${x64Path}`,
`--arm64=${arm64Path}`,
`--output=${outputPath}`,
]);
assert.equal(merged.status, 0, merged.stderr);
assert.equal(fs.statSync(outputPath).mode & 0o777, 0o600);
const report = readJsonFile(outputPath, 'release evidence');
validateReleaseEvidence(report, source);
const audit = runCli([
'--mode=audit',
...sourceArguments(source),
`--report=${outputPath}`,
]);
assert.equal(audit.status, 0, audit.stderr);
assert.equal(JSON.parse(audit.stdout).passed, true);
const overwrite = runCli([
'--mode=merge',
...sourceArguments(source),
`--x64=${x64Path}`,
`--arm64=${arm64Path}`,
`--output=${outputPath}`,
]);
assert.notEqual(overwrite.status, 0);
assert.match(overwrite.stderr, /output must be a new private file/);
const drift = runCli([
'--mode=audit',
...sourceArguments(fixtureSource({ revision: 'b'.repeat(40) })),
`--report=${outputPath}`,
]);
assert.notEqual(drift.status, 0);
assert.match(drift.stderr, /belongs to another source/);
const linkPath = path.join(temporaryDirectory, 'report-link.json');
fs.symlinkSync(outputPath, linkPath);
const symlink = runCli([
'--mode=audit',
...sourceArguments(source),
`--report=${linkPath}`,
]);
assert.notEqual(symlink.status, 0);
assert.match(symlink.stderr, /readable non-symlink file/);
});
test('capture fails closed before Docker without explicit live opt-in', () => {
const result = runCli([
'--mode=capture',
...sourceArguments(),
`--architecture=${process.arch === 'arm64' ? 'arm64' : 'x64'}`,
'--image=qinglong3-cluster-admin:ci-test',
'--output=/tmp/ql3-console-capacity-should-not-exist.json',
]);
assert.notEqual(result.status, 0);
assert.match(
result.stderr,
/QL3_CLUSTER_COPILOT_CONSOLE_CAPACITY_LIVE=1 is required/,
);
});
test('live source freezes native cgroup v2, isolation and assertion rotation mechanics', () => {
for (const contract of [
"process.platform !== 'linux' || process.arch !== architecture",
'process.version !== NODE_VERSION',
"'--memory',\n '192m'",
"'--memory-swap',\n '192m'",
"'--cpus',\n '0.25'",
"'--pids-limit',\n String(PIDS_MAX)",
"'--read-only'",
"'--cap-drop',\n 'ALL'",
"'--security-opt',\n 'no-new-privileges'",
'`127.0.0.1:${port}:${port}/tcp`',
"memoryPeakBytes: integer('memory.peak')",
'memoryEvents: { low: events.low',
"swapMaxBytes: integer('memory.swap.max')",
'requestCert: true',
'rejectUnauthorized: true',
"minVersion: 'TLSv1.3'",
"fs.renameSync(next, '/authority/assertion.jwt')",
"operation: 'run.cancellation.summary'",
'consoleRestarted: false',
'mutation: false',
'created.network = true',
"cleanupDocker(['network', 'rm', network])",
]) {
assert.ok(scriptSource.includes(contract), `missing ${contract}`);
}
assert.doesNotMatch(scriptSource, /run\.cancellation\.(?:rearm|stop|retry)/);
assert.doesNotMatch(scriptSource, /--privileged|--network[= ]host/);
});
@@ -30,6 +30,14 @@ test('accepts the reviewed native CI and digest release contracts', () => {
clusterAdminOperatorContext: true,
clusterAdminContextPreflight: true,
clusterAdminContextReadiness: true,
clusterCopilotConsoleCapacityEvidence: {
nativeArchitectures: ['x64', 'arm64'],
memoryLimitMiB: 192,
minimumHeadroomMiB: 32,
assertionRotation: true,
assertionExpiryRejected: true,
sourceBound: true,
},
releaseVersionAudit: true,
deploymentLockMaterialization: true,
ociAttestations: true,
@@ -315,6 +323,50 @@ test('rejects removal of the native Cluster Admin product facade gate', () => {
);
});
test('rejects a Console capacity capture without the exact native live opt-in', () => {
const mutated = ciSource.replace(
"QL3_CLUSTER_COPILOT_CONSOLE_CAPACITY_LIVE: '1'",
"QL3_CLUSTER_COPILOT_CONSOLE_CAPACITY_LIVE: '0'",
);
assert.throws(
() => auditClusterImageCiWorkflow(mutated),
/exact source-bound Console capacity envelope/,
);
});
test('rejects removal of the Console capacity evidence protocol tests', () => {
const mutated = ciSource.replace(
'test/back/ql3ClusterCopilotConsoleCapacityEvidence.test.cjs',
'test/back/ql3ClusterCopilotConsoleCapacityEvidence.removed.cjs',
);
assert.throws(
() => auditClusterImageCiWorkflow(mutated),
/Console capacity evidence protocol tests/,
);
});
test('rejects Console capacity evidence that is not gated by the native image matrix', () => {
const mutated = ciSource.replace(
'cluster-console-capacity-release-evidence:\n name: Cross-architecture Cluster Copilot Console capacity evidence\n needs: cluster-image',
'cluster-console-capacity-release-evidence:\n name: Cross-architecture Cluster Copilot Console capacity evidence\n needs: image-oci',
);
assert.throws(
() => auditClusterImageCiWorkflow(mutated),
/exact native x64 and arm64 reports/,
);
});
test('rejects removal of the offline Console capacity release audit', () => {
const mutated = ciSource.replace(
' --mode=audit \\\n',
' --mode=merge \\\n',
);
assert.throws(
() => auditClusterImageCiWorkflow(mutated),
/exact native x64 and arm64 reports/,
);
});
test('rejects a Cluster Admin live gate that omits operator context injection', () => {
const contract = fs.readFileSync(
path.join(root, 'scripts/ql3-cluster-admin-product-live-contract.cjs'),