mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
370 lines
11 KiB
JavaScript
370 lines
11 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const crypto = require('node:crypto');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
buildDirectServiceStopReport,
|
|
normalizeStopSession,
|
|
parseArguments,
|
|
validateDirectServiceStopReport,
|
|
} = require('../../scripts/ql3-physical-edge-direct-service-stop.cjs');
|
|
const {
|
|
normalizeDirectServiceStartManifest,
|
|
} = require('../../scripts/ql3-physical-edge-direct-service-start.cjs');
|
|
const {
|
|
buildEvidenceReport,
|
|
canonicalDigest,
|
|
normalizeManifest,
|
|
parseArguments: parseEvidenceArguments,
|
|
validateDirectServiceStopEvidenceReport,
|
|
} = require('../../scripts/ql3-physical-edge-evidence.cjs');
|
|
|
|
function manifest() {
|
|
return normalizeDirectServiceStartManifest({
|
|
schemaVersion: 1,
|
|
evidenceClass: 'physical_edge_direct_service_start_candidate',
|
|
profile: 'edge',
|
|
deviceId: 'router-a1',
|
|
serviceManager: 'systemd',
|
|
expectedArchitecture: 'arm64',
|
|
expectedFilesystem: 'ext4',
|
|
expectedArtifactSha256: 'a'.repeat(64),
|
|
expectedArtifactFiles: 640,
|
|
expectedArtifactBytes: 6_000_000,
|
|
expectedNodeSha256: 'b'.repeat(64),
|
|
maximumBootToActiveMs: 180_000,
|
|
maximumServiceStartBootAgeMs: 60_000,
|
|
maximumServiceStartToActiveMs: 30_000,
|
|
});
|
|
}
|
|
|
|
function directSession() {
|
|
return {
|
|
uid: process.getuid?.() ?? 0,
|
|
sha256: 'c'.repeat(64),
|
|
paths: { deploymentRoot: '/mnt/ql3/direct' },
|
|
};
|
|
}
|
|
|
|
function stopSession() {
|
|
const source = directSession();
|
|
const sessionId = '123e4567-e89b-42d3-a456-426614174031';
|
|
const body = {
|
|
schemaVersion: 1,
|
|
evidenceClass: 'physical_edge_direct_service_stop_session',
|
|
sessionId,
|
|
uid: source.uid,
|
|
preparedAt: '2026-08-11T12:00:00.000Z',
|
|
manifestDigest: canonicalDigest(manifest()),
|
|
directSessionDigest: source.sha256,
|
|
activeReportDigest: 'd'.repeat(64),
|
|
activeBootId: '12345678-1234-4abc-8def-123456789abc',
|
|
startupReceiptDigest: 'e'.repeat(64),
|
|
processId: 101,
|
|
processStartTicks: '1200',
|
|
bridge: {
|
|
actionId: sessionId,
|
|
controllerRoot: '/var/lib/qinglong3-service-bridge',
|
|
intentDigest: 'f'.repeat(64),
|
|
intentPath: `/mnt/ql3/direct/service/service-manager-intents/${sessionId}.json`,
|
|
outcomePath: `/mnt/ql3/direct/service/service-manager-outcomes/${sessionId}.json`,
|
|
},
|
|
};
|
|
return normalizeStopSession(
|
|
{ ...body, sha256: canonicalDigest(body) },
|
|
source,
|
|
);
|
|
}
|
|
|
|
function shutdownReceipt() {
|
|
const body = {
|
|
schemaVersion: 1,
|
|
schema: 'qinglong/local-application-shutdown-receipt@v1',
|
|
instanceId: 'physical-direct-12345678',
|
|
profile: 'edge',
|
|
signal: 'SIGTERM',
|
|
stopResult: 'stopped',
|
|
startupReceiptDigest: 'e'.repeat(64),
|
|
bootId: '12345678-1234-4abc-8def-123456789abc',
|
|
stoppedBootAgeMs: 20_000,
|
|
processId: 101,
|
|
processStartTicks: '1200',
|
|
nodeExecutable: '/usr/bin/node',
|
|
nodeVersion: 'v24.18.0',
|
|
};
|
|
const sha256 = crypto
|
|
.createHash('sha256')
|
|
.update('qinglong.local-application-shutdown-receipt.v1\0', 'utf8')
|
|
.update(JSON.stringify(body), 'utf8')
|
|
.digest('hex');
|
|
return { ...body, sha256 };
|
|
}
|
|
|
|
function observed(overrides = {}) {
|
|
const session = stopSession();
|
|
return {
|
|
currentBootId: session.activeBootId,
|
|
bridge: {
|
|
actionId: session.bridge.actionId,
|
|
intentDigest: session.bridge.intentDigest,
|
|
outcomeDigest: '1'.repeat(64),
|
|
observationDigest: '2'.repeat(64),
|
|
state: 'stopped',
|
|
},
|
|
shutdownReceipt: shutdownReceipt(),
|
|
processIdentityGone: true,
|
|
service: {
|
|
active: false,
|
|
enabled: true,
|
|
fragmentPath: '/etc/systemd/system/qinglong3.service',
|
|
mainPid: 0,
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function baseManifest() {
|
|
return normalizeManifest({
|
|
schemaVersion: 1,
|
|
evidenceClass: 'physical_edge_candidate',
|
|
profile: 'edge',
|
|
deviceId: 'router-a1',
|
|
deviceModel: 'router-a1-model',
|
|
soc: 'arm-soc',
|
|
storageMedium: 'emmc',
|
|
expectedArchitecture: 'arm64',
|
|
memoryBytes: { minimum: 128 * 1024 * 1024, maximum: 512 * 1024 * 1024 },
|
|
expectedFilesystem: 'ext4',
|
|
});
|
|
}
|
|
|
|
function physicalObserved() {
|
|
return {
|
|
platform: 'linux',
|
|
architecture: 'arm64',
|
|
node: 'v24.18.0',
|
|
bootId: stopSession().activeBootId,
|
|
kernel: '6.6.0',
|
|
distribution: { id: 'openwrt', versionId: '24.10' },
|
|
libc: 'musl-ld-musl-aarch64.so.1',
|
|
cpuModel: 'test-cpu',
|
|
cpuCount: 1,
|
|
totalMemoryBytes: 256 * 1024 * 1024,
|
|
observedModel: 'router-a1-model',
|
|
dataPath: '/mnt/ql3',
|
|
dataFilesystem: 'ext4',
|
|
dataMountOptions: ['rw', 'noatime'],
|
|
dataBytes: 4 * 1024 * 1024 * 1024,
|
|
dataAvailableBytes: 2 * 1024 * 1024 * 1024,
|
|
virtualizationIndicators: [],
|
|
};
|
|
}
|
|
|
|
test('requires exact phase-specific stop evidence paths', () => {
|
|
assert.equal(
|
|
parseArguments([
|
|
'prepare',
|
|
'--manifest=/mnt/ql3/manifest.json',
|
|
'--session=/mnt/ql3/session.json',
|
|
'--active-report=/mnt/ql3/active.json',
|
|
'--stop-session=/mnt/ql3/stop-session.json',
|
|
'--root-command-output=/mnt/ql3/stop-root-command.json',
|
|
]).phase,
|
|
'prepare',
|
|
);
|
|
assert.equal(
|
|
parseArguments([
|
|
'prepare',
|
|
'--manifest=/mnt/ql3/manifest.json',
|
|
'--session=/mnt/ql3/session.json',
|
|
'--active-report=/mnt/ql3/active.json',
|
|
'--stop-session=/mnt/ql3/stop-session.json',
|
|
'--root-command-output=/mnt/ql3/stop-root-command.json',
|
|
]).rootCommandOutputPath,
|
|
'/mnt/ql3/stop-root-command.json',
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
parseArguments([
|
|
'resume',
|
|
'--manifest=/mnt/ql3/manifest.json',
|
|
'--session=/mnt/ql3/session.json',
|
|
'--active-report=/mnt/ql3/active.json',
|
|
'--stop-session=/mnt/ql3/stop-session.json',
|
|
'--output=/mnt/ql3/stop.json',
|
|
'--root-command-output=/mnt/ql3/stop-root-command.json',
|
|
]),
|
|
/valid only as a normalized prepare path/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
parseArguments([
|
|
'resume',
|
|
'--manifest=manifest.json',
|
|
'--session=/mnt/ql3/session.json',
|
|
'--active-report=/mnt/ql3/active.json',
|
|
'--stop-session=/mnt/ql3/stop-session.json',
|
|
'--output=/mnt/ql3/stop.json',
|
|
]),
|
|
/manifestPath must be absolute/,
|
|
);
|
|
});
|
|
|
|
test('binds one stop session to the active report and Owner intent', () => {
|
|
const session = stopSession();
|
|
assert.equal(session.bridge.actionId, session.sessionId);
|
|
assert.equal(session.directSessionDigest, directSession().sha256);
|
|
assert.throws(
|
|
() =>
|
|
normalizeStopSession(
|
|
{ ...session, activeReportDigest: '0'.repeat(64) },
|
|
directSession(),
|
|
),
|
|
/invalid or drifted/,
|
|
);
|
|
});
|
|
|
|
test('recomputes a graceful direct service stop report', () => {
|
|
const report = buildDirectServiceStopReport({
|
|
manifest: manifest(),
|
|
stopSession: stopSession(),
|
|
observed: observed(),
|
|
generatedAt: '2026-08-11T12:01:00.000Z',
|
|
});
|
|
assert.equal(report.qualification.passed, true);
|
|
assert.deepEqual(validateDirectServiceStopReport(report, manifest()), []);
|
|
});
|
|
|
|
test('rejects a forged shutdown receipt and a still-live process', () => {
|
|
const valid = shutdownReceipt();
|
|
const report = buildDirectServiceStopReport({
|
|
manifest: manifest(),
|
|
stopSession: stopSession(),
|
|
observed: observed({
|
|
processIdentityGone: false,
|
|
shutdownReceipt: {
|
|
...valid,
|
|
stoppedBootAgeMs: valid.stoppedBootAgeMs + 1,
|
|
},
|
|
}),
|
|
generatedAt: '2026-08-11T12:01:00.000Z',
|
|
});
|
|
assert.equal(report.qualification.passed, false);
|
|
assert.match(
|
|
report.qualification.violations.join('; '),
|
|
/shutdown receipt binding|stopped state/,
|
|
);
|
|
});
|
|
|
|
test('aggregates graceful stop only when bound to direct start evidence', () => {
|
|
const stopReport = buildDirectServiceStopReport({
|
|
manifest: manifest(),
|
|
stopSession: stopSession(),
|
|
observed: observed(),
|
|
generatedAt: '2026-08-11T12:01:00.000Z',
|
|
});
|
|
const startEvidence = {
|
|
evidenceClass: 'physical_edge_direct_service_start_candidate',
|
|
sha256: stopReport.session.activeReportDigest,
|
|
};
|
|
const aggregate = buildEvidenceReport({
|
|
manifest: baseManifest(),
|
|
observed: physicalObserved(),
|
|
workloads: [],
|
|
supplementalEvidence: [startEvidence, stopReport],
|
|
generatedAt: '2026-08-11T12:02:00.000Z',
|
|
});
|
|
assert.ok(
|
|
aggregate.qualification.collectedEvidence.includes(
|
|
'init_managed_graceful_application_stop',
|
|
),
|
|
);
|
|
assert.equal(
|
|
aggregate.qualification.remainingRequiredEvidence.includes(
|
|
'init_managed_graceful_application_stop',
|
|
),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
parseEvidenceArguments([
|
|
'--manifest=/mnt/ql3/manifest.json',
|
|
'--data-path=/mnt/ql3',
|
|
'--direct-service-start-evidence=/mnt/ql3/start.json',
|
|
'--direct-service-stop-evidence=/mnt/ql3/stop.json',
|
|
]).directServiceStopEvidencePath,
|
|
'/mnt/ql3/stop.json',
|
|
);
|
|
const active = {
|
|
...startEvidence,
|
|
session: { sessionDigest: stopReport.session.directSessionDigest },
|
|
observed: {
|
|
after: { bootId: stopReport.session.activeBootId },
|
|
receipt: {
|
|
sha256: stopReport.session.startupReceiptDigest,
|
|
processId: stopReport.session.processId,
|
|
processStartTicks: stopReport.session.processStartTicks,
|
|
instanceId: stopReport.observed.shutdownReceipt.instanceId,
|
|
nodeExecutable: stopReport.observed.shutdownReceipt.nodeExecutable,
|
|
},
|
|
},
|
|
};
|
|
assert.deepEqual(
|
|
validateDirectServiceStopEvidenceReport(
|
|
stopReport,
|
|
baseManifest(),
|
|
physicalObserved(),
|
|
active,
|
|
),
|
|
[],
|
|
);
|
|
});
|
|
|
|
test('documents the exact direct start and graceful stop bridge sequence', () => {
|
|
const runbook = fs.readFileSync(
|
|
path.join(__dirname, '../../docs/operations/ql3-local-deployment.md'),
|
|
'utf8',
|
|
);
|
|
const startPrepare = runbook.indexOf(
|
|
'evidence:physical-edge-direct-service-start -- prepare',
|
|
);
|
|
const startBridge = runbook.indexOf(
|
|
'ql3-service-bridge run --command-file',
|
|
startPrepare,
|
|
);
|
|
const startResume = runbook.indexOf(
|
|
'evidence:physical-edge-direct-service-start -- resume',
|
|
startBridge,
|
|
);
|
|
const stopPrepare = runbook.indexOf(
|
|
'evidence:physical-edge-direct-service-stop -- prepare',
|
|
startResume,
|
|
);
|
|
const stopBridge = runbook.indexOf(
|
|
'ql3-service-bridge run --command-file',
|
|
stopPrepare,
|
|
);
|
|
const stopResume = runbook.indexOf(
|
|
'evidence:physical-edge-direct-service-stop -- resume',
|
|
stopBridge,
|
|
);
|
|
assert.ok(
|
|
startPrepare >= 0 &&
|
|
startPrepare < startBridge &&
|
|
startBridge < startResume &&
|
|
startResume < stopPrepare &&
|
|
stopPrepare < stopBridge &&
|
|
stopBridge < stopResume,
|
|
);
|
|
for (const fragment of [
|
|
'--root-command-output=/opt/qinglong/evidence-scratch/direct-service-start-root-command.json',
|
|
'--active-report=/opt/qinglong/evidence-scratch/direct-service-start-report.json',
|
|
'--stop-session=/opt/qinglong/evidence-scratch/direct-service-stop-session.json',
|
|
'--root-command-output=/opt/qinglong/evidence-scratch/direct-service-stop-root-command.json',
|
|
'--direct-service-stop-evidence=/opt/qinglong/evidence-scratch/direct-service-stop-report.json',
|
|
]) {
|
|
assert.ok(runbook.includes(fragment), fragment);
|
|
}
|
|
});
|