diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index a2aef47..394cd82 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2764,3 +2764,65 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` + +# Self-host runtime metadata observations (2026-10-01) + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-runtime-metadata-fixed-preimages + given: "A bounded canonical-layout candidate fixture with fixed metadata, fourteen Console assets, a nested case/punctuation vector, and one permitted internal .bin link" + when: "The no-argument reader captures local observations and the independent oracle hashes the exact tree preimages" + then: "Pinned raw-byte hashes, recursive per-directory localeCompare traversal, and link-text-only records match; alternate global sorting or target-content hashing does not" + - id: SCN-runtime-metadata-observations-not-authentication + given: "Unsigned package, release, and validation metadata with parseable versions, a declared source commit, and a recomputed matching tree digest" + when: "The reader reports module-evaluation observations" + then: "Useful parsed values and consistency remain visible while authentication is unknown and loaded-memory, delivered-byte, and native-authority claims remain null" + - id: SCN-runtime-metadata-startup-held-pointer-advance + given: "A reader process whose module-evaluation observations capture alpha2" + when: "The fixed active pointer, registry generation, and a Console disk asset advance while that process remains loaded" + then: "Startup observations remain unchanged, current pointer/registry/UI disk observations show the later state, and relations identify the observed differences without claiming served or loaded bytes" + - id: SCN-runtime-metadata-allowlist-and-caps + given: "The exact fourteen fixed Console paths plus an unrelated file, with regular files at and beyond independent per-file and aggregate bounds" + when: "The reader hashes the display-only Console disk allowlist" + then: "Only the fourteen named paths appear; over-cap observations are refused with no partial asset list or digest, before content allocation/read" + - id: SCN-runtime-metadata-malformed-oversize-tamper-race + given: "Malformed, missing, oversized, changed module-evaluation/current-disk observations, strict-path symlink, and invalid artifact-link entries" + when: "Bounded metadata, UI, and candidate inventory reads encounter those entries" + then: "Typed unknown/refusal outcomes contain no partial matching digest; permitted internal links hash bounded link text only and external, absolute, escaping, dangling, oversized, invalid-text, or special entries do not traverse target content" + - id: SCN-runtime-metadata-partial-journal + given: "A fixed runtime directory with an observed pointer and registry and then a partial or malformed activation journal" + when: "Runtime-state observations are read" + then: "Pointer, registry, journal, consistency, and issues remain separately visible and all runtime bytes remain unchanged" + - id: SCN-runtime-metadata-read-only-no-native-authority + given: "A canonical-layout candidate and fixed temporary data-home containing metadata and a sentinel representing protected state" + when: "The sole public reader runs" + then: "Candidate and runtime trees plus the sentinel remain unchanged, and the module imports or invokes no VersionService/status/reconcile, SQLite, ControlPlane, database, DDL, write, or native receipt/subject authority" + - id: SCN-runtime-metadata-head-not-substitute + given: "A candidate fixture with a misleading .git/HEAD file and a declared source commit in validation metadata" + when: "The reader captures the source declaration and candidate-tree consistency" + then: "The declaration is reported as observed metadata, the fake git executable is never invoked, and repository HEAD is not substituted or authenticated" + observable_outcomes: + - "The sole export is synchronous readLoadedRuntimeMetadataV1() with no caller-selected paths, roots, manifests, databases, engines, adapters, callbacks, or verifier" + - "Startup reader-module/package/release/validation/tree observations, current fixed Console disk hashes, and startup/current pointer/registry/journal are separate fields" + - "Hash preimages preserve path NUL type NUL decimal mode NUL raw bytes-or-link-text NUL and candidate-compatible recursive per-directory localeCompare order" + - "UI caps are 64 entries, 4 MiB per file, and 16 MiB total; candidate caps are 8192 entries, 64 MiB per file, and 512 MiB total, checked before content reads" + - "Ordinary parsed versions, declared commits, raw hashes, and matching consistency remain visible independently of authenticationConfidence=unknown" + - "No authentication, atomic snapshot, loaded-memory digest, delivered-browser-byte proof, receipt eligibility, native authority, writes, or runtime repair is asserted" + - "Fixed metadata/UI symlinks are refused; only inventory links use bounded link-text records and only runtime/active uses its separate fixed-path readlink" + determinism_controls: + - "Independent fixed raw-byte vectors and a separate candidate inventory oracle with canonical nested ordering" + - "Canonical-layout child-process fixtures, fixed metadata bytes/versions/commits, sparse cap fixtures, temporary directories, and no network or live runtime state" + - "A held child process exposes module-evaluation versus later disk/pointer changes without clock sleeps or retries" + anti_cheat_rationale: + prevents: + - "Repository HEAD, package labels, unsigned manifests, or a mutable active pointer being presented as authenticated runtime identity" + - "Global-sort substitutions, symlink target traversal/content hashing, or partial digests being presented as candidate consistency" + - "Unbounded reads, truncation-to-match, and console paths outside the fixed display allowlist" + - "Calling a mutating VersionService/status reconciliation path from a dashboard reader" + - "Promoting local observations into loaded-code, delivered-browser-byte, registered-receipt, or native-authority proof" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/src/self-host/runtime-metadata.mjs b/src/self-host/runtime-metadata.mjs new file mode 100644 index 0000000..5e07837 --- /dev/null +++ b/src/self-host/runtime-metadata.mjs @@ -0,0 +1,694 @@ +import { createHash } from 'node:crypto'; +import { + constants as fsConstants, fstatSync, lstatSync, openSync, opendirSync, readSync, + readlinkSync, closeSync, +} from 'node:fs'; +import { homedir } from 'node:os'; +import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const SCHEMA = 'torch.dev/runtime-metadata/v1alpha1'; +const MAX_JSON_BYTES = 64 * 1024; +const MAX_LINK_BYTES = 1024; +const MAX_TREE_ENTRIES = 8192; +const MAX_TREE_FILE_BYTES = 64 * 1024 * 1024; +const MAX_TREE_TOTAL_BYTES = 512 * 1024 * 1024; +const MAX_CONSOLE_FILES = 64; +const MAX_CONSOLE_FILE_BYTES = 4 * 1024 * 1024; +const MAX_CONSOLE_TOTAL_BYTES = 16 * 1024 * 1024; +const MAX_ISSUES = 32; +const READ_CHUNK_BYTES = 64 * 1024; + +const CONSOLE_ASSETS = Object.freeze([ + 'console.html', + 'styles.css', + 'torch-mark.svg', + 'work-views.js', + 'work-progress.js', + 'owner-digest.js', + 'check-evidence.js', + 'task-activity.js', + 'operation-outcomes.js', + 'attention-projection.js', + 'attention-actions.js', + 'live-refresh.js', + 'demo.js', + 'console.js', +]); + +const MODULE_PATH = fileURLToPath(import.meta.url); +const MODULE_DIRECTORY = dirname(MODULE_PATH); +const PACKAGE_ROOT = resolve(MODULE_DIRECTORY, '..', '..'); +const DATA_HOME = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'); +const TORCH_DATA_HOME = join(DATA_HOME, 'torch'); +const RUNTIME_DIRECTORY = join(TORCH_DATA_HOME, 'runtime'); +const VERSIONS_DIRECTORY = join(RUNTIME_DIRECTORY, 'versions'); +const NOFOLLOW = fsConstants.O_NOFOLLOW; +const NONBLOCK = fsConstants.O_NONBLOCK ?? 0; + +class MetadataIssue extends Error { + constructor(code, path = null) { + super(code); + this.code = code; + this.path = path; + } +} + +function issue(code, path = null) { + return { code, path }; +} + +function addIssue(issues, error, fallback = 'RUNTIME_METADATA_READ_FAILED') { + if (issues.length >= MAX_ISSUES) return; + const code = typeof error?.code === 'string' && error.code.startsWith('RUNTIME_METADATA_') + ? error.code + : fallback; + issues.push(issue(code, error?.path ?? null)); +} + +function isMissing(error) { + return error?.code === 'ENOENT' || error?.code === 'ENOTDIR'; +} + +function stableStat(left, right) { + return left.dev === right.dev + && left.ino === right.ino + && left.mode === right.mode + && left.size === right.size + && left.mtimeNs === right.mtimeNs + && left.ctimeNs === right.ctimeNs; +} + +function statNoFollow(path, displayPath) { + try { + return lstatSync(path, { bigint: true }); + } catch (error) { + if (isMissing(error)) throw new MetadataIssue('RUNTIME_METADATA_FILE_MISSING', displayPath); + throw new MetadataIssue('RUNTIME_METADATA_STAT_FAILED', displayPath); + } +} + +function assertNoFollowAvailable(displayPath) { + if (typeof NOFOLLOW !== 'number' || NOFOLLOW === 0) { + throw new MetadataIssue('RUNTIME_METADATA_NOFOLLOW_UNAVAILABLE', displayPath); + } +} + +function assertRegular(stat, displayPath) { + if (stat.isSymbolicLink()) throw new MetadataIssue('RUNTIME_METADATA_LINK_REFUSED', displayPath); + if (!stat.isFile()) throw new MetadataIssue('RUNTIME_METADATA_SPECIAL_ENTRY_REFUSED', displayPath); +} + +function compareOpenedFile(path, displayPath, initialPathStat, initialFdStat, finalFdStat) { + const finalPathStat = statNoFollow(path, displayPath); + if (!stableStat(initialPathStat, initialFdStat) + || !stableStat(initialFdStat, finalFdStat) + || !stableStat(finalFdStat, finalPathStat)) { + throw new MetadataIssue('RUNTIME_METADATA_FILE_CHANGED_DURING_READ', displayPath); + } +} + +function openRegular(path, displayPath, maxBytes) { + assertNoFollowAvailable(displayPath); + const pathStat = statNoFollow(path, displayPath); + assertRegular(pathStat, displayPath); + if (pathStat.size > BigInt(maxBytes)) { + throw new MetadataIssue('RUNTIME_METADATA_FILE_CAP_EXCEEDED', displayPath); + } + let fd; + try { + fd = openSync(path, fsConstants.O_RDONLY | NOFOLLOW | NONBLOCK); + } catch (error) { + throw new MetadataIssue(error?.code === 'ELOOP' + ? 'RUNTIME_METADATA_LINK_REFUSED' + : 'RUNTIME_METADATA_OPEN_FAILED', displayPath); + } + try { + const fdStat = fstatSync(fd, { bigint: true }); + assertRegular(fdStat, displayPath); + if (!stableStat(pathStat, fdStat)) { + throw new MetadataIssue('RUNTIME_METADATA_FILE_CHANGED_DURING_READ', displayPath); + } + if (fdStat.size > BigInt(maxBytes)) { + throw new MetadataIssue('RUNTIME_METADATA_FILE_CAP_EXCEEDED', displayPath); + } + return { fd, pathStat, fdStat }; + } catch (error) { + closeSync(fd); + throw error; + } +} + +function readBoundedRegular(path, displayPath, maxBytes = MAX_JSON_BYTES) { + const opened = openRegular(path, displayPath, maxBytes); + const chunks = []; + let total = 0; + try { + for (;;) { + const remaining = maxBytes + 1 - total; + if (remaining <= 0) throw new MetadataIssue('RUNTIME_METADATA_FILE_CAP_EXCEEDED', displayPath); + const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining)); + const length = readSync(opened.fd, chunk, 0, chunk.length, total); + if (length === 0) break; + total += length; + if (total > maxBytes) throw new MetadataIssue('RUNTIME_METADATA_FILE_CAP_EXCEEDED', displayPath); + chunks.push(chunk.subarray(0, length)); + } + const finalFdStat = fstatSync(opened.fd, { bigint: true }); + compareOpenedFile(path, displayPath, opened.pathStat, opened.fdStat, finalFdStat); + if (total !== Number(finalFdStat.size)) { + throw new MetadataIssue('RUNTIME_METADATA_FILE_CHANGED_DURING_READ', displayPath); + } + return Buffer.concat(chunks, total); + } finally { + closeSync(opened.fd); + } +} + +function parseJsonFile(path, displayPath) { + const bytes = readBoundedRegular(path, displayPath); + let parsed; + try { + parsed = JSON.parse(bytes.toString('utf8')); + } catch { + throw new MetadataIssue('RUNTIME_METADATA_JSON_MALFORMED', displayPath); + } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new MetadataIssue('RUNTIME_METADATA_JSON_MALFORMED', displayPath); + } + return parsed; +} + +function readOptionalJson(path, displayPath, issues) { + try { + return { status: 'observed', value: parseJsonFile(path, displayPath) }; + } catch (error) { + addIssue(issues, error); + return { status: error?.code === 'RUNTIME_METADATA_FILE_MISSING' ? 'missing' : 'unknown', value: null }; + } +} + +function validVersion(value) { + return typeof value === 'string' + && /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value); +} + +function versionValue(value) { + return validVersion(value) ? value : null; +} + +function digestValue(value) { + return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value) ? value.toLowerCase() : null; +} + +function commitValue(value) { + return typeof value === 'string' && /^[a-f0-9]{40}$/i.test(value) ? value.toLowerCase() : null; +} + +function sha256(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +function inside(root, candidate) { + const rootPath = resolve(root); + const candidatePath = resolve(candidate); + return candidatePath !== rootPath && candidatePath.startsWith(`${rootPath}${sep}`); +} + +function utf8RoundTrips(bytes) { + return Buffer.from(bytes.toString('utf8'), 'utf8').equals(bytes); +} + +function readBoundedLink(path, displayPath, cap = MAX_LINK_BYTES) { + let linkStat; + try { + linkStat = lstatSync(path, { bigint: true }); + } catch { + throw new MetadataIssue('RUNTIME_METADATA_LINK_READ_FAILED', displayPath); + } + if (!linkStat.isSymbolicLink()) throw new MetadataIssue('RUNTIME_METADATA_LINK_REFUSED', displayPath); + if (linkStat.size > BigInt(cap)) { + throw new MetadataIssue('RUNTIME_METADATA_LINK_CAP_EXCEEDED', displayPath); + } + let bytes; + try { + bytes = readlinkSync(path, { encoding: 'buffer' }); + } catch { + throw new MetadataIssue('RUNTIME_METADATA_LINK_READ_FAILED', displayPath); + } + if (!Buffer.isBuffer(bytes) || bytes.length > cap) { + throw new MetadataIssue('RUNTIME_METADATA_LINK_CAP_EXCEEDED', displayPath); + } + if (bytes.length !== Number(linkStat.size)) { + throw new MetadataIssue('RUNTIME_METADATA_LINK_CHANGED_DURING_READ', displayPath); + } + if (!utf8RoundTrips(bytes)) throw new MetadataIssue('RUNTIME_METADATA_LINK_UTF8_INVALID', displayPath); + return { bytes, text: bytes.toString('utf8') }; +} + +function assertPermittedLinkTarget(root, linkPath, linkText, displayPath) { + if (isAbsolute(linkText) || /^[A-Za-z]:[\\/]/.test(linkText) || linkText.startsWith('\\\\')) { + throw new MetadataIssue('RUNTIME_METADATA_LINK_ABSOLUTE_REFUSED', displayPath); + } + const target = resolve(dirname(linkPath), linkText); + if (!inside(root, target)) throw new MetadataIssue('RUNTIME_METADATA_LINK_ESCAPE_REFUSED', displayPath); + const rel = relative(root, target).split(sep).join('/'); + if (rel.split('/').includes('.git') || rel === '.torch-validation.json') { + throw new MetadataIssue('RUNTIME_METADATA_LINK_EXCLUDED_TARGET_REFUSED', displayPath); + } + const components = rel.split('/'); + let current = root; + for (let index = 0; index < components.length; index += 1) { + current = join(current, components[index]); + let stat; + try { + stat = lstatSync(current, { bigint: true }); + } catch (error) { + if (isMissing(error)) throw new MetadataIssue('RUNTIME_METADATA_LINK_DANGLING_REFUSED', displayPath); + throw new MetadataIssue('RUNTIME_METADATA_LINK_TARGET_UNKNOWN', displayPath); + } + if (index < components.length - 1 && (!stat.isDirectory() || stat.isSymbolicLink())) { + throw new MetadataIssue('RUNTIME_METADATA_LINK_TARGET_TRAVERSAL_REFUSED', displayPath); + } + if (index === components.length - 1 + && !(stat.isFile() || stat.isDirectory() || stat.isSymbolicLink())) { + throw new MetadataIssue('RUNTIME_METADATA_LINK_SPECIAL_TARGET_REFUSED', displayPath); + } + } +} + +function statRecord(path, relativePath, root) { + const stat = statNoFollow(path, relativePath); + if (stat.isDirectory()) return { path, relativePath, type: 'directory', stat }; + if (stat.isFile()) { + if (stat.size > BigInt(MAX_TREE_FILE_BYTES)) { + throw new MetadataIssue('RUNTIME_METADATA_TREE_FILE_CAP_EXCEEDED', relativePath); + } + return { path, relativePath, type: 'file', stat, bytes: Number(stat.size) }; + } + if (stat.isSymbolicLink()) { + const link = readBoundedLink(path, relativePath); + assertPermittedLinkTarget(root, path, link.text, relativePath); + return { path, relativePath, type: 'symlink', stat, linkBytes: link.bytes, bytes: link.bytes.length }; + } + throw new MetadataIssue('RUNTIME_METADATA_SPECIAL_ENTRY_REFUSED', relativePath); +} + +function directoryEntries(path, relativePath) { + const before = statNoFollow(path, relativePath); + if (!before.isDirectory() || before.isSymbolicLink()) { + throw new MetadataIssue('RUNTIME_METADATA_DIRECTORY_REFUSED', relativePath); + } + let directory; + try { + directory = opendirSync(path); + } catch { + throw new MetadataIssue('RUNTIME_METADATA_DIRECTORY_READ_FAILED', relativePath); + } + const entries = []; + try { + for (;;) { + const entry = directory.readSync(); + if (entry === null) break; + if (entries.length >= MAX_TREE_ENTRIES) { + throw new MetadataIssue('RUNTIME_METADATA_TREE_ENTRY_CAP_EXCEEDED', relativePath || '.'); + } + entries.push(entry); + } + } finally { + directory.closeSync(); + } + const after = statNoFollow(path, relativePath); + if (!stableStat(before, after)) throw new MetadataIssue('RUNTIME_METADATA_DIRECTORY_CHANGED_DURING_READ', relativePath); + entries.sort((left, right) => left.name.localeCompare(right.name)); + return entries; +} + +function inventoryTree(root, { excludeRootValidation = false } = {}) { + const rootStat = statNoFollow(root, '.'); + if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) { + throw new MetadataIssue('RUNTIME_METADATA_PACKAGE_ROOT_REFUSED', '.'); + } + const records = []; + let entryCount = 0; + const visit = (directory, relativeDirectory) => { + const entries = directoryEntries(directory, relativeDirectory); + for (const entry of entries) { + entryCount += 1; + if (entryCount > MAX_TREE_ENTRIES) { + throw new MetadataIssue('RUNTIME_METADATA_TREE_ENTRY_CAP_EXCEEDED', relativeDirectory || '.'); + } + if (entry.name === '.git') continue; + const path = join(directory, entry.name); + const relativePath = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name; + if (excludeRootValidation && relativePath === '.torch-validation.json') continue; + const record = statRecord(path, relativePath, root); + if (record.type === 'directory') visit(path, relativePath); + else records.push(record); + } + }; + visit(root, ''); + let totalBytes = 0; + for (const record of records) { + if (record.type === 'file' || record.type === 'symlink') { + if (record.bytes > MAX_TREE_FILE_BYTES) { + throw new MetadataIssue('RUNTIME_METADATA_TREE_FILE_CAP_EXCEEDED', record.relativePath); + } + if (totalBytes + record.bytes > MAX_TREE_TOTAL_BYTES) { + throw new MetadataIssue('RUNTIME_METADATA_TREE_TOTAL_CAP_EXCEEDED', record.relativePath); + } + totalBytes += record.bytes; + } + } + return { records, totalBytes, entries: entryCount }; +} + +function updateRecordPrefix(digest, record) { + digest.update(record.relativePath, 'utf8').update('\0') + .update(record.type, 'utf8').update('\0') + .update(String(Number(record.stat.mode) & 0o777), 'utf8').update('\0'); +} + +function hashRegularRecord(root, record, digest) { + const opened = openRegular(record.path, record.relativePath, MAX_TREE_FILE_BYTES); + if (!stableStat(record.stat, opened.pathStat) || !stableStat(opened.pathStat, opened.fdStat)) { + closeSync(opened.fd); + throw new MetadataIssue('RUNTIME_METADATA_FILE_CHANGED_DURING_READ', record.relativePath); + } + let total = 0; + try { + for (;;) { + const remaining = MAX_TREE_FILE_BYTES + 1 - total; + if (remaining <= 0) throw new MetadataIssue('RUNTIME_METADATA_TREE_FILE_CAP_EXCEEDED', record.relativePath); + const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining)); + const length = readSync(opened.fd, chunk, 0, chunk.length, total); + if (length === 0) break; + total += length; + if (total > MAX_TREE_FILE_BYTES) { + throw new MetadataIssue('RUNTIME_METADATA_TREE_FILE_CAP_EXCEEDED', record.relativePath); + } + digest.update(chunk.subarray(0, length)); + } + const finalFdStat = fstatSync(opened.fd, { bigint: true }); + compareOpenedFile(record.path, record.relativePath, opened.pathStat, opened.fdStat, finalFdStat); + if (total !== Number(finalFdStat.size) || total !== record.bytes) { + throw new MetadataIssue('RUNTIME_METADATA_FILE_CHANGED_DURING_READ', record.relativePath); + } + } finally { + closeSync(opened.fd); + } +} + +function candidateTreeObservation() { + const inventory = inventoryTree(PACKAGE_ROOT, { excludeRootValidation: true }); + const digest = createHash('sha256'); + for (const record of inventory.records) { + updateRecordPrefix(digest, record); + if (record.type === 'symlink') digest.update(record.linkBytes); + else hashRegularRecord(PACKAGE_ROOT, record, digest); + digest.update('\0'); + } + return { + status: 'observed', + sha256: digest.digest('hex'), + entries: inventory.entries, + files: inventory.records.length, + bytes: inventory.totalBytes, + atomicSnapshot: false, + }; +} + +function candidateTreeOrUnknown(issues) { + try { + return candidateTreeObservation(); + } catch (error) { + addIssue(issues, error); + return { + status: error?.code === 'RUNTIME_METADATA_TREE_ENTRY_CAP_EXCEEDED' + || error?.code === 'RUNTIME_METADATA_TREE_FILE_CAP_EXCEEDED' + || error?.code === 'RUNTIME_METADATA_TREE_TOTAL_CAP_EXCEEDED' + ? 'refused' + : 'unknown', + sha256: null, + entries: null, + files: null, + bytes: null, + atomicSnapshot: false, + }; + } +} + +function observedValue(status, value) { + return { status, value }; +} + +function evaluateReaderModule() { + const issues = []; + const packageJson = readOptionalJson(join(PACKAGE_ROOT, 'package.json'), 'package.json', issues); + const releaseJson = readOptionalJson(join(PACKAGE_ROOT, 'torch-release.json'), 'torch-release.json', issues); + const validationJson = readOptionalJson(join(PACKAGE_ROOT, '.torch-validation.json'), '.torch-validation.json', issues); + let moduleFileSha256 = null; + let moduleFileBytes = null; + try { + const bytes = readBoundedRegular(MODULE_PATH, 'src/self-host/runtime-metadata.mjs', 8 * 1024 * 1024); + moduleFileSha256 = sha256(bytes); + moduleFileBytes = bytes.length; + } catch (error) { + addIssue(issues, error); + } + const tree = candidateTreeOrUnknown(issues); + const packageVersion = versionValue(packageJson.value?.version); + const releaseVersion = versionValue(releaseJson.value?.version); + const validation = validationJson.value; + const declaredDigest = digestValue(validation?.digest); + const declaredSourceCommit = commitValue(validation?.source?.commit); + const consistency = declaredDigest && tree.sha256 + ? (declaredDigest === tree.sha256 ? 'match' : 'mismatch') + : 'unknown'; + if (packageJson.status === 'observed' && packageVersion === null) issues.push(issue('RUNTIME_METADATA_PACKAGE_VERSION_INVALID', 'package.json')); + if (releaseJson.status === 'observed' && releaseVersion === null) issues.push(issue('RUNTIME_METADATA_RELEASE_VERSION_INVALID', 'torch-release.json')); + if (validationJson.status === 'observed' && validation?.digest != null && declaredDigest === null) { + issues.push(issue('RUNTIME_METADATA_DECLARED_DIGEST_INVALID', '.torch-validation.json')); + } + if (validationJson.status === 'observed' && validation?.source?.commit != null && declaredSourceCommit === null) { + issues.push(issue('RUNTIME_METADATA_DECLARED_COMMIT_INVALID', '.torch-validation.json')); + } + return { + status: issues.length === 0 ? 'observed' : 'partial', + moduleUrl: import.meta.url, + modulePath: MODULE_PATH, + moduleEvaluationAt: new Date().toISOString(), + packageRoot: PACKAGE_ROOT, + packageVersion: observedValue(packageJson.status, packageVersion), + releaseVersion: observedValue(releaseJson.status, releaseVersion), + declaredSourceCommit: observedValue(validationJson.status, declaredSourceCommit), + validation: { + status: validationJson.status, + schema: typeof validation?.schema === 'string' ? validation.schema : null, + declaredVersion: versionValue(validation?.version), + declaredDigest, + validatedAt: typeof validation?.validatedAt === 'string' ? validation.validatedAt : null, + declaredPassed: typeof validation?.passed === 'boolean' ? validation.passed : null, + }, + moduleFile: { sha256: moduleFileSha256, bytes: moduleFileBytes, observation: 'disk-at-module-evaluation' }, + candidateTree: { ...tree, declaredDigest, consistency }, + authenticationConfidence: 'unknown', + loadedMemoryCodeDigest: null, + issues: issues.slice(0, MAX_ISSUES), + }; +} + +function readActivePointer(issues) { + const path = join(RUNTIME_DIRECTORY, 'active'); + let stat; + try { + stat = lstatSync(path, { bigint: true }); + } catch (error) { + if (isMissing(error)) return { status: 'missing', target: null, version: null }; + issues.push(issue('RUNTIME_METADATA_ACTIVE_POINTER_STAT_FAILED', 'runtime/active')); + return { status: 'unknown', target: null, version: null }; + } + if (!stat.isSymbolicLink()) { + issues.push(issue('RUNTIME_METADATA_ACTIVE_POINTER_NOT_LINK', 'runtime/active')); + return { status: 'refused', target: null, version: null }; + } + let link; + try { + link = readBoundedLink(path, 'runtime/active', 256); + } catch (error) { + addIssue(issues, error, 'RUNTIME_METADATA_ACTIVE_POINTER_READ_FAILED'); + return { status: 'unknown', target: null, version: null }; + } + const match = /^versions\/([^/]+)$/.exec(link.text); + const version = match ? versionValue(match[1]) : null; + if (!version) { + issues.push(issue('RUNTIME_METADATA_ACTIVE_POINTER_TARGET_INVALID', 'runtime/active')); + return { status: 'refused', target: link.text, version: null }; + } + return { status: 'observed', target: link.text, version }; +} + +function readRuntimeState() { + const issues = []; + const pointer = readActivePointer(issues); + const registry = readOptionalJson(join(RUNTIME_DIRECTORY, 'registry.json'), 'runtime/registry.json', issues); + const journal = readOptionalJson(join(RUNTIME_DIRECTORY, 'activation-journal.json'), 'runtime/activation-journal.json', issues); + let registryValue = null; + if (registry.status === 'observed') { + const raw = registry.value; + if (raw.schema !== 'torch.dev/version-registry/v1alpha1' + || !(raw.activeVersion === null || validVersion(raw.activeVersion)) + || !(raw.previousVersion === null || validVersion(raw.previousVersion)) + || !Number.isSafeInteger(raw.generation) || raw.generation < 0) { + issues.push(issue('RUNTIME_METADATA_REGISTRY_MALFORMED', 'runtime/registry.json')); + } else { + registryValue = { + schema: raw.schema, + activeVersion: raw.activeVersion, + previousVersion: raw.previousVersion, + generation: raw.generation, + updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt : null, + }; + } + } + let journalValue = null; + if (journal.status === 'observed') { + const raw = journal.value; + if (raw.schema !== 'torch.dev/activation-journal/v1alpha1' + || !(raw.from === null || validVersion(raw.from)) + || !validVersion(raw.to) + || typeof raw.startedAt !== 'string') { + issues.push(issue('RUNTIME_METADATA_JOURNAL_MALFORMED', 'runtime/activation-journal.json')); + } else { + journalValue = { schema: raw.schema, from: raw.from, to: raw.to, startedAt: raw.startedAt }; + } + } + let consistency = 'unknown'; + if (pointer.status === 'observed' && registryValue) { + consistency = pointer.version === registryValue.activeVersion ? 'match' : 'mismatch'; + } else if (pointer.status === 'missing' && registryValue?.activeVersion === null) { + consistency = 'match'; + } + if (journal.status === 'observed' && journalValue) { + if (pointer.status === 'observed' && registryValue + && ![journalValue.from, journalValue.to].includes(pointer.version)) consistency = 'mismatch'; + else if (consistency === 'match') consistency = 'partial'; + } + return { + status: issues.length === 0 ? 'observed' : 'partial', + activePointer: pointer, + registry: { status: registry.status, value: registryValue }, + journal: { status: journal.status, value: journalValue }, + consistency, + issues: issues.slice(0, MAX_ISSUES), + }; +} + +function readConsoleDisk() { + const issues = []; + const assets = []; + if (CONSOLE_ASSETS.length > MAX_CONSOLE_FILES) { + return { status: 'refused', assets: [], aggregateSha256: null, bytes: null, issues: [issue('RUNTIME_METADATA_CONSOLE_FILE_CAP_EXCEEDED')] }; + } + const observations = []; + let totalBytes = 0; + try { + for (const relativePath of CONSOLE_ASSETS) { + const path = join(PACKAGE_ROOT, 'site', relativePath); + const displayPath = `site/${relativePath}`; + const stat = statNoFollow(path, displayPath); + assertRegular(stat, displayPath); + if (stat.size > BigInt(MAX_CONSOLE_FILE_BYTES)) { + throw new MetadataIssue('RUNTIME_METADATA_CONSOLE_FILE_CAP_EXCEEDED', displayPath); + } + const bytes = Number(stat.size); + if (totalBytes + bytes > MAX_CONSOLE_TOTAL_BYTES) { + throw new MetadataIssue('RUNTIME_METADATA_CONSOLE_TOTAL_CAP_EXCEEDED', displayPath); + } + totalBytes += bytes; + observations.push({ path, relativePath: displayPath, bytes }); + } + const hashed = []; + for (const item of observations) { + const bytes = readBoundedRegular(item.path, item.relativePath, MAX_CONSOLE_FILE_BYTES); + if (bytes.length !== item.bytes) { + throw new MetadataIssue('RUNTIME_METADATA_FILE_CHANGED_DURING_READ', item.relativePath); + } + hashed.push({ path: item.relativePath, bytes: bytes.length, sha256: sha256(bytes) }); + } + const aggregate = createHash('sha256').update('torch.dev/console-assets/v1alpha1\0', 'utf8'); + for (const asset of hashed) { + aggregate.update(asset.path, 'utf8').update('\0') + .update(String(asset.bytes), 'utf8').update('\0') + .update(asset.sha256, 'utf8').update('\0'); + } + assets.push(...hashed); + return { + status: 'observed', + files: assets.length, + bytes: totalBytes, + aggregateSha256: aggregate.digest('hex'), + assets, + atomicSnapshot: false, + deliveredBytesProof: null, + issues, + }; + } catch (error) { + addIssue(issues, error); + return { + status: 'refused', + files: null, + bytes: null, + aggregateSha256: null, + assets: [], + atomicSnapshot: false, + deliveredBytesProof: null, + issues, + }; + } +} + +function relation(left, right) { + if (left == null || right == null) return 'unknown'; + return left === right ? 'match' : 'mismatch'; +} + +const READER_MODULE_EVALUATION = evaluateReaderModule(); +const CONSOLE_DISK_AT_EVALUATION = readConsoleDisk(); +const RUNTIME_AT_EVALUATION = readRuntimeState(); + +export function readLoadedRuntimeMetadataV1() { + const currentConsoleDisk = readConsoleDisk(); + const currentRuntime = readRuntimeState(); + return { + schema: SCHEMA, + observedAt: new Date().toISOString(), + readerModuleEvaluation: structuredClone(READER_MODULE_EVALUATION), + currentConsoleDisk, + activeRuntimeAtModuleEvaluation: structuredClone(RUNTIME_AT_EVALUATION), + currentActiveRuntime: currentRuntime, + relations: { + localPackageReleaseVersion: relation( + READER_MODULE_EVALUATION.packageVersion.value, + READER_MODULE_EVALUATION.releaseVersion.value, + ), + candidateTreeConsistency: READER_MODULE_EVALUATION.candidateTree.consistency, + consoleDiskSinceModuleEvaluation: relation( + CONSOLE_DISK_AT_EVALUATION.aggregateSha256, + currentConsoleDisk.aggregateSha256, + ), + activePointerSinceModuleEvaluation: relation( + RUNTIME_AT_EVALUATION.activePointer.version, + currentRuntime.activePointer.version, + ), + registryGenerationSinceModuleEvaluation: relation( + RUNTIME_AT_EVALUATION.registry.value?.generation, + currentRuntime.registry.value?.generation, + ), + }, + authenticationConfidence: 'unknown', + loadedMemoryCodeDigest: null, + deliveredBytesProof: null, + nativeAuthority: null, + }; +} diff --git a/test/fleet/runtime-metadata.test.mjs b/test/fleet/runtime-metadata.test.mjs new file mode 100644 index 0000000..70b22a3 --- /dev/null +++ b/test/fleet/runtime-metadata.test.mjs @@ -0,0 +1,631 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { + chmodSync, closeSync, copyFileSync, existsSync, ftruncateSync, lstatSync, mkdirSync, mkdtempSync, openSync, + readFileSync, readlinkSync, readdirSync, rmSync, symlinkSync, writeFileSync, +} from 'node:fs'; +import { createServer as createNetServer } from 'node:net'; +import { tmpdir } from 'node:os'; +import { basename, dirname, join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import test from 'node:test'; + +const MODULE_SOURCE = fileURLToPath(new URL('../../src/self-host/runtime-metadata.mjs', import.meta.url)); +const CONSOLE_ASSETS = [ + 'console.html', 'styles.css', 'torch-mark.svg', 'work-views.js', 'work-progress.js', + 'owner-digest.js', 'check-evidence.js', 'task-activity.js', 'operation-outcomes.js', + 'attention-projection.js', 'attention-actions.js', 'live-refresh.js', 'demo.js', 'console.js', +]; +const PACKAGE_BYTES = Buffer.from('{"name":"torch-metadata-fixture","version":"0.1.0-alpha.2","type":"module"}\n'); +const RELEASE_BYTES = Buffer.from('{"schema":"torch.dev/release/v1alpha1","version":"0.1.0-alpha.2","state":{"reads":["torch.dev/state/v1alpha1"],"writes":"torch.dev/state/v1alpha1","rollback_safe":true}}\n'); +const PACKAGE_ALPHA3_BYTES = Buffer.from('{"name":"torch-metadata-fixture","version":"0.1.0-alpha.3","type":"module"}\n'); +const RELEASE_ALPHA3_BYTES = Buffer.from('{"schema":"torch.dev/release/v1alpha1","version":"0.1.0-alpha.3","state":{"reads":["torch.dev/state/v1alpha1"],"writes":"torch.dev/state/v1alpha1","rollback_safe":true}}\n'); +const PACKAGE_ALPHA4_BYTES = Buffer.from('{"name":"torch-metadata-fixture","version":"0.1.0-alpha.4","type":"module"}\n'); +const RELEASE_ALPHA4_BYTES = Buffer.from('{"schema":"torch.dev/release/v1alpha1","version":"0.1.0-alpha.4","state":{"reads":["torch.dev/state/v1alpha1"],"writes":"torch.dev/state/v1alpha1","rollback_safe":true}}\n'); +const CONSOLE_HTML_BYTES = Buffer.from('
\n'); +const STYLES_BYTES = Buffer.from(':root{color-scheme:dark}\n'); +const OLD_CONSOLE_JS_BYTES = Buffer.from('window.consoleCapabilities = {};\n'); +const LATER_CONSOLE_JS_BYTES = Buffer.from('window.consoleCapabilities = { blockedReason: true };\n'); +const ALPHA2_COMMIT = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; +const ALPHA3_COMMIT = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; +const ALPHA4_COMMIT = 'cccccccccccccccccccccccccccccccccccccccc'; + +function hash(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +function ensureParent(path) { + mkdirSync(dirname(path), { recursive: true }); +} + +function write(root, relativePath, bytes, mode = 0o644) { + const path = join(root, relativePath); + ensureParent(path); + writeFileSync(path, bytes); + chmodSync(path, mode); + return path; +} + +function makeFixture({ includeInternalLink = true } = {}) { + const temporaryRoot = mkdtemp(); + const packageRoot = join(temporaryRoot, 'package'); + const dataHome = join(temporaryRoot, 'xdg-data'); + const home = join(temporaryRoot, 'home'); + mkdirSync(join(packageRoot, 'src', 'self-host'), { recursive: true }); + mkdirSync(join(packageRoot, 'site'), { recursive: true }); + mkdirSync(home, { recursive: true }); + writeFileSync(join(packageRoot, 'package.json'), PACKAGE_BYTES); + writeFileSync(join(packageRoot, 'torch-release.json'), RELEASE_BYTES); + copyFileSync(MODULE_SOURCE, join(packageRoot, 'src', 'self-host', 'runtime-metadata.mjs')); + chmodSync(join(packageRoot, 'src', 'self-host', 'runtime-metadata.mjs'), 0o644); + for (const name of CONSOLE_ASSETS) { + const relativePath = `site/${name}`; + const bytes = name === 'console.html' + ? CONSOLE_HTML_BYTES + : name === 'styles.css' + ? STYLES_BYTES + : name === 'console.js' + ? OLD_CONSOLE_JS_BYTES + : Buffer.from(`fixture:${relativePath}\n`, 'utf8'); + writeFileSync(join(packageRoot, relativePath), bytes); + } + const targetBytes = Buffer.from('TARGET_CONTENT_MUST_ONLY_APPEAR_AT_ITS_OWN_TREE_PATH\n'); + write(packageRoot, 'tools/probe.mjs', targetBytes); + if (includeInternalLink) { + ensureParent(join(packageRoot, 'node_modules', '.bin', 'probe')); + symlinkSync('../../tools/probe.mjs', join(packageRoot, 'node_modules', '.bin', 'probe')); + } + mkdirSync(join(dataHome, 'torch', 'runtime', 'versions', '0.1.0-alpha.2'), { recursive: true }); + symlinkSync('versions/0.1.0-alpha.2', join(dataHome, 'torch', 'runtime', 'active')); + writeFileSync(join(dataHome, 'torch', 'runtime', 'registry.json'), JSON.stringify({ + schema: 'torch.dev/version-registry/v1alpha1', activeVersion: '0.1.0-alpha.2', + previousVersion: null, generation: 1, updatedAt: '2026-10-01T00:00:00.000Z', + }) + '\n'); + const fixture = { temporaryRoot, packageRoot, dataHome, home, targetBytes }; + writeValidation(fixture, ALPHA2_COMMIT); + return fixture; +} + +function mkdtemp() { + return mkdtempSync(join(tmpdir(), 'torch-runtime-metadata-')); +} + +function readlinkBytes(path) { + return readlinkSync(path, { encoding: 'buffer' }); +} + +function independentInventory(root) { + const records = []; + const visit = (directory, relativeDirectory) => { + const entries = readdirSync(directory, { withFileTypes: true }) + .sort((left, right) => left.name.localeCompare(right.name)); + for (const entry of entries) { + if (entry.name === '.git') continue; + const path = join(directory, entry.name); + const relativePath = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name; + if (relativePath === '.torch-validation.json') continue; + const stat = lstatSync(path); + if (stat.isDirectory()) visit(path, relativePath); + else if (stat.isFile()) records.push({ relativePath, type: 'file', mode: stat.mode & 0o777, bytes: readFileSync(path) }); + else if (stat.isSymbolicLink()) records.push({ + relativePath, type: 'symlink', mode: stat.mode & 0o777, bytes: readlinkBytes(path), + }); + else throw new Error(`Unexpected fixture special file at ${relativePath}`); + } + }; + visit(root, ''); + return records; +} + +function independentTreeHash(root) { + const digest = createHash('sha256'); + const records = independentInventory(root); + for (const record of records) { + digest.update(record.relativePath, 'utf8').update('\0') + .update(record.type, 'utf8').update('\0') + .update(String(record.mode), 'utf8').update('\0') + .update(record.bytes).update('\0'); + } + return { digest: digest.digest('hex'), records }; +} + +function writeValidation(fixture, sourceCommit, version = '0.1.0-alpha.2') { + const digest = independentTreeHash(fixture.packageRoot).digest; + const manifest = { + schema: 'torch.dev/candidate-validation/v1alpha1', + version, + digest, + validatedAt: '2026-10-01T00:00:00.000Z', + source: { mode: 'artifact', commit: sourceCommit }, + scenarios: ['fixture-only'], + evidence: ['fixture-only'], + passed: true, + }; + writeFileSync(join(fixture.packageRoot, '.torch-validation.json'), `${JSON.stringify(manifest, null, 2)}\n`); + return manifest; +} + +function fixtureEnv(fixture, extra = {}) { + return { + ...process.env, + XDG_DATA_HOME: fixture.dataHome, + HOME: fixture.home, + PATH: process.env.PATH, + ...extra, + }; +} + +async function importReader(fixture, extraEnv = {}) { + const modulePath = join(fixture.packageRoot, 'src', 'self-host', 'runtime-metadata.mjs'); + const env = fixtureEnv(fixture, extraEnv); + const previous = new Map(['XDG_DATA_HOME', 'HOME', 'PATH'].map((name) => [name, process.env[name]])); + for (const name of previous.keys()) { + if (Object.hasOwn(env, name)) process.env[name] = env[name]; + } + try { + return await import(pathToFileURL(modulePath).href); + } finally { + for (const [name, value] of previous) { + if (value === undefined) delete process.env[name]; + else process.env[name] = value; + } + } +} + +async function runReader(fixture, extraEnv = {}) { + const api = await importReader(fixture, extraEnv); + return { exports: Object.keys(api), value: api.readLoadedRuntimeMetadataV1() }; +} + +function replacePointer(fixture, version) { + const active = join(fixture.dataHome, 'torch', 'runtime', 'active'); + rmSync(active); + symlinkSync(`versions/${version}`, active); +} + +function treeState(root) { + const result = []; + const visit = (directory, relativeDirectory) => { + const entries = readdirSync(directory).sort((left, right) => left.localeCompare(right)); + for (const name of entries) { + const path = join(directory, name); + const relativePath = relativeDirectory ? `${relativeDirectory}/${name}` : name; + const stat = lstatSync(path); + if (stat.isDirectory()) { + result.push({ path: relativePath, kind: 'directory', mode: stat.mode & 0o777, mtimeMs: stat.mtimeMs }); + visit(path, relativePath); + } else if (stat.isSymbolicLink()) { + result.push({ path: relativePath, kind: 'symlink', mode: stat.mode & 0o777, mtimeMs: stat.mtimeMs, target: readlinkSync(path) }); + } else if (stat.isFile()) { + const bytes = readFileSync(path); + result.push({ path: relativePath, kind: 'file', mode: stat.mode & 0o777, mtimeMs: stat.mtimeMs, bytes: hash(bytes) }); + } else result.push({ path: relativePath, kind: 'special', mode: stat.mode & 0o777, mtimeMs: stat.mtimeMs }); + } + }; + visit(root, ''); + return result; +} + +function cleanup(fixture) { + rmSync(fixture.temporaryRoot, { recursive: true, force: true }); +} + +test('SCN-runtime-metadata-fixed-preimages', async () => { + assert.equal(hash(PACKAGE_BYTES), 'bb3f4716de3a74659133d53f81a040a96983084caabd1aa804e06d0925a8b488'); + assert.equal(hash(RELEASE_BYTES), '0f328f1cebc69b7cbedae7a83b5c25170d93ac2cefacbd8ea2cadc3e9e3e09be'); + assert.equal(hash(CONSOLE_HTML_BYTES), '7dde07b4ced6fa4d765b386349d272471af8b02d7d1344c44acb0825e04ffd82'); + assert.equal(hash(STYLES_BYTES), '1f38f2b2437bde2a818f5b6bcfefa969b296d4228a05bd23091ad48f2865f7e6'); + assert.equal(hash(OLD_CONSOLE_JS_BYTES), '72e85e8e7b15d04fa947acf2f76429b3313291470b917754db318275b9a5bac1'); + assert.equal(hash(LATER_CONSOLE_JS_BYTES), 'c23f1c80d89d407a99644ca9f1657184ad2f3110733e9312970ab848478b9b44'); + assert.equal(hash(PACKAGE_ALPHA3_BYTES), 'f63933fa53a8b4c1e76756a3e68fb8afdeee314b6c8d88091df9cf264024472d'); + assert.equal(hash(RELEASE_ALPHA3_BYTES), 'c3380bad909810a252f707229722316e9fabc0963a76e594b386b22394e3167f'); + assert.equal(hash(PACKAGE_ALPHA4_BYTES), '0853e676191bcf8a81ee7c2945fbe2191f7be38c72ace8aa04bd81dabc724196'); + assert.equal(hash(RELEASE_ALPHA4_BYTES), '2a82853e22b85263da96c3fecf9776bf4ac1eef191eb85505a4f9c4188ea101a'); + const names = ['a.txt', 'A.txt', '_under.txt', '.dot.txt', 'punct!.txt', 'z.txt']; + assert.deepEqual([...names].sort((left, right) => left.localeCompare(right)), [ + '_under.txt', '.dot.txt', 'a.txt', 'A.txt', 'punct!.txt', 'z.txt', + ]); + const preimage = [ + ['nested/_under.txt', 'file', '420', Buffer.from('under\n')], + ['nested/.dot.txt', 'file', '420', Buffer.from('dot\n')], + ['nested/a.txt', 'file', '420', Buffer.from('lower\n')], + ['nested/A.txt', 'file', '420', Buffer.from('upper\n')], + ['nested/punct!.txt', 'file', '420', Buffer.from('bang\n')], + ['nested/z.txt', 'file', '420', Buffer.from('zee\n')], + ]; + const digest = createHash('sha256'); + for (const [path, type, mode, bytes] of preimage) { + digest.update(path).update('\0').update(type).update('\0').update(mode).update('\0').update(bytes).update('\0'); + } + assert.equal(digest.digest('hex'), '0ecc979d167c5c42c7de1055c32f227a4556af067c51f1bc750801aa11d3bcbc'); + const linkPreimage = Buffer.concat([ + Buffer.from('node_modules/.bin/probe\0symlink\0', 'utf8'), + Buffer.from('511', 'utf8'), + Buffer.from('\0../../tools/probe.mjs\0', 'utf8'), + ]); + assert.equal(hash(linkPreimage), '97bfe5f2843767ea0ba18980dd150de9e0c58858ccae862bc65757777a4e9066'); + + const fixture = makeFixture(); + try { + const expected = independentTreeHash(fixture.packageRoot); + const declared = writeValidation(fixture, ALPHA2_COMMIT); + assert.equal(declared.digest, expected.digest); + const { exports, value } = await runReader(fixture); + assert.deepEqual(exports, ['readLoadedRuntimeMetadataV1']); + assert.equal(value.readerModuleEvaluation.candidateTree.sha256, expected.digest); + assert.equal(value.readerModuleEvaluation.candidateTree.consistency, 'match'); + const linkRecord = expected.records.find((record) => record.relativePath === 'node_modules/.bin/probe'); + assert.equal(linkRecord.type, 'symlink'); + assert.equal(linkRecord.bytes.toString('utf8'), '../../tools/probe.mjs'); + assert.equal(hash(Buffer.concat([ + Buffer.from(`${linkRecord.relativePath}\0${linkRecord.type}\0${linkRecord.mode}\0`, 'utf8'), + linkRecord.bytes, + Buffer.from('\0'), + ])), '97bfe5f2843767ea0ba18980dd150de9e0c58858ccae862bc65757777a4e9066'); + assert.equal(value.readerModuleEvaluation.candidateTree.status, 'observed'); + const expectedConsoleAssetDigests = { + 'site/console.html': '7dde07b4ced6fa4d765b386349d272471af8b02d7d1344c44acb0825e04ffd82', + 'site/styles.css': '1f38f2b2437bde2a818f5b6bcfefa969b296d4228a05bd23091ad48f2865f7e6', + 'site/torch-mark.svg': 'e0529597ed9e940d876ee62599cf8c59793967e560e0c1bd780b788a6958e645', + 'site/work-views.js': 'dcf41572339c08f20def7c4c3160dfc9cfbca77ff0be814d24bbea1fb4aeb97a', + 'site/work-progress.js': 'faa1dd5bc34e043474614ce08872dbdd127ab5128d113569782509a970f240af', + 'site/owner-digest.js': '357fe69cf90999ecefc8c1173db5cd3a2f0e2c08c01e20dc1e5c070d067d2c9c', + 'site/check-evidence.js': 'fe0e2552a5a4b2d871e8569ae7388dece34944e05cb7cde70e3b5c8ebe9b49e5', + 'site/task-activity.js': '926d931fe30920b580f2d125f5e00bd2451915febfd1d84c188826548a07f2e9', + 'site/operation-outcomes.js': 'd906a79d78684df88c9efb7c5d416514c0130f23ab7bd4cbc92f4ae1f9a07658', + 'site/attention-projection.js': 'f9ebf53fa3b26eb155fa96397a6f3b04b929ddd4b8bfe62269f2230822954758', + 'site/attention-actions.js': '47de3994661702d556c21cdc34de26d6fb52976c53cd16fe51a3e001154ec294', + 'site/live-refresh.js': '797d27032a75268f884fd2b33a8c47cda6dd6be37fd59a37758085cab63f1273', + 'site/demo.js': 'a804da821bea1d2b1e1353511dca7bcf0109cac0d7f23e54bbba7520138586e7', + 'site/console.js': '72e85e8e7b15d04fa947acf2f76429b3313291470b917754db318275b9a5bac1', + }; + assert.deepEqual(Object.fromEntries(value.currentConsoleDisk.assets.map((asset) => [asset.path, asset.sha256])), expectedConsoleAssetDigests); + } finally { cleanup(fixture); } +}); + +test('SCN-runtime-metadata-observations-not-authentication', async () => { + const fixture = makeFixture(); + try { + writeValidation(fixture, ALPHA2_COMMIT); + const { value } = await runReader(fixture); + assert.equal(value.readerModuleEvaluation.packageVersion.value, '0.1.0-alpha.2'); + assert.equal(value.readerModuleEvaluation.releaseVersion.value, '0.1.0-alpha.2'); + assert.equal(value.readerModuleEvaluation.declaredSourceCommit.value, ALPHA2_COMMIT); + assert.equal(Object.hasOwn(value, 'blockedReason'), false); + assert.equal(value.readerModuleEvaluation.candidateTree.consistency, 'match'); + assert.equal(value.authenticationConfidence, 'unknown'); + assert.equal(value.readerModuleEvaluation.authenticationConfidence, 'unknown'); + assert.equal(value.loadedMemoryCodeDigest, null); + assert.equal(value.readerModuleEvaluation.loadedMemoryCodeDigest, null); + assert.equal(value.deliveredBytesProof, null); + assert.equal(value.currentConsoleDisk.deliveredBytesProof, null); + assert.equal(value.nativeAuthority, null); + + } finally { cleanup(fixture); } + + const malformedRelease = makeFixture(); + try { + writeFileSync(join(malformedRelease.packageRoot, 'torch-release.json'), '{malformed\n'); + writeValidation(malformedRelease, ALPHA2_COMMIT); + const partial = (await runReader(malformedRelease)).value; + assert.equal(partial.readerModuleEvaluation.packageVersion.value, '0.1.0-alpha.2'); + assert.equal(partial.readerModuleEvaluation.releaseVersion.value, null); + assert.equal(partial.readerModuleEvaluation.declaredSourceCommit.value, ALPHA2_COMMIT); + assert.equal(partial.readerModuleEvaluation.authenticationConfidence, 'unknown'); + } finally { cleanup(malformedRelease); } + + const oversizedMetadata = makeFixture(); + try { + const fd = openSync(join(oversizedMetadata.packageRoot, 'torch-release.json'), 'r+'); + try { ftruncateSync(fd, 64 * 1024 + 1); } finally { closeSync(fd); } + const result = (await runReader(oversizedMetadata)).value.readerModuleEvaluation; + assert.equal(result.releaseVersion.status, 'unknown'); + assert.equal(result.releaseVersion.value, null); + assert.ok(result.issues.some((entry) => entry.code === 'RUNTIME_METADATA_FILE_CAP_EXCEEDED')); + } finally { cleanup(oversizedMetadata); } + + const laterCases = [ + { version: '0.1.0-alpha.3', packageBytes: PACKAGE_ALPHA3_BYTES, releaseBytes: RELEASE_ALPHA3_BYTES, commit: ALPHA3_COMMIT, consoleBytes: OLD_CONSOLE_JS_BYTES }, + { version: '0.1.0-alpha.4', packageBytes: PACKAGE_ALPHA4_BYTES, releaseBytes: RELEASE_ALPHA4_BYTES, commit: ALPHA4_COMMIT, consoleBytes: LATER_CONSOLE_JS_BYTES }, + ]; + for (const item of laterCases) { + const later = makeFixture(); + try { + writeFileSync(join(later.packageRoot, 'package.json'), item.packageBytes); + writeFileSync(join(later.packageRoot, 'torch-release.json'), item.releaseBytes); + writeFileSync(join(later.packageRoot, 'site', 'console.js'), item.consoleBytes); + const validation = writeValidation(later, item.commit, item.version); + const expected = independentTreeHash(later.packageRoot); + assert.equal(validation.digest, expected.digest); + const { value } = await runReader(later); + assert.equal(value.readerModuleEvaluation.packageVersion.value, item.version); + assert.equal(value.readerModuleEvaluation.releaseVersion.value, item.version); + assert.equal(value.readerModuleEvaluation.declaredSourceCommit.value, item.commit); + assert.equal(value.readerModuleEvaluation.candidateTree.sha256, expected.digest); + assert.equal(value.readerModuleEvaluation.candidateTree.consistency, 'match'); + assert.equal(value.currentActiveRuntime.activePointer.version, '0.1.0-alpha.2'); + assert.equal(Object.hasOwn(value, 'blockedReason'), false); + assert.equal(value.authenticationConfidence, 'unknown'); + } finally { cleanup(later); } + } +}); + +test('SCN-runtime-metadata-startup-held-pointer-advance', async () => { + const fixture = makeFixture(); + try { + const api = await importReader(fixture); + replacePointer(fixture, '0.1.0-alpha.3'); + writeFileSync(join(fixture.dataHome, 'torch', 'runtime', 'registry.json'), JSON.stringify({ + schema: 'torch.dev/version-registry/v1alpha1', activeVersion: '0.1.0-alpha.3', + previousVersion: '0.1.0-alpha.2', generation: 2, updatedAt: '2026-10-01T00:01:00.000Z', + }) + '\n'); + writeFileSync(join(fixture.packageRoot, 'site', 'console.js'), LATER_CONSOLE_JS_BYTES); + writeValidation(fixture, ALPHA2_COMMIT); + const value = api.readLoadedRuntimeMetadataV1(); + assert.equal(value.readerModuleEvaluation.declaredSourceCommit.value, ALPHA2_COMMIT); + assert.equal(value.readerModuleEvaluation.packageVersion.value, '0.1.0-alpha.2'); + assert.equal(value.readerModuleEvaluation.releaseVersion.value, '0.1.0-alpha.2'); + assert.equal(Object.hasOwn(value, 'blockedReason'), false); + assert.equal(value.readerModuleEvaluation.packageVersion.value, '0.1.0-alpha.2'); + assert.equal(value.readerModuleEvaluation.releaseVersion.value, '0.1.0-alpha.2'); + assert.equal(value.activeRuntimeAtModuleEvaluation.activePointer.version, '0.1.0-alpha.2'); + assert.equal(value.currentActiveRuntime.activePointer.version, '0.1.0-alpha.3'); + assert.equal(value.currentActiveRuntime.registry.value.generation, 2); + assert.equal(value.relations.activePointerSinceModuleEvaluation, 'mismatch'); + assert.equal(value.relations.registryGenerationSinceModuleEvaluation, 'mismatch'); + assert.equal(value.relations.consoleDiskSinceModuleEvaluation, 'mismatch'); + const currentConsole = value.currentConsoleDisk.assets.find((asset) => asset.path === 'site/console.js'); + assert.equal(currentConsole.sha256, hash(LATER_CONSOLE_JS_BYTES)); + assert.equal(value.loadedMemoryCodeDigest, null); + assert.equal(value.deliveredBytesProof, null); + } finally { + cleanup(fixture); + } +}); + +test('SCN-runtime-metadata-allowlist-and-caps', async () => { + const fixture = makeFixture(); + try { + writeValidation(fixture, ALPHA2_COMMIT); + writeFileSync(join(fixture.packageRoot, 'site', 'unrelated-secret.txt'), 'not routed\n'); + const { value } = await runReader(fixture); + assert.deepEqual(value.currentConsoleDisk.assets.map((asset) => basename(asset.path)), CONSOLE_ASSETS); + assert.equal(value.currentConsoleDisk.files, 14); + assert.equal(value.currentConsoleDisk.assets.some((asset) => asset.path.endsWith('unrelated-secret.txt')), false); + for (const asset of value.currentConsoleDisk.assets) { + assert.equal(Number.isSafeInteger(asset.bytes), true); + assert.match(asset.sha256, /^[a-f0-9]{64}$/); + } + + const oversizedUi = join(fixture.packageRoot, 'site', 'console.js'); + const fd = openSync(oversizedUi, 'r+'); + try { ftruncateSync(fd, 4 * 1024 * 1024 + 1); } finally { closeSync(fd); } + const uiResult = (await runReader(fixture)).value; + assert.equal(uiResult.currentConsoleDisk.status, 'refused'); + assert.equal(uiResult.currentConsoleDisk.aggregateSha256, null); + assert.equal(uiResult.currentConsoleDisk.assets.length, 0); + assert.equal(uiResult.currentConsoleDisk.issues[0].code, 'RUNTIME_METADATA_CONSOLE_FILE_CAP_EXCEEDED'); + + const missing = join(fixture.packageRoot, 'site', 'torch-mark.svg'); + rmSync(missing); + const missingResult = (await runReader(fixture)).value; + assert.equal(missingResult.currentConsoleDisk.status, 'refused'); + assert.equal(missingResult.currentConsoleDisk.aggregateSha256, null); + } finally { cleanup(fixture); } +}); + +test('SCN-runtime-metadata-malformed-oversize-tamper-race', async () => { + const malformed = makeFixture(); + try { + writeFileSync(join(malformed.packageRoot, 'package.json'), '{broken'); + writeValidation(malformed, ALPHA2_COMMIT); + const malformedResult = (await runReader(malformed)).value; + assert.equal(malformedResult.readerModuleEvaluation.packageVersion.value, null); + assert.ok(malformedResult.readerModuleEvaluation.issues.some((entry) => entry.code === 'RUNTIME_METADATA_JSON_MALFORMED')); + assert.equal(malformedResult.readerModuleEvaluation.authenticationConfidence, 'unknown'); + } finally { cleanup(malformed); } + + const oversized = makeFixture(); + try { + const fd = openSync(join(oversized.packageRoot, 'tools', 'probe.mjs'), 'r+'); + try { ftruncateSync(fd, 64 * 1024 * 1024 + 1); } finally { closeSync(fd); } + const result = (await runReader(oversized)).value.readerModuleEvaluation.candidateTree; + assert.equal(result.status, 'refused'); + assert.equal(result.sha256, null); + } finally { cleanup(oversized); } + + const oversizedTotal = makeFixture({ includeInternalLink: false }); + try { + for (let index = 0; index < 9; index += 1) { + const path = join(oversizedTotal.packageRoot, `large-${index}.bin`); + const fd = openSync(path, 'w'); + try { ftruncateSync(fd, 64 * 1024 * 1024); } finally { closeSync(fd); } + } + const result = (await runReader(oversizedTotal)).value.readerModuleEvaluation.candidateTree; + assert.equal(result.status, 'refused'); + assert.equal(result.sha256, null); + assert.equal(result.bytes, null); + } finally { cleanup(oversizedTotal); } + + const tooManyEntries = makeFixture({ includeInternalLink: false }); + try { + for (let index = 0; index < 8193; index += 1) { + writeFileSync(join(tooManyEntries.packageRoot, `entry-${String(index).padStart(4, '0')}`), ''); + } + const result = (await runReader(tooManyEntries)).value.readerModuleEvaluation.candidateTree; + assert.equal(result.status, 'refused'); + assert.equal(result.sha256, null); + assert.ok(result.entries === null); + } finally { cleanup(tooManyEntries); } + + const oversizedConsoleTotal = makeFixture(); + try { + for (const name of CONSOLE_ASSETS.slice(0, 5)) { + const path = join(oversizedConsoleTotal.packageRoot, 'site', name); + const fd = openSync(path, 'r+'); + try { ftruncateSync(fd, 4 * 1024 * 1024); } finally { closeSync(fd); } + } + const result = (await runReader(oversizedConsoleTotal)).value.currentConsoleDisk; + assert.equal(result.status, 'refused'); + assert.equal(result.aggregateSha256, null); + assert.equal(result.assets.length, 0); + assert.ok(result.issues.some((entry) => entry.code === 'RUNTIME_METADATA_CONSOLE_TOTAL_CAP_EXCEEDED')); + } finally { cleanup(oversizedConsoleTotal); } + + const nofollow = makeFixture({ includeInternalLink: false }); + try { + const original = join(nofollow.packageRoot, 'torch-release.json'); + const moved = join(nofollow.packageRoot, 'real-release.json'); + rmSync(original); + writeFileSync(moved, RELEASE_BYTES); + symlinkSync('real-release.json', original); + writeValidation(nofollow, ALPHA2_COMMIT); + const result = (await runReader(nofollow)).value.readerModuleEvaluation; + assert.equal(result.releaseVersion.value, null); + assert.ok(result.issues.some((entry) => entry.code === 'RUNTIME_METADATA_LINK_REFUSED')); + } finally { cleanup(nofollow); } + + const invalidLinks = [ + ['external', '../outside', 'RUNTIME_METADATA_LINK_ESCAPE_REFUSED'], + ['absolute', '/etc/passwd', 'RUNTIME_METADATA_LINK_ABSOLUTE_REFUSED'], + ['dangling', 'not-present', 'RUNTIME_METADATA_LINK_DANGLING_REFUSED'], + ]; + for (const [name, target, expectedCode] of invalidLinks) { + const fixture = makeFixture({ includeInternalLink: false }); + try { + symlinkSync(target, join(fixture.packageRoot, `bad-${name}`)); + writeValidation(fixture, ALPHA2_COMMIT); + const result = (await runReader(fixture)).value.readerModuleEvaluation; + assert.equal(result.candidateTree.status, 'unknown'); + assert.equal(result.candidateTree.sha256, null); + assert.ok(result.issues.some((entry) => entry.code === expectedCode)); + } finally { cleanup(fixture); } + } + + const invalidUtf8Link = makeFixture({ includeInternalLink: false }); + try { + symlinkSync(Buffer.from([0xff]), join(invalidUtf8Link.packageRoot, 'invalid-utf8-link')); + const result = (await runReader(invalidUtf8Link)).value.readerModuleEvaluation; + assert.equal(result.candidateTree.status, 'unknown'); + assert.equal(result.candidateTree.sha256, null); + assert.ok(result.issues.some((entry) => entry.code === 'RUNTIME_METADATA_LINK_UTF8_INVALID')); + } finally { cleanup(invalidUtf8Link); } + + const longLink = makeFixture({ includeInternalLink: false }); + try { + const linkText = `${'./'.repeat(600)}tools/probe.mjs`; + symlinkSync(linkText, join(longLink.packageRoot, 'oversized-link')); + writeValidation(longLink, ALPHA2_COMMIT); + const result = (await runReader(longLink)).value.readerModuleEvaluation; + assert.equal(result.candidateTree.status, 'unknown'); + assert.ok(result.issues.some((entry) => entry.code === 'RUNTIME_METADATA_LINK_CAP_EXCEEDED')); + } finally { cleanup(longLink); } + + const uiLink = makeFixture(); + try { + rmSync(join(uiLink.packageRoot, 'site', 'console.js')); + symlinkSync('styles.css', join(uiLink.packageRoot, 'site', 'console.js')); + writeValidation(uiLink, ALPHA2_COMMIT); + const result = (await runReader(uiLink)).value.currentConsoleDisk; + assert.equal(result.status, 'refused'); + assert.equal(result.aggregateSha256, null); + assert.ok(result.issues.some((entry) => entry.code === 'RUNTIME_METADATA_LINK_REFUSED')); + } finally { cleanup(uiLink); } + + const special = makeFixture({ includeInternalLink: false }); + const socketPath = join(special.packageRoot, 'special.sock'); + const server = await new Promise((resolvePromise, reject) => { + const netServer = createNetServer(); + netServer.once('error', reject); + netServer.listen(socketPath, () => resolvePromise(netServer)); + }); + try { + const result = (await runReader(special)).value.readerModuleEvaluation.candidateTree; + assert.equal(result.status, 'unknown'); + assert.equal(result.sha256, null); + } finally { + await new Promise((resolvePromise) => server.close(resolvePromise)); + cleanup(special); + } +}); + +test('SCN-runtime-metadata-partial-journal', async () => { + const fixture = makeFixture(); + try { + writeValidation(fixture, ALPHA2_COMMIT); + const before = treeState(join(fixture.dataHome, 'torch', 'runtime')); + writeFileSync(join(fixture.dataHome, 'torch', 'runtime', 'activation-journal.json'), JSON.stringify({ + schema: 'torch.dev/activation-journal/v1alpha1', from: '0.1.0-alpha.1', + to: '0.1.0-alpha.3', startedAt: '2026-10-01T00:02:00.000Z', + }) + '\n'); + const withJournal = treeState(join(fixture.dataHome, 'torch', 'runtime')); + const value = (await runReader(fixture)).value; + assert.equal(value.currentActiveRuntime.journal.status, 'observed'); + assert.equal(value.currentActiveRuntime.consistency, 'mismatch'); + assert.equal(value.currentActiveRuntime.activePointer.version, '0.1.0-alpha.2'); + assert.equal(value.currentActiveRuntime.registry.value.activeVersion, '0.1.0-alpha.2'); + assert.notDeepEqual(before, withJournal); + assert.deepEqual(treeState(join(fixture.dataHome, 'torch', 'runtime')), withJournal); + + writeFileSync(join(fixture.dataHome, 'torch', 'runtime', 'activation-journal.json'), '{partial'); + const partial = treeState(join(fixture.dataHome, 'torch', 'runtime')); + const partialValue = (await runReader(fixture)).value.currentActiveRuntime; + assert.equal(partialValue.journal.status, 'unknown'); + assert.ok(partialValue.issues.some((entry) => entry.code === 'RUNTIME_METADATA_JSON_MALFORMED')); + assert.deepEqual(treeState(join(fixture.dataHome, 'torch', 'runtime')), partial); + + replacePointer(fixture, '0.1.0-alpha.3'); + const mismatched = treeState(join(fixture.dataHome, 'torch', 'runtime')); + const pointerMismatch = (await runReader(fixture)).value.currentActiveRuntime; + assert.equal(pointerMismatch.consistency, 'mismatch'); + assert.deepEqual(treeState(join(fixture.dataHome, 'torch', 'runtime')), mismatched); + + const active = join(fixture.dataHome, 'torch', 'runtime', 'active'); + rmSync(active); + symlinkSync(`versions/${'x'.repeat(300)}`, active); + const oversizedPointer = (await runReader(fixture)).value.currentActiveRuntime; + assert.equal(oversizedPointer.activePointer.status, 'unknown'); + assert.equal(oversizedPointer.activePointer.version, null); + assert.ok(oversizedPointer.issues.some((entry) => entry.code === 'RUNTIME_METADATA_LINK_CAP_EXCEEDED')); + } finally { cleanup(fixture); } +}); + +test('SCN-runtime-metadata-read-only-no-native-authority', async () => { + const fixture = makeFixture(); + try { + writeValidation(fixture, ALPHA2_COMMIT); + const runtimePath = join(fixture.dataHome, 'torch', 'runtime'); + const databaseSentinel = join(fixture.temporaryRoot, 'state.sqlite.sentinel'); + writeFileSync(databaseSentinel, 'unchanged-sentinel'); + const beforePackage = treeState(fixture.packageRoot); + const beforeRuntime = treeState(runtimePath); + const beforeSentinel = hash(readFileSync(databaseSentinel)); + const moduleSource = readFileSync(MODULE_SOURCE, 'utf8'); + assert.doesNotMatch(moduleSource, /VersionService|openControlPlane|DatabaseSync|#reconcile|\.status\s*\(/); + const { value } = await runReader(fixture); + assert.equal(value.nativeAuthority, null); + assert.equal(value.authenticationConfidence, 'unknown'); + assert.deepEqual(treeState(fixture.packageRoot), beforePackage); + assert.deepEqual(treeState(runtimePath), beforeRuntime); + assert.equal(hash(readFileSync(databaseSentinel)), beforeSentinel); + } finally { cleanup(fixture); } +}); + +test('SCN-runtime-metadata-head-not-substitute', async () => { + const fixture = makeFixture(); + try { + write(fixture.packageRoot, '.git/HEAD', Buffer.from('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n')); + writeValidation(fixture, ALPHA2_COMMIT); + const fakeBin = join(fixture.temporaryRoot, 'fake-bin'); + mkdirSync(fakeBin); + const gitMarker = join(fixture.temporaryRoot, 'git-was-called'); + const fakeGit = join(fakeBin, 'git'); + writeFileSync(fakeGit, `#!/bin/sh\nprintf called > ${JSON.stringify(gitMarker)}\nexit 0\n`); + chmodSync(fakeGit, 0o755); + const { value } = await runReader(fixture, { PATH: `${fakeBin}:${process.env.PATH}` }); + assert.equal(value.readerModuleEvaluation.declaredSourceCommit.value, ALPHA2_COMMIT); + assert.equal(value.readerModuleEvaluation.candidateTree.consistency, 'match'); + assert.equal(existsSync(gitMarker), false); + assert.equal(value.authenticationConfidence, 'unknown'); + } finally { cleanup(fixture); } +});