UNPKG

@sellable/install

Version:

One-command installer for Sellable MCP in Claude Code, Codex, and Hermes

786 lines (752 loc) • 27 kB
#!/usr/bin/env node import { spawnSync } from "node:child_process"; import { createHash, randomUUID } from "node:crypto"; import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs"; import { isAbsolute, join } from "node:path"; import { SOUL_BRIDGE_DATA_ROOT, discoverLiveHermesGateway, revalidateLiveHermesGateway, soulProfilePaths, withProfileSoulLock, } from "./fly-soul-bridge.mjs"; import { markHermesSnapshotDirty } from "./hermes-memory-snapshot.mjs"; export const CAPABILITIES_BRIDGE_SCHEMA_VERSION = "sellable-agent-capabilities-bridge/v1"; export const CAPABILITIES_PROOF_SCHEMA_VERSION = "sellable-agent-capabilities-proof/v1"; const CAPABILITIES_CONFIG_AUTHORITY_SCHEMA_VERSION = "sellable-agent-capabilities-config-authority/v1"; export const CAPABILITIES_REQUEST_MAX_BYTES = 16 * 1024; export const SELLABLE_BROWSER_TOOLSET_DEFAULT_MARKER = ".sellable-agent/browser-toolset-default-v1"; export const SELLABLE_DEFAULT_SLACK_TOOLSETS = Object.freeze([ "browser", "clarify", "memory", "terminal", "todo", ]); // Deliberately excludes credential-bearing, external-account, platform-admin, // media-generation, and default-off toolsets. These are the already-bundled // core capabilities a Sellable Agent operator may narrow or restore for Slack. export const SELLABLE_ALLOWED_TOOLSETS = Object.freeze([ "browser", "clarify", "code_execution", "context_engine", "cronjob", "delegation", "file", "memory", "session_search", "skills", "terminal", "todo", ]); export function sellableSlackToolsetsForMaterialization( config, { browserDefaultApplied = false } = {} ) { const configured = config?.platform_toolsets?.slack; const source = Array.isArray(configured) ? configured : SELLABLE_DEFAULT_SLACK_TOOLSETS; const allowed = new Set(SELLABLE_ALLOWED_TOOLSETS); const selected = new Set( source.filter((name) => typeof name === "string" && allowed.has(name)) ); // Legacy profiles predate a browser toggle, so absence cannot represent an // operator decision. Enable it exactly once; the marker then makes later // explicit disablement durable across image/profile refreshes. if (!browserDefaultApplied) selected.add("browser"); return [...selected].sort(); } const ALLOWED_TOOLSETS = new Set(SELLABLE_ALLOWED_TOOLSETS); const PROFILE_ID = /^[a-z0-9][a-z0-9_-]{0,127}$/; const BOUND_ID = /^[A-Za-z0-9_-]{1,128}$/; const TOOLSET_NAME = /^[a-z][a-z0-9_]{0,127}$/; const TOOL_NAME = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/; const SHA256 = /^[a-f0-9]{64}$/; const VERSION = /^[0-9A-Za-z][0-9A-Za-z.+-]{0,127}$/; const PYTHON = "/opt/hermes/.venv/bin/python3"; const EXPECTED_PACKAGES = Object.freeze({ sellable: "@sellable/mcp", "sellable-admin": "@sellable/admin-mcp", }); /** * @typedef {{ * enumerate: (input: any) => any, * toggle: (input: any) => any, * }} CapabilitiesNativeAdapter */ /** * @typedef {{ * profileId?: string, * dataRoot?: string, * nativeAdapter?: CapabilitiesNativeAdapter | null, * packageSpecs?: Record<string, string> | null, * markDirty?: (profileRoot: string) => void, * lockHeld?: boolean, * }} CapabilitiesBridgeOptions */ const sha256 = (value) => createHash("sha256").update(value).digest("hex"); const stableJson = (value) => { if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`; if (value && typeof value === "object") { return `{${Object.keys(value) .sort() .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`) .join(",")}}`; } return JSON.stringify(value); }; export class FlyCapabilitiesBridgeError extends Error { constructor(code) { super(code); this.name = "FlyCapabilitiesBridgeError"; this.code = code; } } function fail(code) { throw new FlyCapabilitiesBridgeError(code); } function regularFile(path, maxBytes = 2 * 1024 * 1024) { const link = lstatSync(path); if ( link.isSymbolicLink() || !link.isFile() || link.nlink !== 1 || link.size > maxBytes ) { fail("capabilities_config_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 !== link.size ) { fail("capabilities_config_changed"); } return readFileSync(fd); } finally { closeSync(fd); } } function assertExactKeys(value, allowed) { if (!value || typeof value !== "object" || Array.isArray(value)) fail("capabilities_request_rejected"); if (Object.keys(value).some((key) => !allowed.has(key))) fail("capabilities_request_rejected"); } function expectedAliases(kind) { return kind === "ADMIN" ? ["sellable", "sellable-admin"] : ["sellable"]; } function assertRequest(request) { const common = ["operation", "expectedRuntimeKind", "expectedWorkspaceId"]; const shapes = { list: common, toggle_tool: [...common, "toolsetName", "enabled", "configSha256"], test_mcp: [...common, "serverName"], }; const keys = shapes[request?.operation]; if (!keys) fail("capabilities_operation_rejected"); assertExactKeys(request, new Set(keys)); if (!["CUSTOMER", "ADMIN"].includes(request.expectedRuntimeKind)) fail("capabilities_runtime_kind_rejected"); if (!BOUND_ID.test(request.expectedWorkspaceId ?? "")) fail("capabilities_workspace_rejected"); if (request.operation === "toggle_tool") { if ( !TOOLSET_NAME.test(request.toolsetName ?? "") || !ALLOWED_TOOLSETS.has(request.toolsetName) ) fail("capabilities_toolset_rejected"); if ( typeof request.enabled !== "boolean" || !SHA256.test(request.configSha256 ?? "") ) fail("capabilities_cas_rejected"); } if ( request.operation === "test_mcp" && !expectedAliases(request.expectedRuntimeKind).includes(request.serverName) ) fail("capabilities_mcp_server_rejected"); return request; } function parsePackageSpec(spec, expectedName) { if (typeof spec !== "string") fail("capabilities_package_identity_rejected"); const delimiter = spec.lastIndexOf("@"); const name = spec.slice(0, delimiter); const version = spec.slice(delimiter + 1); if (name !== expectedName || !VERSION.test(version)) fail("capabilities_package_identity_rejected"); return { packageName: name, version }; } function boundedText(value, max) { return typeof value === "string" && Buffer.byteLength(value, "utf8") <= max; } function normalizeNative(native, request, packageSpecs) { if (!native || !Array.isArray(native.tools) || !Array.isArray(native.servers)) fail("capabilities_native_output_rejected"); const tools = native.tools.map((row) => { if ( !row || !ALLOWED_TOOLSETS.has(row.name) || !boundedText(row.label, 256) || !boundedText(row.description, 2048) || row.platform !== "slack" || typeof row.enabled !== "boolean" || typeof row.configured !== "boolean" || !Array.isArray(row.tools) || row.tools.length > 256 || row.tools.some((name) => !TOOL_NAME.test(name)) ) fail("capabilities_tool_inventory_rejected"); const names = [...new Set(row.tools)].sort(); if (names.length !== row.tools.length) fail("capabilities_tool_inventory_rejected"); return Object.freeze({ name: row.name, label: row.label, description: row.description, platform: "slack", enabled: row.enabled, available: row.enabled && row.configured, configured: row.configured, policy: "allowed", tools: Object.freeze(names), }); }); tools.sort((left, right) => left.name.localeCompare(right.name)); if ( stableJson(tools.map((tool) => tool.name)) !== stableJson([...SELLABLE_ALLOWED_TOOLSETS].sort()) ) fail("capabilities_tool_inventory_rejected"); const aliases = expectedAliases(request.expectedRuntimeKind); const serverConfigAuthority = new Map(); const mcpServers = native.servers.map((row) => { if ( !row || !aliases.includes(row.name) || row.enabled !== true || row.transport !== "stdio" || typeof row.workspaceLocked !== "boolean" || typeof row.requiresWorkspaceLock !== "boolean" || !SHA256.test(row.configAuthoritySha256 ?? "") || !["healthy", "unhealthy", "unknown"].includes(row.health) || !Array.isArray(row.tools) || row.tools.length > 1024 || row.tools.some((name) => !TOOL_NAME.test(name)) || !Array.isArray(row.configuredTools) || row.configuredTools.length > 1024 || row.configuredTools.some((name) => !TOOL_NAME.test(name)) || !Number.isSafeInteger(row.prompts) || row.prompts < 0 || !Number.isSafeInteger(row.resources) || row.resources < 0 || ![null, "probe_failed", "not_tested"].includes(row.errorCode) ) fail("capabilities_mcp_inventory_rejected"); const customerProductServer = request.expectedRuntimeKind === "CUSTOMER" && row.name === "sellable"; if ( (customerProductServer && (!row.workspaceLocked || !row.requiresWorkspaceLock)) || (request.expectedRuntimeKind === "ADMIN" && (row.workspaceLocked || row.requiresWorkspaceLock)) ) fail("capabilities_workspace_lock_rejected"); const probedNames = [...new Set(row.tools)].sort(); const configuredNames = [...new Set(row.configuredTools)].sort(); if ( probedNames.length !== row.tools.length || configuredNames.length !== row.configuredTools.length || (customerProductServer && configuredNames.length === 0) || (customerProductServer && probedNames.length > 0 && stableJson(probedNames) !== stableJson(configuredNames)) || (!customerProductServer && configuredNames.length > 0) ) fail("capabilities_mcp_inventory_rejected"); // A CUSTOMER Product MCP is policy-filtered from its live config. Outside // a signed turn Hermes can successfully probe that server while receiving // an empty tools/list, so the sealed, workspace-bound include list is the // authoritative inventory. ADMIN has no customer workspace lock and its // two direct live probes remain authoritative. const names = customerProductServer ? configuredNames : probedNames; serverConfigAuthority.set(row.name, row.configAuthoritySha256); return Object.freeze({ name: row.name, label: row.name === "sellable" ? "Sellable" : "Sellable Admin", ...parsePackageSpec(packageSpecs[row.name], EXPECTED_PACKAGES[row.name]), enabled: true, managed: true, readOnly: true, transport: "stdio", workspaceLocked: row.workspaceLocked, requiresWorkspaceLock: row.requiresWorkspaceLock, health: row.health, tools: Object.freeze(names), prompts: row.prompts, resources: row.resources, errorCode: row.errorCode, }); }); mcpServers.sort((left, right) => left.name.localeCompare(right.name)); if ( stableJson(mcpServers.map((server) => server.name)) !== stableJson(aliases) ) fail("capabilities_mcp_inventory_rejected"); // The CAS token describes effective Capabilities authority, not YAML bytes. // Hermes' native saver may normalize formatting or materialize defaults, so // hashing the file made an off -> on round trip semantically exact but hash- // unstable. Raw bytes are still fenced around enumeration in readInventory. const configSha256 = sha256( stableJson({ schemaVersion: CAPABILITIES_CONFIG_AUTHORITY_SCHEMA_VERSION, platform: "slack", tools: tools.map(({ name, enabled, configured }) => ({ name, enabled, configured, })), mcpServers: mcpServers.map( ({ name, enabled, transport, workspaceLocked, requiresWorkspaceLock, }) => ({ name, enabled, transport, workspaceLocked, requiresWorkspaceLock, configAuthoritySha256: serverConfigAuthority.get(name), }) ), }) ); const inventory = Object.freeze({ complete: true, configSha256, tools: Object.freeze(tools), mcpServers: Object.freeze(mcpServers), }); return Object.freeze({ ...inventory, inventorySha256: sha256(stableJson(inventory)), }); } const NATIVE_SCRIPT = String.raw` import hashlib, json, os, stat, sys request = json.loads(sys.stdin.read()) from hermes_cli.config import load_config from hermes_cli.mcp_config import _get_mcp_servers, _probe_single_server from hermes_cli.tools_config import CONFIGURABLE_TOOLSETS, _get_platform_tools, _save_platform_tools, _toolset_has_keys from toolsets import resolve_toolset def configured_customer_tools(name, server): if request["expectedRuntimeKind"] != "CUSTOMER" or name != "sellable": return [] env = server.get("env") if isinstance(server.get("env"), dict) else {} config_path = env.get("SELLABLE_CONFIG_PATH") expected_path = os.path.join(request["profileRoot"], "sellable-mcp.json") if config_path != expected_path: raise RuntimeError("managed_server_config_path") linked = os.lstat(config_path) if (not stat.S_ISREG(linked.st_mode) or linked.st_nlink != 1 or linked.st_size < 2 or linked.st_size > 2 * 1024 * 1024 or linked.st_mode & 0o077): raise RuntimeError("managed_server_config_file") descriptor = os.open(config_path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) try: opened = os.fstat(descriptor) if (not stat.S_ISREG(opened.st_mode) or opened.st_dev != linked.st_dev or opened.st_ino != linked.st_ino or opened.st_nlink != 1 or opened.st_size != linked.st_size): raise RuntimeError("managed_server_config_changed") content = os.read(descriptor, opened.st_size + 1) finally: os.close(descriptor) product = json.loads(content.decode("utf-8")) include = product.get("tools", {}).get("include") if (product.get("workspaceId") != request["expectedWorkspaceId"] or product.get("activeWorkspaceId") != request["expectedWorkspaceId"] or not isinstance(include, list) or not include or any(not isinstance(tool_name, str) for tool_name in include) or len(set(include)) != len(include)): raise RuntimeError("managed_server_config_identity") return sorted(include) def enumerate_capabilities(): allowed = request["allowedToolsets"] config = load_config() catalog = {name: (label, description) for name, label, description in CONFIGURABLE_TOOLSETS} enabled = _get_platform_tools(config, "slack", include_default_mcp_servers=False) tool_rows = [] for name in allowed: if name not in catalog: raise RuntimeError("missing_policy_toolset") label, description = catalog[name] label_parts = str(label).strip().split(None, 1) if len(label_parts) == 2 and not any(ch.isascii() and ch.isalnum() for ch in label_parts[0]): label = label_parts[1] tool_rows.append({ "name": name, "label": str(label), "description": str(description), "platform": "slack", "enabled": name in enabled, "configured": bool(_toolset_has_keys(name, config)), "tools": sorted(set(resolve_toolset(name, include_registry=False))), }) servers = _get_mcp_servers(config) expected_aliases = request["expectedAliases"] if sorted(servers.keys()) != sorted(expected_aliases): raise RuntimeError("managed_server_inventory") server_rows = [] for name in expected_aliases: server = servers[name] env = server.get("env") if isinstance(server.get("env"), dict) else {} details = {} try: found = _probe_single_server(name, server, connect_timeout=12, details=details) health = "healthy" tools = sorted(set(str(tool_name) for tool_name, _description in found)) prompts = int(details.get("prompts", 0) or 0) resources = int(details.get("resources", 0) or 0) error_code = None except BaseException: health, tools, prompts, resources, error_code = "unhealthy", [], 0, 0, "probe_failed" server_rows.append({ "name": name, "enabled": server.get("enabled", True) is not False, "transport": "http" if server.get("url") else "stdio" if server.get("command") else "unknown", "workspaceLocked": env.get("SELLABLE_LOCK_WORKSPACE_ID") == request["expectedWorkspaceId"], "requiresWorkspaceLock": str(env.get("SELLABLE_REQUIRE_WORKSPACE_LOCK", "")) == "1", "configAuthoritySha256": hashlib.sha256(json.dumps(server, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")).hexdigest(), "health": health, "tools": tools, "configuredTools": configured_customer_tools(name, server), "prompts": prompts, "resources": resources, "errorCode": error_code, }) return {"tools": tool_rows, "servers": server_rows} if request["operation"] == "enumerate": output = enumerate_capabilities() elif request["operation"] == "toggle": config = load_config() enabled = set(_get_platform_tools(config, "slack", include_default_mcp_servers=False)) if request["enabled"]: enabled.add(request["name"]) else: enabled.discard(request["name"]) _save_platform_tools(config, "slack", enabled) output = {"ok": True} else: raise RuntimeError("operation") sys.stdout.write(json.dumps(output, separators=(",", ":"))) `; function runNative(payload, paths, { python = PYTHON } = {}) { const child = spawnSync(python, ["-c", NATIVE_SCRIPT], { input: JSON.stringify(payload), encoding: "utf8", timeout: 30_000, maxBuffer: 4 * 1024 * 1024, env: { HOME: paths.dataRoot, HERMES_HOME: paths.profileRoot, HERMES_PROFILE: paths.profileId, SELLABLE_HERMES_PROFILE_ID: paths.profileId, SELLABLE_AGENT_RUNTIME: "1", PYTHONDONTWRITEBYTECODE: "1", PATH: "/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin", }, }); if (child.status !== 0 || child.signal || child.error) fail("capabilities_native_call_failed"); try { return JSON.parse(child.stdout); } catch { fail("capabilities_native_output_rejected"); } } function defaultAdapter(paths) { return { enumerate: ({ expectedAliases: aliases, expectedWorkspaceId, expectedRuntimeKind, probeServerName, }) => runNative( { operation: "enumerate", allowedToolsets: SELLABLE_ALLOWED_TOOLSETS, expectedAliases: aliases, expectedWorkspaceId, expectedRuntimeKind, profileRoot: paths.profileRoot, ...(probeServerName ? { probeServerName } : {}), }, paths ), toggle: ({ name, enabled }) => runNative({ operation: "toggle", name, enabled }, paths), }; } function runtimePackageSpecs(kind, provided) { if (provided) return provided; return kind === "ADMIN" ? { sellable: process.env.SELLABLE_AGENT_PRODUCT_MCP_PACKAGE, "sellable-admin": process.env.SELLABLE_AGENT_ADMIN_MCP_PACKAGE, } : { sellable: process.env.SELLABLE_AGENT_MCP_PACKAGE }; } function atomicProof(paths, inventory) { const root = join(paths.profileRoot, ".sellable-agent"); mkdirSync(root, { recursive: true, mode: 0o700 }); const path = join(root, "capabilities-proof.json"); const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`; writeFileSync( temporary, `${JSON.stringify({ schemaVersion: CAPABILITIES_PROOF_SCHEMA_VERSION, inventorySha256: inventory.inventorySha256, configSha256: inventory.configSha256, })}\n`, { mode: 0o600 } ); renameSync(temporary, path); } function readInventory(request, paths, adapter, packageSpecs) { const configPath = join(paths.profileRoot, "config.yaml"); const rawConfigSha256 = sha256(regularFile(configPath)); const aliases = expectedAliases(request.expectedRuntimeKind); const native = adapter.enumerate({ expectedAliases: aliases, expectedWorkspaceId: request.expectedWorkspaceId, expectedRuntimeKind: request.expectedRuntimeKind, ...(request.operation === "test_mcp" ? { probeServerName: request.serverName } : {}), }); if (sha256(regularFile(configPath)) !== rawConfigSha256) fail("capabilities_config_changed"); return normalizeNative(native, request, packageSpecs); } function executeLocked(request, paths, adapter, options) { const packageSpecs = runtimePackageSpecs( request.expectedRuntimeKind, options.packageSpecs ); const before = readInventory(request, paths, adapter, packageSpecs); if (request.operation === "list" || request.operation === "test_mcp") { atomicProof(paths, before); return { ok: true, schemaVersion: CAPABILITIES_BRIDGE_SCHEMA_VERSION, status: "proven", operation: request.operation, ...(request.operation === "test_mcp" ? { testedServer: request.serverName } : {}), ...before, }; } if (request.configSha256 !== before.configSha256) { return { ok: false, code: "capabilities_config_conflict", configSha256: before.configSha256, inventorySha256: before.inventorySha256, }; } const detail = before.tools.find((tool) => tool.name === request.toolsetName); if (!detail) fail("capabilities_toolset_rejected"); if (detail.enabled === request.enabled) { atomicProof(paths, before); return { ok: true, schemaVersion: CAPABILITIES_BRIDGE_SCHEMA_VERSION, status: "noop", operation: "toggle_tool", ...before, detail, }; } const configPath = join(paths.profileRoot, "config.yaml"); const configBackup = regularFile(configPath); const proofPath = join( paths.profileRoot, ".sellable-agent", "capabilities-proof.json" ); const proofBackup = existsSync(proofPath) ? regularFile(proofPath, 4096) : null; const rollback = () => { writeFileSync(configPath, configBackup, { mode: 0o600 }); if (proofBackup) writeFileSync(proofPath, proofBackup, { mode: 0o600 }); else rmSync(proofPath, { force: true }); }; try { const result = adapter.toggle({ name: request.toolsetName, enabled: request.enabled, platform: "slack", }); if (result?.ok !== true) fail("capabilities_native_refused"); const after = readInventory(request, paths, adapter, packageSpecs); const afterDetail = after.tools.find( (tool) => tool.name === request.toolsetName ); if ( !afterDetail || afterDetail.enabled !== request.enabled || after.configSha256 === before.configSha256 || stableJson(after.mcpServers.map((server) => server.name)) !== stableJson(before.mcpServers.map((server) => server.name)) ) fail("capabilities_readback_failed"); atomicProof(paths, after); options.markDirty(paths.profileRoot); return { ok: true, schemaVersion: CAPABILITIES_BRIDGE_SCHEMA_VERSION, status: "applied", operation: "toggle_tool", effectId: randomUUID(), ...after, detail: afterDetail, }; } catch (error) { rollback(); try { const restored = readInventory(request, paths, adapter, packageSpecs); if (restored.configSha256 !== before.configSha256) return { ok: false, code: "capabilities_rollback_unproven" }; } catch { return { ok: false, code: "capabilities_rollback_unproven" }; } return { ok: false, code: "capabilities_apply_failed_rolled_back", reason: error instanceof FlyCapabilitiesBridgeError ? error.code : "capabilities_bridge_failed", }; } } /** * @param {any} request * @param {CapabilitiesBridgeOptions} [options] */ export function executeCapabilitiesBridgeOperation( request, { profileId = process.env.SELLABLE_HERMES_PROFILE_ID, dataRoot = SOUL_BRIDGE_DATA_ROOT, nativeAdapter = null, packageSpecs = null, markDirty = markHermesSnapshotDirty, lockHeld = false, } = {} ) { assertRequest(request); if (typeof profileId !== "string" || !PROFILE_ID.test(profileId)) fail("capabilities_profile_id_rejected"); const paths = soulProfilePaths(profileId, { dataRoot }); const adapter = nativeAdapter ?? defaultAdapter(paths); if ( !adapter || typeof adapter.enumerate !== "function" || typeof adapter.toggle !== "function" ) fail("capabilities_native_adapter_rejected"); if (typeof markDirty !== "function") fail("capabilities_dirty_marker_rejected"); const run = () => executeLocked(request, paths, adapter, { packageSpecs, markDirty }); return lockHeld ? run() : withProfileSoulLock(profileId, run, { dataRoot }); } export function executeExternalCapabilitiesBridgeOperation( request, { procRoot = "/proc", dataRoot = SOUL_BRIDGE_DATA_ROOT, nativeAdapter = null, packageSpecs = null, markDirty = markHermesSnapshotDirty, callerProfileId = process.env.SELLABLE_HERMES_PROFILE_ID, } = {} ) { const gateway = discoverLiveHermesGateway({ procRoot, dataRoot }); if (callerProfileId && callerProfileId !== gateway.profileId) fail("capabilities_gateway_caller_profile_mismatch"); revalidateLiveHermesGateway(gateway, { procRoot, dataRoot }); const result = executeCapabilitiesBridgeOperation(request, { profileId: gateway.profileId, dataRoot, nativeAdapter, packageSpecs, markDirty, }); revalidateLiveHermesGateway(gateway, { procRoot, dataRoot }); return Object.freeze({ ...result, profileId: gateway.profileId, gatewayPid: gateway.pid, gatewayStartTicks: gateway.startTicks, }); } export function readCapabilitiesProofObservation(profileRoot) { try { if (!isAbsolute(profileRoot)) return null; const value = JSON.parse( regularFile( join( realpathSync(profileRoot), ".sellable-agent", "capabilities-proof.json" ), 4096 ).toString("utf8") ); if ( value.schemaVersion !== CAPABILITIES_PROOF_SCHEMA_VERSION || !SHA256.test(value.inventorySha256 ?? "") || !SHA256.test(value.configSha256 ?? "") || Object.keys(value).length !== 3 ) return null; return Object.freeze({ inventorySha256: value.inventorySha256, configSha256: value.configSha256, }); } catch { return null; } }