mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:00:24 +08:00
290 lines
7.9 KiB
JavaScript
290 lines
7.9 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
buildEvidenceReport,
|
|
canonicalDigest,
|
|
normalizeManifest,
|
|
parseArguments,
|
|
validateServiceStartEvidenceReport,
|
|
} = require('../../scripts/ql3-physical-edge-evidence.cjs');
|
|
const {
|
|
buildServiceStartReport,
|
|
normalizeServiceStartManifest,
|
|
normalizeSession,
|
|
} = require('../../scripts/ql3-physical-edge-service-start.cjs');
|
|
|
|
const deviceBootId = '019f0000-0000-4000-8000-000000000002';
|
|
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 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 observed() {
|
|
return {
|
|
platform: 'linux',
|
|
architecture: 'arm64',
|
|
bootId: deviceBootId,
|
|
bootAgeMs: 15_000,
|
|
dataFilesystem: 'ext4',
|
|
nodeExecutable: '/usr/bin/node',
|
|
nodeSha256: 'd'.repeat(64),
|
|
nodeVersion: 'v24.18.0',
|
|
virtualizationIndicators: [],
|
|
};
|
|
}
|
|
|
|
function physicalObserved() {
|
|
return {
|
|
platform: 'linux',
|
|
architecture: 'arm64',
|
|
node: 'v24.18.0',
|
|
bootId: deviceBootId,
|
|
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 artifact() {
|
|
return {
|
|
artifactSha256: 'a'.repeat(64),
|
|
artifactMetadataSha256: 'b'.repeat(64),
|
|
artifactFiles: 627,
|
|
artifactBytes: 5_045_360,
|
|
entrypointSha256: 'c'.repeat(64),
|
|
packages,
|
|
};
|
|
}
|
|
|
|
function serviceManifest() {
|
|
return normalizeServiceStartManifest({
|
|
schemaVersion: 1,
|
|
evidenceClass: 'physical_edge_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,
|
|
});
|
|
}
|
|
|
|
function serviceReport() {
|
|
const sessionId = '019f0000-0000-4000-8000-000000000020';
|
|
const dataPath = '/mnt/ql3-evidence';
|
|
const deploymentRoot = `${dataPath}/.ql3-service-start-deployment-${sessionId}`;
|
|
const toolRoot = `${deploymentRoot}/physical-service-start`;
|
|
const serviceName = 'qinglong3-physical-019f0000';
|
|
const sessionBody = {
|
|
schemaVersion: 1,
|
|
evidenceClass: 'physical_edge_service_start_session',
|
|
sessionId,
|
|
manifestDigest: canonicalDigest(serviceManifest()),
|
|
uid: 1000,
|
|
preparedAt: '2026-07-29T08:00:00.000Z',
|
|
artifact: artifact(),
|
|
environment: {
|
|
...observed(),
|
|
bootId: '019f0000-0000-4000-8000-000000000001',
|
|
},
|
|
paths: {
|
|
dataPath,
|
|
toolRoot,
|
|
deploymentRoot,
|
|
artifactRoot: '/opt/qinglong3-release',
|
|
applicationEntrypoint:
|
|
'/opt/qinglong3-release/node_modules/@qinglong/local-application/dist/cli.js',
|
|
applicationConfig: `${deploymentRoot}/local-application.json`,
|
|
wrapper: `${toolRoot}/boot-probe.sh`,
|
|
wrapperStartRecord: `${toolRoot}/wrapper-start.record`,
|
|
nodeRecord: `${toolRoot}/node.record`,
|
|
activeRecord: `${toolRoot}/active.record`,
|
|
eventLog: `${toolRoot}/events.jsonl`,
|
|
stderrLog: `${toolRoot}/stderr.log`,
|
|
fifo: `${toolRoot}/events.fifo`,
|
|
},
|
|
service: {
|
|
kind: 'systemd',
|
|
serviceName,
|
|
managerExecutable: '/usr/bin/systemctl',
|
|
managerSha256: 'e'.repeat(64),
|
|
enableExecutable: '/usr/bin/systemctl',
|
|
enableSha256: 'e'.repeat(64),
|
|
descriptorSource: `${deploymentRoot}/service/qinglong3.service`,
|
|
descriptorDestination: `/etc/systemd/system/${serviceName}.service`,
|
|
descriptorMode: 0o644,
|
|
descriptorSha256: 'f'.repeat(64),
|
|
wrapperSha256: '1'.repeat(64),
|
|
installArguments: [
|
|
'-o',
|
|
'root',
|
|
'-g',
|
|
'root',
|
|
'-m',
|
|
'644',
|
|
`${deploymentRoot}/service/qinglong3.service`,
|
|
`/etc/systemd/system/${serviceName}.service`,
|
|
],
|
|
enableArguments: ['enable', serviceName],
|
|
},
|
|
};
|
|
const session = normalizeSession({
|
|
...sessionBody,
|
|
sha256: canonicalDigest(sessionBody),
|
|
});
|
|
return buildServiceStartReport({
|
|
manifest: serviceManifest(),
|
|
session,
|
|
observed: {
|
|
after: observed(),
|
|
artifact: artifact(),
|
|
process: {
|
|
bootId: deviceBootId,
|
|
wrapperPid: 101,
|
|
wrapperStartTicks: 1200,
|
|
nodePid: 102,
|
|
nodeStartTicks: 1201,
|
|
},
|
|
service: {
|
|
kind: 'systemd',
|
|
serviceName,
|
|
managerExecutable: '/usr/bin/systemctl',
|
|
managerSha256: 'e'.repeat(64),
|
|
descriptorSha256: 'f'.repeat(64),
|
|
mainPid: 101,
|
|
},
|
|
},
|
|
measurements: {
|
|
serviceStartBootAgeMs: 12_000,
|
|
activeBootAgeMs: 13_250,
|
|
bootToActiveMs: 13_250,
|
|
serviceStartToActiveMs: 1_250,
|
|
activeEventOrdinal: 2,
|
|
},
|
|
outcomes: {
|
|
activeEventCount: 1,
|
|
aiStatus: 'deployment_excluded',
|
|
descriptorInstalled: true,
|
|
serviceActive: true,
|
|
serviceEnabled: true,
|
|
wrapperProcessIdentityMatched: true,
|
|
nodeProcessIdentityMatched: true,
|
|
stderrBytes: 0,
|
|
},
|
|
generatedAt: '2026-07-29T08:01:00.000Z',
|
|
});
|
|
}
|
|
|
|
test('accepts the service-start importer path', () => {
|
|
assert.equal(
|
|
parseArguments([
|
|
'--manifest=/mnt/data/manifest.json',
|
|
'--data-path=/mnt/data',
|
|
'--service-start-evidence=/mnt/data/service-start.json',
|
|
]).serviceStartEvidencePath,
|
|
'/mnt/data/service-start.json',
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
parseArguments([
|
|
'--manifest=/mnt/data/manifest.json',
|
|
'--data-path=/mnt/data',
|
|
'--service-start-evidence=service-start.json',
|
|
]),
|
|
/serviceStartEvidencePath must be absolute/,
|
|
);
|
|
});
|
|
|
|
test('validates same-device current-boot service evidence', () => {
|
|
assert.deepEqual(
|
|
validateServiceStartEvidenceReport(
|
|
serviceReport(),
|
|
baseManifest(),
|
|
observed(),
|
|
),
|
|
[],
|
|
);
|
|
assert.notDeepEqual(
|
|
validateServiceStartEvidenceReport(serviceReport(), baseManifest(), {
|
|
...observed(),
|
|
bootId: '019f0000-0000-4000-8000-000000000099',
|
|
}),
|
|
[],
|
|
);
|
|
});
|
|
|
|
test('refines but does not erase the broad power-on startup gate', () => {
|
|
const report = buildEvidenceReport({
|
|
manifest: baseManifest(),
|
|
observed: physicalObserved(),
|
|
workloads: {},
|
|
supplementalEvidence: [serviceReport()],
|
|
generatedAt: '2026-07-29T08:02:00.000Z',
|
|
});
|
|
assert.ok(
|
|
report.qualification.collectedEvidence.includes(
|
|
'kernel_boot_to_init_managed_native_application_active',
|
|
),
|
|
);
|
|
assert.ok(
|
|
report.qualification.remainingRequiredEvidence.includes(
|
|
'firmware_and_bootloader_power_on_to_linux_kernel_clock',
|
|
),
|
|
);
|
|
assert.ok(
|
|
report.qualification.remainingRequiredEvidence.includes(
|
|
'direct_release_unit_without_evidence_wrapper',
|
|
),
|
|
);
|
|
assert.equal(
|
|
report.qualification.remainingRequiredEvidence.includes(
|
|
'cold_start_and_first_ready',
|
|
),
|
|
false,
|
|
);
|
|
assert.equal(report.supported, false);
|
|
});
|