mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:58:17 +08:00
528 lines
15 KiB
JavaScript
528 lines
15 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const crypto = require('node:crypto');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
buildDirectServiceStartReport,
|
|
normalizeDirectServiceStartManifest,
|
|
normalizeSession,
|
|
parseArguments,
|
|
parseAuxiliaryVectorClockTicks,
|
|
parseOpenRcDirectState,
|
|
parseStartupReceipt,
|
|
parseSystemdDirectShow,
|
|
processStartBootAgeMs,
|
|
readLinuxClockTicksPerSecond,
|
|
validateDirectServiceStartReport,
|
|
} = require('../../scripts/ql3-physical-edge-direct-service-start.cjs');
|
|
const {
|
|
buildEvidenceReport,
|
|
canonicalDigest,
|
|
normalizeManifest,
|
|
parseArguments: parseEvidenceArguments,
|
|
validateDirectServiceStartEvidenceReport,
|
|
} = require('../../scripts/ql3-physical-edge-evidence.cjs');
|
|
|
|
const packages = [
|
|
'@qinglong/local-admin',
|
|
'@qinglong/local-application',
|
|
'@qinglong/local-command-file',
|
|
'@qinglong/local-execution',
|
|
'@qinglong/local-process',
|
|
'@qinglong/local-secret',
|
|
'@qinglong/local-sqlite',
|
|
'@qinglong/runtime-core',
|
|
'croner',
|
|
'semver',
|
|
];
|
|
|
|
function artifact() {
|
|
return {
|
|
artifactSha256: 'a'.repeat(64),
|
|
artifactMetadataSha256: 'b'.repeat(64),
|
|
artifactFiles: 627,
|
|
artifactBytes: 5_045_360,
|
|
entrypointSha256: 'c'.repeat(64),
|
|
packages,
|
|
};
|
|
}
|
|
|
|
function manifest(overrides = {}) {
|
|
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: 627,
|
|
expectedArtifactBytes: 5_045_360,
|
|
expectedNodeSha256: 'd'.repeat(64),
|
|
maximumBootToActiveMs: 180_000,
|
|
maximumServiceStartBootAgeMs: 60_000,
|
|
maximumServiceStartToActiveMs: 30_000,
|
|
...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 environment(bootId) {
|
|
return {
|
|
platform: 'linux',
|
|
architecture: 'arm64',
|
|
bootId,
|
|
bootAgeMs: 15_000,
|
|
dataFilesystem: 'ext4',
|
|
nodeExecutable: '/usr/bin/node',
|
|
nodeSha256: 'd'.repeat(64),
|
|
nodeVersion: 'v24.18.0',
|
|
virtualizationIndicators: [],
|
|
};
|
|
}
|
|
|
|
function physicalObserved(bootId) {
|
|
return {
|
|
platform: 'linux',
|
|
architecture: 'arm64',
|
|
node: 'v24.18.0',
|
|
bootId,
|
|
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-evidence',
|
|
dataFilesystem: 'ext4',
|
|
dataMountOptions: ['rw', 'noatime'],
|
|
dataBytes: 4 * 1024 * 1024 * 1024,
|
|
dataAvailableBytes: 2 * 1024 * 1024 * 1024,
|
|
virtualizationIndicators: [],
|
|
};
|
|
}
|
|
|
|
function sessionFixture() {
|
|
const sessionId = '019f0000-0000-4000-8000-000000000030';
|
|
const dataPath = '/mnt/ql3-evidence';
|
|
const deploymentRoot = `${dataPath}/.ql3-direct-service-start-deployment-${sessionId}`;
|
|
const serviceName = 'qinglong3';
|
|
const descriptorSource = `${deploymentRoot}/service/qinglong3.service`;
|
|
const body = {
|
|
schemaVersion: 1,
|
|
evidenceClass: 'physical_edge_direct_service_start_session',
|
|
sessionId,
|
|
manifestDigest: canonicalDigest(manifest()),
|
|
uid: 1000,
|
|
preparedAt: '2026-07-29T08:00:00.000Z',
|
|
artifact: artifact(),
|
|
environment: environment('019f0000-0000-4000-8000-000000000001'),
|
|
bridge: {
|
|
actionId: sessionId,
|
|
controllerRoot: '/var/lib/qinglong3-service-bridge',
|
|
intentDigest: '9'.repeat(64),
|
|
intentPath: `${deploymentRoot}/service/service-manager-intents/${sessionId}.json`,
|
|
outcomePath: `${deploymentRoot}/service/service-manager-outcomes/${sessionId}.json`,
|
|
},
|
|
paths: {
|
|
dataPath,
|
|
deploymentRoot,
|
|
artifactRoot: '/opt/qinglong3-release',
|
|
applicationEntrypoint:
|
|
'/opt/qinglong3-release/node_modules/@qinglong/local-application/dist/cli.js',
|
|
applicationConfig: `${deploymentRoot}/local-application.json`,
|
|
startupReceipt: `${deploymentRoot}/local-application.json.active.json`,
|
|
},
|
|
service: {
|
|
kind: 'systemd',
|
|
serviceName,
|
|
managerExecutable: '/usr/bin/systemctl',
|
|
managerSha256: 'e'.repeat(64),
|
|
enableExecutable: '/usr/bin/systemctl',
|
|
enableSha256: 'e'.repeat(64),
|
|
supervisorExecutable: null,
|
|
supervisorSha256: null,
|
|
descriptorSource,
|
|
descriptorDestination: `/etc/systemd/system/${serviceName}.service`,
|
|
descriptorMode: 0o644,
|
|
descriptorSha256: 'f'.repeat(64),
|
|
applicationConfigSha256: '8'.repeat(64),
|
|
installArguments: [
|
|
'-o',
|
|
'root',
|
|
'-g',
|
|
'root',
|
|
'-m',
|
|
'644',
|
|
descriptorSource,
|
|
`/etc/systemd/system/${serviceName}.service`,
|
|
],
|
|
enableArguments: ['enable', serviceName],
|
|
},
|
|
};
|
|
return normalizeSession({ ...body, sha256: canonicalDigest(body) });
|
|
}
|
|
|
|
function receipt(session, overrides = {}) {
|
|
const body = {
|
|
schemaVersion: 1,
|
|
schema: 'qinglong/local-application-startup-receipt@v1',
|
|
instanceId: `physical-direct-${session.sessionId.slice(0, 8)}`,
|
|
profile: 'edge',
|
|
aiStatus: 'deployment_excluded',
|
|
bootId: '019f0000-0000-4000-8000-000000000002',
|
|
activeBootAgeMs: 13_250,
|
|
processId: 101,
|
|
processStartTicks: '1200',
|
|
nodeExecutable: '/usr/bin/node',
|
|
nodeVersion: 'v24.18.0',
|
|
...overrides,
|
|
};
|
|
const sha256 = crypto
|
|
.createHash('sha256')
|
|
.update('qinglong.local-application-startup-receipt.v1\0', 'utf8')
|
|
.update(JSON.stringify(body), 'utf8')
|
|
.digest('hex');
|
|
return parseStartupReceipt(JSON.stringify({ ...body, sha256 }));
|
|
}
|
|
|
|
function reportFixture(overrides = {}) {
|
|
const session = sessionFixture();
|
|
const activeReceipt = receipt(session);
|
|
const after = environment(activeReceipt.bootId);
|
|
return buildDirectServiceStartReport({
|
|
manifest: manifest(),
|
|
session,
|
|
observed: {
|
|
after,
|
|
artifact: artifact(),
|
|
bridge: {
|
|
actionId: session.bridge.actionId,
|
|
intentDigest: session.bridge.intentDigest,
|
|
outcomeDigest: '1'.repeat(64),
|
|
observationDigest: '2'.repeat(64),
|
|
state: 'active',
|
|
},
|
|
process: {
|
|
bootId: after.bootId,
|
|
nodePid: 101,
|
|
nodeParentPid: 1,
|
|
nodeStartTicks: 1200,
|
|
clockTicksPerSecond: 100,
|
|
},
|
|
receipt: activeReceipt,
|
|
service: {
|
|
kind: 'systemd',
|
|
serviceName: session.service.serviceName,
|
|
managerExecutable: '/usr/bin/systemctl',
|
|
managerSha256: 'e'.repeat(64),
|
|
descriptorSha256: 'f'.repeat(64),
|
|
mainPid: 101,
|
|
mainStartMonotonicUs: 12_000_000,
|
|
},
|
|
},
|
|
measurements: {
|
|
serviceStartBootAgeMs: 12_000,
|
|
activeBootAgeMs: 13_250,
|
|
bootToActiveMs: 13_250,
|
|
serviceStartToActiveMs: 1_250,
|
|
},
|
|
outcomes: {
|
|
aiStatus: 'deployment_excluded',
|
|
descriptorInstalled: true,
|
|
initSupervisionMatched: true,
|
|
managerStartMonotonicMatched: true,
|
|
nodeProcessIdentityMatched: true,
|
|
ownerBridgeOutcomeVerified: true,
|
|
serviceActive: true,
|
|
serviceEnabled: true,
|
|
startupReceiptValidated: true,
|
|
},
|
|
generatedAt: '2026-07-29T08:01:00.000Z',
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
test('normalizes direct systemd and OpenRC measurement budgets', () => {
|
|
assert.equal(manifest().serviceManager, 'systemd');
|
|
assert.equal(manifest({ serviceManager: 'openrc' }).serviceManager, 'openrc');
|
|
assert.throws(
|
|
() => manifest({ maximumServiceStartToActiveMs: 120_001 }),
|
|
/measurement budget/,
|
|
);
|
|
});
|
|
|
|
test('requires phase-specific canonical authority inputs', () => {
|
|
assert.deepEqual(
|
|
parseArguments([
|
|
'inspect',
|
|
'--artifact-root=/opt/qinglong3-release',
|
|
'--json',
|
|
]),
|
|
{
|
|
phase: 'inspect',
|
|
artifactRoot: '/opt/qinglong3-release',
|
|
json: true,
|
|
},
|
|
);
|
|
assert.equal(
|
|
parseArguments([
|
|
'prepare',
|
|
'--manifest=/mnt/data/manifest.json',
|
|
'--data-path=/mnt/data',
|
|
'--artifact-root=/opt/qinglong3-release',
|
|
'--session=/mnt/data/session.json',
|
|
'--root-command-output=/mnt/data/start-root-command.json',
|
|
]).phase,
|
|
'prepare',
|
|
);
|
|
assert.equal(
|
|
parseArguments([
|
|
'prepare',
|
|
'--manifest=/mnt/data/manifest.json',
|
|
'--data-path=/mnt/data',
|
|
'--artifact-root=/opt/qinglong3-release',
|
|
'--session=/mnt/data/session.json',
|
|
'--root-command-output=/mnt/data/start-root-command.json',
|
|
]).rootCommandOutputPath,
|
|
'/mnt/data/start-root-command.json',
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
parseArguments([
|
|
'resume',
|
|
'--manifest=/mnt/data/manifest.json',
|
|
'--session=/mnt/data/session.json',
|
|
'--output=/mnt/data/report.json',
|
|
'--root-command-output=/mnt/data/start-root-command.json',
|
|
]),
|
|
/phase resume received an invalid option/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
parseArguments([
|
|
'resume',
|
|
'--manifest=manifest.json',
|
|
'--session=/mnt/data/session.json',
|
|
'--output=/mnt/data/report.json',
|
|
]),
|
|
/manifestPath must be absolute/,
|
|
);
|
|
});
|
|
|
|
test('parses init manager state and monotonic process clocks', () => {
|
|
assert.deepEqual(
|
|
parseSystemdDirectShow(
|
|
[
|
|
'LoadState=loaded',
|
|
'ActiveState=active',
|
|
'SubState=running',
|
|
'UnitFileState=enabled',
|
|
'FragmentPath=/etc/systemd/system/qinglong3.service',
|
|
'MainPID=101',
|
|
'ExecMainStartTimestampMonotonic=12000000',
|
|
].join('\n'),
|
|
),
|
|
{
|
|
active: true,
|
|
enabled: true,
|
|
fragmentPath: '/etc/systemd/system/qinglong3.service',
|
|
mainPid: 101,
|
|
mainStartMonotonicUs: 12_000_000,
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseOpenRcDirectState(0, ' qinglong3 | default', 'qinglong3'),
|
|
{ active: true, enabled: true },
|
|
);
|
|
const auxiliaryVector = Buffer.alloc(32);
|
|
auxiliaryVector.writeBigUInt64LE(17n, 0);
|
|
auxiliaryVector.writeBigUInt64LE(100n, 8);
|
|
assert.equal(parseAuxiliaryVectorClockTicks(auxiliaryVector, 8, 'LE'), 100);
|
|
assert.equal(processStartBootAgeMs('1200', 100), 12_000);
|
|
assert.throws(
|
|
() => parseAuxiliaryVectorClockTicks(Buffer.alloc(16), 8, 'LE'),
|
|
/AT_CLKTCK/,
|
|
);
|
|
});
|
|
|
|
test(
|
|
'reads AT_CLKTCK without an external utility on Linux',
|
|
{ skip: process.platform !== 'linux' },
|
|
() => {
|
|
const value = readLinuxClockTicksPerSecond();
|
|
assert.equal(Number.isSafeInteger(value), true);
|
|
assert.ok(value >= 10 && value <= 10_000);
|
|
},
|
|
);
|
|
|
|
test('binds the startup receipt and direct deployment session digests', () => {
|
|
const session = sessionFixture();
|
|
const activeReceipt = receipt(session);
|
|
assert.equal(activeReceipt.processId, 101);
|
|
assert.equal(
|
|
activeReceipt.instanceId,
|
|
`physical-direct-${session.sessionId.slice(0, 8)}`,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
parseStartupReceipt(
|
|
JSON.stringify({ ...activeReceipt, activeBootAgeMs: 13_251 }),
|
|
),
|
|
/values or digest/,
|
|
);
|
|
});
|
|
|
|
test('recomputes a passing direct release report without widening', () => {
|
|
const report = reportFixture();
|
|
assert.equal(report.qualification.passed, true);
|
|
assert.deepEqual(
|
|
validateDirectServiceStartReport(
|
|
report,
|
|
manifest(),
|
|
environment(report.observed.after.bootId),
|
|
),
|
|
[],
|
|
);
|
|
const widened = {
|
|
...report,
|
|
qualification: {
|
|
...report.qualification,
|
|
doesNotProve: [],
|
|
},
|
|
};
|
|
const body = { ...widened };
|
|
delete body.sha256;
|
|
assert.notDeepEqual(
|
|
validateDirectServiceStartReport(
|
|
{ ...body, sha256: canonicalDigest(body) },
|
|
manifest(),
|
|
environment(report.observed.after.bootId),
|
|
),
|
|
[],
|
|
);
|
|
});
|
|
|
|
test('fails qualification when direct process or service facts drift', () => {
|
|
const report = reportFixture({
|
|
outcomes: {
|
|
aiStatus: 'deployment_excluded',
|
|
descriptorInstalled: true,
|
|
initSupervisionMatched: false,
|
|
managerStartMonotonicMatched: true,
|
|
nodeProcessIdentityMatched: true,
|
|
ownerBridgeOutcomeVerified: true,
|
|
serviceActive: true,
|
|
serviceEnabled: true,
|
|
startupReceiptValidated: true,
|
|
},
|
|
});
|
|
assert.equal(report.qualification.passed, false);
|
|
assert.match(
|
|
report.qualification.violations.join('; '),
|
|
/manager or application outcome/,
|
|
);
|
|
});
|
|
|
|
test('fails qualification when Owner service bridge evidence drifts', () => {
|
|
const baseline = reportFixture();
|
|
const report = reportFixture({
|
|
observed: {
|
|
...baseline.observed,
|
|
bridge: {
|
|
...baseline.observed.bridge,
|
|
intentDigest: '7'.repeat(64),
|
|
},
|
|
},
|
|
});
|
|
assert.equal(report.qualification.passed, false);
|
|
assert.match(
|
|
report.qualification.violations.join('; '),
|
|
/service bridge outcome binding/,
|
|
);
|
|
});
|
|
|
|
test('imports direct service evidence and removes only the wrapper gap', () => {
|
|
const report = reportFixture();
|
|
const current = environment(report.observed.after.bootId);
|
|
assert.deepEqual(
|
|
validateDirectServiceStartEvidenceReport(report, baseManifest(), current),
|
|
[],
|
|
);
|
|
assert.equal(
|
|
parseEvidenceArguments([
|
|
'--manifest=/mnt/data/manifest.json',
|
|
'--data-path=/mnt/data',
|
|
'--direct-service-start-evidence=/mnt/data/direct.json',
|
|
]).directServiceStartEvidencePath,
|
|
'/mnt/data/direct.json',
|
|
);
|
|
const aggregated = buildEvidenceReport({
|
|
manifest: baseManifest(),
|
|
observed: physicalObserved(report.observed.after.bootId),
|
|
workloads: [],
|
|
supplementalEvidence: [report],
|
|
generatedAt: '2026-07-29T08:02:00.000Z',
|
|
});
|
|
assert.ok(
|
|
aggregated.qualification.collectedEvidence.includes(
|
|
'kernel_boot_to_direct_init_managed_release_application_active',
|
|
),
|
|
);
|
|
assert.equal(
|
|
aggregated.qualification.remainingRequiredEvidence.includes(
|
|
'direct_release_unit_without_evidence_wrapper',
|
|
),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
aggregated.qualification.remainingRequiredEvidence.includes(
|
|
'firmware_and_bootloader_power_on_to_linux_kernel_clock',
|
|
),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test('rejects a forged inner receipt even with a recomputed outer report', () => {
|
|
const report = reportFixture();
|
|
const body = {
|
|
...report,
|
|
observed: {
|
|
...report.observed,
|
|
receipt: {
|
|
...report.observed.receipt,
|
|
activeBootAgeMs: report.observed.receipt.activeBootAgeMs + 1,
|
|
},
|
|
},
|
|
};
|
|
delete body.sha256;
|
|
const forged = { ...body, sha256: canonicalDigest(body) };
|
|
assert.notDeepEqual(
|
|
validateDirectServiceStartReport(
|
|
forged,
|
|
manifest(),
|
|
environment(report.observed.after.bootId),
|
|
),
|
|
[],
|
|
);
|
|
});
|