@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
1,519 lines (1,457 loc) • 46.3 kB
JavaScript
import { spawnSync } from "node:child_process";
import { createHash, randomUUID } from "node:crypto";
import {
chmodSync,
chownSync,
closeSync,
constants,
existsSync,
fchmodSync,
fchownSync,
fstatSync,
fsyncSync,
lstatSync,
mkdirSync,
openSync,
readFileSync,
readSync,
readdirSync,
realpathSync,
renameSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import { dirname, isAbsolute, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
SOUL_ARTIFACT_MAX_BYTES,
SoulArtifactValidationError,
assertSoulArtifact,
validateSoulArtifact,
} from "./soul-artifact-validator.mjs";
export const SOUL_BRIDGE_DATA_ROOT = "/opt/data";
export const SOUL_LOCK_DIRECTORY = ".sellable-agent/soul-locks";
export const SOUL_JOURNAL_DIRECTORY = ".sellable-agent/soul-journals";
export const SOUL_PROOF_DIRECTORY = ".sellable-agent/soul-proofs";
export const SOUL_PROOF_SCHEMA_VERSION = "sellable-agent-soul-proof/v1";
export const SOUL_PROOF_STATE_PROVEN = "PROVEN";
export const SOUL_PROOF_STATE_UNPROVEN = "UNPROVEN";
export const SOUL_JOURNAL_SCHEMA_VERSION = "sellable-agent-soul-journal/v1";
const PROFILE_ID = /^[a-z0-9][a-z0-9_-]{0,127}$/;
// Runtime replacement must never disappear behind an unbounded synchronous
// flock wait. Ten seconds stays below the operation heartbeat interval and is
// long enough for the short profile metadata critical sections used here.
const SOUL_LOCK_ACQUIRE_TIMEOUT_SECONDS = 10;
const SHA256 = /^[a-f0-9]{64}$/;
const JOURNAL_LIMIT = 256 * 1024;
// JSON.stringify may encode one accepted content byte as a six-byte `\uXXXX`
// escape. Bound the closed stdin envelope without rejecting any SOUL that the
// portable validator can admit.
export const SOUL_BRIDGE_REQUEST_LIMIT = SOUL_ARTIFACT_MAX_BYTES * 6 + 4 * 1024;
const LOCK_MODE = 0o660;
const STATE_FILE_MODE = 0o660;
const PROC_ENTRY_LIMIT = 8_192;
const PROC_CMDLINE_LIMIT = 16 * 1024;
const PROC_ENVIRON_LIMIT = 128 * 1024;
const PROC_STAT_LIMIT = 8 * 1024;
const PINNED_HERMES_LAUNCHER = "/opt/hermes/.venv/bin/hermes";
const PINNED_HERMES_PYTHON = "/opt/hermes/.venv/bin/python3";
// Fly images provide util-linux at the immutable system path. Local macOS
// verification uses Homebrew's equivalent because Apple does not ship flock.
const FLOCK_EXECUTABLE = existsSync("/usr/bin/flock")
? "/usr/bin/flock"
: existsSync("/opt/homebrew/bin/flock")
? "/opt/homebrew/bin/flock"
: existsSync("/usr/local/bin/flock")
? "/usr/local/bin/flock"
: "/usr/bin/flock";
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
export class FlySoulBridgeError extends Error {
constructor(code) {
super(code);
this.name = "FlySoulBridgeError";
this.code = code;
}
}
function fail(code) {
throw new FlySoulBridgeError(code);
}
function assertProfileId(profileId) {
if (typeof profileId !== "string" || !PROFILE_ID.test(profileId)) {
fail("soul_profile_id_rejected");
}
return profileId;
}
function assertDataRoot(dataRoot) {
if (
typeof dataRoot !== "string" ||
!isAbsolute(dataRoot) ||
resolve(dataRoot) !== dataRoot ||
!existsSync(dataRoot)
) {
fail("soul_data_root_rejected");
}
const info = lstatSync(dataRoot);
if (info.isSymbolicLink() || !info.isDirectory())
fail("soul_data_root_rejected");
return realpathSync(dataRoot);
}
export function soulProfilePaths(
profileId,
{ dataRoot = SOUL_BRIDGE_DATA_ROOT } = {}
) {
const id = assertProfileId(profileId);
const root = assertDataRoot(dataRoot);
const profileRoot = join(root, "profiles", id);
return Object.freeze({
dataRoot: root,
profileId: id,
profileRoot,
soulPath: join(profileRoot, "SOUL.md"),
lockRoot: join(root, SOUL_LOCK_DIRECTORY),
lockPath: join(root, SOUL_LOCK_DIRECTORY, `${id}.lock`),
journalRoot: join(root, SOUL_JOURNAL_DIRECTORY),
journalPath: join(root, SOUL_JOURNAL_DIRECTORY, `${id}.json`),
proofRoot: join(root, SOUL_PROOF_DIRECTORY),
proofPath: join(root, SOUL_PROOF_DIRECTORY, `${id}.json`),
legacyRootSoulPath: join(root, "SOUL.md"),
});
}
function readBoundedProcFile(path, limit) {
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
try {
const chunks = [];
let total = 0;
while (true) {
const chunk = Buffer.allocUnsafe(Math.min(8 * 1024, limit + 1 - total));
const count = readSync(fd, chunk, 0, chunk.byteLength, null);
if (count === 0) break;
chunks.push(chunk.subarray(0, count));
total += count;
if (total > limit) fail("soul_gateway_process_metadata_exceeded");
}
return Buffer.concat(chunks, total);
} finally {
closeSync(fd);
}
}
function decodeProcNulList(bytes, code) {
if (bytes.byteLength < 2 || bytes.at(-1) !== 0) fail(code);
let decoded;
try {
decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
} catch {
fail(code);
}
const entries = decoded.split("\0");
entries.pop();
if (
entries.length < 1 ||
entries.some((entry) => !entry || entry.includes("\0"))
) {
fail(code);
}
return entries;
}
function parseProcStartTicks(bytes, pid) {
const value = bytes.toString("utf8").trim();
const close = value.lastIndexOf(")");
if (!value.startsWith(`${pid} (`) || close < 4)
fail("soul_gateway_stat_rejected");
const fields = value.slice(close + 2).split(" ");
const startTicks = fields[19];
if (fields.length < 20 || !/^[1-9][0-9]*$/.test(startTicks ?? "")) {
fail("soul_gateway_stat_rejected");
}
return startTicks;
}
function parseProcEnvironment(entries) {
const environment = new Map();
for (const entry of entries) {
const separator = entry.indexOf("=");
if (separator < 1) fail("soul_gateway_environment_rejected");
const name = entry.slice(0, separator);
const value = entry.slice(separator + 1);
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || environment.has(name)) {
fail("soul_gateway_environment_rejected");
}
environment.set(name, value);
}
return environment;
}
function stableGatewayIdentity(gateway) {
return JSON.stringify({
pid: gateway.pid,
startTicks: gateway.startTicks,
profileId: gateway.profileId,
profileRoot: gateway.profileRoot,
executable: gateway.executable,
launcher: gateway.launcher,
cwd: gateway.cwd,
hermesHome: gateway.environment.HERMES_HOME,
configPath: gateway.environment.HERMES_CONFIG_PATH,
});
}
function realpathOrFail(path, code) {
try {
return realpathSync(path);
} catch {
fail(code);
}
}
function inspectGatewayPid(
pid,
{ procRoot, dataRoot, pinnedHermesLauncher, pinnedHermesPython }
) {
const pidRoot = join(procRoot, pid);
let startBefore;
let argv;
try {
startBefore = parseProcStartTicks(
readBoundedProcFile(join(pidRoot, "stat"), PROC_STAT_LIMIT),
pid
);
const cmdline = readBoundedProcFile(
join(pidRoot, "cmdline"),
PROC_CMDLINE_LIMIT
);
// Fly Machines expose kernel threads in the guest PID namespace. They
// have a valid /proc/<pid>/stat entry but an intentionally empty cmdline
// and therefore cannot be a userspace Hermes gateway.
if (cmdline.byteLength === 0) return null;
argv = decodeProcNulList(cmdline, "soul_gateway_argv_rejected");
} catch (error) {
if (["ENOENT", "ESRCH", "EACCES"].includes(error?.code)) return null;
throw error;
}
const gatewayRunIndex = argv.findIndex(
(entry, index) => entry === "gateway" && argv[index + 1] === "run"
);
const looksLikeGateway =
argv.includes(pinnedHermesLauncher) && gatewayRunIndex >= 0;
if (!looksLikeGateway) return null;
// Hermes 0.21.5 runs as Sellable's foreground child with this exact supervisor
// suffix. Keep the prior bare/--replace forms for rolling upgrades, but do not
// admit partial, reordered, or arbitrary extra gateway flags.
const supervisorSuffix = [
"--replace",
"--force",
"--no-supervise",
"--external-supervisor",
];
const suffixLength = supervisorSuffix.every(
(entry, index) => argv.at(index - supervisorSuffix.length) === entry
)
? supervisorSuffix.length
: argv.at(-1) === "--replace"
? 1
: 0;
const profileId = argv.at(-3 - suffixLength);
const prefix = argv.slice(0, -4 - suffixLength);
if (
argv.at(-4 - suffixLength) !== "--profile" ||
argv.at(-2 - suffixLength) !== "gateway" ||
argv.at(-1 - suffixLength) !== "run" ||
!PROFILE_ID.test(profileId ?? "") ||
prefix.length !== 2 ||
prefix[1] !== pinnedHermesLauncher
) {
fail("soul_gateway_argv_rejected");
}
const expectedPython = realpathOrFail(
pinnedHermesPython,
"soul_gateway_executable_rejected"
);
if (
!isAbsolute(prefix[0]) ||
realpathOrFail(prefix[0], "soul_gateway_executable_rejected") !==
expectedPython
) {
fail("soul_gateway_executable_rejected");
}
const executable = realpathOrFail(
join(pidRoot, "exe"),
"soul_gateway_identity_changed"
);
if (executable !== expectedPython) fail("soul_gateway_executable_rejected");
const paths = soulProfilePaths(profileId, { dataRoot });
const cwd = realpathOrFail(
join(pidRoot, "cwd"),
"soul_gateway_identity_changed"
);
if (cwd !== paths.profileRoot) fail("soul_gateway_cwd_rejected");
let environment;
try {
environment = parseProcEnvironment(
decodeProcNulList(
readBoundedProcFile(join(pidRoot, "environ"), PROC_ENVIRON_LIMIT),
"soul_gateway_environment_rejected"
)
);
} catch (error) {
if (["ENOENT", "ESRCH"].includes(error?.code)) {
fail("soul_gateway_identity_changed");
}
throw error;
}
const expectedEnvironment = {
SELLABLE_AGENT_RUNTIME: "1",
HERMES_PROFILE: profileId,
SELLABLE_HERMES_PROFILE_ID: profileId,
HERMES_HOME: paths.dataRoot,
HERMES_CONFIG_PATH: join(paths.profileRoot, "config.yaml"),
};
for (const [name, value] of Object.entries(expectedEnvironment)) {
if (environment.get(name) !== value)
fail("soul_gateway_environment_rejected");
}
let startAfter;
try {
startAfter = parseProcStartTicks(
readBoundedProcFile(join(pidRoot, "stat"), PROC_STAT_LIMIT),
pid
);
} catch {
fail("soul_gateway_identity_changed");
}
if (startAfter !== startBefore) fail("soul_gateway_identity_changed");
return Object.freeze({
pid: Number(pid),
startTicks: startBefore,
profileId,
profileRoot: paths.profileRoot,
executable,
launcher: pinnedHermesLauncher,
cwd,
environment: Object.freeze(expectedEnvironment),
});
}
export function discoverLiveHermesGateway({
procRoot = "/proc",
dataRoot = SOUL_BRIDGE_DATA_ROOT,
pinnedHermesLauncher = PINNED_HERMES_LAUNCHER,
pinnedHermesPython = PINNED_HERMES_PYTHON,
} = {}) {
const root = assertDataRoot(dataRoot);
if (
!isAbsolute(procRoot) ||
resolve(procRoot) !== procRoot ||
!isAbsolute(pinnedHermesLauncher) ||
!isAbsolute(pinnedHermesPython)
) {
fail("soul_gateway_discovery_configuration_rejected");
}
const pids = readdirSync(procRoot)
.filter((entry) => /^[1-9][0-9]*$/.test(entry))
.sort((left, right) => Number(left) - Number(right));
if (pids.length > PROC_ENTRY_LIMIT)
fail("soul_gateway_process_inventory_exceeded");
const matches = [];
for (const pid of pids) {
const match = inspectGatewayPid(pid, {
procRoot,
dataRoot: root,
pinnedHermesLauncher,
pinnedHermesPython,
});
if (match) matches.push(match);
if (matches.length > 1) fail("soul_gateway_ambiguous");
}
if (matches.length === 0) fail("soul_gateway_not_running");
return matches[0];
}
export function revalidateLiveHermesGateway(
gateway,
{
procRoot = "/proc",
dataRoot = SOUL_BRIDGE_DATA_ROOT,
pinnedHermesLauncher = PINNED_HERMES_LAUNCHER,
pinnedHermesPython = PINNED_HERMES_PYTHON,
} = {}
) {
if (!gateway || !Number.isSafeInteger(gateway.pid) || gateway.pid < 1) {
fail("soul_gateway_identity_rejected");
}
const current = inspectGatewayPid(String(gateway.pid), {
procRoot,
dataRoot: assertDataRoot(dataRoot),
pinnedHermesLauncher,
pinnedHermesPython,
});
if (
!current ||
stableGatewayIdentity(current) !== stableGatewayIdentity(gateway)
) {
fail("soul_gateway_identity_changed");
}
return current;
}
/** Prepare the stable, setgid lock root before a runtime drops privileges. */
export function prepareSoulLockRoot({
dataRoot = SOUL_BRIDGE_DATA_ROOT,
gid = process.getgid?.(),
} = {}) {
const root = assertDataRoot(dataRoot);
const stateRoot = join(root, ".sellable-agent");
const lockRoot = join(root, SOUL_LOCK_DIRECTORY);
const journalRoot = join(root, SOUL_JOURNAL_DIRECTORY);
const proofRoot = join(root, SOUL_PROOF_DIRECTORY);
mkdirSync(stateRoot, { recursive: true, mode: 0o2770 });
for (const path of [stateRoot, lockRoot, journalRoot, proofRoot]) {
mkdirSync(path, {
recursive: true,
mode: 0o2770,
});
const info = lstatSync(path);
if (info.isSymbolicLink() || !info.isDirectory())
fail("soul_state_root_rejected");
}
if (process.getuid?.() === 0 && Number.isSafeInteger(gid) && gid >= 0) {
for (const path of [stateRoot, lockRoot, journalRoot, proofRoot]) {
chownSync(path, 0, gid);
}
}
for (const path of [stateRoot, lockRoot, journalRoot, proofRoot])
chmodSync(path, 0o2770);
return Object.freeze({ lockRoot, journalRoot, proofRoot });
}
export function prepareProfileSoulLock(
profileId,
{ dataRoot = SOUL_BRIDGE_DATA_ROOT, gid = process.getgid?.() } = {}
) {
const paths = soulProfilePaths(profileId, { dataRoot });
prepareSoulLockRoot({ dataRoot, gid });
const fd = openSync(
paths.lockPath,
constants.O_RDWR | constants.O_CREAT | constants.O_NOFOLLOW,
LOCK_MODE
);
try {
fchmodSync(fd, LOCK_MODE);
if (process.getuid?.() === 0 && Number.isSafeInteger(gid) && gid >= 0) {
fchownSync(fd, 0, gid);
}
fsyncSync(fd);
} finally {
closeSync(fd);
}
fsyncDirectory(paths.lockRoot);
return Object.freeze({ lockPath: paths.lockPath });
}
function safeSharedOwner(uid) {
const current = process.getuid?.();
return (
uid === 0 ||
uid === current ||
(current === 0 && Number.isSafeInteger(uid) && uid > 0)
);
}
function openAndLock(paths) {
if (!existsSync(paths.lockRoot)) {
prepareSoulLockRoot({ dataRoot: paths.dataRoot });
}
const before = lstatSync(paths.lockRoot);
if (
before.isSymbolicLink() ||
!before.isDirectory() ||
(before.mode & 0o7777) !== 0o2770
)
fail("soul_lock_root_rejected");
const existed = existsSync(paths.lockPath);
const fd = openSync(
paths.lockPath,
constants.O_RDWR | constants.O_CREAT | constants.O_NOFOLLOW,
LOCK_MODE
);
try {
if (!existed) fchmodSync(fd, LOCK_MODE);
const link = lstatSync(paths.lockPath);
const opened = fstatSync(fd);
if (
link.isSymbolicLink() ||
!link.isFile() ||
!opened.isFile() ||
link.dev !== opened.dev ||
link.ino !== opened.ino ||
opened.nlink !== 1 ||
(opened.mode & 0o777) !== LOCK_MODE ||
opened.gid !== before.gid ||
!safeSharedOwner(opened.uid)
) {
fail("soul_lock_file_rejected");
}
const locked = spawnSync(
FLOCK_EXECUTABLE,
[
"--exclusive",
"--timeout",
String(SOUL_LOCK_ACQUIRE_TIMEOUT_SECONDS),
"3",
],
{
stdio: ["ignore", "ignore", "pipe", fd],
encoding: "utf8",
timeout: (SOUL_LOCK_ACQUIRE_TIMEOUT_SECONDS + 2) * 1_000,
}
);
if (locked.status === 1) fail("soul_lock_busy");
if (locked.status !== 0) fail("soul_lock_failed");
return fd;
} catch (error) {
closeSync(fd);
throw error;
}
}
/**
* Hold the one Machine-external POSIX flock for a synchronous or async
* callback. The child `flock(1)` applies LOCK_EX to an inherited duplicate of
* the parent's open-file description, so the lock remains held until this
* function closes the parent descriptor after the callback settles.
*/
export function withProfileSoulLock(
profileId,
fn,
{ dataRoot = SOUL_BRIDGE_DATA_ROOT } = {}
) {
if (typeof fn !== "function") fail("soul_lock_callback_rejected");
const paths = soulProfilePaths(profileId, { dataRoot });
const fd = openAndLock(paths);
let value;
try {
value = fn(paths);
} catch (error) {
closeSync(fd);
throw error;
}
if (value && typeof value.then === "function") {
return Promise.resolve(value).finally(() => closeSync(fd));
}
closeSync(fd);
return value;
}
function fsyncDirectory(path) {
const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY);
try {
fsyncSync(fd);
} finally {
closeSync(fd);
}
}
function assertSafeDirectory(path, expectedParent) {
const info = lstatSync(path);
if (info.isSymbolicLink() || !info.isDirectory())
fail("soul_parent_rejected");
if (realpathSync(path) !== path || dirname(path) !== expectedParent) {
fail("soul_parent_rejected");
}
return info;
}
function readSafeRegular(path, { maxBytes = JOURNAL_LIMIT } = {}) {
const link = lstatSync(path);
if (link.isSymbolicLink() || !link.isFile() || link.nlink !== 1) {
fail("soul_file_type_rejected");
}
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
try {
const opened = fstatSync(fd);
if (
!opened.isFile() ||
opened.dev !== link.dev ||
opened.ino !== link.ino ||
opened.nlink !== 1 ||
opened.size > maxBytes
) {
fail("soul_file_descriptor_rejected");
}
return { bytes: readFileSync(fd), stat: opened };
} finally {
closeSync(fd);
}
}
function readSharedStateFile(path, parent, maxBytes = JOURNAL_LIMIT) {
const parentInfo = assertSharedStateDirectory(parent);
const result = readSafeRegular(path, { maxBytes });
if (
(result.stat.mode & 0o777) !== STATE_FILE_MODE ||
result.stat.gid !== parentInfo.gid ||
!safeSharedOwner(result.stat.uid)
) {
fail("soul_state_file_metadata_rejected");
}
return result;
}
function assertSharedStateDirectory(path) {
const info = lstatSync(path);
const lockRoot = join(dirname(path), "soul-locks");
const lockInfo = lstatSync(lockRoot);
if (
info.isSymbolicLink() ||
!info.isDirectory() ||
realpathSync(path) !== path ||
(info.mode & 0o7777) !== 0o2770 ||
lockInfo.isSymbolicLink() ||
!lockInfo.isDirectory() ||
realpathSync(lockRoot) !== lockRoot ||
(lockInfo.mode & 0o7777) !== 0o2770 ||
info.gid !== lockInfo.gid
) {
fail("soul_state_root_rejected");
}
return info;
}
function readCanonical(paths) {
const profilesRoot = join(paths.dataRoot, "profiles");
const profileParent = assertSafeDirectory(profilesRoot, paths.dataRoot);
const profile = assertSafeDirectory(paths.profileRoot, profilesRoot);
if (profile.dev !== profileParent.dev) fail("soul_profile_volume_rejected");
if (!existsSync(paths.soulPath)) fail("canonical_file_absent");
const result = readSafeRegular(paths.soulPath, { maxBytes: 64 * 1024 });
const mode = result.stat.mode & 0o777;
if (![0o600, 0o640].includes(mode) || !safeSharedOwner(result.stat.uid))
fail("soul_file_mode_rejected");
return result;
}
function atomicReplace(path, bytes, { mode, uid, gid }) {
const parent = dirname(path);
const parentBefore = lstatSync(parent);
const temporary = join(parent, `.SOUL.${process.pid}.${randomUUID()}.tmp`);
let fd;
try {
fd = openSync(
temporary,
constants.O_WRONLY |
constants.O_CREAT |
constants.O_EXCL |
constants.O_NOFOLLOW,
mode
);
writeFileSync(fd, bytes);
fchmodSync(fd, mode);
if (
process.getuid?.() === 0 &&
Number.isSafeInteger(uid) &&
Number.isSafeInteger(gid)
) {
fchownSync(fd, uid, gid);
}
fsyncSync(fd);
closeSync(fd);
fd = undefined;
renameSync(temporary, path);
fsyncDirectory(parent);
const parentAfter = lstatSync(parent);
if (
parentBefore.dev !== parentAfter.dev ||
parentBefore.ino !== parentAfter.ino ||
realpathSync(parent) !== parent
) {
fail("soul_parent_changed_during_replace");
}
const readback = readSafeRegular(path, { maxBytes: 64 * 1024 });
if (
sha256(readback.bytes) !== sha256(bytes) ||
(readback.stat.mode & 0o777) !== mode ||
readback.stat.uid !== uid ||
readback.stat.gid !== gid
)
fail("soul_write_readback_mismatch");
return readback;
} finally {
if (fd !== undefined) closeSync(fd);
if (existsSync(temporary)) unlinkSync(temporary);
}
}
function atomicJson(path, value, mode = STATE_FILE_MODE) {
const bytes = Buffer.from(`${JSON.stringify(value)}\n`, "utf8");
const parent = dirname(path);
assertSharedStateDirectory(parent);
const temporary = join(parent, `.${process.pid}.${randomUUID()}.tmp`);
let fd;
try {
fd = openSync(
temporary,
constants.O_WRONLY |
constants.O_CREAT |
constants.O_EXCL |
constants.O_NOFOLLOW,
mode
);
writeFileSync(fd, bytes);
fchmodSync(fd, mode);
fsyncSync(fd);
closeSync(fd);
fd = undefined;
renameSync(temporary, path);
fsyncDirectory(parent);
const readback = readSharedStateFile(path, parent, JOURNAL_LIMIT);
if (sha256(readback.bytes) !== sha256(bytes))
fail("soul_state_write_readback_mismatch");
} finally {
if (fd !== undefined) closeSync(fd);
if (existsSync(temporary)) unlinkSync(temporary);
}
}
function readJson(path, maxBytes = JOURNAL_LIMIT) {
if (!existsSync(path)) return null;
try {
return JSON.parse(
readSharedStateFile(path, dirname(path), maxBytes).bytes.toString("utf8")
);
} catch (error) {
if (error instanceof FlySoulBridgeError) throw error;
fail("soul_metadata_rejected");
}
}
function validProofMarker(marker, profileId) {
return (
marker &&
Object.keys(marker).sort().join(",") ===
"effectiveSha256,generation,profileId,rawFileSha256,schemaVersion,state" &&
marker.schemaVersion === SOUL_PROOF_SCHEMA_VERSION &&
marker.state === SOUL_PROOF_STATE_PROVEN &&
marker.profileId === profileId &&
SHA256.test(marker.rawFileSha256 ?? "") &&
SHA256.test(marker.effectiveSha256 ?? "") &&
typeof marker.generation === "string" &&
marker.generation.length >= 8 &&
marker.generation.length <= 128
);
}
export function readSoulProofMarker(
profileId,
{ dataRoot = SOUL_BRIDGE_DATA_ROOT } = {}
) {
const paths = soulProfilePaths(profileId, { dataRoot });
const marker = readJson(paths.proofPath, 8 * 1024);
return validProofMarker(marker, profileId)
? Object.freeze({ ...marker })
: null;
}
function writeProofMarker(
paths,
{ rawFileSha256, effectiveSha256, generation }
) {
const marker = {
schemaVersion: SOUL_PROOF_SCHEMA_VERSION,
state: SOUL_PROOF_STATE_PROVEN,
profileId: paths.profileId,
rawFileSha256,
effectiveSha256,
generation,
};
atomicJson(paths.proofPath, marker);
return marker;
}
function markUnproven(paths, prior = null) {
atomicJson(paths.proofPath, {
schemaVersion: SOUL_PROOF_SCHEMA_VERSION,
state: SOUL_PROOF_STATE_UNPROVEN,
profileId: paths.profileId,
rawFileSha256: SHA256.test(prior?.rawFileSha256 ?? "")
? prior.rawFileSha256
: null,
effectiveSha256: SHA256.test(prior?.effectiveSha256 ?? "")
? prior.effectiveSha256
: null,
generation: typeof prior?.generation === "string" ? prior.generation : null,
});
}
function runInstalledSoulLoader(
paths,
{ python = PINNED_HERMES_PYTHON, environment }
) {
const source = [
"import hashlib, json, sys",
"from hermes_constants import get_hermes_home",
"from agent.prompt_builder import load_soul_md",
"value = load_soul_md()",
"if not isinstance(value, str) or not value: raise RuntimeError('soul_loader_empty')",
"raw = value.encode('utf-8')",
"print(json.dumps({'effectiveSha256': hashlib.sha256(raw).hexdigest(), 'byteLength': len(raw), 'path': str(get_hermes_home() / 'SOUL.md')}))",
].join("\n");
const result = spawnSync(python, ["-c", source], {
encoding: "utf8",
env: environment,
timeout: 25_000,
maxBuffer: 16 * 1024,
});
if (result.status !== 0) fail("soul_loader_proof_failed");
let parsed;
try {
parsed = JSON.parse(result.stdout);
} catch {
fail("soul_loader_proof_failed");
}
if (
parsed.path !== paths.soulPath ||
!SHA256.test(parsed.effectiveSha256 ?? "") ||
!Number.isSafeInteger(parsed.byteLength) ||
parsed.byteLength < 1
) {
fail("soul_loader_proof_failed");
}
return parsed;
}
function offlineEffectiveLoader(paths) {
return runInstalledSoulLoader(paths, {
environment: {
HOME: paths.dataRoot,
HERMES_HOME: paths.profileRoot,
SELLABLE_HERMES_PROFILE_ID: paths.profileId,
PYTHONDONTWRITEBYTECODE: "1",
PATH: "/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin",
},
});
}
function gatewayEffectiveLoader(
gateway,
{ procRoot, dataRoot, pinnedHermesLauncher, pinnedHermesPython }
) {
return (paths) => {
const result = runInstalledSoulLoader(paths, {
python: pinnedHermesPython,
environment: {
HOME: paths.dataRoot,
// The attested Gateway argv is `hermes --profile <profileId>`. Hermes'
// CLI pre-parser turns that into this exact profile home before its
// prompt modules load. Supply the already-proven result directly so a
// SOUL read executes the installed `load_soul_md()` without importing
// the entire CLI command graph on every dashboard refresh.
HERMES_HOME: paths.profileRoot,
HERMES_PROFILE: gateway.environment.HERMES_PROFILE,
SELLABLE_HERMES_PROFILE_ID:
gateway.environment.SELLABLE_HERMES_PROFILE_ID,
HERMES_CONFIG_PATH: gateway.environment.HERMES_CONFIG_PATH,
SELLABLE_AGENT_RUNTIME: gateway.environment.SELLABLE_AGENT_RUNTIME,
PYTHONDONTWRITEBYTECODE: "1",
PATH: "/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin",
},
});
revalidateLiveHermesGateway(gateway, {
procRoot,
dataRoot,
pinnedHermesLauncher,
pinnedHermesPython,
});
return result;
};
}
function proveCanonical(paths, raw, effectiveLoader) {
const effective = effectiveLoader(paths);
const validated = assertSoulArtifact(raw.bytes, {
enforceEffectiveLimit: false,
});
if (
effective.path !== paths.soulPath ||
effective.effectiveSha256 !== validated.effectiveSha256 ||
effective.byteLength !== validated.effectiveByteLength
) {
fail("soul_loader_effective_mismatch");
}
return {
rawFileSha256: validated.rawFileSha256,
effectiveSha256: effective.effectiveSha256,
};
}
function safelyRemoveLegacyRoot(paths) {
if (!existsSync(paths.legacyRootSoulPath)) return null;
let legacy;
try {
legacy = readSafeRegular(paths.legacyRootSoulPath, {
maxBytes: 64 * 1024,
});
} catch (error) {
if (error?.code !== "EACCES" && error?.code !== "EPERM") throw error;
const blocked = lstatSync(paths.legacyRootSoulPath);
if (
blocked.isSymbolicLink() ||
!blocked.isFile() ||
blocked.nlink !== 1 ||
blocked.size > 64 * 1024 ||
(blocked.mode & 0o022) !== 0 ||
!safeSharedOwner(blocked.uid)
) {
fail("soul_legacy_root_rejected");
}
// The exact live Gateway and canonical profile SOUL were already proven
// before this cleanup runs. A root-owned 0600 legacy copy cannot be read
// by the deliberately de-privileged bridge, but the Hermes-owned data
// directory still permits removing that non-authoritative entry safely.
try {
unlinkSync(paths.legacyRootSoulPath);
fsyncDirectory(paths.dataRoot);
} catch (unlinkError) {
if (unlinkError?.code !== "EACCES" && unlinkError?.code !== "EPERM") {
throw unlinkError;
}
}
return null;
}
if ((legacy.stat.mode & 0o022) !== 0 || !safeSharedOwner(legacy.stat.uid))
fail("soul_legacy_root_rejected");
const digest = sha256(legacy.bytes);
try {
unlinkSync(paths.legacyRootSoulPath);
fsyncDirectory(paths.dataRoot);
return { legacyRootSha256: digest, legacyRootRemovalPending: false };
} catch (error) {
if (error?.code === "EACCES" || error?.code === "EPERM") {
return { legacyRootSha256: digest, legacyRootRemovalPending: true };
}
throw error;
}
}
function writeJournal(paths, journal) {
atomicJson(paths.journalPath, journal, STATE_FILE_MODE);
}
function canonicalBase64(value) {
if (
typeof value !== "string" ||
value.length < 1 ||
value.length > 88 * 1024 ||
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(
value
)
) {
return null;
}
const bytes = Buffer.from(value, "base64");
return bytes.toString("base64") === value ? bytes : null;
}
function validJournal(journal, paths) {
const prior = canonicalBase64(journal?.priorBase64);
const next = canonicalBase64(journal?.nextBase64);
return (
journal &&
journal.schemaVersion === SOUL_JOURNAL_SCHEMA_VERSION &&
journal.profileId === paths.profileId &&
["prepared", "renamed", "proven", "acked"].includes(journal.phase) &&
typeof journal.generation === "string" &&
journal.generation.length >= 8 &&
journal.generation.length <= 128 &&
SHA256.test(journal.priorRawFileSha256 ?? "") &&
SHA256.test(journal.nextRawFileSha256 ?? "") &&
prior &&
next &&
prior.byteLength > 0 &&
prior.byteLength <= 64 * 1024 &&
next.byteLength > 0 &&
next.byteLength <= 64 * 1024 &&
sha256(prior) === journal.priorRawFileSha256 &&
sha256(next) === journal.nextRawFileSha256 &&
[0o600, 0o640].includes(journal.priorMode) &&
Number.isSafeInteger(journal.priorUid) &&
journal.priorUid >= 0 &&
Number.isSafeInteger(journal.priorGid) &&
journal.priorGid >= 0 &&
(journal.priorMarker === null ||
validProofMarker(journal.priorMarker, paths.profileId)) &&
(journal.phase === "prepared" ||
journal.phase === "renamed" ||
SHA256.test(journal.effectiveSha256 ?? ""))
);
}
function removeJournal(paths) {
if (!existsSync(paths.journalPath)) return;
assertSharedStateDirectory(paths.journalRoot);
unlinkSync(paths.journalPath);
fsyncDirectory(paths.journalRoot);
}
function rollback(paths, journal, effectiveLoader) {
try {
const restored = atomicReplace(
paths.soulPath,
Buffer.from(journal.priorBase64, "base64"),
{
mode: journal.priorMode,
uid: journal.priorUid,
gid: journal.priorGid,
}
);
const proof = proveCanonical(paths, restored, effectiveLoader);
const priorMarker = journal.priorMarker;
if (
validProofMarker(priorMarker, paths.profileId) &&
priorMarker.rawFileSha256 === proof.rawFileSha256 &&
priorMarker.effectiveSha256 === proof.effectiveSha256
) {
atomicJson(paths.proofPath, priorMarker);
} else {
writeProofMarker(paths, {
...proof,
generation: `rollback-${journal.generation}`,
});
}
removeJournal(paths);
return {
ok: false,
code: "apply_failed_rolled_back",
rawFileSha256: proof.rawFileSha256,
};
} catch {
markUnproven(paths, journal.priorMarker);
let observedRawFileSha256 = null;
try {
observedRawFileSha256 = sha256(readCanonical(paths).bytes);
} catch {
// The unproven marker is the durable terminal when even readback fails.
}
return {
ok: false,
code: "rollback_unproven",
observedRawFileSha256,
};
}
}
function recoverJournal(paths, acknowledgedGeneration, effectiveLoader) {
const journal = readJson(paths.journalPath);
if (!journal) return { pending: null };
if (!validJournal(journal, paths)) fail("soul_journal_rejected");
let current = readCanonical(paths);
let currentHash = sha256(current.bytes);
if (journal.phase === "acked") {
if (currentHash !== journal.nextRawFileSha256)
fail("soul_recovery_conflict");
removeJournal(paths);
return { pending: null };
}
if (journal.phase === "prepared") {
if (currentHash === journal.priorRawFileSha256) {
removeJournal(paths);
return { pending: null };
}
if (currentHash !== journal.nextRawFileSha256)
fail("soul_recovery_conflict");
journal.phase = "renamed";
writeJournal(paths, journal);
}
if (journal.phase === "renamed") {
if (currentHash === journal.priorRawFileSha256) {
removeJournal(paths);
return { pending: null };
}
if (currentHash !== journal.nextRawFileSha256)
fail("soul_recovery_conflict");
try {
const proof = proveCanonical(paths, current, effectiveLoader);
journal.phase = "proven";
journal.effectiveSha256 = proof.effectiveSha256;
writeProofMarker(paths, { ...proof, generation: journal.generation });
writeJournal(paths, journal);
} catch {
return { terminal: rollback(paths, journal, effectiveLoader) };
}
}
current = readCanonical(paths);
currentHash = sha256(current.bytes);
if (currentHash !== journal.nextRawFileSha256) fail("soul_recovery_conflict");
const marker = readJson(paths.proofPath, 8 * 1024);
if (
!validProofMarker(marker, paths.profileId) ||
marker.generation !== journal.generation ||
marker.rawFileSha256 !== journal.nextRawFileSha256
) {
// Phase 207 execution 2f07cd28 retained a proven save journal after a
// later loader failure invalidated the marker. Recover from the actual
// canonical file and installed loader, never from journal claims alone.
const proof = proveCanonical(paths, current, effectiveLoader);
if (proof.effectiveSha256 !== journal.effectiveSha256)
fail("soul_recovery_conflict");
writeProofMarker(paths, { ...proof, generation: journal.generation });
}
if (acknowledgedGeneration === journal.generation) {
journal.phase = "acked";
writeJournal(paths, journal);
removeJournal(paths);
return { pending: null };
}
return { pending: journal };
}
function assertRequest(request) {
if (!request || typeof request !== "object" || Array.isArray(request)) {
fail("soul_request_rejected");
}
if (request.operation === "read") {
const allowed = new Set(["operation", "acknowledgedGeneration"]);
if (Object.keys(request).some((key) => !allowed.has(key)))
fail("soul_request_rejected");
} else if (request.operation === "save") {
const allowed = new Set([
"operation",
"content",
"expectedRawFileSha256",
"acknowledgedGeneration",
]);
if (
Object.keys(request).some((key) => !allowed.has(key)) ||
typeof request.content !== "string" ||
!SHA256.test(request.expectedRawFileSha256 ?? "")
) {
fail("soul_request_rejected");
}
} else {
fail("soul_operation_rejected");
}
if (
request.acknowledgedGeneration !== undefined &&
(typeof request.acknowledgedGeneration !== "string" ||
request.acknowledgedGeneration.length < 8 ||
request.acknowledgedGeneration.length > 128)
) {
fail("soul_request_rejected");
}
return request;
}
function executeLocked(
request,
paths,
effectiveLoader,
{ gatewayProven = false, allowLegacyRemoval = false } = {}
) {
const recovery = recoverJournal(
paths,
request.acknowledgedGeneration,
effectiveLoader
);
if (recovery.terminal) return { ...recovery.terminal, gatewayProven };
if (request.operation === "read") {
const raw = readCanonical(paths);
const validated = validateSoulArtifact(raw.bytes, {
enforceEffectiveLimit: false,
});
if (!validated.ok) {
markUnproven(paths);
return {
ok: false,
code: "live_invalid_unmirrorable",
validationCode: validated.code,
rawFileSha256: sha256(raw.bytes),
byteLength: raw.bytes.byteLength,
proofStatus: SOUL_PROOF_STATE_UNPROVEN,
gatewayProven,
};
}
let proof;
try {
proof = proveCanonical(paths, raw, effectiveLoader);
} catch (error) {
if (
error instanceof FlySoulBridgeError &&
error.code === "soul_loader_effective_mismatch"
) {
markUnproven(paths);
return {
ok: false,
code: "live_invalid_unmirrorable",
validationCode: error.code,
rawFileSha256: validated.rawFileSha256,
byteLength: validated.byteLength,
proofStatus: SOUL_PROOF_STATE_UNPROVEN,
gatewayProven,
};
}
// An unavailable loader supplies no new integrity evidence. Return its
// failure without erasing an earlier proof; the gateway still checks
// the canonical file hash, and the next read must prove the loader again.
throw error;
}
const marker = readJson(paths.proofPath, 8 * 1024);
const generation =
recovery.pending?.generation ??
(validProofMarker(marker, paths.profileId) &&
marker.rawFileSha256 === proof.rawFileSha256
? marker.generation
: `observe-${randomUUID()}`);
writeProofMarker(paths, { ...proof, generation });
const legacyRoot = allowLegacyRemoval
? safelyRemoveLegacyRoot(paths)
: null;
return {
ok: true,
status: recovery.pending ? "proven_pending_mirror" : "proven",
content: validated.content,
rawFileSha256: proof.rawFileSha256,
effectiveSha256: proof.effectiveSha256,
byteLength: validated.byteLength,
characterCount: validated.characterCount,
generation,
pendingMirror: Boolean(recovery.pending),
gatewayProven,
observedAt: new Date().toISOString(),
...(legacyRoot ?? {}),
};
}
const next = assertSoulArtifact(request.content);
const current = readCanonical(paths);
const currentValidation = validateSoulArtifact(current.bytes, {
enforceEffectiveLimit: false,
});
if (!currentValidation.ok) {
markUnproven(paths);
return {
ok: false,
code: "live_invalid_unmirrorable",
validationCode: currentValidation.code,
rawFileSha256: sha256(current.bytes),
byteLength: current.bytes.byteLength,
proofStatus: SOUL_PROOF_STATE_UNPROVEN,
gatewayProven,
};
}
if (recovery.pending) {
if (next.rawFileSha256 !== recovery.pending.nextRawFileSha256) {
return {
ok: false,
code: "soul_mirror_backlog",
observedRawFileSha256: currentValidation.rawFileSha256,
generation: recovery.pending.generation,
gatewayProven,
};
}
return {
ok: true,
status: "applied_pending_mirror",
rawFileSha256: currentValidation.rawFileSha256,
effectiveSha256: recovery.pending.effectiveSha256,
generation: recovery.pending.generation,
pendingMirror: true,
gatewayProven,
};
}
if (currentValidation.rawFileSha256 !== request.expectedRawFileSha256) {
return {
ok: false,
code: "soul_conflict",
observedRawFileSha256: currentValidation.rawFileSha256,
gatewayProven,
};
}
if (next.rawFileSha256 === currentValidation.rawFileSha256) {
const proof = proveCanonical(paths, current, effectiveLoader);
const marker = readJson(paths.proofPath, 8 * 1024);
const generation =
validProofMarker(marker, paths.profileId) &&
marker.rawFileSha256 === proof.rawFileSha256
? marker.generation
: `noop-${randomUUID()}`;
writeProofMarker(paths, { ...proof, generation });
return {
ok: true,
status: "noop",
...proof,
generation,
pendingMirror: false,
gatewayProven,
};
}
const generation = randomUUID();
const priorMarker = readJson(paths.proofPath, 8 * 1024);
const journal = {
schemaVersion: SOUL_JOURNAL_SCHEMA_VERSION,
profileId: paths.profileId,
generation,
phase: "prepared",
priorRawFileSha256: currentValidation.rawFileSha256,
nextRawFileSha256: next.rawFileSha256,
priorBase64: current.bytes.toString("base64"),
nextBase64: next.bytes.toString("base64"),
priorMode: current.stat.mode & 0o777,
priorUid: current.stat.uid,
priorGid: current.stat.gid,
priorMarker: validProofMarker(priorMarker, paths.profileId)
? priorMarker
: null,
effectiveSha256: null,
};
writeJournal(paths, journal);
const replaced = atomicReplace(paths.soulPath, next.bytes, {
mode: current.stat.mode & 0o777,
uid: current.stat.uid,
gid: current.stat.gid,
});
journal.phase = "renamed";
writeJournal(paths, journal);
let proof;
try {
proof = proveCanonical(paths, replaced, effectiveLoader);
} catch {
return { ...rollback(paths, journal, effectiveLoader), gatewayProven };
}
journal.phase = "proven";
journal.effectiveSha256 = proof.effectiveSha256;
writeProofMarker(paths, { ...proof, generation });
writeJournal(paths, journal);
const legacyRoot = allowLegacyRemoval ? safelyRemoveLegacyRoot(paths) : null;
return {
ok: true,
status: "applied_pending_mirror",
...proof,
generation,
pendingMirror: true,
gatewayProven,
...(legacyRoot ?? {}),
};
}
/** Internal/test seam. Browser/MCP callers only reach the fixed read/save CLI. */
function executeSoulBridgeOperationWithPolicy(
request,
{
profileId = process.env.SELLABLE_HERMES_PROFILE_ID,
dataRoot = SOUL_BRIDGE_DATA_ROOT,
effectiveLoader = offlineEffectiveLoader,
lockHeld = false,
} = {},
{
gatewayProven = false,
allowLegacyRemoval = false,
beforeOperation = null,
} = {}
) {
assertRequest(request);
const id = assertProfileId(profileId);
if (beforeOperation !== null && typeof beforeOperation !== "function") {
fail("soul_gateway_preflight_rejected");
}
const run = (paths) => {
beforeOperation?.(paths);
return executeLocked(request, paths, effectiveLoader, {
gatewayProven: gatewayProven === true,
allowLegacyRemoval: gatewayProven === true && allowLegacyRemoval === true,
});
};
if (lockHeld) return run(soulProfilePaths(id, { dataRoot }));
return withProfileSoulLock(id, run, { dataRoot });
}
export function executeSoulBridgeOperation(request, options = {}) {
return executeSoulBridgeOperationWithPolicy(request, options, {
gatewayProven: false,
allowLegacyRemoval: false,
});
}
export function executeExternalSoulBridgeOperation(
request,
{
procRoot = "/proc",
dataRoot = SOUL_BRIDGE_DATA_ROOT,
pinnedHermesLauncher = PINNED_HERMES_LAUNCHER,
pinnedHermesPython = PINNED_HERMES_PYTHON,
callerProfileId = /** @type {string | null | undefined} */ (
process.env.SELLABLE_HERMES_PROFILE_ID
),
effectiveLoader:
injectedEffectiveLoader = /** @type {((paths: any, gateway?: any) => any) | null} */ (
null
),
} = {}
) {
const gateway = discoverLiveHermesGateway({
procRoot,
dataRoot,
pinnedHermesLauncher,
pinnedHermesPython,
});
if (callerProfileId && callerProfileId !== gateway.profileId) {
fail("soul_gateway_caller_profile_mismatch");
}
const revalidate = () =>
revalidateLiveHermesGateway(gateway, {
procRoot,
dataRoot,
pinnedHermesLauncher,
pinnedHermesPython,
});
let effectiveLoader;
if (injectedEffectiveLoader === null) {
effectiveLoader = gatewayEffectiveLoader(gateway, {
procRoot,
dataRoot,
pinnedHermesLauncher,
pinnedHermesPython,
});
} else {
if (typeof injectedEffectiveLoader !== "function") {
fail("soul_gateway_loader_rejected");
}
effectiveLoader = (paths) => {
const result = injectedEffectiveLoader(paths, gateway);
revalidate();
return result;
};
}
const result = executeSoulBridgeOperationWithPolicy(
request,
{
profileId: gateway.profileId,
dataRoot,
effectiveLoader,
},
{
gatewayProven: true,
allowLegacyRemoval: true,
beforeOperation: revalidate,
}
);
return Object.freeze({
...result,
profileId: gateway.profileId,
gatewayPid: gateway.pid,
gatewayStartTicks: gateway.startTicks,
});
}
/** Lifecycle-only proof initializer; deliberately omits SOUL content. */
export function observeProfileSoul(
profileId,
{
dataRoot = SOUL_BRIDGE_DATA_ROOT,
effectiveLoader = offlineEffectiveLoader,
} = {}
) {
const result = executeSoulBridgeOperationWithPolicy(
{ operation: "read" },
{
profileId,
dataRoot,
effectiveLoader,
},
{
gatewayProven: false,
allowLegacyRemoval: false,
}
);
const { content: _content, ...receipt } = result;
return Object.freeze(receipt);
}
export async function runSoulBridgeCli() {
try {
if (process.argv.length !== 2) fail("soul_request_rejected");
const chunks = [];
let size = 0;
for await (const chunk of process.stdin) {
size += chunk.length;
if (size > SOUL_BRIDGE_REQUEST_LIMIT) fail("soul_request_too_large");
chunks.push(chunk);
}
let request;
try {
request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
} catch {
fail("soul_request_rejected");
}
const result = executeExternalSoulBridgeOperation(request);
process.stdout.write(`${JSON.stringify(result)}\n`);
process.exitCode = result.ok ? 0 : 2;
} catch (error) {
const code =
error instanceof FlySoulBridgeError ||
error instanceof SoulArtifactValidationError
? error.code
: "soul_bridge_failed";
process.stdout.write(`${JSON.stringify({ ok: false, code })}\n`);
process.exitCode = 2;
}
}
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : "";
if (invokedPath === resolve(fileURLToPath(import.meta.url))) {
await runSoulBridgeCli();
}