UNPKG

@sellable/install

Version:

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

824 lines (797 loc) • 27.9 kB
import { createHash, randomUUID } from "node:crypto"; import { chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs"; import { basename, dirname, isAbsolute, join } from "node:path"; import { withProfileSoulLock } from "./fly-soul-bridge.mjs"; import { ensureHermesNamedCron } from "./hermes-memory-snapshot.mjs"; export const DEFAULT_PROFILE_RECONCILE_RECEIPT = ".sellable-agent/default-profile-reconcile.json"; export const DEFAULT_PROFILE_RECONCILE_SCHEMA = "sellable-agent-default-profile-reconcile/v1"; const BUNDLE_SCHEMA = "sellable-agent-default-profile-bundle/v1"; const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/; const MANAGED_ID = /^[a-z0-9][a-z0-9-]{0,63}$/; const SHA256 = /^[a-f0-9]{64}$/; const FENCE = /^[1-9][0-9]{0,19}$/; const STAGE_PREFIX = ".sellable-default-stage-"; const MAX_BYTES = 1024 * 1024; const ADMIN_MATERIALIZER_ANCHOR = "skills/sellable-admin/SKILL.md"; const ADMIN_NAMESPACE_RECEIPT_SCHEMA = "sellable-admin-skill-namespace-receipt/v1"; // Immutable shared skill bytes shipped at 9878ec7a (@sellable/install 0.1.758). // A partial upgrade can leave these legacy copies outside its older receipt. // Operation d99cebfe proved that Hermes rejects their duplicate native names. // Only these known bytes, current bundle bytes, or receipt-proven bytes may // be retired; unknown customer content must still fail closed. const LEGACY_PUBLIC_SKILL_DIGESTS = Object.freeze({ "create-campaign": "0c1e7d514f6ff15c433fe6e9625c3b226ac9cd0add8e0590b10ca99ef0fa427b", "refill-sends": "2d0896a8024e0a3fee0ff8cf040ea6acdc8bca3292cddb48b41b4ac9f4b4065a", "campaign-daily-review": "2b0abf90d283835362d81bdcbd1759cbd2bc1048bddee3f2805466882f55320e", "campaign-daily-results": "bd12c54601285819cc70e54f0e41a04aba4a072664c1ce7a425d4be62188780c", "campaign-weekly-review": "09690edac84758ee3d874791d6235874577ac7d300192ef30328f003b641083d", }); const legacyManagedSkillPath = (entry) => `skills/sellable-defaults/${entry.id}/SKILL.md`; const canonicalPublicSkillPath = (entry) => `skills/sellable/${entry.nativeName}/SKILL.md`; const acceptedSkillPath = (entry) => entry.path === legacyManagedSkillPath(entry) || (entry.nativeName === `sellable-${entry.id}` && entry.path === canonicalPublicSkillPath(entry)); const sha256 = (value) => createHash("sha256").update(value).digest("hex"); const canonicalJson = (value) => { if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`; if (value && typeof value === "object") { return `{${Object.keys(value) .sort() .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`) .join(",")}}`; } return JSON.stringify(value); }; const refused = (code) => ({ ok: false, status: "REFUSED", code }); export function buildDefaultProfileProjection({ kind, claim, env = process.env, imageBundle, fixtureTarget, } = {}) { const operation = claim?.operation; const authority = claim?.runtimeAuthority; if ( authority?.bundleVersion === undefined && authority?.bundleDigest === undefined ) { return null; } const verifiedImage = imageBundle ?? { bundleVersion: Number( env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_VERSION ?? "" ), bundleDigest: env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST ?? "", }; if ( !["CUSTOMER", "ADMIN"].includes(kind) || !Number.isSafeInteger(verifiedImage.bundleVersion) || verifiedImage.bundleVersion < 1 || !SHA256.test(verifiedImage.bundleDigest) ) { throw new Error("default_profile_image_bundle_rejected"); } if ( !operation || !Number.isSafeInteger(authority?.authorityGeneration) || authority.authorityGeneration < 1 || authority.bundleVersion !== verifiedImage.bundleVersion || authority.bundleDigest !== verifiedImage.bundleDigest ) { throw new Error("default_profile_bundle_identity_rejected"); } const verifiedFixture = fixtureTarget ?? (env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_TEST_ONLY === "1" ? { testOnly: true, workspaceId: env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_WORKSPACE_ID ?? "", agentId: env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_AGENT_ID ?? "", } : undefined); const exactFixture = verifiedFixture?.testOnly === true && verifiedFixture.workspaceId === operation.workspaceId && verifiedFixture.agentId === operation.agentId ? { ...verifiedFixture } : undefined; return { kind, profileId: operation.profileId, workspaceId: operation.workspaceId, agentId: operation.agentId, operationId: operation.id, authorityGeneration: authority.authorityGeneration, fence: String(operation.fence), ...verifiedImage, ...(exactFixture ? { fixtureTarget: exactFixture } : {}), }; } export function bindDefaultProfileProbeCron({ activeProjection, profileRoot, job, bootId, } = {}) { if (!activeProjection?.fixtureTarget) return { changed: false }; if ( !isAbsolute(profileRoot ?? "") || !ID.test(bootId ?? "") || !ID.test(job?.id ?? "") || job.name !== "sellable-default-probe" ) { throw new Error("default_profile_cron_binding_rejected"); } const scriptPath = join(profileRoot, "scripts", "sellable-default-probe.sh"); const binding = { operationId: activeProjection.operationId, bootId, bundleDigest: activeProjection.bundleDigest, nonce: sha256( `${activeProjection.operationId}\0${bootId}\0${activeProjection.bundleDigest}` ).slice(0, 32), jobId: job.id, name: job.name, }; const script = `#!/bin/sh set -eu exec /opt/hermes/.venv/bin/python3 - ${JSON.stringify(profileRoot)} <<'PY' import json, os, sys, time root = sys.argv[1] binding = json.loads(${JSON.stringify(JSON.stringify(binding))}) binding["firedAt"] = int(time.time() * 1000) marker_root = os.path.join(root, ".sellable-agent", "cron-markers") os.makedirs(marker_root, mode=0o700, exist_ok=True) target = os.path.join(marker_root, binding["name"] + ".json") stage = target + ".tmp" with open(stage, "w", encoding="utf-8") as handle: json.dump(binding, handle, separators=(",", ":")) handle.write("\\n") os.chmod(stage, 0o600) os.replace(stage, target) PY `; const changed = !existsSync(scriptPath) || readFileSync(scriptPath, "utf8") !== script; if (changed) atomicWrite(scriptPath, script, 0o700); return { changed, binding }; } function readRegular(path, maxBytes = MAX_BYTES) { const link = lstatSync(path); if ( link.isSymbolicLink() || !link.isFile() || link.nlink !== 1 || link.size > maxBytes ) throw new Error("default_profile_file_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 ) { throw new Error("default_profile_file_rejected"); } return readFileSync(fd); } finally { closeSync(fd); } } function adminSkillNamespaceReceiptHash(profileRoot) { const namespaceRoot = join(profileRoot, "skills", "sellable-admin"); if ( !existsSync(namespaceRoot) || lstatSync(namespaceRoot).isSymbolicLink() || !lstatSync(namespaceRoot).isDirectory() || realpathSync(namespaceRoot) !== namespaceRoot ) { throw new Error("default_profile_admin_skill_namespace_rejected"); } const names = readdirSync(namespaceRoot).sort(); if (names.length < 1 || names.length > 64) { throw new Error("default_profile_admin_skill_namespace_rejected"); } let totalBytes = 0; const entries = names.map((name) => { if (!MANAGED_ID.test(name)) { throw new Error("default_profile_admin_skill_namespace_rejected"); } const directory = join(namespaceRoot, name); if ( lstatSync(directory).isSymbolicLink() || !lstatSync(directory).isDirectory() || realpathSync(directory) !== directory ) { throw new Error("default_profile_admin_skill_namespace_rejected"); } const relativePath = `skills/sellable-admin/${name}/SKILL.md`; const content = readRegular(join(directory, "SKILL.md"), 64 * 1024); totalBytes += content.byteLength; if (totalBytes > MAX_BYTES) { throw new Error("default_profile_admin_skill_namespace_rejected"); } return { path: relativePath, sha256: sha256(content) }; }); return sha256( canonicalJson({ schemaVersion: ADMIN_NAMESPACE_RECEIPT_SCHEMA, entries, }) ); } function materializerReceiptHash(profileRoot, path) { const target = join(profileRoot, ...path.split("/")); if (path === ADMIN_MATERIALIZER_ANCHOR && !existsSync(target)) { return adminSkillNamespaceReceiptHash(profileRoot); } return sha256(readRegular(target, 64 * 1024)); } export function buildDefaultProfileMaterializerReceipt({ profileId, profileRoot, materializerReceipts, } = {}) { const allowed = new Set([ "SOUL.md", "skills/sellable/SKILL.md", ADMIN_MATERIALIZER_ANCHOR, ]); if ( !ID.test(profileId ?? "") || !isAbsolute(profileRoot ?? "") || !Array.isArray(materializerReceipts) || materializerReceipts.length < 1 || materializerReceipts.length > allowed.size || new Set(materializerReceipts).size !== materializerReceipts.length || materializerReceipts.some((path) => !allowed.has(path)) ) { throw new Error("default_profile_materializer_receipt_rejected"); } return { profileId, fileHashes: Object.fromEntries( materializerReceipts.map((path) => [ path, materializerReceiptHash(profileRoot, path), ]) ), }; } export function verifyDefaultProfileMaterializerReceipt({ profileRoot, materializerReceipts, materializerReceipt, } = {}) { if ( !ID.test(materializerReceipt?.profileId ?? "") || !materializerReceipt?.fileHashes || Array.isArray(materializerReceipt.fileHashes) ) { return false; } try { const current = buildDefaultProfileMaterializerReceipt({ profileId: materializerReceipt.profileId, profileRoot, materializerReceipts, }); return materializerReceipts.every( (path) => SHA256.test(materializerReceipt.fileHashes[path] ?? "") && materializerReceipt.fileHashes[path] === current.fileHashes[path] ); } catch { return false; } } function atomicWrite(path, bytes, mode) { mkdirSync(dirname(path), { recursive: true, mode: 0o700 }); const stage = join( dirname(path), `${STAGE_PREFIX}${basename(path)}-${process.pid}-${randomUUID()}` ); writeFileSync(stage, bytes, { flag: "wx", mode }); chmodSync(stage, mode); const fd = openSync(stage, constants.O_RDONLY); try { fsyncSync(fd); } finally { closeSync(fd); } renameSync(stage, path); } function readBundle(bundleRoot) { if ( !isAbsolute(bundleRoot ?? "") || !existsSync(bundleRoot) || lstatSync(bundleRoot).isSymbolicLink() || !lstatSync(bundleRoot).isDirectory() || realpathSync(bundleRoot) !== bundleRoot ) { throw new Error("default_profile_bundle_rejected"); } const manifest = JSON.parse( readRegular(join(bundleRoot, "manifest.json")).toString("utf8") ); if ( manifest?.schemaVersion !== BUNDLE_SCHEMA || !["CUSTOMER", "ADMIN"].includes(manifest.kind) || !Number.isSafeInteger(manifest.bundleVersion) || manifest.bundleVersion < 1 || !SHA256.test(manifest.bundleDigest ?? "") || !Array.isArray(manifest.materializerReceipts) || !Array.isArray(manifest.files) ) { throw new Error("default_profile_bundle_rejected"); } const files = manifest.files.map((entry) => { const expectedPath = entry?.type === "cron" ? `crons/${entry?.name}.json` : null; if ( !MANAGED_ID.test(entry?.id ?? "") || !["skill", "cron"].includes(entry.type) || entry.ownership !== "system_managed" || !["SHARED", "CUSTOMER", "ADMIN", "FIXTURE"].includes(entry.kind) || entry.mode !== "0444" || (entry.type === "skill" ? !acceptedSkillPath(entry) : entry.path !== expectedPath) || !SHA256.test(entry.sha256 ?? "") || (entry.type === "skill" && !MANAGED_ID.test(entry.nativeName ?? "")) || (entry.type === "cron" && (!MANAGED_ID.test(entry.name ?? "") || !entry.name.startsWith("sellable-default-"))) ) { throw new Error("default_profile_bundle_rejected"); } const content = readRegular( join(bundleRoot, ...entry.path.split("/")), 64 * 1024 ); if (sha256(content) !== entry.sha256) throw new Error("default_profile_bundle_rejected"); return { ...entry, content: content.toString("utf8") }; }); const { bundleDigest, ...unsignedManifest } = manifest; if ( sha256(canonicalJson({ manifest: unsignedManifest, files })) !== bundleDigest ) throw new Error("default_profile_bundle_rejected"); return { manifest, files }; } function activeEntries(bundle, projection) { if ( bundle.manifest.kind !== projection.kind || bundle.manifest.bundleVersion !== projection.bundleVersion || bundle.manifest.bundleDigest !== projection.bundleDigest ) { throw new Error("default_profile_projection_mismatch"); } const target = projection.fixtureTarget; const fixtureAllowed = target?.testOnly === true && target.workspaceId === projection.workspaceId && target.agentId === projection.agentId; return bundle.files .filter((entry) => entry.kind !== "FIXTURE" || fixtureAllowed) .sort( (left, right) => (left.type === "skill" ? 0 : 1) - (right.type === "skill" ? 0 : 1) || left.path.localeCompare(right.path) ); } function validateProjection(value) { return Boolean( value && ["CUSTOMER", "ADMIN"].includes(value.kind) && ["profileId", "workspaceId", "agentId", "operationId"].every((key) => ID.test(value[key] ?? "") ) && Number.isSafeInteger(value.authorityGeneration) && value.authorityGeneration > 0 && FENCE.test(value.fence ?? "") && Number.isSafeInteger(value.bundleVersion) && value.bundleVersion > 0 && SHA256.test(value.bundleDigest ?? "") ); } function cronDesired(entry) { const spec = JSON.parse(entry.content); if ( !spec || Object.keys(spec).sort().join("\0") !== "deliver\0marker\0noAgent\0schedule\0script" || typeof spec.schedule !== "string" || !/^(?:\S+\s+){4}\S+$/.test(spec.schedule) || !/^[a-z0-9][a-z0-9-]{0,63}\.sh$/.test(spec.script ?? "") || spec.noAgent !== true || spec.deliver !== "local" || !MANAGED_ID.test(spec.marker ?? "") ) { throw new Error("default_profile_cron_spec_rejected"); } const desired = { name: entry.name, enabled: true, no_agent: true, schedule: { kind: "cron", expr: spec.schedule }, script: spec.script, deliver: spec.deliver, }; return { spec, desired, sha256: sha256(canonicalJson(desired)) }; } export async function reconcileDefaultProfileBundle({ activeProjection, materializerReceipt, profileRoot, bundleRoot, dataRoot, withProfileLock = withProfileSoulLock, reattestAuthority, cronAdapter, } = {}) { if (activeProjection == null) return { ok: true, status: "EMPTY", changed: false, actions: [] }; if (!validateProjection(activeProjection)) return refused("default_profile_projection_rejected"); if ( !isAbsolute(dataRoot ?? "") || !isAbsolute(profileRoot ?? "") || typeof withProfileLock !== "function" || typeof reattestAuthority !== "function" ) { return refused("default_profile_reconcile_input_rejected"); } try { if (activeEntries(readBundle(bundleRoot), activeProjection).length === 0) return { ok: true, status: "EMPTY", changed: false, actions: [] }; return await withProfileLock( activeProjection.profileId, async () => { const authority = await reattestAuthority(activeProjection); if ( authority?.operationId !== activeProjection.operationId || authority?.authorityGeneration !== activeProjection.authorityGeneration || authority?.fence !== activeProjection.fence ) { return refused("default_profile_authority_stale"); } if ( !existsSync(profileRoot) || lstatSync(profileRoot).isSymbolicLink() || !lstatSync(profileRoot).isDirectory() || realpathSync(profileRoot) !== profileRoot ) { throw new Error("default_profile_root_rejected"); } const bundle = readBundle(bundleRoot); const entries = activeEntries(bundle, activeProjection); if ( materializerReceipt?.profileId !== activeProjection.profileId || !verifyDefaultProfileMaterializerReceipt({ profileRoot, materializerReceipts: bundle.manifest.materializerReceipts, materializerReceipt, }) ) { throw new Error("default_profile_materializer_receipt_rejected"); } const materializerReceiptSha256 = sha256( canonicalJson(materializerReceipt) ); const receiptPath = join( profileRoot, DEFAULT_PROFILE_RECONCILE_RECEIPT ); let prior = null; if (existsSync(receiptPath)) { prior = JSON.parse(readRegular(receiptPath).toString("utf8")); if ( prior?.schemaVersion !== DEFAULT_PROFILE_RECONCILE_SCHEMA || prior.profileId !== activeProjection.profileId || !Array.isArray(prior.entries) ) { throw new Error("default_profile_receipt_rejected"); } } const historical = (entry) => prior?.entries.find( (value) => value?.type === entry.type && value.id === entry.id && (entry.type !== "cron" || value.name === entry.name) && SHA256.test(value.sha256 ?? "") ) ?? null; // Preflight every skill and cron spec before the first mutation. const skillPlans = new Map(); const cronSpecs = new Map(); for (const entry of entries) { if (entry.type === "cron") { cronSpecs.set(entry.id, cronDesired(entry)); continue; } const path = join(profileRoot, ...entry.path.split("/")); const directory = dirname(path); const publicDirectory = entry.path === canonicalPublicSkillPath(entry); if (existsSync(directory)) { const stat = lstatSync(directory); if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("default_profile_skill_collision"); for (const name of readdirSync(directory)) { if (name === "SKILL.md") continue; if (publicDirectory && name === "SOUL.md") { const soul = lstatSync(join(directory, name)); if (soul.isSymbolicLink() || !soul.isFile() || soul.nlink !== 1) throw new Error("default_profile_skill_collision"); continue; } const stage = join(directory, name); const stageStat = lstatSync(stage); if ( !name.startsWith(STAGE_PREFIX) || stageStat.isSymbolicLink() || !stageStat.isFile() || stageStat.nlink !== 1 ) { throw new Error("default_profile_skill_collision"); } rmSync(stage); } } const priorEntry = historical(entry); const legacyPath = legacyManagedSkillPath(entry); const priorPath = priorEntry?.path ?? (priorEntry ? legacyPath : null); let legacyDirectory = null; if ( priorPath !== null && priorPath !== entry.path && priorPath !== legacyPath ) { throw new Error("default_profile_receipt_rejected"); } if (publicDirectory && legacyPath !== entry.path) { const legacySkillPath = join(profileRoot, ...legacyPath.split("/")); const candidateLegacyDirectory = dirname(legacySkillPath); if (existsSync(candidateLegacyDirectory)) { const stat = lstatSync(candidateLegacyDirectory); if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("default_profile_skill_collision"); for (const name of readdirSync(candidateLegacyDirectory)) { if (name === "SKILL.md") continue; const stage = join(candidateLegacyDirectory, name); const stageStat = lstatSync(stage); if ( !name.startsWith(STAGE_PREFIX) || stageStat.isSymbolicLink() || !stageStat.isFile() || stageStat.nlink !== 1 ) { throw new Error("default_profile_skill_collision"); } rmSync(stage); } if (!existsSync(legacySkillPath)) throw new Error("default_profile_skill_collision"); const legacySha256 = sha256( readRegular(legacySkillPath, 64 * 1024) ); if ( legacySha256 !== entry.sha256 && legacySha256 !== priorEntry?.sha256 && legacySha256 !== LEGACY_PUBLIC_SKILL_DIGESTS[entry.id] ) { throw new Error("default_profile_skill_collision"); } legacyDirectory = candidateLegacyDirectory; } } let status = "CREATED"; let write = true; if (existsSync(path)) { const current = sha256(readRegular(path, 64 * 1024)); if (current === entry.sha256) { status = "REUSED"; write = false; } else if (priorEntry?.sha256 === current) status = "UPDATED"; else throw new Error("default_profile_skill_collision"); } if ( legacyDirectory || (priorPath !== null && priorPath !== entry.path) ) status = "UPDATED"; skillPlans.set(entry.id, { directory, path, status, write, legacyDirectory, }); } const actions = []; const receiptEntries = []; for (const entry of entries) { if (entry.type === "skill") { const plan = skillPlans.get(entry.id); if (plan.write) { mkdirSync(plan.directory, { recursive: true, mode: 0o700 }); atomicWrite(plan.path, entry.content, 0o444); } if (sha256(readRegular(plan.path, 64 * 1024)) !== entry.sha256) throw new Error("default_profile_skill_readback_rejected"); if (plan.legacyDirectory) rmSync(plan.legacyDirectory, { recursive: true, force: true }); actions.push({ type: "skill", id: entry.id, nativeName: entry.nativeName, status: plan.status, }); receiptEntries.push({ type: "skill", id: entry.id, path: entry.path, sha256: entry.sha256, }); continue; } if ( typeof cronAdapter?.create !== "function" || typeof cronAdapter?.update !== "function" ) { throw new Error("default_profile_cron_adapter_rejected"); } const { spec, desired, sha256: desiredSha256, } = cronSpecs.get(entry.id); const cron = await ensureHermesNamedCron({ profileRoot, name: entry.name, isExpected: (job) => Boolean( job?.id && job.name === desired.name && job.enabled === desired.enabled && job.no_agent === desired.no_agent && job.script === desired.script && job.deliver === desired.deliver && job.schedule?.kind === "cron" && job.schedule.expr === desired.schedule.expr ), isUpdatable: (job) => historical(entry)?.sha256 === sha256( canonicalJson({ name: job?.name, enabled: job?.enabled, no_agent: job?.no_agent, schedule: job?.schedule, script: job?.script, deliver: job?.deliver, }) ), create: () => cronAdapter.create({ profileRoot, entry, spec, desired, activeProjection, }), update: (existing) => cronAdapter.update({ profileRoot, entry, spec, desired, existing, activeProjection, }), codePrefix: "default_profile_cron", }); if (!cron.ok) throw new Error(cron.code); const bound = typeof cronAdapter.bind === "function" ? await cronAdapter.bind({ profileRoot, entry, spec, desired, job: cron.job, activeProjection, }) : { changed: false }; if (!bound || typeof bound.changed !== "boolean") throw new Error("default_profile_cron_binding_rejected"); actions.push({ type: "cron", id: entry.id, name: entry.name, status: cron.status, bindingChanged: bound.changed, }); receiptEntries.push({ type: "cron", id: entry.id, name: entry.name, sha256: desiredSha256, }); } const receipt = `${canonicalJson({ schemaVersion: DEFAULT_PROFILE_RECONCILE_SCHEMA, profileId: activeProjection.profileId, bundleVersion: activeProjection.bundleVersion, bundleDigest: activeProjection.bundleDigest, materializerReceiptSha256, entries: receiptEntries, })}\n`; const currentReceipt = existsSync(receiptPath) ? readRegular(receiptPath).toString("utf8") : null; if (currentReceipt !== receipt) atomicWrite(receiptPath, receipt, 0o600); const changed = actions.some( (action) => action.status !== "REUSED" || action.bindingChanged === true ); return { ok: true, status: changed ? "APPLIED" : "REUSED", changed, bundleVersion: activeProjection.bundleVersion, bundleDigest: activeProjection.bundleDigest, materializerReceiptSha256, actions, }; }, { dataRoot } ); } catch (error) { const code = error instanceof Error && /^default_profile_[a-z0-9_]+$/.test(error.message) ? error.message : "default_profile_reconcile_failed"; return refused(code); } }