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

1585 lines
47 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const crypto = require('node:crypto');
const fs = require('node:fs');
const path = require('node:path');
const { spawn } = require('node:child_process');
const {
canonicalDigest,
collectObservedPlatform,
} = require('./ql3-physical-edge-evidence.cjs');
const {
parseProcIo,
parseProcStat,
parseProcStatus,
} = require('./ql3-physical-edge-idle-sampler.cjs');
const { readReleaseIdentity } = require('./lib/ql3-release-identity.cjs');
const MIB = 1024 * 1024;
const QL3_VERSION = readReleaseIdentity(path.resolve(__dirname, '..')).version;
const MAX_INPUT_BYTES = 256 * 1024;
const MAX_OUTPUT_BYTES = 64 * 1024;
const MAX_ARTIFACT_FILES = 768;
const MAX_ARTIFACT_DIRECTORIES = 256;
const MAX_ARTIFACT_BYTES = 8 * MIB;
const MAX_EVENTS = 64;
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 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',
'maximumBootAgeMs',
'maximumFirstActiveMs',
'maximumSampledRssBytes',
'profile',
'sampleIntervalMs',
'schemaVersion',
]);
const ARTIFACT_KEYS = Object.freeze([
'artifactBytes',
'artifactFiles',
'artifactMetadataSha256',
'artifactSha256',
'entrypointSha256',
'packages',
]);
const ENVIRONMENT_KEYS = Object.freeze([
'architecture',
'bootAgeMs',
'bootId',
'dataFilesystem',
'nodeExecutable',
'nodeSha256',
'nodeVersion',
'platform',
]);
const PATH_KEYS = Object.freeze([
'applicationConfig',
'applicationEntrypoint',
'artifactRoot',
'dataPath',
'deploymentRoot',
]);
const SESSION_KEYS = Object.freeze([
'artifact',
'environment',
'evidenceClass',
'manifestDigest',
'paths',
'preparedAt',
'schemaVersion',
'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([
'preparedAt',
'sessionDigest',
'sessionId',
]);
const REPORT_OBSERVED_KEYS = Object.freeze(['after', 'artifact', 'before']);
const OBSERVED_BOOT_KEYS = Object.freeze([
'architecture',
'bootAgeMs',
'bootId',
'dataFilesystem',
'nodeExecutable',
'nodeSha256',
'nodeVersion',
'platform',
]);
const MEASUREMENT_KEYS = Object.freeze([
'eventCount',
'firstActiveMs',
'maximumSampledRssBytes',
'processReadBytes',
'processWriteBytes',
'sampleCount',
]);
const OUTCOME_KEYS = Object.freeze([
'activeEventCount',
'aiStatus',
'exitCode',
'exitSignal',
'gracefulStop',
'sqliteContractVersion',
'stderrBytes',
]);
const QUALIFICATION_KEYS = Object.freeze([
'doesNotProve',
'measures',
'passed',
'violations',
]);
const MEASURES = Object.freeze([
'different_boot_identity',
'bounded_post_reboot_boot_age',
'exact_ai_excluded_native_release_closure',
'production_application_process_to_active_event',
'sampled_process_rss_until_active',
'sampled_process_io_after_first_proc_sample',
'graceful_sigterm_stop',
'sqlite_v35_after_stop',
]);
const EXCLUSIONS = Object.freeze([
'cold_node_runtime_or_dynamic_linker_cache',
'cold_artifact_directory_or_inode_cache',
'exclusive_application_page_cache_provenance',
'process_metrics_before_first_proc_sample',
'rss_peaks_between_samples',
'firmware_power_on_to_service_manager_start',
'systemd_or_openrc_supervisor_latency',
'compose_or_container_runtime_start',
'abrupt_power_loss_recovery',
'whole_device_cpu_wakeups',
'whole_device_flash_write_amplification',
'release_archive_signature_or_attestation',
'standalone_or_cluster_profile',
]);
class QingLong3PhysicalApplicationStartEvidenceError extends Error {
constructor(message) {
super(
`QingLong 3.0 physical Edge application start evidence failed: ${message}`,
);
this.name = 'QingLong3PhysicalApplicationStartEvidenceError';
}
}
function exactKeys(value, expected, label) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`${label} must be an object`,
);
}
if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`${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 normalizeApplicationStartManifest(value) {
exactKeys(value, MANIFEST_KEYS, 'manifest');
if (
value.schemaVersion !== 1 ||
value.evidenceClass !== 'physical_edge_application_start_candidate' ||
value.profile !== 'edge'
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'manifest identity is invalid',
);
}
if (
typeof value.deviceId !== 'string' ||
!/^[a-z0-9][a-z0-9._-]{2,63}$/.test(value.deviceId)
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'manifest deviceId is invalid',
);
}
if (!['x64', 'arm64', 'arm'].includes(value.expectedArchitecture)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'manifest expectedArchitecture is invalid',
);
}
if (
typeof value.expectedFilesystem !== 'string' ||
!/^[a-z0-9][a-z0-9._-]{1,31}$/.test(value.expectedFilesystem)
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'manifest expectedFilesystem is invalid',
);
}
if (
!SHA256_PATTERN.test(value.expectedArtifactSha256 ?? '') ||
!Number.isSafeInteger(value.expectedArtifactFiles) ||
value.expectedArtifactFiles < 1 ||
value.expectedArtifactFiles > MAX_ARTIFACT_FILES ||
!Number.isSafeInteger(value.expectedArtifactBytes) ||
value.expectedArtifactBytes < 1 ||
value.expectedArtifactBytes > MAX_ARTIFACT_BYTES ||
!SHA256_PATTERN.test(value.expectedNodeSha256 ?? '')
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'manifest release artifact identity is invalid',
);
}
if (
!Number.isSafeInteger(value.maximumBootAgeMs) ||
value.maximumBootAgeMs < 10_000 ||
value.maximumBootAgeMs > 600_000 ||
!Number.isSafeInteger(value.maximumFirstActiveMs) ||
value.maximumFirstActiveMs < 100 ||
value.maximumFirstActiveMs > 120_000 ||
!Number.isSafeInteger(value.maximumSampledRssBytes) ||
value.maximumSampledRssBytes < 16 * MIB ||
value.maximumSampledRssBytes > 512 * MIB ||
!Number.isSafeInteger(value.sampleIntervalMs) ||
value.sampleIntervalMs < 10 ||
value.sampleIntervalMs > 1_000
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'manifest measurement budget is invalid',
);
}
return Object.freeze({ ...value });
}
function parseArguments(argv) {
const options = { json: false };
let phase;
let separatorSeen = false;
for (const argument of argv) {
if (argument === '--' && !separatorSeen) {
separatorSeen = true;
continue;
}
if (argument === '--json') {
options.json = true;
continue;
}
if (
!phase &&
(argument === 'inspect' ||
argument === 'prepare' ||
argument === 'resume')
) {
phase = argument;
continue;
}
const separator = argument.indexOf('=');
if (separator < 1) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`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 QingLong3PhysicalApplicationStartEvidenceError(
`unsupported argument ${argument}`,
);
}
}
if (!phase) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'phase must be inspect, prepare or resume',
);
}
const required =
phase === 'inspect'
? ['artifactRoot']
: phase === 'prepare'
? ['manifestPath', 'dataPath', 'artifactRoot', 'sessionPath']
: ['manifestPath', 'sessionPath', 'outputPath'];
for (const name of required) {
if (!path.isAbsolute(options[name] ?? '')) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`${name} must be absolute`,
);
}
}
const allowed = new Set(['json', ...required]);
for (const name of Object.keys(options)) {
if (!allowed.has(name)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`${name} is not accepted for ${phase}`,
);
}
}
return Object.freeze({ phase, ...options });
}
function readBoundedPrivateJson(filePath, label) {
let stat;
try {
stat = fs.lstatSync(filePath);
} catch (error) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`${label} is unavailable: ${error.message}`,
);
}
const uid = process.geteuid?.();
if (
!Number.isSafeInteger(uid) ||
!stat.isFile() ||
stat.isSymbolicLink() ||
stat.uid !== uid ||
(stat.mode & 0o777) !== 0o600 ||
stat.nlink !== 1 ||
stat.size < 2 ||
stat.size > MAX_INPUT_BYTES
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`${label} must be a bounded current-user 0600 single-link file`,
);
}
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (error) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`${label} is invalid: ${error.message}`,
);
}
}
function readManifest(filePath) {
return normalizeApplicationStartManifest(
readBoundedPrivateJson(filePath, 'manifest'),
);
}
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 QingLong3PhysicalApplicationStartEvidenceError(
`${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 QingLong3PhysicalApplicationStartEvidenceError(
`${label} must remain inside dataPath`,
);
}
}
function writePrivateNoReplace(filePath, contents) {
const parent = fs.realpathSync(path.dirname(filePath));
assertPrivateDirectory(parent, 'output parent');
const resolved = path.join(parent, path.basename(filePath));
if (resolved !== filePath) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'output path must be canonical',
);
}
const descriptor = fs.openSync(filePath, 'wx', 0o600);
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 installedPackages(nodeModulesRoot) {
const values = [];
for (const entry of fs.readdirSync(nodeModulesRoot, {
withFileTypes: true,
})) {
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
if (!entry.name.startsWith('@')) {
values.push(entry.name);
continue;
}
for (const scoped of fs.readdirSync(
path.join(nodeModulesRoot, entry.name),
{ withFileTypes: true },
)) {
if (scoped.isDirectory()) values.push(`${entry.name}/${scoped.name}`);
}
}
return values.sort();
}
function artifactMetadataEntry(artifactRoot, target, kind, stat) {
return Object.freeze({
path: path.relative(artifactRoot, target) || '.',
kind,
device: stat.dev.toString(),
inode: stat.ino.toString(),
bytes: stat.size.toString(),
mode: Number(stat.mode & 0o777n),
uid: Number(stat.uid),
gid: Number(stat.gid),
links: stat.nlink.toString(),
modifiedNs: stat.mtimeNs.toString(),
changedNs: stat.ctimeNs.toString(),
});
}
function collectArtifactIdentity(artifactRootInput) {
const artifactRoot = fs.realpathSync(artifactRootInput);
if (
artifactRoot !== artifactRootInput ||
!path.isAbsolute(artifactRoot) ||
artifactRoot === path.parse(artifactRoot).root
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'artifactRoot must be a canonical non-root directory',
);
}
const uid = process.geteuid?.();
if (!Number.isSafeInteger(uid)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'a stable POSIX identity is required',
);
}
const files = [];
const metadata = [];
let directories = 0;
let artifactBytes = 0;
const visit = (target) => {
const stat = fs.lstatSync(target, { bigint: true });
if (
stat.isSymbolicLink() ||
(stat.uid !== 0n && stat.uid !== BigInt(uid)) ||
(stat.mode & 0o022n) !== 0n
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact contains an untrusted entry',
);
}
if (stat.isDirectory()) {
metadata.push(
artifactMetadataEntry(artifactRoot, target, 'directory', stat),
);
directories += 1;
if (directories > MAX_ARTIFACT_DIRECTORIES) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact directory count exceeded',
);
}
for (const entry of fs.readdirSync(target).sort()) {
visit(path.join(target, entry));
}
return;
}
if (!stat.isFile() || stat.nlink !== 1n) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact contains a non-regular or linked entry',
);
}
const fileBytes = Number(stat.size);
if (!Number.isSafeInteger(fileBytes)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact file size is invalid',
);
}
artifactBytes += fileBytes;
if (
files.length >= MAX_ARTIFACT_FILES ||
artifactBytes > MAX_ARTIFACT_BYTES
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact budget exceeded',
);
}
files.push(
Object.freeze({
path: path.relative(artifactRoot, target),
mode: Number(stat.mode & 0o777n),
bytes: fileBytes,
sha256: fileSha256(target),
}),
);
metadata.push(artifactMetadataEntry(artifactRoot, target, 'file', stat));
};
visit(artifactRoot);
const nodeModulesRoot = path.join(artifactRoot, 'node_modules');
const packages = installedPackages(nodeModulesRoot);
if (JSON.stringify(packages) !== JSON.stringify(REQUIRED_PACKAGES)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact package closure is invalid',
);
}
const packageRoot = path.join(
nodeModulesRoot,
'@qinglong',
'local-application',
);
const packageManifest = JSON.parse(
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
);
const applicationEntrypoint = path.join(packageRoot, 'dist', 'cli.js');
if (
packageManifest.name !== '@qinglong/local-application' ||
packageManifest.version !== QL3_VERSION ||
packageManifest.bin?.['ql3-local-application'] !== 'dist/cli.js' ||
packageManifest.engines?.node !== '>=24.18.0 <25' ||
fs.realpathSync(applicationEntrypoint) !== applicationEntrypoint
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release application entrypoint contract is invalid',
);
}
const artifactSha256 = canonicalDigest({ files });
const artifactMetadataSha256 = canonicalDigest({ entries: metadata });
return Object.freeze({
artifactRoot,
applicationEntrypoint,
artifact: Object.freeze({
artifactSha256,
artifactMetadataSha256,
artifactFiles: files.length,
artifactBytes,
entrypointSha256: fileSha256(applicationEntrypoint),
packages: REQUIRED_PACKAGES,
}),
});
}
function preflightArtifactMetadata(artifactRootInput, expectedEntrypoint) {
const artifactRoot = fs.realpathSync(artifactRootInput);
const uid = process.geteuid?.();
let files = 0;
let directories = 0;
let entrypointFound = false;
const metadata = [];
const visit = (target) => {
const stat = fs.lstatSync(target, { bigint: true });
if (
stat.isSymbolicLink() ||
(stat.uid !== 0n && stat.uid !== BigInt(uid)) ||
(stat.mode & 0o022n) !== 0n
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact metadata is untrusted',
);
}
if (stat.isDirectory()) {
metadata.push(
artifactMetadataEntry(artifactRoot, target, 'directory', stat),
);
directories += 1;
if (directories > MAX_ARTIFACT_DIRECTORIES) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact directory count exceeded',
);
}
for (const entry of fs.readdirSync(target).sort()) {
visit(path.join(target, entry));
}
return;
}
if (!stat.isFile() || stat.nlink !== 1n) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact metadata contains an unsupported entry',
);
}
files += 1;
metadata.push(artifactMetadataEntry(artifactRoot, target, 'file', stat));
if (files > MAX_ARTIFACT_FILES) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact file count exceeded',
);
}
if (target === expectedEntrypoint) entrypointFound = true;
};
visit(artifactRoot);
if (!entrypointFound) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release application entrypoint is unavailable',
);
}
return Object.freeze({
files,
directories,
artifactMetadataSha256: canonicalDigest({ entries: metadata }),
});
}
function validArtifactSummary(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 &&
value.artifactFiles <= MAX_ARTIFACT_FILES &&
Number.isSafeInteger(value.artifactBytes) &&
value.artifactBytes >= 1 &&
value.artifactBytes <= MAX_ARTIFACT_BYTES &&
JSON.stringify(value.packages) === JSON.stringify(REQUIRED_PACKAGES)
);
}
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 QingLong3PhysicalApplicationStartEvidenceError(
'Node executable is not trusted',
);
}
return Object.freeze({
nodeExecutable,
nodeSha256: fileSha256(nodeExecutable),
nodeVersion: process.version,
});
}
function validateArtifactAgainstManifest(manifest, artifact, node) {
const violations = [];
if (
artifact.artifactSha256 !== manifest.expectedArtifactSha256 ||
artifact.artifactFiles !== manifest.expectedArtifactFiles ||
artifact.artifactBytes !== manifest.expectedArtifactBytes
) {
violations.push('release artifact did not match manifest');
}
if (node.nodeSha256 !== manifest.expectedNodeSha256) {
violations.push('Node executable did not match manifest');
}
return Object.freeze(violations);
}
function normalizeSession(value) {
exactKeys(value, SESSION_KEYS, 'session');
const { sha256, ...body } = value;
const expectedDeploymentRoot = path.join(
value.paths?.dataPath ?? '',
`.ql3-application-start-${value.sessionId ?? ''}`,
);
const expectedConfig = path.join(
expectedDeploymentRoot,
'local-application.json',
);
const expectedEntrypoint = path.join(
value.paths?.artifactRoot ?? '',
'node_modules',
'@qinglong',
'local-application',
'dist',
'cli.js',
);
if (
value.schemaVersion !== 1 ||
value.evidenceClass !== 'physical_edge_application_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)) ||
!validArtifactSummary(value.artifact) ||
!hasExactKeys(value.environment, ENVIRONMENT_KEYS) ||
value.environment.platform !== 'linux' ||
typeof value.environment.bootId !== 'string' ||
value.environment.bootId.length < 8 ||
!Number.isSafeInteger(value.environment.bootAgeMs) ||
value.environment.bootAgeMs < 0 ||
!path.isAbsolute(value.environment.nodeExecutable ?? '') ||
!SHA256_PATTERN.test(value.environment.nodeSha256 ?? '') ||
typeof value.environment.nodeVersion !== 'string' ||
!hasExactKeys(value.paths, PATH_KEYS) ||
!path.isAbsolute(value.paths.dataPath ?? '') ||
!path.isAbsolute(value.paths.artifactRoot ?? '') ||
value.paths.deploymentRoot !== expectedDeploymentRoot ||
value.paths.applicationConfig !== expectedConfig ||
value.paths.applicationEntrypoint !== expectedEntrypoint ||
value.sha256 !== canonicalDigest(body)
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'session is invalid or drifted',
);
}
return Object.freeze(value);
}
function readBootAgeMs() {
const raw = fs.readFileSync('/proc/uptime', 'utf8').trim().split(/\s+/)[0];
const seconds = Number(raw);
const milliseconds = Math.floor(seconds * 1000);
if (
!Number.isFinite(seconds) ||
seconds < 0 ||
!Number.isSafeInteger(milliseconds)
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'Linux boot age is invalid',
);
}
return milliseconds;
}
function bootObservation(dataPath, node, bootAgeMs) {
const observed = collectObservedPlatform(dataPath);
return Object.freeze({
platform: observed.platform,
architecture: observed.architecture,
bootId: observed.bootId,
dataFilesystem: observed.dataFilesystem,
nodeExecutable: node.nodeExecutable,
nodeSha256: node.nodeSha256,
nodeVersion: node.nodeVersion,
bootAgeMs,
});
}
function validateBootObservation(manifest, observation, dataPath) {
const violations = [];
if (observation.platform !== 'linux')
violations.push('platform is not Linux');
if (observation.architecture !== manifest.expectedArchitecture) {
violations.push('architecture did not match manifest');
}
if (observation.dataFilesystem !== manifest.expectedFilesystem) {
violations.push('filesystem did not match manifest');
}
if (
!path.isAbsolute(dataPath ?? '') ||
typeof observation.bootId !== 'string' ||
observation.bootId.length < 8 ||
!path.isAbsolute(observation.nodeExecutable ?? '') ||
!SHA256_PATTERN.test(observation.nodeSha256 ?? '') ||
typeof observation.nodeVersion !== 'string' ||
!/^v24\.\d+\.\d+$/.test(observation.nodeVersion) ||
!Number.isSafeInteger(observation.bootAgeMs) ||
observation.bootAgeMs < 0
) {
violations.push('boot observation is invalid');
}
return Object.freeze(violations);
}
function requireBuiltProducts() {
const root = path.resolve(__dirname, '..');
try {
return Object.freeze({
deployment: require(path.join(
root,
'packages/ql3-local-owner-cli/dist/deployment/localDeployment.js',
)),
rolloutSafety: require(path.join(
root,
'packages/ql3-local-sqlite/dist/readiness/rolloutSafety.js',
)),
});
} catch (error) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`QingLong 3.0 packages must be built first: ${error.message}`,
);
}
}
async function preparePhase(options, manifest) {
if (process.platform !== 'linux') {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'prepare requires Linux',
);
}
const dataPath = fs.realpathSync(options.dataPath);
assertPrivateDirectory(dataPath, 'dataPath');
assertChildPath(dataPath, options.sessionPath, 'sessionPath');
if (fs.existsSync(options.sessionPath)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'sessionPath already exists',
);
}
const identity = collectArtifactIdentity(options.artifactRoot);
const node = collectNodeIdentity();
const artifactViolations = validateArtifactAgainstManifest(
manifest,
identity.artifact,
node,
);
if (artifactViolations.length > 0) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
artifactViolations.join('; '),
);
}
const before = bootObservation(dataPath, node, readBootAgeMs());
const bootViolations = validateBootObservation(manifest, before, dataPath);
if (bootViolations.length > 0) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`device preflight rejected: ${bootViolations.join('; ')}`,
);
}
const uid = process.getuid?.();
if (!Number.isSafeInteger(uid)) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'a stable POSIX UID is required',
);
}
const sessionId = crypto.randomUUID();
const deploymentRoot = path.join(
dataPath,
`.ql3-application-start-${sessionId}`,
);
const timestamp = Date.now();
const products = requireBuiltProducts();
const prepared = await products.deployment.prepareLocalDeployment({
schemaVersion: 1,
operation: 'local.deployment.prepare',
options: {
deploymentRoot,
profile: 'edge',
instanceId: `physical-start-${sessionId.slice(0, 8)}`,
busyTimeoutMs: 100,
service: {
kind: 'openrc',
nodeExecutable: node.nodeExecutable,
applicationEntrypoint: identity.applicationEntrypoint,
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 QingLong3PhysicalApplicationStartEvidenceError(
'fresh native Edge deployment was not prepared',
);
}
const body = {
schemaVersion: 1,
evidenceClass: 'physical_edge_application_start_session',
sessionId,
manifestDigest: canonicalDigest(manifest),
uid,
preparedAt: new Date().toISOString(),
artifact: identity.artifact,
environment: {
platform: before.platform,
architecture: before.architecture,
bootId: before.bootId,
bootAgeMs: before.bootAgeMs,
dataFilesystem: before.dataFilesystem,
nodeExecutable: node.nodeExecutable,
nodeSha256: node.nodeSha256,
nodeVersion: node.nodeVersion,
},
paths: {
dataPath,
deploymentRoot,
artifactRoot: identity.artifactRoot,
applicationEntrypoint: identity.applicationEntrypoint,
applicationConfig: path.join(deploymentRoot, 'local-application.json'),
},
};
const session = Object.freeze({ ...body, sha256: canonicalDigest(body) });
writePrivateNoReplace(
options.sessionPath,
`${JSON.stringify(session, null, 2)}\n`,
);
process.stdout.write(
`${JSON.stringify({
schemaVersion: 1,
status: 'awaiting_external_reboot',
sessionId,
profile: 'edge',
applicationClosureVerified: true,
supported: false,
})}\n`,
);
}
function readProcessSample(processId, expectedExecutable) {
const root = `/proc/${processId}`;
const stat = parseProcStat(fs.readFileSync(`${root}/stat`, 'utf8'));
const status = parseProcStatus(fs.readFileSync(`${root}/status`, 'utf8'));
const io = parseProcIo(fs.readFileSync(`${root}/io`, 'utf8'));
const executable = fs.realpathSync(`${root}/exe`);
if (
executable !== expectedExecutable ||
status.uid !== process.getuid?.() ||
stat.processId !== processId
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application process identity drifted',
);
}
return Object.freeze({
startTicks: stat.startTicks,
rssBytes: status.rssBytes,
readBytes: io.readBytes,
writeBytes: io.writeBytes,
});
}
function parseEventLines(buffer, events) {
let remaining = buffer;
while (remaining.includes('\n')) {
const index = remaining.indexOf('\n');
const line = remaining.slice(0, index);
remaining = remaining.slice(index + 1);
if (!line) continue;
if (Buffer.byteLength(line, 'utf8') > 4096) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application event line exceeded',
);
}
let value;
try {
value = JSON.parse(line);
} catch {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application emitted non-JSON stdout',
);
}
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
value.schemaVersion !== 1 ||
value.component !== 'qinglong3-local-application' ||
typeof value.event !== 'string' ||
value.profile !== 'edge'
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application event shape is invalid',
);
}
events.push(Object.freeze(value));
if (events.length > MAX_EVENTS) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application event count exceeded',
);
}
}
return remaining;
}
function timeoutPromise(milliseconds, message) {
return new Promise((_, reject) => {
const timer = setTimeout(() => {
reject(new QingLong3PhysicalApplicationStartEvidenceError(message));
}, milliseconds);
timer.unref();
});
}
async function measureApplicationStart(session, manifest) {
const startNs = process.hrtime.bigint();
const child = spawn(
session.environment.nodeExecutable,
[
session.paths.applicationEntrypoint,
'--config',
session.paths.applicationConfig,
],
{
cwd: session.paths.artifactRoot,
env: { NODE_ENV: 'production' },
stdio: ['ignore', 'pipe', 'pipe'],
},
);
const events = [];
let stdout = '';
let stderr = '';
let firstActiveNs;
let resolveActive;
let rejectActive;
const activePromise = new Promise((resolve, reject) => {
resolveActive = resolve;
rejectActive = reject;
});
const exitPromise = new Promise((resolve, reject) => {
child.once('error', reject);
child.once('exit', (code, signal) => resolve({ code, signal }));
});
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
try {
stdout += chunk;
if (Buffer.byteLength(stdout, 'utf8') > MAX_OUTPUT_BYTES) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application stdout exceeded',
);
}
stdout = parseEventLines(stdout, events);
const activeEvents = events.filter(({ event }) => event === 'active');
if (activeEvents.length === 1 && firstActiveNs === undefined) {
firstActiveNs = process.hrtime.bigint();
resolveActive(activeEvents[0]);
} else if (activeEvents.length > 1) {
rejectActive(
new QingLong3PhysicalApplicationStartEvidenceError(
'application emitted multiple active events',
),
);
}
} catch (error) {
rejectActive(error);
}
});
child.stderr.on('data', (chunk) => {
stderr += chunk;
if (Buffer.byteLength(stderr, 'utf8') > MAX_OUTPUT_BYTES) {
rejectActive(
new QingLong3PhysicalApplicationStartEvidenceError(
'application stderr exceeded',
),
);
}
});
exitPromise.then(({ code, signal }) => {
if (firstActiveNs === undefined) {
rejectActive(
new QingLong3PhysicalApplicationStartEvidenceError(
`application exited before active: ${String(code)}/${String(signal)}`,
),
);
}
}, rejectActive);
let maximumSampledRssBytes = 0;
let sampleCount = 0;
let firstSample;
let lastSample;
let startTicks;
const sample = () => {
try {
const value = readProcessSample(
child.pid,
session.environment.nodeExecutable,
);
if (startTicks !== undefined && value.startTicks !== startTicks) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application process generation changed',
);
}
startTicks ??= value.startTicks;
firstSample ??= value;
lastSample = value;
maximumSampledRssBytes = Math.max(maximumSampledRssBytes, value.rssBytes);
sampleCount += 1;
} catch (error) {
if (error?.code !== 'ENOENT' && firstActiveNs === undefined) {
rejectActive(error);
}
}
};
sample();
const sampler = setInterval(sample, manifest.sampleIntervalMs);
try {
const activeEvent = await Promise.race([
activePromise,
timeoutPromise(
manifest.maximumFirstActiveMs,
'application did not become active within the manifest budget',
),
]);
sample();
if (
activeEvent.level !== 'info' ||
activeEvent.aiStatus !== 'deployment_excluded' ||
activeEvent.instanceId !==
`physical-start-${session.sessionId.slice(0, 8)}` ||
!firstSample ||
!lastSample ||
sampleCount < 1
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application active evidence is invalid',
);
}
child.kill('SIGTERM');
const outcome = await Promise.race([
exitPromise,
timeoutPromise(35_000, 'application did not stop after SIGTERM'),
]);
if (stdout !== '' || stderr !== '') {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'application left partial stdout or emitted stderr',
);
}
const activeEventCount = events.filter(
({ event }) => event === 'active',
).length;
const gracefulStop =
events.some(
({ event, signal }) =>
event === 'shutdown_requested' && signal === 'SIGTERM',
) &&
events.some(
({ event, stopResult }) =>
event === 'stopped' && stopResult === 'stopped',
);
return Object.freeze({
measurements: Object.freeze({
firstActiveMs: Math.ceil(Number(firstActiveNs - startNs) / 1_000_000),
maximumSampledRssBytes,
processReadBytes: Math.max(
0,
lastSample.readBytes - firstSample.readBytes,
),
processWriteBytes: Math.max(
0,
lastSample.writeBytes - firstSample.writeBytes,
),
sampleCount,
eventCount: events.length,
}),
outcomes: Object.freeze({
activeEventCount,
aiStatus: activeEvent.aiStatus,
gracefulStop,
exitCode: outcome.code,
exitSignal: outcome.signal,
stderrBytes: Buffer.byteLength(stderr, 'utf8'),
sqliteContractVersion: 0,
}),
});
} finally {
clearInterval(sampler);
if (child.exitCode === null && child.signalCode === null) {
child.kill('SIGKILL');
}
}
}
function evaluateApplicationStartEvidence({
manifest,
session,
observed,
measurements,
outcomes,
}) {
const violations = [];
if (
!hasExactKeys(observed, REPORT_OBSERVED_KEYS) ||
!hasExactKeys(observed.before, OBSERVED_BOOT_KEYS) ||
!hasExactKeys(observed.after, OBSERVED_BOOT_KEYS) ||
!validArtifactSummary(observed.artifact)
) {
return Object.freeze(['application start observation shape is invalid']);
}
if (
observed.before.bootId === observed.after.bootId ||
observed.after.bootAgeMs > manifest.maximumBootAgeMs
) {
violations.push('reboot boundary or boot age budget is invalid');
}
for (const candidate of [observed.before, observed.after]) {
violations.push(
...validateBootObservation(manifest, candidate, session.paths.dataPath),
);
}
if (
JSON.stringify(observed.artifact) !== JSON.stringify(session.artifact) ||
observed.artifact.artifactSha256 !== manifest.expectedArtifactSha256 ||
observed.before.nodeSha256 !== manifest.expectedNodeSha256 ||
observed.after.nodeSha256 !== manifest.expectedNodeSha256 ||
observed.before.nodeExecutable !== observed.after.nodeExecutable ||
observed.before.nodeVersion !== observed.after.nodeVersion
) {
violations.push('release artifact or Node identity drifted');
}
if (
!hasExactKeys(measurements, MEASUREMENT_KEYS) ||
MEASUREMENT_KEYS.some(
(key) =>
!Number.isSafeInteger(measurements[key]) || measurements[key] < 0,
) ||
measurements.firstActiveMs < 1 ||
measurements.firstActiveMs > manifest.maximumFirstActiveMs ||
measurements.maximumSampledRssBytes > manifest.maximumSampledRssBytes ||
measurements.sampleCount < 1 ||
measurements.eventCount < 1 ||
measurements.eventCount > MAX_EVENTS
) {
violations.push('application start measurement budget is invalid');
}
if (
!hasExactKeys(outcomes, OUTCOME_KEYS) ||
outcomes.activeEventCount !== 1 ||
outcomes.aiStatus !== 'deployment_excluded' ||
outcomes.gracefulStop !== true ||
outcomes.exitCode !== 0 ||
outcomes.exitSignal !== null ||
outcomes.stderrBytes !== 0 ||
outcomes.sqliteContractVersion !== 41
) {
violations.push('application lifecycle outcome is invalid');
}
return Object.freeze(violations);
}
function buildApplicationStartReport({
manifest,
session,
observed,
measurements,
outcomes,
generatedAt,
}) {
const violations = evaluateApplicationStartEvidence({
manifest,
session,
observed,
measurements,
outcomes,
});
const body = {
schemaVersion: 1,
evidenceClass: 'physical_edge_application_start_candidate',
supported: false,
generatedAt,
manifest,
session: {
sessionId: session.sessionId,
sessionDigest: session.sha256,
preparedAt: session.preparedAt,
},
observed,
measurements,
outcomes,
qualification: {
passed: violations.length === 0,
violations,
measures: MEASURES,
doesNotProve: EXCLUSIONS,
},
};
return Object.freeze({ ...body, sha256: canonicalDigest(body) });
}
function validateApplicationStartReport(report, manifest, currentObserved) {
const violations = [];
if (!hasExactKeys(report, REPORT_KEYS)) {
return Object.freeze(['application start report shape is invalid']);
}
const { sha256, ...body } = report;
if (!SHA256_PATTERN.test(sha256 ?? '') || canonicalDigest(body) !== sha256) {
violations.push('application start report SHA-256 is invalid');
}
if (
report.schemaVersion !== 1 ||
report.evidenceClass !== 'physical_edge_application_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 ?? '')) ||
!hasExactKeys(report.qualification, QUALIFICATION_KEYS)
) {
violations.push('application start report identity is invalid');
}
if (
report.observed?.after?.bootId !== currentObserved.bootId ||
report.observed?.after?.architecture !== currentObserved.architecture ||
report.observed?.after?.dataFilesystem !== currentObserved.dataFilesystem
) {
violations.push('application start report current device did not match');
}
const syntheticSession = {
paths: { dataPath: currentObserved.dataPath },
artifact: report.observed?.artifact,
};
const recomputed = evaluateApplicationStartEvidence({
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('application start report qualification was widened');
}
return Object.freeze(violations);
}
async function resumePhase(options, manifest) {
if (process.platform !== 'linux') {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'resume requires Linux',
);
}
const session = normalizeSession(
readBoundedPrivateJson(options.sessionPath, 'session'),
);
if (
session.manifestDigest !== canonicalDigest(manifest) ||
session.uid !== process.getuid?.()
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'session manifest or POSIX identity changed',
);
}
const dataPath = fs.realpathSync(session.paths.dataPath);
assertPrivateDirectory(dataPath, 'dataPath');
assertChildPath(dataPath, options.sessionPath, 'sessionPath');
assertChildPath(dataPath, options.outputPath, 'outputPath');
if (
fs.realpathSync(session.paths.deploymentRoot) !==
session.paths.deploymentRoot ||
fs.realpathSync(session.paths.artifactRoot) !== session.paths.artifactRoot
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'session path identity changed',
);
}
assertPrivateDirectory(session.paths.deploymentRoot, 'deploymentRoot');
const metadata = preflightArtifactMetadata(
session.paths.artifactRoot,
session.paths.applicationEntrypoint,
);
if (
metadata.files !== session.artifact.artifactFiles ||
metadata.artifactMetadataSha256 !== session.artifact.artifactMetadataSha256
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'release artifact metadata count changed',
);
}
const nodeBefore = Object.freeze({
nodeExecutable: fs.realpathSync(process.execPath),
nodeSha256: session.environment.nodeSha256,
nodeVersion: process.version,
});
if (
nodeBefore.nodeExecutable !== session.environment.nodeExecutable ||
process.version !== session.environment.nodeVersion
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
'Node runtime path or version changed',
);
}
const bootAgeMs = readBootAgeMs();
const after = bootObservation(dataPath, nodeBefore, bootAgeMs);
const preflightViolations = validateBootObservation(
manifest,
after,
dataPath,
);
if (
after.bootId === session.environment.bootId ||
bootAgeMs > manifest.maximumBootAgeMs ||
preflightViolations.length > 0
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`resume preflight rejected: ${[
...(after.bootId === session.environment.bootId
? ['boot identity did not change']
: []),
...(bootAgeMs > manifest.maximumBootAgeMs
? ['boot age exceeded manifest']
: []),
...preflightViolations,
].join('; ')}`,
);
}
const measurement = await measureApplicationStart(session, manifest);
const identityAfter = collectArtifactIdentity(session.paths.artifactRoot);
const nodeAfter = collectNodeIdentity();
const artifactViolations = validateArtifactAgainstManifest(
manifest,
identityAfter.artifact,
nodeAfter,
);
if (
artifactViolations.length > 0 ||
JSON.stringify(identityAfter.artifact) !==
JSON.stringify(session.artifact) ||
nodeAfter.nodeExecutable !== session.environment.nodeExecutable
) {
throw new QingLong3PhysicalApplicationStartEvidenceError(
`post-run release identity rejected: ${artifactViolations.join('; ')}`,
);
}
const products = requireBuiltProducts();
const sqlite = await products.rolloutSafety.inspectLocalSqliteSnapshot({
databasePath: path.join(session.paths.deploymentRoot, 'qinglong3.sqlite'),
profile: 'edge',
});
const outcomes = Object.freeze({
...measurement.outcomes,
sqliteContractVersion: sqlite.contractVersion,
});
const before = Object.freeze({
platform: session.environment.platform,
architecture: session.environment.architecture,
bootId: session.environment.bootId,
bootAgeMs: session.environment.bootAgeMs,
dataFilesystem: session.environment.dataFilesystem,
nodeExecutable: session.environment.nodeExecutable,
nodeSha256: session.environment.nodeSha256,
nodeVersion: session.environment.nodeVersion,
});
const report = buildApplicationStartReport({
manifest,
session,
observed: {
before,
after: Object.freeze({
...after,
nodeSha256: nodeAfter.nodeSha256,
}),
artifact: identityAfter.artifact,
},
measurements: measurement.measurements,
outcomes,
generatedAt: new Date().toISOString(),
});
writePrivateNoReplace(
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 identity = collectArtifactIdentity(options.artifactRoot);
const node = collectNodeIdentity();
process.stdout.write(
`${JSON.stringify(
{
schemaVersion: 1,
evidenceClass: 'physical_edge_application_start_inspection',
profile: 'edge',
artifact: identity.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 QingLong3PhysicalApplicationStartEvidenceError(
'Node.js 24 or newer is required',
);
}
const options = parseArguments(process.argv.slice(2));
if (options.phase === 'inspect') {
inspectPhase(options);
return;
}
const manifest = readManifest(options.manifestPath);
if (options.phase === 'prepare') {
await preparePhase(options, manifest);
} else {
await resumePhase(options, manifest);
}
}
module.exports = {
QingLong3PhysicalApplicationStartEvidenceError,
buildApplicationStartReport,
collectArtifactIdentity,
evaluateApplicationStartEvidence,
normalizeApplicationStartManifest,
normalizeSession,
parseArguments,
parseEventLines,
preflightArtifactMetadata,
readBootAgeMs,
validateApplicationStartReport,
validateArtifactAgainstManifest,
validateBootObservation,
};
if (require.main === module) {
main().catch((error) => {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
});
}