Files
qinglong/scripts/ql3-physical-edge-service-start.cjs
T

1715 lines
52 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const crypto = require('node:crypto');
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const {
collectArtifactIdentity,
parseEventLines,
preflightArtifactMetadata,
readBootAgeMs,
validateArtifactAgainstManifest,
} = require('./ql3-physical-edge-application-start.cjs');
const {
canonicalDigest,
collectObservedPlatform,
} = require('./ql3-physical-edge-evidence.cjs');
const {
parseProcStat,
parseProcStatus,
} = require('./ql3-physical-edge-idle-sampler.cjs');
const MIB = 1024 * 1024;
const MAX_INPUT_BYTES = 256 * 1024;
const MAX_EVENT_BYTES = 64 * 1024;
const UUID_V4_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
const SAFE_SERVICE_NAME_PATTERN = /^qinglong3-physical-[0-9a-f]{8}$/;
const REQUIRED_PACKAGES = Object.freeze([
'@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',
]);
const MANIFEST_KEYS = Object.freeze([
'deviceId',
'evidenceClass',
'expectedArchitecture',
'expectedArtifactBytes',
'expectedArtifactFiles',
'expectedArtifactSha256',
'expectedFilesystem',
'expectedNodeSha256',
'maximumBootToActiveMs',
'maximumServiceStartBootAgeMs',
'maximumServiceStartToActiveMs',
'profile',
'schemaVersion',
'serviceManager',
]);
const ARTIFACT_KEYS = Object.freeze([
'artifactBytes',
'artifactFiles',
'artifactMetadataSha256',
'artifactSha256',
'entrypointSha256',
'packages',
]);
const ENVIRONMENT_KEYS = Object.freeze([
'architecture',
'bootAgeMs',
'bootId',
'dataFilesystem',
'nodeExecutable',
'nodeSha256',
'nodeVersion',
'platform',
'virtualizationIndicators',
]);
const PATH_KEYS = Object.freeze([
'activeRecord',
'applicationConfig',
'applicationEntrypoint',
'artifactRoot',
'dataPath',
'deploymentRoot',
'eventLog',
'fifo',
'nodeRecord',
'stderrLog',
'toolRoot',
'wrapper',
'wrapperStartRecord',
]);
const SERVICE_KEYS = Object.freeze([
'descriptorDestination',
'descriptorMode',
'descriptorSha256',
'descriptorSource',
'enableArguments',
'enableExecutable',
'enableSha256',
'installArguments',
'kind',
'managerExecutable',
'managerSha256',
'serviceName',
'wrapperSha256',
]);
const SESSION_KEYS = Object.freeze([
'artifact',
'environment',
'evidenceClass',
'manifestDigest',
'paths',
'preparedAt',
'schemaVersion',
'service',
'sessionId',
'sha256',
'uid',
]);
const REPORT_KEYS = Object.freeze([
'evidenceClass',
'generatedAt',
'manifest',
'measurements',
'observed',
'outcomes',
'qualification',
'schemaVersion',
'session',
'sha256',
'supported',
]);
const REPORT_SESSION_KEYS = Object.freeze([
'prepareBootId',
'preparedAt',
'sessionDigest',
'sessionId',
]);
const QUALIFICATION_KEYS = Object.freeze([
'doesNotProve',
'measures',
'passed',
'violations',
]);
const MEASUREMENT_KEYS = Object.freeze([
'activeBootAgeMs',
'activeEventOrdinal',
'bootToActiveMs',
'serviceStartBootAgeMs',
'serviceStartToActiveMs',
]);
const OUTCOME_KEYS = Object.freeze([
'activeEventCount',
'aiStatus',
'descriptorInstalled',
'nodeProcessIdentityMatched',
'serviceActive',
'serviceEnabled',
'stderrBytes',
'wrapperProcessIdentityMatched',
]);
const OBSERVED_KEYS = Object.freeze([
'after',
'artifact',
'process',
'service',
]);
const PROCESS_KEYS = Object.freeze([
'bootId',
'nodePid',
'nodeStartTicks',
'wrapperPid',
'wrapperStartTicks',
]);
const OBSERVED_SERVICE_KEYS = Object.freeze([
'descriptorSha256',
'kind',
'mainPid',
'managerExecutable',
'managerSha256',
'serviceName',
]);
const START_RECORD_KEYS = Object.freeze([
'boot_id',
'schema',
'service_start_uptime',
'wrapper_pid',
]);
const NODE_RECORD_KEYS = Object.freeze(['node_pid', 'schema']);
const ACTIVE_RECORD_KEYS = Object.freeze([
'active_uptime',
'boot_id',
'event_ordinal',
'schema',
]);
const MEASURES = Object.freeze([
'different_boot_identity',
'kernel_boot_to_evidence_service_wrapper_start',
'kernel_boot_to_official_native_application_active',
'init_managed_service_active_and_enabled',
'exact_installed_production_rendered_service_descriptor',
'pre_node_posix_uptime_anchor',
'exact_ai_excluded_native_release_closure',
'live_wrapper_and_node_process_identity',
]);
const EXCLUSIONS = Object.freeze([
'firmware_or_bootloader_power_on_to_linux_kernel_clock',
'exclusive_cold_page_cache_or_dynamic_linker_provenance',
'direct_release_unit_without_evidence_wrapper',
'application_rss_or_io_before_active',
'graceful_service_manager_stop_or_disable',
'unexpected_power_loss_recovery',
'compose_or_container_runtime_start',
'standalone_or_cluster_profile',
'release_archive_signature_or_attestation',
]);
class QingLong3PhysicalServiceStartEvidenceError extends Error {
constructor(message) {
super(
`QingLong 3.0 physical Edge service start evidence failed: ${message}`,
);
this.name = 'QingLong3PhysicalServiceStartEvidenceError';
}
}
function exactKeys(value, expected, label) {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} keys must be exactly ${expected.join(', ')}`,
);
}
}
function hasExactKeys(value, expected) {
return (
value !== null &&
typeof value === 'object' &&
!Array.isArray(value) &&
JSON.stringify(Object.keys(value).sort()) === JSON.stringify(expected)
);
}
function normalizeServiceStartManifest(value) {
exactKeys(value, MANIFEST_KEYS, 'manifest');
if (
value.schemaVersion !== 1 ||
value.evidenceClass !== 'physical_edge_service_start_candidate' ||
value.profile !== 'edge' ||
!['systemd', 'openrc'].includes(value.serviceManager) ||
typeof value.deviceId !== 'string' ||
!/^[a-z0-9][a-z0-9._-]{2,63}$/.test(value.deviceId) ||
!['x64', 'arm64', 'arm'].includes(value.expectedArchitecture) ||
typeof value.expectedFilesystem !== 'string' ||
!/^[a-z0-9][a-z0-9._-]{1,31}$/.test(value.expectedFilesystem) ||
!SHA256_PATTERN.test(value.expectedArtifactSha256 ?? '') ||
!Number.isSafeInteger(value.expectedArtifactFiles) ||
value.expectedArtifactFiles < 1 ||
value.expectedArtifactFiles > 768 ||
!Number.isSafeInteger(value.expectedArtifactBytes) ||
value.expectedArtifactBytes < 1 ||
value.expectedArtifactBytes > 8 * MIB ||
!SHA256_PATTERN.test(value.expectedNodeSha256 ?? '') ||
!Number.isSafeInteger(value.maximumBootToActiveMs) ||
value.maximumBootToActiveMs < 10_000 ||
value.maximumBootToActiveMs > 600_000 ||
!Number.isSafeInteger(value.maximumServiceStartBootAgeMs) ||
value.maximumServiceStartBootAgeMs < 1_000 ||
value.maximumServiceStartBootAgeMs > 300_000 ||
!Number.isSafeInteger(value.maximumServiceStartToActiveMs) ||
value.maximumServiceStartToActiveMs < 100 ||
value.maximumServiceStartToActiveMs > 120_000 ||
value.maximumServiceStartBootAgeMs > value.maximumBootToActiveMs ||
value.maximumServiceStartToActiveMs > value.maximumBootToActiveMs
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'manifest identity or measurement budget is invalid',
);
}
return Object.freeze({ ...value });
}
function parseArguments(argv) {
const options = { json: false };
let phase;
for (const argument of argv) {
if (argument === '--json') {
options.json = true;
continue;
}
if (!phase && ['inspect', 'prepare', 'resume'].includes(argument)) {
phase = argument;
continue;
}
const separator = argument.indexOf('=');
if (separator < 1) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`unsupported argument ${argument}`,
);
}
const name = argument.slice(0, separator);
const value = argument.slice(separator + 1);
if (name === '--manifest') options.manifestPath = value;
else if (name === '--data-path') options.dataPath = value;
else if (name === '--artifact-root') options.artifactRoot = value;
else if (name === '--session') options.sessionPath = value;
else if (name === '--output') options.outputPath = value;
else {
throw new QingLong3PhysicalServiceStartEvidenceError(
`unsupported argument ${argument}`,
);
}
}
if (!phase) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'phase must be inspect, prepare or resume',
);
}
const required =
phase === 'inspect'
? ['artifactRoot']
: phase === 'prepare'
? ['artifactRoot', 'dataPath', 'manifestPath', 'sessionPath']
: ['manifestPath', 'outputPath', 'sessionPath'];
for (const name of required) {
if (!path.isAbsolute(options[name] ?? '')) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${name} must be absolute`,
);
}
}
const allowed = new Set(['json', ...required]);
if (Object.keys(options).some((name) => !allowed.has(name))) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`phase ${phase} received an invalid option`,
);
}
return Object.freeze({ phase, ...options });
}
function assertPrivateDirectory(directory, label) {
const stat = fs.lstatSync(directory);
const uid = process.geteuid?.();
if (
!Number.isSafeInteger(uid) ||
!stat.isDirectory() ||
stat.isSymbolicLink() ||
stat.uid !== uid ||
(stat.mode & 0o777) !== 0o700 ||
fs.realpathSync(directory) !== directory
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} must be a canonical current-user 0700 directory`,
);
}
}
function assertChildPath(parent, candidate, label) {
if (
path.normalize(candidate) !== candidate ||
candidate === parent ||
!candidate.startsWith(`${parent}${path.sep}`)
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} must remain inside dataPath`,
);
}
}
function readPrivateFile(filePath, label, maximumBytes = MAX_INPUT_BYTES) {
const stat = fs.lstatSync(filePath);
const uid = process.geteuid?.();
if (
!Number.isSafeInteger(uid) ||
!stat.isFile() ||
stat.isSymbolicLink() ||
stat.uid !== uid ||
(stat.mode & 0o777) !== 0o600 ||
stat.nlink !== 1 ||
stat.size < 1 ||
stat.size > maximumBytes
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} must be a bounded current-user 0600 single-link file`,
);
}
return fs.readFileSync(filePath, 'utf8');
}
function readPrivateJson(filePath, label) {
try {
return JSON.parse(readPrivateFile(filePath, label));
} catch (error) {
if (error instanceof QingLong3PhysicalServiceStartEvidenceError) {
throw error;
}
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} is invalid: ${error.message}`,
);
}
}
function writeNoReplace(filePath, contents, mode = 0o600) {
const parent = fs.realpathSync(path.dirname(filePath));
assertPrivateDirectory(parent, 'output parent');
if (path.join(parent, path.basename(filePath)) !== filePath) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'output path must be canonical',
);
}
const descriptor = fs.openSync(filePath, 'wx', mode);
try {
fs.writeFileSync(descriptor, contents, 'utf8');
fs.fsyncSync(descriptor);
} finally {
fs.closeSync(descriptor);
}
const directoryDescriptor = fs.openSync(parent, 'r');
try {
fs.fsyncSync(directoryDescriptor);
} finally {
fs.closeSync(directoryDescriptor);
}
}
function fileSha256(filePath) {
return crypto
.createHash('sha256')
.update(fs.readFileSync(filePath))
.digest('hex');
}
function collectNodeIdentity() {
const nodeExecutable = fs.realpathSync(process.execPath);
const stat = fs.lstatSync(nodeExecutable);
const uid = process.geteuid?.();
if (
!Number.isSafeInteger(uid) ||
!stat.isFile() ||
stat.isSymbolicLink() ||
(stat.uid !== 0 && stat.uid !== uid) ||
(stat.mode & 0o022) !== 0 ||
(stat.mode & 0o111) === 0
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'Node executable is not trusted',
);
}
return Object.freeze({
nodeExecutable,
nodeSha256: fileSha256(nodeExecutable),
nodeVersion: process.version,
});
}
function shellQuote(value) {
return `'${String(value).replaceAll("'", "'\\''")}'`;
}
function renderEvidenceWrapper({
applicationEntrypoint,
nodeExecutable,
paths,
}) {
const q = shellQuote;
return `#!/bin/sh
set -eu
umask 077
expected_node=${q(nodeExecutable)}
entrypoint=${q(applicationEntrypoint)}
config=${q(paths.applicationConfig)}
fifo=${q(paths.fifo)}
start_record=${q(paths.wrapperStartRecord)}
node_record=${q(paths.nodeRecord)}
active_record=${q(paths.activeRecord)}
event_log=${q(paths.eventLog)}
stderr_log=${q(paths.stderrLog)}
[ "$#" -eq 3 ] || exit 64
[ "$1" = "$expected_node" ] || exit 64
[ "$2" = "--config" ] || exit 64
[ "$3" = "$config" ] || exit 64
[ -p "$fifo" ] || exit 64
[ ! -e "$start_record" ] || exit 64
[ ! -e "$node_record" ] || exit 64
[ ! -e "$active_record" ] || exit 64
[ ! -e "$event_log" ] || exit 64
[ ! -e "$stderr_log" ] || exit 64
IFS= read -r boot_id < /proc/sys/kernel/random/boot_id
IFS=' ' read -r service_start_uptime ignored < /proc/uptime
case "$boot_id" in
????????-????-4???-[89ab]???-????????????) ;;
*) exit 64 ;;
esac
case "$service_start_uptime" in
''|*[!0-9.]*) exit 64 ;;
esac
set -C
: > "$start_record"
: > "$event_log"
: > "$stderr_log"
set +C
printf '%s\\n' \\
'schema=qinglong/physical-edge-service-wrapper-start@v1' \\
"boot_id=$boot_id" \\
"service_start_uptime=$service_start_uptime" \\
"wrapper_pid=$$" > "$start_record"
"$expected_node" "$entrypoint" --config "$config" > "$fifo" 2> "$stderr_log" &
node_pid=$!
set -C
: > "$node_record"
set +C
printf '%s\\n' \\
'schema=qinglong/physical-edge-service-node@v1' \\
"node_pid=$node_pid" > "$node_record"
terminate_child() {
kill -TERM "$node_pid" 2>/dev/null || true
}
trap terminate_child TERM INT HUP
event_ordinal=0
active_seen=0
while IFS= read -r line; do
event_ordinal=$((event_ordinal + 1))
[ "$event_ordinal" -le 64 ] || {
terminate_child
wait "$node_pid" || true
exit 64
}
[ "\${#line}" -le 4096 ] || {
terminate_child
wait "$node_pid" || true
exit 64
}
printf '%s\\n' "$line" >> "$event_log"
active_candidate=1
case "$line" in
*'"component":"qinglong3-local-application"'*) ;;
*) active_candidate=0 ;;
esac
case "$line" in
*'"event":"active"'*) ;;
*) active_candidate=0 ;;
esac
case "$line" in
*'"profile":"edge"'*) ;;
*) active_candidate=0 ;;
esac
case "$line" in
*'"aiStatus":"deployment_excluded"'*) ;;
*) active_candidate=0 ;;
esac
if [ "$active_candidate" -eq 1 ]; then
[ "$active_seen" -eq 0 ] || {
terminate_child
wait "$node_pid" || true
exit 64
}
active_seen=1
IFS=' ' read -r active_uptime ignored < /proc/uptime
set -C
: > "$active_record"
set +C
printf '%s\\n' \\
'schema=qinglong/physical-edge-service-active@v1' \\
"boot_id=$boot_id" \\
"event_ordinal=$event_ordinal" \\
"active_uptime=$active_uptime" > "$active_record"
fi
done < "$fifo"
wait "$node_pid"
`;
}
function parseRecord(contents, expectedKeys, schema, label) {
const value = {};
const lines = contents.endsWith('\n')
? contents.slice(0, -1).split('\n')
: contents.split('\n');
for (const line of lines) {
const separator = line.indexOf('=');
if (separator < 1) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} contains an invalid line`,
);
}
const key = line.slice(0, separator);
if (Object.hasOwn(value, key)) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} contains a duplicate key`,
);
}
value[key] = line.slice(separator + 1);
}
exactKeys(value, expectedKeys, label);
if (value.schema !== schema) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} schema is invalid`,
);
}
return Object.freeze(value);
}
function positiveInteger(value, label) {
if (!/^[1-9][0-9]{0,9}$/.test(value ?? '')) {
throw new QingLong3PhysicalServiceStartEvidenceError(`${label} is invalid`);
}
const number = Number(value);
if (!Number.isSafeInteger(number)) {
throw new QingLong3PhysicalServiceStartEvidenceError(`${label} is invalid`);
}
return number;
}
function uptimeMilliseconds(value, label) {
if (!/^(?:0|[1-9][0-9]{0,8})(?:\.[0-9]{1,2})?$/.test(value ?? '')) {
throw new QingLong3PhysicalServiceStartEvidenceError(`${label} is invalid`);
}
const milliseconds = Math.round(Number(value) * 1000);
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
throw new QingLong3PhysicalServiceStartEvidenceError(`${label} is invalid`);
}
return milliseconds;
}
function parseWrapperObservations({
startContents,
nodeContents,
activeContents,
eventContents,
}) {
const start = parseRecord(
startContents,
START_RECORD_KEYS,
'qinglong/physical-edge-service-wrapper-start@v1',
'wrapper start record',
);
const node = parseRecord(
nodeContents,
NODE_RECORD_KEYS,
'qinglong/physical-edge-service-node@v1',
'node record',
);
const active = parseRecord(
activeContents,
ACTIVE_RECORD_KEYS,
'qinglong/physical-edge-service-active@v1',
'active record',
);
const events = [];
const remaining = parseEventLines(eventContents, events);
if (remaining !== '') {
throw new QingLong3PhysicalServiceStartEvidenceError(
'event log ended with a partial line',
);
}
const activeEvents = events
.map((event, index) => ({ event, ordinal: index + 1 }))
.filter(({ event }) => event.event === 'active');
const activeOrdinal = positiveInteger(
active.event_ordinal,
'active event ordinal',
);
const activeEvent = activeEvents.find(
({ ordinal }) => ordinal === activeOrdinal,
)?.event;
if (
activeEvents.length !== 1 ||
!activeEvent ||
activeEvent.level !== 'info' ||
activeEvent.profile !== 'edge' ||
activeEvent.aiStatus !== 'deployment_excluded'
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'event log does not contain one ordinal-bound official active event',
);
}
const serviceStartBootAgeMs = uptimeMilliseconds(
start.service_start_uptime,
'service start uptime',
);
const activeBootAgeMs = uptimeMilliseconds(
active.active_uptime,
'active uptime',
);
if (
active.boot_id !== start.boot_id ||
activeBootAgeMs < serviceStartBootAgeMs
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'wrapper boot identity or monotonic ordering drifted',
);
}
return Object.freeze({
bootId: start.boot_id,
wrapperPid: positiveInteger(start.wrapper_pid, 'wrapper PID'),
nodePid: positiveInteger(node.node_pid, 'Node PID'),
serviceStartBootAgeMs,
activeBootAgeMs,
serviceStartToActiveMs: activeBootAgeMs - serviceStartBootAgeMs,
activeEventOrdinal: activeOrdinal,
activeEvent,
activeEventCount: activeEvents.length,
eventCount: events.length,
});
}
function parseSystemdShow(contents) {
const values = {};
for (const line of contents.trim().split('\n')) {
const separator = line.indexOf('=');
if (separator < 1) continue;
values[line.slice(0, separator)] = line.slice(separator + 1);
}
const expected = [
'ActiveState',
'FragmentPath',
'LoadState',
'MainPID',
'SubState',
'UnitFileState',
];
exactKeys(values, expected, 'systemd show');
return Object.freeze({
active:
values.LoadState === 'loaded' &&
values.ActiveState === 'active' &&
values.SubState === 'running',
enabled: ['enabled', 'enabled-runtime'].includes(values.UnitFileState),
fragmentPath: values.FragmentPath,
mainPid: positiveInteger(values.MainPID, 'systemd MainPID'),
});
}
function parseOpenRcState(statusCode, updateOutput, serviceName) {
if (
statusCode !== 0 ||
!SAFE_SERVICE_NAME_PATTERN.test(serviceName) ||
!updateOutput
.split('\n')
.some(
(line) =>
new RegExp(
`^\\\\s*${serviceName.replaceAll('-', '\\\\-')}\\\\s+\\\\|`,
).test(line) && /\bdefault\b/.test(line),
)
) {
return Object.freeze({ active: false, enabled: false, mainPid: null });
}
return Object.freeze({ active: true, enabled: true, mainPid: null });
}
function validArtifact(value) {
return (
hasExactKeys(value, ARTIFACT_KEYS) &&
SHA256_PATTERN.test(value.artifactSha256 ?? '') &&
SHA256_PATTERN.test(value.artifactMetadataSha256 ?? '') &&
SHA256_PATTERN.test(value.entrypointSha256 ?? '') &&
Number.isSafeInteger(value.artifactFiles) &&
value.artifactFiles >= 1 &&
Number.isSafeInteger(value.artifactBytes) &&
value.artifactBytes >= 1 &&
JSON.stringify(value.packages) === JSON.stringify(REQUIRED_PACKAGES)
);
}
function normalizeSession(value) {
exactKeys(value, SESSION_KEYS, 'session');
const { sha256, ...body } = value;
const expectedDeploymentRoot = path.join(
value.paths?.dataPath ?? '',
`.ql3-service-start-deployment-${value.sessionId ?? ''}`,
);
const expectedToolRoot = path.join(
expectedDeploymentRoot,
'physical-service-start',
);
const expectedServiceName = `qinglong3-physical-${String(
value.sessionId ?? '',
).slice(0, 8)}`;
const expectedDescriptorSource = path.join(
expectedDeploymentRoot,
'service',
value.service?.kind === 'systemd'
? 'qinglong3.service'
: 'qinglong3.openrc',
);
const expectedDescriptorDestination =
value.service?.kind === 'systemd'
? `/etc/systemd/system/${expectedServiceName}.service`
: `/etc/init.d/${expectedServiceName}`;
const expectedDescriptorMode =
value.service?.kind === 'systemd' ? 0o644 : 0o755;
const expectedInstall = [
'-o',
'root',
'-g',
'root',
'-m',
expectedDescriptorMode.toString(8),
expectedDescriptorSource,
expectedDescriptorDestination,
];
const expectedEnable =
value.service?.kind === 'systemd'
? ['enable', expectedServiceName]
: ['add', expectedServiceName, 'default'];
const expectedManagerPattern =
value.service?.kind === 'systemd'
? /^\/(?:usr\/)?bin\/systemctl$/
: /^\/(?:usr\/)?sbin\/rc-service$/;
const expectedEnablePattern =
value.service?.kind === 'systemd'
? /^\/(?:usr\/)?bin\/systemctl$/
: /^\/(?:usr\/)?sbin\/rc-update$/;
const expectedEntrypoint = path.join(
value.paths?.artifactRoot ?? '',
'node_modules',
'@qinglong',
'local-application',
'dist',
'cli.js',
);
if (
value.schemaVersion !== 1 ||
value.evidenceClass !== 'physical_edge_service_start_session' ||
!UUID_V4_PATTERN.test(value.sessionId ?? '') ||
!SHA256_PATTERN.test(value.manifestDigest ?? '') ||
!Number.isSafeInteger(value.uid) ||
value.uid < 0 ||
typeof value.preparedAt !== 'string' ||
!Number.isFinite(Date.parse(value.preparedAt)) ||
!validArtifact(value.artifact) ||
!hasExactKeys(value.environment, ENVIRONMENT_KEYS) ||
value.environment.platform !== 'linux' ||
!hasExactKeys(value.paths, PATH_KEYS) ||
!hasExactKeys(value.service, SERVICE_KEYS) ||
!['systemd', 'openrc'].includes(value.service.kind) ||
value.service.serviceName !== expectedServiceName ||
!SAFE_SERVICE_NAME_PATTERN.test(value.service.serviceName ?? '') ||
!SHA256_PATTERN.test(value.service.descriptorSha256 ?? '') ||
!SHA256_PATTERN.test(value.service.managerSha256 ?? '') ||
!SHA256_PATTERN.test(value.service.enableSha256 ?? '') ||
!SHA256_PATTERN.test(value.service.wrapperSha256 ?? '') ||
value.service.descriptorMode !== expectedDescriptorMode ||
JSON.stringify(value.service.installArguments) !==
JSON.stringify(expectedInstall) ||
JSON.stringify(value.service.enableArguments) !==
JSON.stringify(expectedEnable) ||
!expectedManagerPattern.test(value.service.managerExecutable ?? '') ||
!expectedEnablePattern.test(value.service.enableExecutable ?? '') ||
(value.service.kind === 'systemd' &&
(value.service.enableExecutable !== value.service.managerExecutable ||
value.service.enableSha256 !== value.service.managerSha256)) ||
!path.isAbsolute(value.paths.dataPath ?? '') ||
!path.isAbsolute(value.paths.artifactRoot ?? '') ||
value.paths.deploymentRoot !== expectedDeploymentRoot ||
value.paths.toolRoot !== expectedToolRoot ||
value.paths.applicationEntrypoint !== expectedEntrypoint ||
value.paths.applicationConfig !==
path.join(expectedDeploymentRoot, 'local-application.json') ||
value.paths.wrapper !== path.join(expectedToolRoot, 'boot-probe.sh') ||
value.paths.wrapperStartRecord !==
path.join(expectedToolRoot, 'wrapper-start.record') ||
value.paths.nodeRecord !== path.join(expectedToolRoot, 'node.record') ||
value.paths.activeRecord !== path.join(expectedToolRoot, 'active.record') ||
value.paths.eventLog !== path.join(expectedToolRoot, 'events.jsonl') ||
value.paths.stderrLog !== path.join(expectedToolRoot, 'stderr.log') ||
value.paths.fifo !== path.join(expectedToolRoot, 'events.fifo') ||
value.service.descriptorSource !== expectedDescriptorSource ||
value.service.descriptorDestination !== expectedDescriptorDestination ||
value.sha256 !== canonicalDigest(body)
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'session is invalid or drifted',
);
}
for (const name of [
'deploymentRoot',
'toolRoot',
'wrapper',
'wrapperStartRecord',
'nodeRecord',
'activeRecord',
'eventLog',
'stderrLog',
'fifo',
]) {
assertChildPath(value.paths.dataPath, value.paths[name], `paths.${name}`);
}
return Object.freeze(value);
}
function findExecutable(candidates, label) {
const uid = process.geteuid?.();
for (const candidate of candidates) {
try {
const resolved = fs.realpathSync(candidate);
const stat = fs.lstatSync(resolved);
if (
resolved === candidate &&
stat.isFile() &&
!stat.isSymbolicLink() &&
(stat.uid === 0 || stat.uid === uid) &&
(stat.mode & 0o022) === 0 &&
(stat.mode & 0o111) !== 0
) {
return candidate;
}
} catch {}
}
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} executable is unavailable`,
);
}
function managerExecutable(kind) {
return kind === 'systemd'
? findExecutable(['/usr/bin/systemctl', '/bin/systemctl'], 'systemd')
: findExecutable(['/sbin/rc-service', '/usr/sbin/rc-service'], 'OpenRC');
}
function serviceEnableExecutable(kind, manager) {
return kind === 'systemd'
? manager
: findExecutable(
['/sbin/rc-update', '/usr/sbin/rc-update'],
'OpenRC update',
);
}
function createFifo(filePath) {
const executable = findExecutable(
['/usr/bin/mkfifo', '/bin/mkfifo'],
'mkfifo',
);
const result = spawnSync(executable, ['-m', '600', filePath], {
encoding: 'utf8',
timeout: 5_000,
});
if (result.status !== 0 || result.signal !== null) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'private event FIFO could not be created',
);
}
const stat = fs.lstatSync(filePath);
if (!stat.isFIFO() || (stat.mode & 0o777) !== 0o600) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'private event FIFO is invalid',
);
}
}
function requireDeploymentProduct() {
try {
return require(path.join(
__dirname,
'..',
'packages/ql3-local-owner-cli/dist/deployment/localDeployment.js',
));
} catch (error) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`QingLong 3.0 local deployment package must be built first: ${error.message}`,
);
}
}
function currentBoot(dataPath, node) {
const observed = collectObservedPlatform(dataPath);
return Object.freeze({
platform: observed.platform,
architecture: observed.architecture,
bootId: observed.bootId,
bootAgeMs: readBootAgeMs(),
dataFilesystem: observed.dataFilesystem,
nodeExecutable: node.nodeExecutable,
nodeSha256: node.nodeSha256,
nodeVersion: node.nodeVersion,
virtualizationIndicators: observed.virtualizationIndicators,
});
}
function validateEnvironment(manifest, environment) {
const violations = [];
if (
environment.platform !== 'linux' ||
environment.architecture !== manifest.expectedArchitecture ||
environment.dataFilesystem !== manifest.expectedFilesystem ||
environment.nodeSha256 !== manifest.expectedNodeSha256 ||
!/^v24\.\d+\.\d+$/.test(environment.nodeVersion ?? '') ||
!path.isAbsolute(environment.nodeExecutable ?? '') ||
typeof environment.bootId !== 'string' ||
environment.bootId.length < 8 ||
!Array.isArray(environment.virtualizationIndicators) ||
environment.virtualizationIndicators.length > 0
) {
violations.push('device, Node or boot environment did not match manifest');
}
return Object.freeze(violations);
}
function installContract(kind, serviceName, descriptorSource) {
const descriptorDestination =
kind === 'systemd'
? `/etc/systemd/system/${serviceName}.service`
: `/etc/init.d/${serviceName}`;
const descriptorMode = kind === 'systemd' ? 0o644 : 0o755;
return Object.freeze({
descriptorDestination,
descriptorMode,
installArguments: Object.freeze([
'-o',
'root',
'-g',
'root',
'-m',
descriptorMode.toString(8),
descriptorSource,
descriptorDestination,
]),
enableArguments: Object.freeze(
kind === 'systemd'
? ['enable', serviceName]
: ['add', serviceName, 'default'],
),
});
}
async function preparePhase(options, manifest) {
if (process.platform !== 'linux') {
throw new QingLong3PhysicalServiceStartEvidenceError(
'prepare requires Linux',
);
}
const dataPath = fs.realpathSync(options.dataPath);
assertPrivateDirectory(dataPath, 'dataPath');
assertChildPath(dataPath, options.sessionPath, 'sessionPath');
if (fs.existsSync(options.sessionPath)) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'sessionPath already exists',
);
}
const artifactIdentity = collectArtifactIdentity(options.artifactRoot);
const node = collectNodeIdentity();
const artifactViolations = validateArtifactAgainstManifest(
manifest,
artifactIdentity.artifact,
node,
);
if (artifactViolations.length > 0) {
throw new QingLong3PhysicalServiceStartEvidenceError(
artifactViolations.join('; '),
);
}
const environment = currentBoot(dataPath, node);
const environmentViolations = validateEnvironment(manifest, environment);
if (environmentViolations.length > 0) {
throw new QingLong3PhysicalServiceStartEvidenceError(
environmentViolations.join('; '),
);
}
const uid = process.geteuid?.();
if (!Number.isSafeInteger(uid)) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'a stable POSIX UID is required',
);
}
const sessionId = crypto.randomUUID();
const suffix = sessionId.slice(0, 8);
const serviceName = `qinglong3-physical-${suffix}`;
const deploymentRoot = path.join(
dataPath,
`.ql3-service-start-deployment-${sessionId}`,
);
const toolRoot = path.join(deploymentRoot, 'physical-service-start');
fs.mkdirSync(deploymentRoot, { mode: 0o700 });
fs.mkdirSync(toolRoot, { mode: 0o700 });
assertPrivateDirectory(deploymentRoot, 'deploymentRoot');
assertPrivateDirectory(toolRoot, 'toolRoot');
const paths = Object.freeze({
dataPath,
toolRoot,
deploymentRoot,
artifactRoot: artifactIdentity.artifactRoot,
applicationEntrypoint: artifactIdentity.applicationEntrypoint,
applicationConfig: path.join(deploymentRoot, 'local-application.json'),
wrapper: path.join(toolRoot, 'boot-probe.sh'),
wrapperStartRecord: path.join(toolRoot, 'wrapper-start.record'),
nodeRecord: path.join(toolRoot, 'node.record'),
activeRecord: path.join(toolRoot, 'active.record'),
eventLog: path.join(toolRoot, 'events.jsonl'),
stderrLog: path.join(toolRoot, 'stderr.log'),
fifo: path.join(toolRoot, 'events.fifo'),
});
const wrapper = renderEvidenceWrapper({
applicationEntrypoint: paths.applicationEntrypoint,
nodeExecutable: node.nodeExecutable,
paths,
});
writeNoReplace(paths.wrapper, wrapper, 0o700);
createFifo(paths.fifo);
const timestamp = Date.now();
const product = requireDeploymentProduct();
const prepared = await product.prepareLocalDeployment({
schemaVersion: 1,
operation: 'local.deployment.prepare',
options: {
deploymentRoot,
profile: 'edge',
instanceId: `physical-service-${suffix}`,
busyTimeoutMs: 100,
service: {
kind: manifest.serviceManager,
nodeExecutable: paths.wrapper,
applicationEntrypoint: node.nodeExecutable,
allowRootService: uid === 0,
},
},
request: {
ownerPepperKeyId: 'owner-v1',
registerMutationId: crypto.randomUUID(),
activateMutationId: crypto.randomUUID(),
registeredAtMs: timestamp,
activatedAtMs: timestamp + 1,
},
});
if (prepared.status !== 'prepared' || prepared.profile !== 'edge') {
throw new QingLong3PhysicalServiceStartEvidenceError(
'fresh Edge service deployment was not prepared',
);
}
const descriptorSource = path.join(
deploymentRoot,
'service',
manifest.serviceManager === 'systemd'
? 'qinglong3.service'
: 'qinglong3.openrc',
);
const manager = managerExecutable(manifest.serviceManager);
const enable = serviceEnableExecutable(manifest.serviceManager, manager);
const install = installContract(
manifest.serviceManager,
serviceName,
descriptorSource,
);
const body = {
schemaVersion: 1,
evidenceClass: 'physical_edge_service_start_session',
sessionId,
manifestDigest: canonicalDigest(manifest),
uid,
preparedAt: new Date().toISOString(),
artifact: artifactIdentity.artifact,
environment,
paths,
service: {
kind: manifest.serviceManager,
serviceName,
managerExecutable: manager,
managerSha256: fileSha256(manager),
enableExecutable: enable,
enableSha256: fileSha256(enable),
descriptorSource,
descriptorDestination: install.descriptorDestination,
descriptorMode: install.descriptorMode,
descriptorSha256: fileSha256(descriptorSource),
wrapperSha256: fileSha256(paths.wrapper),
installArguments: install.installArguments,
enableArguments: install.enableArguments,
},
};
const session = Object.freeze({ ...body, sha256: canonicalDigest(body) });
writeNoReplace(options.sessionPath, `${JSON.stringify(session, null, 2)}\n`);
process.stdout.write(
`${JSON.stringify({
schemaVersion: 1,
status: 'awaiting_operator_install_enable_and_reboot',
sessionId,
service: {
kind: session.service.kind,
serviceName,
descriptorSource,
descriptorDestination: session.service.descriptorDestination,
descriptorSha256: session.service.descriptorSha256,
install: {
arguments: session.service.installArguments,
},
enable: {
executable: session.service.enableExecutable,
arguments: session.service.enableArguments,
},
},
automaticServiceManagerMutationPerformed: false,
supported: false,
})}\n`,
);
}
function readCmdline(processId) {
return fs
.readFileSync(`/proc/${processId}/cmdline`)
.toString('utf8')
.split('\0')
.filter(Boolean);
}
function processIdentity(session, observations) {
const wrapperStat = parseProcStat(
fs.readFileSync(`/proc/${observations.wrapperPid}/stat`, 'utf8'),
);
const wrapperStatus = parseProcStatus(
fs.readFileSync(`/proc/${observations.wrapperPid}/status`, 'utf8'),
);
const nodeStat = parseProcStat(
fs.readFileSync(`/proc/${observations.nodePid}/stat`, 'utf8'),
);
const nodeStatusSource = fs.readFileSync(
`/proc/${observations.nodePid}/status`,
'utf8',
);
const nodeStatus = parseProcStatus(nodeStatusSource);
const parentMatch = /^PPid:\s+([0-9]+)$/m.exec(nodeStatusSource);
const nodeExecutable = fs.realpathSync(`/proc/${observations.nodePid}/exe`);
const wrapperArguments = readCmdline(observations.wrapperPid);
const nodeArguments = readCmdline(observations.nodePid);
const expectedNodeArguments = [
session.environment.nodeExecutable,
session.paths.applicationEntrypoint,
'--config',
session.paths.applicationConfig,
];
const wrapperMatched =
wrapperStatus.uid === session.uid &&
wrapperStat.processId === observations.wrapperPid &&
wrapperArguments.includes(session.paths.wrapper) &&
wrapperArguments.includes(session.environment.nodeExecutable);
const nodeMatched =
nodeStatus.uid === session.uid &&
nodeStat.processId === observations.nodePid &&
Number(parentMatch?.[1]) === observations.wrapperPid &&
nodeExecutable === session.environment.nodeExecutable &&
JSON.stringify(nodeArguments) === JSON.stringify(expectedNodeArguments);
return Object.freeze({
wrapperMatched,
nodeMatched,
wrapperPid: observations.wrapperPid,
wrapperStartTicks: wrapperStat.startTicks,
nodePid: observations.nodePid,
nodeStartTicks: nodeStat.startTicks,
});
}
function runBounded(executable, arguments_, label) {
const result = spawnSync(executable, arguments_, {
encoding: 'utf8',
timeout: 5_000,
maxBuffer: 64 * 1024,
env: { LANG: 'C', LC_ALL: 'C', PATH: '/usr/sbin:/usr/bin:/sbin:/bin' },
});
if (result.error || result.signal !== null) {
throw new QingLong3PhysicalServiceStartEvidenceError(
`${label} probe failed`,
);
}
return result;
}
function inspectServiceManager(session, wrapperPid) {
if (session.service.kind === 'systemd') {
const result = runBounded(
session.service.managerExecutable,
[
'--no-pager',
'show',
`${session.service.serviceName}.service`,
'--property=LoadState,ActiveState,SubState,UnitFileState,FragmentPath,MainPID',
],
'systemd',
);
if (result.status !== 0) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'systemd service is unavailable',
);
}
const state = parseSystemdShow(result.stdout);
return Object.freeze({
active: state.active,
enabled: state.enabled,
mainPid: state.mainPid,
fragmentPath: state.fragmentPath,
mainPidMatched: state.mainPid === wrapperPid,
});
}
const status = runBounded(
session.service.managerExecutable,
[session.service.serviceName, 'status'],
'OpenRC status',
);
const enabled = runBounded(
session.service.enableExecutable,
['show', 'default'],
'OpenRC enabled state',
);
const state = parseOpenRcState(
status.status,
enabled.stdout,
session.service.serviceName,
);
return Object.freeze({
...state,
fragmentPath: session.service.descriptorDestination,
mainPidMatched: true,
});
}
function evaluateServiceStartEvidence({
manifest,
session,
observed,
measurements,
outcomes,
}) {
const violations = [];
if (
!hasExactKeys(observed, OBSERVED_KEYS) ||
!hasExactKeys(observed.after, ENVIRONMENT_KEYS) ||
!validArtifact(observed.artifact) ||
!hasExactKeys(observed.process, PROCESS_KEYS) ||
!hasExactKeys(observed.service, OBSERVED_SERVICE_KEYS)
) {
return Object.freeze(['service start observation shape is invalid']);
}
if (
observed.after.bootId === session.environment.bootId ||
observed.after.bootId !== observed.process.bootId
) {
violations.push('external reboot identity was not proven');
}
violations.push(...validateEnvironment(manifest, observed.after));
if (
JSON.stringify(observed.artifact) !== JSON.stringify(session.artifact) ||
observed.artifact.artifactSha256 !== manifest.expectedArtifactSha256 ||
observed.artifact.artifactFiles !== manifest.expectedArtifactFiles ||
observed.artifact.artifactBytes !== manifest.expectedArtifactBytes ||
observed.service.kind !== manifest.serviceManager ||
observed.service.serviceName !== session.service.serviceName ||
observed.service.managerExecutable !== session.service.managerExecutable ||
observed.service.managerSha256 !== session.service.managerSha256 ||
observed.service.descriptorSha256 !== session.service.descriptorSha256
) {
violations.push('artifact, manager or descriptor identity drifted');
}
if (
!hasExactKeys(measurements, MEASUREMENT_KEYS) ||
MEASUREMENT_KEYS.some(
(key) =>
!Number.isSafeInteger(measurements[key]) || measurements[key] < 0,
) ||
measurements.bootToActiveMs !== measurements.activeBootAgeMs ||
measurements.serviceStartBootAgeMs >
manifest.maximumServiceStartBootAgeMs ||
measurements.serviceStartToActiveMs >
manifest.maximumServiceStartToActiveMs ||
measurements.bootToActiveMs > manifest.maximumBootToActiveMs ||
measurements.activeEventOrdinal < 1
) {
violations.push('service start measurement budget is invalid');
}
if (
!hasExactKeys(outcomes, OUTCOME_KEYS) ||
outcomes.activeEventCount !== 1 ||
outcomes.aiStatus !== 'deployment_excluded' ||
outcomes.descriptorInstalled !== true ||
outcomes.nodeProcessIdentityMatched !== true ||
outcomes.serviceActive !== true ||
outcomes.serviceEnabled !== true ||
outcomes.stderrBytes !== 0 ||
outcomes.wrapperProcessIdentityMatched !== true
) {
violations.push('service manager or application outcome is invalid');
}
return Object.freeze(violations);
}
function buildServiceStartReport({
manifest,
session,
observed,
measurements,
outcomes,
generatedAt,
}) {
const violations = evaluateServiceStartEvidence({
manifest,
session,
observed,
measurements,
outcomes,
});
const body = {
schemaVersion: 1,
evidenceClass: 'physical_edge_service_start_candidate',
supported: false,
generatedAt,
manifest,
session: {
sessionId: session.sessionId,
sessionDigest: session.sha256,
preparedAt: session.preparedAt,
prepareBootId: session.environment.bootId,
},
observed,
measurements,
outcomes,
qualification: {
passed: violations.length === 0,
violations,
measures: MEASURES,
doesNotProve: EXCLUSIONS,
},
};
return Object.freeze({ ...body, sha256: canonicalDigest(body) });
}
function validateServiceStartReport(report, manifest, currentObserved) {
const violations = [];
if (!hasExactKeys(report, REPORT_KEYS)) {
return Object.freeze(['service start report shape is invalid']);
}
const { sha256, ...body } = report;
if (!SHA256_PATTERN.test(sha256 ?? '') || sha256 !== canonicalDigest(body)) {
violations.push('service start report digest is invalid');
}
if (
report.schemaVersion !== 1 ||
report.evidenceClass !== 'physical_edge_service_start_candidate' ||
report.supported !== false ||
!Number.isFinite(Date.parse(report.generatedAt ?? '')) ||
canonicalDigest(report.manifest) !== canonicalDigest(manifest) ||
!hasExactKeys(report.session, REPORT_SESSION_KEYS) ||
!UUID_V4_PATTERN.test(report.session?.sessionId ?? '') ||
!SHA256_PATTERN.test(report.session?.sessionDigest ?? '') ||
!Number.isFinite(Date.parse(report.session?.preparedAt ?? '')) ||
typeof report.session?.prepareBootId !== 'string' ||
report.session.prepareBootId.length < 8 ||
!hasExactKeys(report.qualification, QUALIFICATION_KEYS) ||
report.observed?.after?.bootId !== currentObserved.bootId ||
report.observed?.after?.architecture !== currentObserved.architecture ||
report.observed?.after?.dataFilesystem !== currentObserved.dataFilesystem
) {
violations.push('service start report qualification or device drifted');
}
const syntheticSession = {
environment: { bootId: report.session?.prepareBootId },
artifact: report.observed?.artifact,
service: {
kind: report.observed?.service?.kind,
serviceName: report.observed?.service?.serviceName,
managerExecutable: report.observed?.service?.managerExecutable,
managerSha256: report.observed?.service?.managerSha256,
descriptorSha256: report.observed?.service?.descriptorSha256,
},
};
const recomputed = evaluateServiceStartEvidence({
manifest,
session: syntheticSession,
observed: report.observed,
measurements: report.measurements,
outcomes: report.outcomes,
});
if (
report.qualification?.passed !== (recomputed.length === 0) ||
JSON.stringify(report.qualification?.violations) !==
JSON.stringify(recomputed) ||
JSON.stringify(report.qualification?.measures) !==
JSON.stringify(MEASURES) ||
JSON.stringify(report.qualification?.doesNotProve) !==
JSON.stringify(EXCLUSIONS) ||
recomputed.length > 0
) {
violations.push('service start report qualification was widened');
}
return Object.freeze(violations);
}
async function resumePhase(options, manifest) {
if (process.platform !== 'linux') {
throw new QingLong3PhysicalServiceStartEvidenceError(
'resume requires Linux',
);
}
const session = normalizeSession(
readPrivateJson(options.sessionPath, 'session'),
);
if (session.manifestDigest !== canonicalDigest(manifest)) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'manifest did not match session',
);
}
if (process.geteuid?.() !== session.uid) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'resume UID did not match prepare',
);
}
const dataPath = fs.realpathSync(session.paths.dataPath);
assertPrivateDirectory(dataPath, 'dataPath');
assertChildPath(dataPath, options.outputPath, 'outputPath');
if (fs.existsSync(options.outputPath)) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'outputPath already exists',
);
}
const metadata = preflightArtifactMetadata(
session.paths.artifactRoot,
session.paths.applicationEntrypoint,
);
if (
metadata.artifactMetadataSha256 !== session.artifact.artifactMetadataSha256
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'artifact metadata drifted before report collection',
);
}
const node = collectNodeIdentity();
const after = currentBoot(dataPath, node);
if (
after.bootId === session.environment.bootId ||
validateEnvironment(manifest, after).length > 0
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'reboot or current environment preflight failed',
);
}
const installedStat = fs.lstatSync(session.service.descriptorDestination);
if (
!installedStat.isFile() ||
installedStat.isSymbolicLink() ||
installedStat.uid !== 0 ||
(installedStat.mode & 0o777) !== session.service.descriptorMode ||
installedStat.nlink !== 1 ||
fileSha256(session.service.descriptorDestination) !==
session.service.descriptorSha256 ||
fileSha256(session.paths.wrapper) !== session.service.wrapperSha256 ||
fileSha256(session.service.managerExecutable) !==
session.service.managerSha256 ||
fileSha256(session.service.enableExecutable) !==
session.service.enableSha256
) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'installed descriptor, wrapper or service manager drifted',
);
}
const wrapper = parseWrapperObservations({
startContents: readPrivateFile(
session.paths.wrapperStartRecord,
'wrapper start record',
),
nodeContents: readPrivateFile(session.paths.nodeRecord, 'node record'),
activeContents: readPrivateFile(
session.paths.activeRecord,
'active record',
),
eventContents: readPrivateFile(
session.paths.eventLog,
'event log',
MAX_EVENT_BYTES,
),
});
if (wrapper.bootId !== after.bootId) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'wrapper record did not belong to current boot',
);
}
const process = processIdentity(session, wrapper);
const serviceState = inspectServiceManager(session, wrapper.wrapperPid);
const artifactIdentity = collectArtifactIdentity(session.paths.artifactRoot);
const stderrBytes = Buffer.byteLength(
readPrivateFile(session.paths.stderrLog, 'stderr log', MAX_EVENT_BYTES),
'utf8',
);
const report = buildServiceStartReport({
manifest,
session,
observed: {
after,
artifact: artifactIdentity.artifact,
process: {
bootId: wrapper.bootId,
wrapperPid: process.wrapperPid,
wrapperStartTicks: process.wrapperStartTicks,
nodePid: process.nodePid,
nodeStartTicks: process.nodeStartTicks,
},
service: {
kind: session.service.kind,
serviceName: session.service.serviceName,
managerExecutable: session.service.managerExecutable,
managerSha256: session.service.managerSha256,
descriptorSha256: session.service.descriptorSha256,
mainPid: serviceState.mainPid,
},
},
measurements: {
serviceStartBootAgeMs: wrapper.serviceStartBootAgeMs,
activeBootAgeMs: wrapper.activeBootAgeMs,
bootToActiveMs: wrapper.activeBootAgeMs,
serviceStartToActiveMs: wrapper.serviceStartToActiveMs,
activeEventOrdinal: wrapper.activeEventOrdinal,
},
outcomes: {
activeEventCount: wrapper.activeEventCount,
aiStatus: wrapper.activeEvent.aiStatus,
descriptorInstalled:
serviceState.fragmentPath === session.service.descriptorDestination,
serviceActive: serviceState.active,
serviceEnabled: serviceState.enabled,
wrapperProcessIdentityMatched:
process.wrapperMatched && serviceState.mainPidMatched,
nodeProcessIdentityMatched: process.nodeMatched,
stderrBytes,
},
generatedAt: new Date().toISOString(),
});
writeNoReplace(
options.outputPath,
`${JSON.stringify(report, null, options.json ? 0 : 2)}\n`,
);
process.stdout.write(
`${JSON.stringify(report, null, options.json ? 0 : 2)}\n`,
);
if (!report.qualification.passed) process.exitCode = 1;
}
function inspectPhase(options) {
const artifact = collectArtifactIdentity(options.artifactRoot);
const node = collectNodeIdentity();
process.stdout.write(
`${JSON.stringify(
{
schemaVersion: 1,
evidenceClass: 'physical_edge_service_start_inspection',
profile: 'edge',
artifact: artifact.artifact,
node: {
executable: node.nodeExecutable,
sha256: node.nodeSha256,
version: node.nodeVersion,
},
supported: false,
},
null,
options.json ? 0 : 2,
)}\n`,
);
}
async function main() {
if (Number(process.versions.node.split('.')[0]) < 24) {
throw new QingLong3PhysicalServiceStartEvidenceError(
'Node.js 24 or newer is required',
);
}
const options = parseArguments(process.argv.slice(2));
if (options.phase === 'inspect') {
inspectPhase(options);
return;
}
const manifest = normalizeServiceStartManifest(
readPrivateJson(options.manifestPath, 'manifest'),
);
if (options.phase === 'prepare') {
await preparePhase(options, manifest);
} else {
await resumePhase(options, manifest);
}
}
module.exports = {
QingLong3PhysicalServiceStartEvidenceError,
buildServiceStartReport,
evaluateServiceStartEvidence,
installContract,
normalizeServiceStartManifest,
normalizeSession,
parseArguments,
parseOpenRcState,
parseSystemdShow,
parseWrapperObservations,
renderEvidenceWrapper,
validateServiceStartReport,
};
if (require.main === module) {
main().catch((error) => {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
});
}