mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-27 07:51:52 +00:00
[TASK-conversation-console-workflow] Add read-only runtime metadata observations
This commit is contained in:
1 parent
4fa9bd7d49
commit
18a92d3c3d
3 files changed
+1387
No files matched your search
@@ -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: ""
|
||||
```
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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('<main id="torch-console"></main>\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); }
|
||||
});
|
||||
Reference in new issue
Block a user