import { link, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { dirname, isAbsolute, join, posix, relative, sep } from "node:path"; import { createHash, randomUUID } from "node:crypto"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; const execFileAsync = promisify(execFile); export const ROLES = ["supervisor", "lead", "peer"]; export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"]; export const DEFAULT_PEER_ROUTES = { fast: "Low-cost, low-latency bounded triage and simple read-only work.", general: "Balanced default for mixed repository work.", reasoning: "Deep analysis for ambiguous or high-complexity problems.", coding: "Implementation, debugging, and verification.", architecture: "Architecture, migration, lifecycle, and hard-to-reverse decisions.", reviewer: "Independent review of correctness, security, regressions, and maintainability.", }; const REQUIRED_PEER_ROUTES = ["fast", "general", "reasoning", "coding", "architecture"]; const ROUTE_ID = /^[a-z][a-z0-9_-]{0,31}$/; export function configDir(env = process.env, home = homedir()) { return env.PI_CODING_AGENT_DIR || join(home, ".pi", "agent"); } export function settingsPath(dir) { return join(dir, "pi-paseo-orchestration", "settings.json"); } /** @returns {Array<[string, any]>} */ function objectEntries(value) { return Object.entries(value); } function validateModelSelection(entry, label, { description = false } = {}) { if (entry === null || typeof entry !== "object" || Array.isArray(entry)) { return { ok: false, error: `${label} must be an object` }; } const expected = description ? "description,model,provider,thinking" : "model,provider,thinking"; if (Object.keys(entry).sort().join(",") !== expected) { return { ok: false, error: `${label} must contain exactly ${description ? "description, " : ""}provider, model, thinking` }; } if (description && (typeof entry.description !== "string" || entry.description.trim() === "" || entry.description.length > 240)) { return { ok: false, error: `${label}.description must be a nonempty string of at most 240 characters` }; } if (typeof entry.provider !== "string" || entry.provider.trim() === "") return { ok: false, error: `${label}.provider must be a nonempty string` }; if (typeof entry.model !== "string" || entry.model.trim() === "") return { ok: false, error: `${label}.model must be a nonempty string` }; if (!THINKING_LEVELS.includes(entry.thinking)) return { ok: false, error: `${label}.thinking must be one of ${THINKING_LEVELS.join("|")}` }; return { ok: true }; } // Closed v2 document: fixed Supervisor/Lead selections plus Human-owned Peer // model routes. The original five routes remain required for v2 compatibility; // settings newly written by the wizard also include reviewer. export function validateSettings(doc) { if (doc === null || typeof doc !== "object" || Array.isArray(doc)) return { ok: false, error: "settings must be an object" }; if (doc.version !== 2) return { ok: false, error: "settings.version must be 2" }; if (Object.keys(doc).sort().join(",") !== "peer_routes,roles,version") return { ok: false, error: "settings must contain exactly version, roles, peer_routes" }; const roles = doc.roles; if (roles === null || typeof roles !== "object" || Array.isArray(roles) || Object.keys(roles).sort().join(",") !== "lead,supervisor") { return { ok: false, error: "settings.roles must contain exactly supervisor and lead" }; } for (const role of ["supervisor", "lead"]) { const check = validateModelSelection(roles[role], `settings.roles.${role}`); if (!check.ok) return check; } const routes = doc.peer_routes; if (routes === null || typeof routes !== "object" || Array.isArray(routes)) return { ok: false, error: "settings.peer_routes must be an object" }; for (const route of REQUIRED_PEER_ROUTES) { if (!Object.prototype.hasOwnProperty.call(routes, route)) return { ok: false, error: `settings.peer_routes.${route} is required` }; } for (const [route, entry] of Object.entries(routes)) { if (!ROUTE_ID.test(route)) return { ok: false, error: `Peer route ID ${JSON.stringify(route)} is invalid` }; const check = validateModelSelection(entry, `settings.peer_routes.${route}`, { description: true }); if (!check.ok) return check; } return { ok: true }; } function migrateSettingsV1(doc) { if (doc?.version !== 1 || doc === null || typeof doc !== "object" || Array.isArray(doc)) return null; const roles = doc.roles; if (roles === null || typeof roles !== "object" || Array.isArray(roles) || Object.keys(roles).sort().join(",") !== "lead,peer,supervisor") return null; for (const role of ROLES) { if (!validateModelSelection(roles[role], `settings.roles.${role}`).ok) return null; } return { version: 2, roles: { supervisor: structuredClone(roles.supervisor), lead: structuredClone(roles.lead) }, peer_routes: Object.fromEntries(Object.entries(DEFAULT_PEER_ROUTES).map(([route, description]) => [route, { description, ...structuredClone(roles.peer) }])), }; } export async function readSettings(dir) { const path = settingsPath(dir); let text; try { text = await readFile(path, "utf8"); } catch (err) { if (err.code === "ENOENT") return null; throw new Error(`settings read failed: ${err.message}`); } let doc; try { doc = JSON.parse(text); } catch { throw new Error(`settings.json is not valid JSON; refusing to overwrite it`); } const migrated = migrateSettingsV1(doc); if (migrated !== null) return migrated; const check = validateSettings(doc); if (!check.ok) throw new Error(`settings.json invalid (${check.error}); refusing to overwrite it`); return doc; } export async function writeSettings(dir, doc) { const check = validateSettings(doc); if (!check.ok) throw new Error(`refusing to write invalid settings: ${check.error}`); const target = settingsPath(dir); await mkdir(dirname(target), { recursive: true }); const tmp = `${target}.tmp`; try { await writeFile(tmp, `${JSON.stringify(doc, null, 2)}\n`, { mode: 0o600 }); await rename(tmp, target); } catch (err) { await unlink(tmp).catch(() => {}); throw new Error(`settings write failed: ${err.message}`); } } export const ROLE_ENV = "PI_PASEO_ORCHESTRATION_ROLE"; export const PROFILES_ENV = "PI_PASEO_ORCHESTRATION_PROFILES_DIR"; export const PEER_ALIAS_ENV = "PI_PASEO_ORCHESTRATION_PEER_ALIAS"; export const SUPERVISOR_ALIAS_ENV = "PI_PASEO_ORCHESTRATION_SUPERVISOR_ALIAS"; export const LEAD_ALIAS_ENV = "PI_PASEO_ORCHESTRATION_LEAD_ALIAS"; export const AGENT_ENV = "PASEO_AGENT_ID"; // Closed role ceilings: Peer is read+Bash; Supervisor and Lead add the outer // mcp tool (whose inner targets are validated separately). write/edit are never // in a ceiling — they come only from a current-run Task Authority Envelope // grant. mcp_script is in no ceiling. export const CEILINGS = { supervisor: ["read", "bash", "mcp"], lead: ["read", "bash", "mcp"], peer: ["read", "bash"], }; // Validated outer-MCP inner targets. Lead lifecycle calls are restricted to // exact child IDs minted in this process; Paseo remains the control plane. export const MCP_TARGETS = { supervisor: { paseo: new Set(["paseo_create_agent", "paseo_list_agents", "paseo_get_agent_status", "paseo_get_agent_activity"]) }, lead: { paseo: new Set(["paseo_list_workspaces", "paseo_list_providers", "paseo_list_agents", "paseo_create_agent", "paseo_send_agent_prompt", "paseo_get_agent_status", "paseo_get_agent_activity", "paseo_cancel_agent", "paseo_archive_agent"]) }, peer: {}, }; const PASEO_CHILD_TOOLS = new Set(["paseo_send_agent_prompt", "paseo_get_agent_status", "paseo_get_agent_activity", "paseo_cancel_agent", "paseo_archive_agent"]); const createdPeerIds = new Set(); // Read-family tools whose targets are checked against the protocol path by the // peer read gate inside checkToolCall. const PROTOCOL_READ_TOOLS = ["read", "grep", "ls", "find"]; // Cooperative, recognizable-only command detection. Not a sandbox: aliases, // scripts, and child programs can bypass it. The git patterns also catch global // flag forms (`git --no-pager commit`) by scanning the command line for the // subcommand after any `git` invocation. const GIT_COMMIT = /\bgit\b[^\n;&|]*\bcommit\b/; // commits need a current-run local_commit grant const GIT_COMMIT_INVOCATION = /\bgit\b[^\n;&|]*\bcommit\b/g; const GIT_AMEND = /\bgit\b[^\n;&|]*\bcommit\b[^\n;&|]*--amend\b/; // amend is forbidden even with a grant function recognizableCommitCount(command) { return String(command ?? "").replace(/\r?\n/g, ";").match(GIT_COMMIT_INVOCATION)?.length ?? 0; } const PUBLICATION = [ /\bgit\b[^\n;&|]*\b(?:push|merge)\b/, // push/merge never allowed /\bgh\s+pr\b/, /\b(?:vercel|netlify|flyctl?|railway|supabase|render|amplify)\s+deploy\b/, ]; // Task Authority Envelope: one canonical v1 JSON object between exact markers, // accepted only as the first nonempty content of a submitted Human message. export const ENVELOPE_BEGIN = ''; export const ENVELOPE_END = ""; const OBJECTIVE_MAX = 2000; const CAPABILITY_NAMES = ["edit", "local_commit"]; const GRANT_KIND_ROLE = { peer: "peer", lead_tiny: "lead", supervisor_recovery: "lead" }; const FULL_SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/; const SHA256_HEX = /^[0-9a-f]{64}$/; const PROFILE_MARKER = (role, digest) => ``; export function parseRole(env) { const raw = env[ROLE_ENV]; if (raw === undefined || raw === null || raw === "") return { ok: true, role: null }; if (CEILINGS[raw]) return { ok: true, role: raw }; return { ok: false, error: `${ROLE_ENV} must be exactly supervisor, lead, or peer (got ${JSON.stringify(raw)})` }; } async function validateProfileDir(dir) { if (!isAbsolute(dir)) return { ok: false, error: "profile directory must be an absolute path" }; let realDir; try { realDir = await realpath(dir); if (!(await stat(realDir)).isDirectory()) return { ok: false, error: "profile directory is not a directory" }; } catch { return { ok: false, error: "profile directory is not readable" }; } for (const role of ROLES) { const file = join(dir, `${role}.md`); try { const real = await realpath(file); if (real !== join(realDir, `${role}.md`)) return { ok: false, error: `profile ${role}.md must be a direct descendant without symlink escape` }; if (!(await stat(real)).isFile()) return { ok: false, error: `profile ${role}.md must be a regular file` }; if ((await readFile(real, "utf8")).trim() === "") return { ok: false, error: `profile ${role}.md must be nonempty` }; } catch { return { ok: false, error: `profile ${role}.md must be readable` }; } } return { ok: true, dir }; } export async function resolveProfileSource(env, bundledDir) { const override = env[PROFILES_ENV]; if (override !== undefined && override !== null && override !== "") { const check = await validateProfileDir(override); if (!check.ok) return { ok: false, error: check.error }; return { ok: true, dir: override, source: "override" }; } if (!bundledDir) return { ok: false, error: "no profile source (bundled profiles unavailable)" }; const check = await validateProfileDir(bundledDir); if (!check.ok) return { ok: false, error: check.error }; return { ok: true, dir: bundledDir, source: "bundled" }; } export async function readProfile(dir, role) { const path = join(dir, `${role}.md`); const text = await readFile(path, "utf8"); if (text.trim() === "") throw new Error(`profile ${role}.md must be nonempty`); return text; } export function profileDigest(text) { return createHash("sha256").update(text).digest("hex"); } async function findModel(models, provider, id) { if (!models) return undefined; return Array.isArray(models) ? models.find((m) => m.provider === provider && m.id === id) : models.find(provider, id); } // First successful activation latches role, agent identity, settings snapshot, // profile source, and profile digest. Everything later is drift-checked against // this latch; a fresh Paseo process is required to change the role. export async function activate({ env, dir, profileDir, models, setModel, setThinkingLevel, getThinkingLevel, currentModel, currentThinking }) { const roleCheck = parseRole(env); if (!roleCheck.ok) return { ok: false, error: roleCheck.error }; if (roleCheck.role === null) return { ok: true, latch: null }; const agentId = (env[AGENT_ENV] ?? "").trim(); if (agentId === "") return { ok: false, error: `${AGENT_ENV} must be nonblank for a governed ${roleCheck.role} process` }; let settings; try { settings = await readSettings(dir); } catch (err) { return { ok: false, error: err.message }; } if (settings === null) return { ok: false, error: `settings document is missing; run /ppo:settings first` }; const source = await validateProfileDir(profileDir); if (!source.ok) return { ok: false, error: source.error }; let profileText; const profileDigests = {}; try { for (const role of ROLES) { profileDigests[role] = profileDigest(await readProfile(profileDir, role)); } profileText = await readProfile(profileDir, roleCheck.role); } catch (err) { return { ok: false, error: err.message }; } // Supervisor and Lead have one fixed selection. A Peer must already be // launched with one exact Human-configured route; match the observed runtime // tuple rather than silently replacing an unapproved requested model. let selectedRoute = null; let sel = settings.roles[roleCheck.role]; if (roleCheck.role === "peer") { let matchedSelection = null; for (const [routeId, route] of objectEntries(settings.peer_routes)) { /** @type {any} */ const candidate = route; if (currentModel?.provider === Reflect.get(Object(candidate), "provider") && currentModel?.id === Reflect.get(Object(candidate), "model") && currentThinking === Reflect.get(Object(candidate), "thinking")) { selectedRoute = routeId; matchedSelection = candidate; break; } } if (matchedSelection === null) return { ok: false, error: "Peer runtime model/thinking is not an allowed Human-configured route" }; sel = matchedSelection; } const model = await findModel(models, sel.provider, sel.model); if (!model) return { ok: false, error: `model ${sel.provider}/${sel.model} is not in the current model registry` }; if (typeof setModel !== "function") return { ok: false, error: "model selection API is unavailable" }; let applied = false; try { applied = await setModel(model); } catch { applied = false; } if (!applied) return { ok: false, error: `model ${sel.provider}/${sel.model} is unavailable or unauthenticated` }; if (typeof setThinkingLevel !== "function") return { ok: false, error: "thinking level API is unavailable" }; try { setThinkingLevel(sel.thinking); } catch { return { ok: false, error: `thinking level ${sel.thinking} is unavailable or clamped to ${String(null)}` }; } // setThinkingLevel returns void in the Pi API; the effective level must be // read back. Unobservable or clamped states block (fail closed). if (typeof getThinkingLevel !== "function") { return { ok: false, error: "thinking level observation API is unavailable" }; } let effective; try { effective = getThinkingLevel(); } catch { effective = null; } if (effective !== sel.thinking) { return { ok: false, error: `thinking level ${sel.thinking} is unavailable or clamped to ${String(effective)}` }; } const latch = { role: roleCheck.role, agentId, settings: structuredClone(settings), profileDir, profileOverride: env[PROFILES_ENV] ?? null, profileText, profileDigest: profileDigest(profileText), profileDigests, peerProviderAlias: env[PEER_ALIAS_ENV] ?? null, selectedModel: { provider: sel.provider, id: sel.model }, selectedThinking: sel.thinking, selectedPeerRoute: selectedRoute, }; return { ok: true, latch }; } function verifyRuntimeSelection(latch, ctx) { if (!isRecord(ctx) || !isRecord(ctx.model) || typeof ctx.model.provider !== "string" || ctx.model.provider.trim() === "" || typeof ctx.model.id !== "string" || ctx.model.id.trim() === "") { return { ok: false, error: "runtime model selection is missing or unobservable" }; } if (ctx.model.provider !== latch.selectedModel?.provider || ctx.model.id !== latch.selectedModel?.id) { return { ok: false, error: "runtime model drifted from the latched role setting" }; } if (typeof ctx.thinkingLevel !== "string" || ctx.thinkingLevel.trim() === "") { return { ok: false, error: "runtime thinking level is missing or unobservable" }; } if (ctx.thinkingLevel !== latch.selectedThinking) { return { ok: false, error: "runtime thinking level drifted from the latched role setting" }; } return { ok: true }; } export async function verifyLatch(latch, env, dir, ctx = {}, { runtime = true } = {}) { if (parseRole(env).role !== latch.role) return { ok: false, error: "role environment drifted" }; if ((env[AGENT_ENV] ?? "").trim() !== latch.agentId) return { ok: false, error: "Paseo agent identity drifted" }; if ((env[PROFILES_ENV] ?? null) !== latch.profileOverride) return { ok: false, error: "profile source drifted" }; if ((env[PEER_ALIAS_ENV] ?? null) !== latch.peerProviderAlias) return { ok: false, error: "Peer provider alias drifted" }; let current; try { current = await readSettings(dir); } catch (err) { return { ok: false, error: err.message }; } if (current === null || JSON.stringify(current) !== JSON.stringify(latch.settings)) { return { ok: false, error: "role settings document drifted" }; } try { for (const role of ROLES) { const digest = profileDigest(await readProfile(latch.profileDir, role)); if (digest !== latch.profileDigests?.[role]) return { ok: false, error: `role profile ${role}.md content drifted` }; } } catch (err) { return { ok: false, error: err.message }; } if (runtime) { const runtimeCheck = verifyRuntimeSelection(latch, ctx); if (!runtimeCheck.ok) return runtimeCheck; } return { ok: true }; } export function intersectTools(baseline, role) { const ceiling = CEILINGS[role] ?? []; return baseline.filter((tool) => ceiling.includes(tool)); } // One shared policy decision for run shaping and call-time gating (one // mechanism, not scattered checks). policy = { role, allowed, mcpTargets }. function closedKeys(value, required, optional = []) { if (!isRecord(value)) return false; const allowed = new Set([...required, ...optional]); return required.every((key) => Object.prototype.hasOwnProperty.call(value, key)) && Object.keys(value).every((key) => allowed.has(key)); } function validProviderAlias(value) { return typeof value === "string" && value !== "" && value === value.trim() && !value.includes("/"); } function createAgentPolicyPrompt(activeLatch, authority) { const envelope = authority?.envelope ?? null; if (activeLatch.role === "lead") { if (!validProviderAlias(activeLatch.peerProviderAlias)) { return `Paseo paseo_create_agent is blocked until ${PEER_ALIAS_ENV} names the Human-configured Peer provider alias.`; } const routes = objectEntries(activeLatch.settings.peer_routes).map(([id, route]) => { /** @type {any} */ const candidate = route; return `- ${id}: ${Reflect.get(Object(candidate), "description")} => provider ${activeLatch.peerProviderAlias}/${Reflect.get(Object(candidate), "provider")}/${Reflect.get(Object(candidate), "model")}; settings {\"thinkingOptionId\":${JSON.stringify(Reflect.get(Object(candidate), "thinking"))}}`; }); return [ "Paseo paseo_create_agent policy (closed v2): choose one Human-configured model route for this assignment:", ...routes, "- initialPrompt must bind the chosen route exactly once as \"model_route\":\"\"", "- omit workspaceId and labels (the child inherits this exact workspace and Paseo supplies parentage)", `- initialPrompt must bind \"parent_lead_agent_id\" to ${activeLatch.agentId}`, "- notifyOnFinish must be true; title and initialPrompt must be nonempty", ].join("\n"); } if (activeLatch.role === "supervisor" && envelope?.grant_kind === "supervisor_recovery") { const selection = activeLatch.settings.roles.lead; return [ "Paseo recovery paseo_create_agent policy (closed v1):", `- provider must be ${envelope.provider}/${selection.provider}/${selection.model}`, `- workspaceId must be ${envelope.workspace_id}`, `- labels must be exactly {\"pi-paseo-orchestration.handoff-id\":${JSON.stringify(envelope.handoff_id)}}`, `- settings must be exactly {\"thinkingOptionId\":${JSON.stringify(selection.thinking)}}`, "- notifyOnFinish must be true; initialPrompt must contain the exact recovery objective, handoff ID, and require Human /ppo:doctor evidence before handoff", ].join("\n"); } return "Paseo create_agent is unavailable for this run."; } function validateCreateAgentArgs(args, policy) { const block = (reason) => ({ block: true, reason }); let selection = policy.role === "supervisor" ? policy.roleSettings?.lead : null; let selectedRoute = null; if (policy.role === "lead") { if (!isRecord(policy.peerRoutes)) return block("Peer model routes are unavailable"); const bindings = typeof args?.initialPrompt === "string" ? [...args.initialPrompt.matchAll(/\"model_route\"\s*:\s*\"([^\"]*)\"/g)] : []; if (bindings.length !== 1 || !Object.prototype.hasOwnProperty.call(policy.peerRoutes, bindings[0][1])) { return block("Peer create_agent prompt must bind exactly one configured model_route"); } selectedRoute = bindings[0][1]; selection = policy.peerRoutes[selectedRoute]; } if (!isRecord(selection) || typeof selection.provider !== "string" || selection.provider === "" || typeof selection.model !== "string" || selection.model === "" || typeof selection.thinking !== "string" || selection.thinking === "") { return block("create_agent target role settings are unavailable"); } const recovery = policy.role === "supervisor" ? policy.envelope : null; const alias = policy.role === "supervisor" ? recovery?.provider : policy.peerProviderAlias; if (!validProviderAlias(alias)) { return block(`create_agent ${policy.role === "lead" ? PEER_ALIAS_ENV : "recovery provider alias"} is unavailable or invalid`); } if (policy.role === "supervisor" && recovery?.grant_kind !== "supervisor_recovery") { return block("Supervisor create_agent requires a current-run supervisor_recovery grant"); } const supervisor = policy.role === "supervisor"; const required = ["title", "provider", "settings", "initialPrompt", "notifyOnFinish"]; if (supervisor) required.push("workspaceId", "labels"); if (!closedKeys(args, required)) return block("create_agent arguments are not the closed role-specific shape"); if (typeof args.title !== "string" || args.title.trim() === "" || args.title !== args.title.trim() || args.title.length > 60) { return block("create_agent title must be a trimmed nonempty string of at most 60 characters"); } const expectedProvider = `${alias}/${selection.provider}/${selection.model}`; if (args.provider !== expectedProvider) return block(`create_agent provider must be exactly ${expectedProvider}`); if (!closedKeys(args.settings, ["thinkingOptionId"]) || args.settings.thinkingOptionId !== selection.thinking) { return block(`create_agent thinking must be exactly ${selection.thinking}`); } if (typeof args.initialPrompt !== "string" || args.initialPrompt.trim() === "") { return block("create_agent initialPrompt must be a nonempty string"); } if (!supervisor) { const parentBindings = [...args.initialPrompt.matchAll(/"parent_lead_agent_id"\s*:\s*"([^"]*)"/g)]; if (typeof policy.currentAgentId !== "string" || policy.currentAgentId === "" || parentBindings.length !== 1 || parentBindings[0][1] !== policy.currentAgentId) { return block("Peer create_agent prompt must bind parent_lead_agent_id exactly once to the current Lead"); } } if (args.notifyOnFinish !== true) return block("create_agent must request the native finish notification"); if (supervisor) { if (args.workspaceId !== recovery.workspace_id) return block("recovery create_agent workspace does not match the current grant"); if (!closedKeys(args.labels, ["pi-paseo-orchestration.handoff-id"]) || args.labels["pi-paseo-orchestration.handoff-id"] !== recovery.handoff_id) { return block("recovery create_agent handoff does not match the current grant"); } if (!args.initialPrompt.includes(recovery.objective) || !args.initialPrompt.includes(recovery.handoff_id) || !args.initialPrompt.includes("/ppo:doctor")) { return block("recovery create_agent prompt must bind the objective and handoff and require doctor"); } } return undefined; } export function checkToolCall(toolName, input, policy) { const block = (reason) => ({ block: true, reason }); const allowed = policy.allowed instanceof Set ? policy.allowed : new Set(policy.allowed); if (toolName === "mcp_script") { return block("mcp_script is unavailable to every governed role"); } // Peer read gate: the repository-wide Workspace Protocol is Lead governance // material. Reading the full protocol is a governance violation for the peer // role — assignment-relevant constraints arrive via the prompt, not the // file. The gate is role-based, so a current-run edit grant never unlocks // protocol reads, and protocol presence never bypasses this check. if (policy.role === "peer" && PROTOCOL_READ_TOOLS.includes(toolName)) { const target = input?.path ?? input?.file_path ?? null; if (typeof target === "string" && target !== "") { if (policy.repoRoot == null) { return block("peer read target cannot be checked without a repository root"); } const rel = targetToRepoRelative(policy.repoRoot, target); if (rel !== null && (rel === ".orchestration" || rel.startsWith(".orchestration/"))) { return block("reading the workspace protocol is a governance violation for the peer role"); } } } if (!allowed.has(toolName)) { return block(`${toolName} is not permitted for the ${policy.role} role`); } if (toolName === "mcp") { if (input === null || typeof input !== "object" || Array.isArray(input)) { return block("outer mcp call must carry an object input"); } const map = policy.mcpTargets ?? {}; const targets = Object.prototype.hasOwnProperty.call(map, input.server) ? map[input.server] : undefined; if (!targets || !targets.has(input.tool)) { return block(`outer mcp target ${JSON.stringify(input.server)}/${JSON.stringify(input.tool)} is not validated`); } if (input.args !== undefined && (input.args === null || typeof input.args !== "object" || Array.isArray(input.args))) { return block("outer mcp args must be an object"); } if (input.server === "paseo" && input.tool === "paseo_create_agent") { return validateCreateAgentArgs(input.args, policy); } if (input.server === "paseo" && PASEO_CHILD_TOOLS.has(input.tool)) { if (policy.role === "supervisor" && (input.tool === "paseo_get_agent_status" || input.tool === "paseo_get_agent_activity")) { if (!closedKeys(input.args, ["agentId"])) return block(`${input.tool} arguments must contain exactly agentId`); return undefined; } if (policy.role !== "lead") return block(`${input.tool} is restricted to the Lead role`); if (!closedKeys(input.args, ["agentId"], input.tool === "paseo_send_agent_prompt" ? ["prompt", "background", "notifyOnFinish"] : [])) { return block(`${input.tool} arguments are not the closed child-specific shape`); } if (!policy.createdPeerIds?.has(input.args.agentId)) return block(`${input.tool} target is not a Peer created by this Lead process`); if (input.tool === "paseo_send_agent_prompt" && (typeof input.args.prompt !== "string" || input.args.prompt.trim() === "")) { return block("paseo_send_agent_prompt prompt must be nonempty"); } } if (input.server === "paseo" && input.tool === "paseo_list_agents") { if (!["supervisor", "lead"].includes(policy.role) || !closedKeys(input.args ?? {}, [], ["includeArchived", "cwd", "sinceHours", "statuses", "limit"])) { return block("paseo_list_agents is restricted to bounded Supervisor observation or Lead duplicate/ownership checks"); } return undefined; } if (input.server === "paseo" && (input.tool === "paseo_list_workspaces" || input.tool === "paseo_list_providers")) { if (policy.role !== "lead" || !closedKeys(input.args ?? {}, [])) { return block(`${input.tool} is restricted to argument-free Lead discovery`); } return undefined; } return undefined; } if (toolName === "bash") { if (typeof input?.command !== "string") return block("bash call without a command string"); for (const pattern of PUBLICATION) { if (pattern.test(input.command)) return block("publication route is always blocked"); } if (GIT_AMEND.test(input.command)) return block("git commit --amend is forbidden"); if (GIT_COMMIT.test(input.command) && !policy.envelope?.capabilities?.includes("local_commit")) { return block("git commit requires a current-run local_commit grant"); } return undefined; } if (toolName === "write" || toolName === "edit") { if (!policy.envelope) return block(`${toolName} requires a current-run edit grant`); if (policy.repoRoot == null) return block(`${toolName} target cannot be checked without a repository root`); const target = input?.path ?? input?.file_path; const rel = targetToRepoRelative(policy.repoRoot, target); if (rel === null || !isPathInScope(rel, policy.envelope.scope, policy.envelope.exclusions)) { return block(`${toolName} target ${JSON.stringify(target)} is outside the granted scope`); } return undefined; } return undefined; } // ─── Task Authority Envelope ───────────────────────────────────────────────── // Raw duplicate-key scan: JSON.parse silently keeps the last duplicate, but the // closed schema must reject duplicate fields. Keys are compared decoded (via // JSON.parse of the raw key token) so escape variants (`"\u0061"` vs `"a"`) // cannot slip through. function findDuplicateKey(jsonText) { const stack = []; let i = 0; const n = jsonText.length; while (i < n) { const c = jsonText[i]; if (c === '"') { const start = i; i++; while (i < n) { if (jsonText[i] === "\\") { i += 2; continue; } if (jsonText[i] === '"') break; i++; } let j = i + 1; while (j < n && /\s/.test(jsonText[j])) j++; if (jsonText[j] === ":") { const top = stack[stack.length - 1]; if (top !== undefined && top !== null) { let key; try { key = JSON.parse(jsonText.slice(start, i + 1)); } catch { key = jsonText.slice(start + 1, i); } if (top.has(key)) return key; top.set(key, true); } } i = j; // resume just past the string (and any whitespace) continue; } if (c === "{") stack.push(new Map()); else if (c === "}") stack.pop(); else if (c === "[") stack.push(null); else if (c === "]") stack.pop(); i++; } return null; } // Closed v1 schema per grant kind. Unknown version/kind/field, duplicate field, // mistyped, conflicting (e.g. base without local_commit), and role-mismatched // data all fail closed with an explicit reason. function validateEnvelopeShape(obj) { if (obj === null || typeof obj !== "object" || Array.isArray(obj)) { return { ok: false, error: "authority envelope body must be a single JSON object" }; } if (obj.version !== 1) { return { ok: false, error: `authority envelope version must be exactly 1 (got ${JSON.stringify(obj.version)})` }; } const kind = obj.grant_kind; if (!Object.prototype.hasOwnProperty.call(GRANT_KIND_ROLE, kind)) { return { ok: false, error: `grant_kind must be one of peer|lead_tiny|supervisor_recovery (got ${JSON.stringify(kind)})` }; } if (obj.role !== GRANT_KIND_ROLE[kind]) { return { ok: false, error: `grant_kind ${kind} requires role ${GRANT_KIND_ROLE[kind]} (got ${JSON.stringify(obj.role)})` }; } if (obj.issuer !== "human") { return { ok: false, error: `issuer must be exactly "human" (got ${JSON.stringify(obj.issuer)})` }; } for (const field of ["agent_id", "task_id"]) { if (typeof obj[field] !== "string" || obj[field].trim() === "") { return { ok: false, error: `${field} must be a nonempty string` }; } } if (typeof obj.objective !== "string" || obj.objective.trim() === "") { return { ok: false, error: "objective must be a nonempty string" }; } if (obj.objective.length > OBJECTIVE_MAX) { return { ok: false, error: `objective exceeds the ${OBJECTIVE_MAX}-character bound` }; } if (kind === "supervisor_recovery") { const fields = ["version", "grant_kind", "role", "issuer", "agent_id", "task_id", "objective", "provider", "workspace_id", "handoff_id"]; const extra = Object.keys(obj).find((k) => !fields.includes(k)); if (extra !== undefined) return { ok: false, error: `unknown field ${JSON.stringify(extra)} in supervisor_recovery envelope` }; for (const field of ["provider", "workspace_id", "handoff_id"]) { if (typeof obj[field] !== "string" || obj[field].trim() === "") { return { ok: false, error: `${field} must be a nonempty string` }; } } return { ok: true, envelope: { ...obj, capabilities: [] } }; } const fields = ["version", "grant_kind", "role", "issuer", "agent_id", "task_id", "objective", "capabilities", "scope", "exclusions", "base"]; if (kind === "lead_tiny") fields.push("protocol_digest"); const extra = Object.keys(obj).find((k) => !fields.includes(k)); if (extra !== undefined) return { ok: false, error: `unknown field ${JSON.stringify(extra)} in ${kind} envelope` }; if (!Array.isArray(obj.capabilities) || obj.capabilities.length === 0) { return { ok: false, error: "capabilities must be a nonempty array" }; } if (new Set(obj.capabilities).size !== obj.capabilities.length) { return { ok: false, error: "capabilities must not repeat" }; } for (const cap of obj.capabilities) { if (typeof cap !== "string" || !CAPABILITY_NAMES.includes(cap)) { return { ok: false, error: `unknown capability ${JSON.stringify(cap)}` }; } } if (typeof obj.scope !== "string" || obj.scope === "") { return { ok: false, error: "scope must be a nonempty repository-relative string" }; } let exclusions = []; if (obj.exclusions !== undefined) { if (!Array.isArray(obj.exclusions)) return { ok: false, error: "exclusions must be an array" }; for (const e of obj.exclusions) { if (typeof e !== "string" || e === "") return { ok: false, error: "each exclusion must be a nonempty string" }; } exclusions = obj.exclusions; } const commitGranted = obj.capabilities.includes("local_commit"); if (commitGranted) { if (typeof obj.base !== "string" || !FULL_SHA.test(obj.base)) { return { ok: false, error: "base must be a full git commit SHA when local_commit is granted" }; } } else if (obj.base !== undefined) { return { ok: false, error: "base is only valid when local_commit is granted" }; } if (kind === "lead_tiny" && (typeof obj.protocol_digest !== "string" || !SHA256_HEX.test(obj.protocol_digest))) { return { ok: false, error: "protocol_digest must be a full sha256 hex digest" }; } return { ok: true, envelope: { ...obj, exclusions } }; } // Parses the authority envelope from a submitted message. Returns // { ok: true, envelope: null } when no envelope is present, { ok: true, // envelope } for a schema-valid envelope, or { ok: false, error } when an // envelope attempt exists but is misplaced, duplicated, malformed, quoted, or // otherwise invalid — nothing is granted in that case. export function parseEnvelope(text) { if (typeof text !== "string") return { ok: true, envelope: null }; const stripped = text.trimStart(); if (!stripped.includes(ENVELOPE_BEGIN)) { if (stripped.includes(" rel === e || rel.startsWith(e + "/")); } // Converts a write/edit target (absolute or repo-relative) to a canonical // repo-relative path, or null when it is outside the repository or ambiguous. function targetToRepoRelative(repoRoot, target) { if (typeof target !== "string" || target.trim() === "") return null; let rel = target; if (isAbsolute(target) || /^[A-Za-z]:[\\/]/.test(target)) { rel = relative(repoRoot, target); if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return null; } rel = posix.normalize(rel); if (rel === "." || rel === ".." || rel.startsWith("../")) return null; return rel; } // One granted scope path: nonempty, repository-relative, no absolute/home // path, no backslashes, no empty/dot/traversal segments, no glob characters, // no symlink components, and at most a new final component inside an existing // real directory ("new files outside an existing real directory" are rejected). async function checkScopePath(repoRoot, p, label) { if (typeof p !== "string" || p === "") return { ok: false, error: `${label} must be a nonempty string` }; if (p !== p.trim()) return { ok: false, error: `${label} must not have leading or trailing whitespace` }; if (isAbsolute(p) || /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("~")) { return { ok: false, error: `${label} must be repository-relative, not an absolute or home path` }; } if (p.includes("\\")) return { ok: false, error: `${label} must use forward-slash repository-relative paths` }; const segments = p.split("/"); for (const segment of segments) { if (segment === "" || segment === ".") { return { ok: false, error: `${label} has an empty or ambiguous segment (no ".", trailing "/", or "//")` }; } if (segment === "..") return { ok: false, error: `${label} must not traverse outside the repository` }; if (/[*?[\]{}]/.test(segment)) return { ok: false, error: `${label} must not contain glob characters` }; } for (let i = 1; i <= segments.length; i++) { const current = join(repoRoot, ...segments.slice(0, i)); let st; try { st = await lstat(current); } catch { if (i < segments.length) { return { ok: false, error: `${label} names a new file outside an existing real directory` }; } return { ok: true, canonical: p }; // new final component inside an existing real directory } if (st.isSymbolicLink()) return { ok: false, error: `${label} contains a symlink component` }; if (!st.isDirectory()) { if (i < segments.length) return { ok: false, error: `${label} descends through a non-directory` }; if (!st.isFile()) return { ok: false, error: `${label} must be a directory or a regular file` }; } } return { ok: true, canonical: p }; } // Normalized repository-relative writable scope plus in-scope exclusions, // checked at envelope activation against the real repository filesystem. export async function validateScope(repoRoot, scope, exclusions = []) { const scopeCheck = await checkScopePath(repoRoot, scope, "scope"); if (!scopeCheck.ok) return { ok: false, error: scopeCheck.error }; const canonical = []; for (const exclusion of exclusions) { const check = await checkScopePath(repoRoot, exclusion, `exclusion ${JSON.stringify(exclusion)}`); if (!check.ok) return { ok: false, error: check.error }; if (!isPathInScope(exclusion, scope, [])) { return { ok: false, error: `exclusion ${JSON.stringify(exclusion)} must lie within scope ${JSON.stringify(scope)}` }; } canonical.push(exclusion); } return { ok: true, scope, exclusions: canonical }; } // One shared effective-policy computation: baseline ∩ (role ceiling ∪ // current-run envelope capabilities). The envelope's `edit` capability maps to // the write and edit tools; `local_commit` gates git commit through bash // instead of adding a tool. Tools outside the baseline are never re-enabled. export function effectiveTools(baseline, role, authority = null) { if (!Array.isArray(baseline)) return []; const ceiling = CEILINGS[role] ?? []; const extra = authority?.envelope?.capabilities?.includes("edit") ? ["write", "edit"] : []; return baseline.filter((tool) => ceiling.includes(tool) || extra.includes(tool)); } function gitOut(repoRoot, args, trim = true) { return execFileAsync("git", args, { cwd: repoRoot, timeout: 15000, env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" }, }).then(({ stdout }) => (trim ? stdout.trim() : stdout)).catch(() => null); } function findRepoRoot(cwd = process.cwd()) { return gitOut(cwd, ["rev-parse", "--show-toplevel"]).then((root) => (root === "" ? null : root)); } async function gitChangedPaths(repoRoot) { const [unstaged, staged, untracked] = await Promise.all([ gitOut(repoRoot, ["diff", "--name-only", "HEAD"]), gitOut(repoRoot, ["diff", "--cached", "--name-only", "HEAD"]), gitOut(repoRoot, ["ls-files", "--others", "--exclude-standard"]), ]); if (unstaged === null || staged === null || untracked === null) return null; const paths = new Set(); for (const list of [unstaged, staged, untracked]) { for (const line of list.split("\n")) { const p = line.trim(); if (p !== "") paths.add(p); } } return [...paths]; } // Call-time gate for recognizable `git commit` under a local_commit grant: // HEAD must still equal the granted candidate base and the current/cumulative // diff (staged, unstaged, and untracked paths) must stay within the granted // scope. Cooperative like the rest of the guardrail — aliases, scripts, and // child programs can bypass it. export async function checkCommitGate(command, authority) { const { envelope, repoRoot, scope, exclusions } = authority; if (!envelope.capabilities.includes("local_commit")) { return { block: true, reason: "git commit requires a current-run local_commit grant" }; } const commitCount = recognizableCommitCount(command); if (commitCount === 0) return undefined; if (commitCount > 1) { return { block: true, reason: "git commit blocked: one candidate-producing run may contain exactly one git commit" }; } const head = await gitOut(repoRoot, ["rev-parse", "HEAD"]); if (head !== envelope.base) { return { block: true, reason: "git commit blocked: current HEAD does not equal the granted candidate base" }; } const changed = await gitChangedPaths(repoRoot); if (changed === null) { return { block: true, reason: "git commit blocked: cannot inspect the current diff" }; } for (const p of changed) { if (!isPathInScope(p, scope, exclusions)) { return { block: true, reason: `git commit blocked: ${p} is outside the granted scope` }; } } return undefined; } // ─── Peer Report ───────────────────────────────────────────────────────────── // One strict v1 Peer Report, parsed as the first nonempty content of a Peer // run's final response. A report is validated as a document only: it NEVER // grants authority, NEVER changes the envelope/authority state, and NEVER // accepts anything (8.16). Emission, transport, and consumption are Peer/Lead // conduct — the extension validates the format and the correlation facts and // implements no mailbox, queue, retry, or notification arming. export const REPORT_BEGIN = ''; export const REPORT_END = ""; export const REPORT_KINDS = ["PROGRESS", "HANDOFF", "REOPEN_REQUEST", "DEPENDENCY_REQUEST", "BLOCKED"]; // Closed common block: report version/kind, Peer agent ID, exact parent Lead // agent ID, task/assignment IDs, nonempty summary + evidence, typed payload // per kind, and optional superseded report ID. Unknown, duplicate, malformed, // mistyped, misplaced, or mismatched data rejects the report. const REPORT_FIELDS = [ "version", "kind", "report_id", "peer_agent_id", "parent_lead_agent_id", "task_id", "assignment_id", "summary", "evidence", "payload", "supersedes_report_id", ]; const REPORT_PAYLOAD = { PROGRESS: { completed: { type: "strings", min: 1 }, next: { type: "strings", min: 1 }, risks: { type: "strings", min: 1 } }, HANDOFF: { artifacts: { type: "strings", min: 1 }, candidate_ref: { type: "candidate" }, verification: { type: "verification", min: 1 }, residual_risks: { type: "strings" }, unfinished_dependencies: { type: "strings" } }, REOPEN_REQUEST: { failed_premise: { type: "string" }, impact: { type: "string" }, options: { type: "strings", min: 1 }, requested_decision: { type: "string" } }, DEPENDENCY_REQUEST: { needed: { type: "string" }, needed_from: { type: "string" }, impact: { type: "string" }, human_decision_required: { type: "boolean" } }, BLOCKED: { blocker: { type: "string" }, impact: { type: "string" }, unblock_condition: { type: "string" }, bounded_attempts: { type: "strings", min: 1 }, can_continue_elsewhere: { type: "boolean" } }, }; function checkReportPayload(payload, kind) { if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return { ok: false, error: `payload must be a single object for kind ${kind}` }; const schema = kind === "PROGRESS" ? REPORT_PAYLOAD.PROGRESS : kind === "HANDOFF" ? REPORT_PAYLOAD.HANDOFF : kind === "REOPEN_REQUEST" ? REPORT_PAYLOAD.REOPEN_REQUEST : kind === "DEPENDENCY_REQUEST" ? REPORT_PAYLOAD.DEPENDENCY_REQUEST : kind === "BLOCKED" ? REPORT_PAYLOAD.BLOCKED : undefined; for (const field of Object.keys(payload)) { if (!Object.prototype.hasOwnProperty.call(schema, field)) return { ok: false, error: `unknown field ${JSON.stringify(field)} in ${kind} payload` }; } for (const [field, rule] of Object.entries(schema)) { if (!Object.prototype.hasOwnProperty.call(payload, field)) return { ok: false, error: `payload.${field} is missing for kind ${kind}` }; const value = payload[field]; if (rule.type === "string") { if (typeof value !== "string" || value.trim() === "") return { ok: false, error: `payload.${field} must be a nonempty string` }; } else if (rule.type === "boolean") { if (typeof value !== "boolean") return { ok: false, error: `payload.${field} must be a boolean` }; } else if (rule.type === "candidate") { if (value !== null && (typeof value !== "string" || value.trim() === "")) return { ok: false, error: `payload.${field} must be a candidate reference string or null` }; if (typeof value === "string" && !parseCandidateRef(value).ok) return { ok: false, error: `payload.${field} must be a valid Stable Candidate reference` }; } else if (rule.type === "strings") { if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) return { ok: false, error: `payload.${field} must be an array of nonempty strings` }; if ("min" in rule && value.length < rule.min) return { ok: false, error: `payload.${field} must contain at least ${rule.min} item(s)` }; } else if (rule.type === "verification") { if (!Array.isArray(value) || value.length < ("min" in rule ? rule.min : 0)) return { ok: false, error: `payload.${field} must be a nonempty verification array` }; for (const [index, item] of value.entries()) { const closed = checkClosedObject(item, ["command", "result", "output"], `payload.${field}[${index}]`); if (!closed.ok) return { ok: false, error: closed.error }; for (const key of ["command", "output"]) { const check = checkNonemptyString(item[key], `payload.${field}[${index}].${key}`); if (!check.ok) return { ok: false, error: check.error }; } if (!COMMAND_RESULTS.includes(item.result)) return { ok: false, error: `payload.${field}[${index}].result must be PASS|FAIL|NOT_RUN` }; } } } return { ok: true }; } function validateReportShape(obj) { if (obj === null || typeof obj !== "object" || Array.isArray(obj)) { return { ok: false, error: "peer report body must be a single JSON object" }; } if (obj.version !== 1) { return { ok: false, error: `peer report version must be exactly 1 (got ${JSON.stringify(obj.version)})` }; } if (!REPORT_KINDS.includes(obj.kind)) { return { ok: false, error: `kind must be one of ${REPORT_KINDS.join("|")} (got ${JSON.stringify(obj.kind)})` }; } const extra = Object.keys(obj).find((k) => !REPORT_FIELDS.includes(k)); if (extra !== undefined) return { ok: false, error: `unknown field ${JSON.stringify(extra)} in peer report` }; for (const field of ["report_id", "peer_agent_id", "parent_lead_agent_id", "task_id", "assignment_id"]) { if (typeof obj[field] !== "string" || obj[field].trim() === "") return { ok: false, error: `${field} must be a nonempty string` }; } if (typeof obj.summary !== "string" || obj.summary.trim() === "") return { ok: false, error: "summary must be a nonempty string" }; if (!Array.isArray(obj.evidence) || obj.evidence.length === 0 || obj.evidence.some((item) => typeof item !== "string" || item.trim() === "")) { return { ok: false, error: "evidence must be a nonempty array of nonempty strings" }; } if (obj.supersedes_report_id !== undefined && (typeof obj.supersedes_report_id !== "string" || obj.supersedes_report_id.trim() === "")) { return { ok: false, error: "supersedes_report_id must be a nonempty string when present" }; } const payload = checkReportPayload(obj.payload, obj.kind); if (!payload.ok) return { ok: false, error: payload.error }; return { ok: true, report: { ...obj } }; } // Parses the strict v1 Peer Report from a Peer run's final response. Returns // { ok: true, report: null } when no report is present, { ok: true, report } // for a schema-valid report, or { ok: false, error } when a report attempt // exists but is misplaced, duplicated, malformed, or invalid — nothing is // accepted in that case. Validation never touches authority state. export function parseReport(text) { if (typeof text !== "string") return { ok: true, report: null }; const stripped = text.trimStart(); if (!stripped.includes(REPORT_BEGIN)) { if (stripped.includes("= max) { return { ok: false, error: `inspection budget exhausted: at most ${max} bounded inspection(s) without new evidence` }; } inspections += 1; return { ok: true, remaining: max - inspections }; }, }; } // ─── Stable Candidate, review, verdict, and Local Acceptance ──────────────── // These are document and Git-fact seams only. They keep no candidate registry, // acceptance state, refs, notes, or other persistence and are not wired into // Peer input parsing. The caller supplies the current repository and authority // facts each time, so HEAD/worktree drift naturally fails revalidation. export const CANDIDATE_EVIDENCE_BEGIN = ''; export const CANDIDATE_EVIDENCE_END = ""; export const REVIEW_BEGIN = ''; export const REVIEW_END = ""; export const VERDICT_BEGIN = ''; export const VERDICT_END = ""; export const ACCEPTANCE_BEGIN = ''; export const ACCEPTANCE_END = ""; const DIRECT_ACCEPTANCE = Symbol("direct Human acceptance route"); const COMMAND_RESULTS = ["PASS", "FAIL", "NOT_RUN"]; function isRecord(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } function checkClosedObject(value, fields, label) { if (!isRecord(value)) return { ok: false, error: `${label} must be a single object` }; const extra = Object.keys(value).find((field) => !fields.includes(field)); if (extra !== undefined) return { ok: false, error: `unknown field ${JSON.stringify(extra)} in ${label}` }; return { ok: true }; } function checkNonemptyString(value, label) { return typeof value === "string" && value.trim() !== "" ? { ok: true } : { ok: false, error: `${label} must be a nonempty string` }; } function checkStringList(value, label, { min = 0, unique = false } = {}) { if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) { return { ok: false, error: `${label} must be an array of nonempty strings` }; } if (value.length < min) return { ok: false, error: `${label} must contain at least ${min} item(s)` }; if (unique && new Set(value).size !== value.length) return { ok: false, error: `${label} must not repeat values` }; return { ok: true }; } function sameList(left, right) { // Order-insensitive set comparison: the active tool set is a shared runtime // resource, so ordering or unrelated additions by co-extensions must not // look like policy drift. if (!Array.isArray(left) || !Array.isArray(right)) return false; if (left.length !== right.length) return false; const set = new Set(right); return left.every((item) => set.has(item)); } // Shared strict marker parser for the four Slice 6 documents. Like authority and // report parsing, the marker must be first, duplicate JSON keys are rejected // before JSON.parse, and any unknown pi-paseo marker fails closed. function parseV1Block(text, begin, label, resultKey, validate) { if (typeof text !== "string") return { ok: true, [resultKey]: null }; const stripped = text.trimStart(); if (!stripped.includes(begin)) { if (stripped.includes(":" }; } if (match[1].length !== match[2].length) { return { ok: false, error: "candidate reference task base and candidate must use the same full object-id length" }; } return { ok: true, candidate: { ref, taskBaseOid: match[1], candidateOid: match[2] }, }; } async function exactCommit(repoRoot, oid, label) { const resolved = await gitOut(repoRoot, ["rev-parse", "--verify", `${oid}^{commit}`]); if (resolved !== oid) return { ok: false, error: `${label} ${oid} is not a retrievable full commit object` }; return { ok: true }; } async function committedPaths(repoRoot, from, to) { const output = await gitOut(repoRoot, [ "diff", "--name-only", "--no-renames", "-z", from, to, "--", ], false); if (output === null) return null; return output.split("\0").filter((path) => path !== "").sort(); } async function candidateGitFacts({ candidateRef, repoRoot, grantedBase, scope, exclusions = [] }) { const parsed = parseCandidateRef(candidateRef); if (!parsed.ok) return { ok: false, error: parsed.error }; const { taskBaseOid, candidateOid } = parsed.candidate; if (typeof repoRoot !== "string" || repoRoot.trim() === "") { return { ok: false, error: "candidate eligibility requires an exact repository root" }; } const observedRoot = await findRepoRoot(repoRoot); if (observedRoot === null) return { ok: false, error: "candidate repository is not a retrievable Git worktree" }; if (observedRoot !== repoRoot) return { ok: false, error: "repoRoot is not the exact Git repository root" }; if (typeof grantedBase !== "string" || !FULL_SHA.test(grantedBase) || grantedBase.length !== candidateOid.length) { return { ok: false, error: "granted candidate base must be a full Git commit oid in the repository object format" }; } const scopeCheck = await validateScope(repoRoot, scope, exclusions); if (!scopeCheck.ok) return { ok: false, error: `candidate scope is invalid: ${scopeCheck.error}` }; for (const [oid, label] of [ [taskBaseOid, "task base"], [candidateOid, "candidate"], [grantedBase, "granted candidate base"], ]) { const check = await exactCommit(repoRoot, oid, label); if (!check.ok) return { ok: false, error: check.error }; } const parentLine = await gitOut(repoRoot, ["rev-list", "--parents", "-n", "1", candidateOid]); if (parentLine === null) return { ok: false, error: "candidate parentage is not retrievable" }; const parentParts = parentLine.split(/\s+/); if (parentParts.length !== 2) return { ok: false, error: "candidate must be a single-parent commit" }; if (parentParts[1] !== grantedBase) { return { ok: false, error: "candidate parent does not equal the granted candidate base" }; } const runCommitCount = await gitOut(repoRoot, ["rev-list", "--count", `${grantedBase}..${candidateOid}`]); if (runCommitCount !== "1") { return { ok: false, error: "candidate-producing run must create exactly one commit" }; } if (await gitOut(repoRoot, ["merge-base", "--is-ancestor", taskBaseOid, candidateOid]) === null) { return { ok: false, error: "candidate is not a descendant of the referenced task base" }; } const lineage = await gitOut(repoRoot, ["rev-list", "--parents", candidateOid, `^${taskBaseOid}`]); if (lineage === null) return { ok: false, error: "candidate ancestry is not retrievable" }; if (lineage.split("\n").filter(Boolean).some((line) => line.trim().split(/\s+/).length !== 2)) { return { ok: false, error: "candidate history from task base is not linear" }; } const head = await gitOut(repoRoot, ["rev-parse", "--verify", "HEAD"]); if (head !== candidateOid) return { ok: false, error: "current HEAD does not equal the candidate oid" }; const status = await gitOut(repoRoot, ["status", "--porcelain=v1", "--untracked-files=all"], false); if (status === null) return { ok: false, error: "candidate worktree cleanliness is not observable" }; if (status !== "") return { ok: false, error: "candidate worktree is not clean (staged, unstaged, or untracked residue exists)" }; const [currentPaths, cumulativePaths] = await Promise.all([ committedPaths(repoRoot, grantedBase, candidateOid), committedPaths(repoRoot, taskBaseOid, candidateOid), ]); if (currentPaths === null || cumulativePaths === null) { return { ok: false, error: "candidate changed paths are not retrievable" }; } if (cumulativePaths.length === 0) return { ok: false, error: "candidate cumulative diff has no changed paths" }; for (const path of [...new Set([...currentPaths, ...cumulativePaths])]) { if (!isPathInScope(path, scope, exclusions)) { return { ok: false, error: `candidate changed path ${JSON.stringify(path)} is outside the granted scope` }; } } const diff = await gitOut(repoRoot, [ "diff", "--binary", "--full-index", "--no-color", "--no-ext-diff", taskBaseOid, candidateOid, "--", ]); if (diff === null) return { ok: false, error: "candidate cumulative diff is not retrievable" }; return { ok: true, candidate: parsed.candidate, currentPaths, cumulativePaths, diff }; } export async function checkCandidateEligibility(facts) { const check = await candidateGitFacts(facts ?? {}); return check.ok ? { ok: true } : { ok: false, error: check.error }; } const EVIDENCE_FIELDS = [ "version", "evidence_id", "project_id", "task_id", "task_revision", "assignment_id", "writer_id", "parent_id", "repository_root", "workspace_id", "workspace_protocol_digest", "candidate_ref", "cumulative_diff", "changed_paths", "scope", "objective_relevance", "verification", "post_commit", "clean", "residual_risks", "unfinished_dependencies", ]; function authorityScope(authority) { const grant = authority?.envelope ?? authority; const scope = authority?.scope ?? grant?.scope; const exclusions = authority?.exclusions ?? grant?.exclusions ?? []; if (typeof scope !== "string" || scope === "" || !Array.isArray(exclusions)) { return { ok: false, error: "candidate evidence validation requires the granted scope and exclusions" }; } return { ok: true, scope, exclusions }; } function validateCandidateEvidenceShape(object, authority) { const closed = checkClosedObject(object, EVIDENCE_FIELDS, "candidate evidence"); if (!closed.ok) return { ok: false, error: closed.error }; if (object.version !== 1) return { ok: false, error: "candidate evidence version must be exactly 1" }; for (const field of [ "evidence_id", "project_id", "task_id", "task_revision", "writer_id", "repository_root", "workspace_id", ]) { const check = checkNonemptyString(object[field], field); if (!check.ok) return { ok: false, error: check.error }; } for (const field of ["assignment_id", "parent_id"]) { if (object[field] !== null) { const check = checkNonemptyString(object[field], field); if (!check.ok) return { ok: false, error: check.error }; } } if ((object.assignment_id === null) !== (object.parent_id === null)) { return { ok: false, error: "assignment_id and parent_id must both be nonempty strings or both be null for Lead tiny" }; } if (typeof object.workspace_protocol_digest !== "string" || !SHA256_HEX.test(object.workspace_protocol_digest)) { return { ok: false, error: "workspace_protocol_digest must be a full sha256 hex digest" }; } const candidate = parseCandidateRef(object.candidate_ref); if (!candidate.ok) return { ok: false, error: `candidate evidence ${candidate.error}` }; let check = checkClosedObject(object.cumulative_diff, ["evidence_id", "base_oid", "candidate_oid", "diff"], "candidate evidence cumulative_diff"); if (!check.ok) return { ok: false, error: check.error }; check = checkNonemptyString(object.cumulative_diff.evidence_id, "cumulative_diff.evidence_id"); if (!check.ok) return { ok: false, error: check.error }; if (object.cumulative_diff.base_oid !== candidate.candidate.taskBaseOid || object.cumulative_diff.candidate_oid !== candidate.candidate.candidateOid) { return { ok: false, error: "cumulative_diff object ids must match candidate_ref" }; } check = checkNonemptyString(object.cumulative_diff.diff, "cumulative_diff.diff"); if (!check.ok) return { ok: false, error: check.error }; check = checkStringList(object.changed_paths, "changed_paths", { min: 1, unique: true }); if (!check.ok) return { ok: false, error: check.error }; if (!sameList(object.changed_paths, [...object.changed_paths].sort())) { return { ok: false, error: "changed_paths must be sorted canonically" }; } const granted = authorityScope(authority); if (!granted.ok) return { ok: false, error: granted.error }; for (const path of object.changed_paths) { if (path.includes("\\") || isAbsolute(path) || posix.normalize(path) !== path || path === "." || path === ".." || path.startsWith("../")) { return { ok: false, error: `changed path ${JSON.stringify(path)} must be canonical and repository-relative` }; } if (!isPathInScope(path, granted.scope, granted.exclusions)) { return { ok: false, error: `changed path ${JSON.stringify(path)} is outside the granted scope` }; } } check = checkClosedObject(object.scope, [ "writable_scope", "exclusions", "current_result", "cumulative_result", "evidence_refs", ], "candidate evidence scope"); if (!check.ok) return { ok: false, error: check.error }; if (object.scope.writable_scope !== granted.scope || !sameList(object.scope.exclusions, granted.exclusions)) { return { ok: false, error: "candidate evidence scope and exclusions do not match the granted scope" }; } for (const field of ["current_result", "cumulative_result"]) { if (!["PASS", "FAIL"].includes(object.scope[field])) { return { ok: false, error: `scope.${field} must be PASS or FAIL` }; } } check = checkStringList(object.scope.evidence_refs, "scope.evidence_refs", { min: 1, unique: true }); if (!check.ok) return { ok: false, error: check.error }; check = checkClosedObject(object.objective_relevance, ["result", "rationale", "evidence_refs"], "candidate evidence objective_relevance"); if (!check.ok) return { ok: false, error: check.error }; if (object.objective_relevance.result !== "PASS") { return { ok: false, error: "objective_relevance.result must be PASS" }; } check = checkNonemptyString(object.objective_relevance.rationale, "objective_relevance.rationale"); if (!check.ok) return { ok: false, error: check.error }; check = checkStringList(object.objective_relevance.evidence_refs, "objective_relevance.evidence_refs", { min: 1, unique: true }); if (!check.ok) return { ok: false, error: check.error }; if (!Array.isArray(object.verification) || object.verification.length === 0) { return { ok: false, error: "verification must be a nonempty array" }; } const verificationIds = []; for (const [index, item] of object.verification.entries()) { check = checkClosedObject(item, ["evidence_id", "command", "result", "output"], `verification[${index}]`); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["evidence_id", "command"]) { check = checkNonemptyString(item[field], `verification[${index}].${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!COMMAND_RESULTS.includes(item.result)) { return { ok: false, error: `verification[${index}].result must be PASS|FAIL|NOT_RUN` }; } if (typeof item.output !== "string") return { ok: false, error: `verification[${index}].output must be a string` }; verificationIds.push(item.evidence_id); } if (new Set(verificationIds).size !== verificationIds.length) { return { ok: false, error: "verification evidence_id values must not repeat" }; } check = checkClosedObject(object.post_commit, ["head_oid", "verification_evidence_ids"], "candidate evidence post_commit"); if (!check.ok) return { ok: false, error: check.error }; if (object.post_commit.head_oid !== candidate.candidate.candidateOid) { return { ok: false, error: "post_commit.head_oid must equal the candidate oid" }; } check = checkStringList(object.post_commit.verification_evidence_ids, "post_commit.verification_evidence_ids", { min: 1, unique: true }); if (!check.ok) return { ok: false, error: check.error }; if (!sameList(object.post_commit.verification_evidence_ids, verificationIds)) { return { ok: false, error: "post_commit must bind every verification evidence id in order" }; } check = checkClosedObject(object.clean, ["evidence_id", "command", "result", "output"], "candidate evidence clean"); if (!check.ok) return { ok: false, error: check.error }; check = checkNonemptyString(object.clean.evidence_id, "clean.evidence_id"); if (!check.ok) return { ok: false, error: check.error }; if (object.clean.command !== "git status --porcelain=v1 --untracked-files=all") { return { ok: false, error: "clean.command must be exactly git status --porcelain=v1 --untracked-files=all" }; } if (!["PASS", "FAIL"].includes(object.clean.result)) { return { ok: false, error: "clean.result must be PASS or FAIL" }; } if (typeof object.clean.output !== "string") return { ok: false, error: "clean.output must be a string" }; for (const field of ["residual_risks", "unfinished_dependencies"]) { check = checkStringList(object[field], field); if (!check.ok) return { ok: false, error: check.error }; } return { ok: true, evidence: object }; } export function parseCandidateEvidence(text, authority) { return parseV1Block( text, CANDIDATE_EVIDENCE_BEGIN, "candidate evidence", "evidence", (object) => validateCandidateEvidenceShape(object, authority), ); } const REVIEW_FIELDS = [ "version", "review_result_id", "reviewer_id", "reviewer_assignment_id", "candidate_ref", "mandate", "commands", "evidence", "coverage", "gaps", "outcome", "findings", "correction_of", "correction_classifications", ]; function validateReviewShape(object) { let check = checkClosedObject(object, REVIEW_FIELDS, "review"); if (!check.ok) return { ok: false, error: check.error }; if (object.version !== 1) return { ok: false, error: "review version must be exactly 1" }; for (const field of ["review_result_id", "reviewer_id", "reviewer_assignment_id"]) { check = checkNonemptyString(object[field], field); if (!check.ok) return { ok: false, error: check.error }; } const candidate = parseCandidateRef(object.candidate_ref); if (!candidate.ok) return { ok: false, error: `review ${candidate.error}` }; if (object.mandate !== "NEUTRAL_FALSIFICATION") { return { ok: false, error: "review mandate must be exactly NEUTRAL_FALSIFICATION" }; } if (!Array.isArray(object.commands) || object.commands.length === 0) { return { ok: false, error: "review commands must be a nonempty array" }; } for (const [index, command] of object.commands.entries()) { check = checkClosedObject(command, ["command", "result", "output_ref"], `review commands[${index}]`); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["command", "output_ref"]) { check = checkNonemptyString(command[field], `review commands[${index}].${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!COMMAND_RESULTS.includes(command.result)) { return { ok: false, error: `review commands[${index}].result must be PASS|FAIL|NOT_RUN` }; } } const mins = { evidence: 1, coverage: 1, gaps: 0 }; for (const [field, min] of Object.entries(mins)) { check = checkStringList(object[field], field, { min }); if (!check.ok) return { ok: false, error: check.error }; } if (!["APPROVE", "FINDINGS"].includes(object.outcome)) { return { ok: false, error: "review outcome must be exactly APPROVE or FINDINGS" }; } if (!Array.isArray(object.findings)) return { ok: false, error: "review findings must be an array" }; const findingIds = []; for (const [index, finding] of object.findings.entries()) { check = checkClosedObject(finding, ["finding_id", "severity", "statement", "impact", "evidence", "scope"], `finding[${index}]`); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["finding_id", "statement", "impact", "scope"]) { check = checkNonemptyString(finding[field], `finding[${index}].${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!["BLOCKER", "NON_BLOCKING"].includes(finding.severity)) { return { ok: false, error: `finding[${index}].severity must be BLOCKER or NON_BLOCKING` }; } check = checkStringList(finding.evidence, `finding[${index}].evidence`, { min: 1 }); if (!check.ok) return { ok: false, error: check.error }; findingIds.push(finding.finding_id); } if (new Set(findingIds).size !== findingIds.length) { return { ok: false, error: "finding_id values must be stable and unique within a review" }; } if (object.outcome === "APPROVE" && object.findings.length !== 0) { return { ok: false, error: "APPROVE requires an empty findings array" }; } if (object.outcome === "FINDINGS" && object.findings.length === 0) { return { ok: false, error: "FINDINGS requires at least one finding" }; } const correction = Object.prototype.hasOwnProperty.call(object, "correction_of"); const classifications = Object.prototype.hasOwnProperty.call(object, "correction_classifications"); if (correction !== classifications) { return { ok: false, error: "correction reviews require both correction_of and correction_classifications" }; } if (correction) { check = checkNonemptyString(object.correction_of, "correction_of"); if (!check.ok) return { ok: false, error: check.error }; if (!Array.isArray(object.correction_classifications) || object.correction_classifications.length === 0) { return { ok: false, error: "correction_classifications must be a nonempty array on a correction review" }; } const classified = []; for (const [index, classification] of object.correction_classifications.entries()) { check = checkClosedObject(classification, ["finding_id", "classification", "evidence"], `correction_classifications[${index}]`); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["finding_id", "evidence"]) { check = checkNonemptyString(classification[field], `correction_classifications[${index}].${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!["resolved", "open", "obsolete"].includes(classification.classification)) { return { ok: false, error: `correction_classifications[${index}].classification must be resolved|open|obsolete` }; } classified.push(classification.finding_id); } if (new Set(classified).size !== classified.length) { return { ok: false, error: "correction classification finding ids must not repeat" }; } } return { ok: true, review: object }; } export function parseReview(text) { return parseV1Block(text, REVIEW_BEGIN, "review", "review", validateReviewShape); } export function reviewValidForCandidate(review, candidateId) { return validateReviewShape(review).ok && parseCandidateRef(candidateId).ok && review.candidate_ref === candidateId; } const VERDICT_FIELDS = [ "version", "verdict_id", "project_id", "task_id", "task_revision", "assignment_id", "repository_root", "workspace_id", "workspace_protocol_digest", "candidate_ref", "origin", "scope_result", "scope_evidence", "verification", "review", "unfinished_dependencies", "residual_risks", "human_decisions", "verdict", "rationale", ]; // Computes rather than trusts the declared enum. Missing/failed technical // evidence always wins; only an otherwise-ready document can need a Human; // READY is the remainder. The enum itself is never acceptance. export function verdictStatus(document) { if (!isRecord(document)) return "NOT_READY"; const candidate = parseCandidateRef(document.candidate_ref); const review = document.review; const technicalFailure = !candidate.ok || !isRecord(document.origin) || checkNonemptyString(document.origin.evidence_id, "origin.evidence_id").ok === false || document.scope_result !== "PASS" || !Array.isArray(document.scope_evidence) || document.scope_evidence.length === 0 || document.scope_evidence.some((item) => typeof item !== "string" || item.trim() === "") || !Array.isArray(document.verification) || document.verification.length === 0 || document.verification.some((item) => !isRecord(item) || checkNonemptyString(item.command, "verification.command").ok === false || item.result !== "PASS" || checkNonemptyString(item.output_ref, "verification.output_ref").ok === false) || !Array.isArray(document.unfinished_dependencies) || document.unfinished_dependencies.length !== 0 || !isRecord(review) || (review?.required === true ? checkNonemptyString(review.review_result_id, "review_result_id").ok === false || review.candidate_ref !== document.candidate_ref || review.outcome !== "APPROVE" || !Array.isArray(review.open_findings) || review.open_findings.length !== 0 : review?.required !== false || review.review_result_id !== null || review.candidate_ref !== null || review.outcome !== "NOT_REQUIRED" || !Array.isArray(review.open_findings) || review.open_findings.length !== 0); if (technicalFailure) return "NOT_READY"; if (!Array.isArray(document.human_decisions) || document.human_decisions.some((decision) => !isRecord(decision) || !["RESOLVED", "UNRESOLVED"].includes(decision.status))) { return "NOT_READY"; } if (document.human_decisions.some((decision) => decision.status === "UNRESOLVED")) return "NEEDS_HUMAN"; return "READY"; } function validateVerdictShape(object) { let check = checkClosedObject(object, VERDICT_FIELDS, "verdict"); if (!check.ok) return { ok: false, error: check.error }; if (object.version !== 1) return { ok: false, error: "verdict version must be exactly 1" }; for (const field of [ "verdict_id", "project_id", "task_id", "task_revision", "repository_root", "workspace_id", "rationale", ]) { check = checkNonemptyString(object[field], field); if (!check.ok) return { ok: false, error: check.error }; } if (object.assignment_id !== null) { check = checkNonemptyString(object.assignment_id, "assignment_id"); if (!check.ok) return { ok: false, error: check.error }; } if (typeof object.workspace_protocol_digest !== "string" || !SHA256_HEX.test(object.workspace_protocol_digest)) { return { ok: false, error: "workspace_protocol_digest must be a full sha256 hex digest" }; } const candidate = parseCandidateRef(object.candidate_ref); if (!candidate.ok) return { ok: false, error: `verdict ${candidate.error}` }; check = checkClosedObject(object.origin, ["kind", "evidence_id"], "verdict origin"); if (!check.ok) return { ok: false, error: check.error }; if (!["PEER_HANDOFF", "LEAD_TINY"].includes(object.origin.kind)) { return { ok: false, error: "origin.kind must be PEER_HANDOFF or LEAD_TINY" }; } check = checkNonemptyString(object.origin.evidence_id, "origin.evidence_id"); if (!check.ok) return { ok: false, error: check.error }; if (object.origin.kind === "PEER_HANDOFF" && object.assignment_id === null) { return { ok: false, error: "PEER_HANDOFF verdict requires a non-null assignment_id" }; } if (object.origin.kind === "LEAD_TINY" && object.assignment_id !== null) { return { ok: false, error: "LEAD_TINY verdict requires assignment_id null" }; } if (!["PASS", "FAIL"].includes(object.scope_result)) { return { ok: false, error: "scope_result must be PASS or FAIL" }; } check = checkStringList(object.scope_evidence, "scope_evidence", { unique: true }); if (!check.ok) return { ok: false, error: check.error }; if (!Array.isArray(object.verification)) return { ok: false, error: "verdict verification must be an array" }; for (const [index, item] of object.verification.entries()) { check = checkClosedObject(item, ["command", "result", "output_ref"], `verdict verification[${index}]`); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["command", "output_ref"]) { check = checkNonemptyString(item[field], `verdict verification[${index}].${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!COMMAND_RESULTS.includes(item.result)) { return { ok: false, error: `verdict verification[${index}].result must be PASS|FAIL|NOT_RUN` }; } } check = checkClosedObject(object.review, ["required", "review_result_id", "candidate_ref", "outcome", "open_findings"], "verdict review"); if (!check.ok) return { ok: false, error: check.error }; if (typeof object.review.required !== "boolean") return { ok: false, error: "review.required must be a boolean" }; check = checkStringList(object.review.open_findings, "review.open_findings", { unique: true }); if (!check.ok) return { ok: false, error: check.error }; if (object.review.required) { check = checkNonemptyString(object.review.review_result_id, "review.review_result_id"); if (!check.ok) return { ok: false, error: check.error }; const reviewCandidate = parseCandidateRef(object.review.candidate_ref); if (!reviewCandidate.ok) return { ok: false, error: `verdict review ${reviewCandidate.error}` }; if (!["APPROVE", "FINDINGS"].includes(object.review.outcome)) { return { ok: false, error: "required review outcome must be APPROVE or FINDINGS" }; } } else if (object.review.review_result_id !== null || object.review.candidate_ref !== null || object.review.outcome !== "NOT_REQUIRED" || object.review.open_findings.length !== 0) { return { ok: false, error: "non-required review must be exactly null/null/NOT_REQUIRED with no open findings" }; } for (const field of ["unfinished_dependencies", "residual_risks"]) { check = checkStringList(object[field], field); if (!check.ok) return { ok: false, error: check.error }; } if (!Array.isArray(object.human_decisions)) return { ok: false, error: "human_decisions must be an array" }; const decisionIds = []; for (const [index, decision] of object.human_decisions.entries()) { check = checkClosedObject(decision, ["decision_id", "status", "evidence_ref"], `human_decisions[${index}]`); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["decision_id", "evidence_ref"]) { check = checkNonemptyString(decision[field], `human_decisions[${index}].${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!["RESOLVED", "UNRESOLVED"].includes(decision.status)) { return { ok: false, error: `human_decisions[${index}].status must be RESOLVED or UNRESOLVED` }; } decisionIds.push(decision.decision_id); } if (new Set(decisionIds).size !== decisionIds.length) { return { ok: false, error: "human decision ids must not repeat" }; } if (!["NOT_READY", "NEEDS_HUMAN", "READY"].includes(object.verdict)) { return { ok: false, error: "verdict must be NOT_READY|NEEDS_HUMAN|READY" }; } const computed = verdictStatus(object); if (object.verdict !== computed) { return { ok: false, error: `declared verdict ${object.verdict} violates precedence; computed verdict is ${computed}` }; } return { ok: true, verdict: object }; } export function parseVerdict(text) { return parseV1Block(text, VERDICT_BEGIN, "verdict", "verdict", validateVerdictShape); } function validateAcceptanceShape(object) { let check = checkClosedObject(object, ["version", "decision", "candidate_ref", "project_verdict_id"], "local acceptance"); if (!check.ok) return { ok: false, error: check.error }; if (object.version !== 1) return { ok: false, error: "local acceptance version must be exactly 1" }; if (object.decision !== "LOCAL_ACCEPT") { return { ok: false, error: "local acceptance decision must be exactly LOCAL_ACCEPT" }; } const candidate = parseCandidateRef(object.candidate_ref); if (!candidate.ok) return { ok: false, error: `local acceptance ${candidate.error}` }; check = checkNonemptyString(object.project_verdict_id, "project_verdict_id"); if (!check.ok) return { ok: false, error: check.error }; return { ok: true, acceptance: object }; } // `source` is mandatory: only Pi's direct interactive Human route is accepted. // A private non-enumerable mark lets full revalidation require this parser // without adding a route field to the closed acceptance document. export function parseAcceptance(text, source) { const parsed = parseV1Block( text, ACCEPTANCE_BEGIN, "local acceptance", "acceptance", validateAcceptanceShape, ); if (!parsed.ok || parsed.acceptance === null) return parsed; if (source !== "interactive") { return { ok: false, error: "local acceptance requires the direct Human interactive route" }; } Object.defineProperty(parsed.acceptance, DIRECT_ACCEPTANCE, { value: true }); return parsed; } function acceptanceAuthority(authority) { if (!isRecord(authority) || !isRecord(authority.envelope)) { return { ok: false, error: "acceptance requires the validated candidate authority" }; } const envelope = validateEnvelopeShape(authority.envelope); if (!envelope.ok) return { ok: false, error: `candidate authority invalid: ${envelope.error}` }; const grant = envelope.envelope; if (!["peer", "lead_tiny"].includes(grant.grant_kind)) { return { ok: false, error: "acceptance authority must be a peer or lead_tiny candidate grant" }; } if (!grant.capabilities.includes("local_commit")) { return { ok: false, error: "candidate authority must include local_commit" }; } for (const [field, value] of [ ["taskRevision", authority.taskRevision], ["workspaceId", authority.workspaceId], ]) { const check = checkNonemptyString(value, `authority.${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (typeof authority.reviewRequired !== "boolean") { return { ok: false, error: "authority.reviewRequired must be a boolean" }; } if (grant.grant_kind === "peer") { for (const field of ["assignmentId", "parentId"]) { const check = checkNonemptyString(authority[field], `authority.${field}`); if (!check.ok) return { ok: false, error: check.error }; } } else if (authority.assignmentId !== null || authority.parentId !== null) { return { ok: false, error: "lead_tiny authority requires null assignmentId and parentId" }; } for (const [field, value] of [["task_id", grant.task_id], ["agent_id", grant.agent_id]]) { const check = checkNonemptyString(value, `authority envelope ${field}`); if (!check.ok) return { ok: false, error: check.error }; } const scope = authorityScope(authority); if (!scope.ok) return { ok: false, error: scope.error }; return { ok: true, grant, scope: scope.scope, exclusions: scope.exclusions }; } function mismatch(label, actual, expected) { return actual === expected ? null : { ok: false, error: `${label} ${JSON.stringify(actual)} does not match ${JSON.stringify(expected)}` }; } // The sole acceptance gate. It revalidates all closed documents, the pinned // protocol, exact context and evidence references, then current Git objects, // HEAD, cumulative/current scope, canonical diff, and porcelain cleanliness. // Success is only {ok:true}; there is deliberately no write or stored state. export async function validateAcceptance(options = {}) { const acceptance = options["acceptance"]; const verdict = options["verdict"]; const review = options["review"]; const evidence = options["evidence"]; const authority = options["authority"]; const repoRoot = options["repoRoot"]; const protocolPin = options["protocolPin"]; if (!isRecord(acceptance) || acceptance[DIRECT_ACCEPTANCE] !== true) { return { ok: false, error: "acceptance must be a valid block parsed from the direct Human interactive route" }; } let check = validateAcceptanceShape(acceptance); if (!check.ok) return { ok: false, error: check.error }; const auth = acceptanceAuthority(authority); if (!auth.ok) return { ok: false, error: auth.error }; const evidenceCheck = validateCandidateEvidenceShape(evidence, authority); if (!evidenceCheck.ok) return { ok: false, error: `candidate evidence invalid: ${evidenceCheck.error}` }; const verdictCheck = validateVerdictShape(verdict); if (!verdictCheck.ok) return { ok: false, error: `verdict invalid: ${verdictCheck.error}` }; if (typeof repoRoot !== "string" || repoRoot.trim() === "") { return { ok: false, error: "acceptance requires the exact repository root" }; } if (!isRecord(protocolPin) || protocolPin.repoRoot !== repoRoot || typeof protocolPin.projectId !== "string" || protocolPin.projectId.trim() === "" || typeof protocolPin.version !== "number" || typeof protocolPin.digest !== "string" || !SHA256_HEX.test(protocolPin.digest)) { return { ok: false, error: "acceptance requires a complete exact Workspace Protocol pin" }; } const currentProtocol = await readAndValidateProtocol(repoRoot); if (!currentProtocol.ok) return { ok: false, error: `workspace protocol revalidation failed: ${currentProtocol.error}` }; if (currentProtocol.protocol.digest !== protocolPin.digest || currentProtocol.protocol.meta.project_id !== protocolPin.projectId || currentProtocol.protocol.meta.version !== protocolPin.version) { return { ok: false, error: "current Workspace Protocol does not match the acceptance protocol pin" }; } const expectedContext = [ ["candidate evidence project_id", evidence.project_id, protocolPin.projectId], ["verdict project_id", verdict.project_id, protocolPin.projectId], ["candidate evidence task_id", evidence.task_id, auth.grant.task_id], ["verdict task_id", verdict.task_id, auth.grant.task_id], ["candidate evidence task_revision", evidence.task_revision, authority.taskRevision], ["verdict task_revision", verdict.task_revision, authority.taskRevision], ["candidate evidence assignment_id", evidence.assignment_id, authority.assignmentId], ["verdict assignment_id", verdict.assignment_id, authority.assignmentId], ["candidate evidence repository_root", evidence.repository_root, repoRoot], ["verdict repository_root", verdict.repository_root, repoRoot], ["candidate evidence workspace_id", evidence.workspace_id, authority.workspaceId], ["verdict workspace_id", verdict.workspace_id, authority.workspaceId], ["candidate evidence protocol digest", evidence.workspace_protocol_digest, protocolPin.digest], ["verdict protocol digest", verdict.workspace_protocol_digest, protocolPin.digest], ["candidate evidence writer_id", evidence.writer_id, auth.grant.agent_id], ["candidate evidence parent_id", evidence.parent_id, authority.parentId], ]; for (const [label, actual, expected] of expectedContext) { const failed = mismatch(label, actual, expected); if (failed) return { ok: false, error: failed.error }; } if (acceptance.candidate_ref !== evidence.candidate_ref || verdict.candidate_ref !== evidence.candidate_ref) { return { ok: false, error: "acceptance, evidence, and verdict must bind the exact same candidate" }; } if (acceptance.project_verdict_id !== verdict.verdict_id) { return { ok: false, error: "local acceptance project_verdict_id does not match the immutable verdict id" }; } if (verdict.origin.evidence_id !== evidence.evidence_id) { return { ok: false, error: "verdict origin does not reference the candidate evidence id" }; } const expectedOrigin = auth.grant.grant_kind === "peer" ? "PEER_HANDOFF" : "LEAD_TINY"; if (verdict.origin.kind !== expectedOrigin) { return { ok: false, error: `verdict origin must be ${expectedOrigin} for this authority` }; } if (verdict.verdict !== "READY" || verdictStatus(verdict) !== "READY") { return { ok: false, error: "Local Acceptance requires a READY verdict after precedence revalidation" }; } if (evidence.scope.current_result !== "PASS" || evidence.scope.cumulative_result !== "PASS") { return { ok: false, error: "candidate evidence current and cumulative scope results must both PASS" }; } if (evidence.verification.some((item) => item.result !== "PASS")) { return { ok: false, error: "every candidate verification result must PASS" }; } if (evidence.clean.result !== "PASS" || evidence.clean.output !== "") { return { ok: false, error: "candidate evidence clean check must PASS with empty porcelain output" }; } if (evidence.unfinished_dependencies.length !== 0) { return { ok: false, error: "candidate evidence has unfinished dependencies" }; } if (!sameList(verdict.unfinished_dependencies, evidence.unfinished_dependencies) || !sameList(verdict.residual_risks, evidence.residual_risks)) { return { ok: false, error: "verdict dependencies and residual risks must match candidate evidence" }; } const scopeRefs = [evidence.cumulative_diff.evidence_id, ...evidence.scope.evidence_refs].sort(); if (!sameList([...verdict.scope_evidence].sort(), scopeRefs)) { return { ok: false, error: "verdict scope evidence references do not exactly bind candidate evidence" }; } if (verdict.verification.length !== evidence.verification.length) { return { ok: false, error: "verdict verification references do not cover every candidate verification" }; } for (const item of verdict.verification) { const source = evidence.verification.find((entry) => entry.evidence_id === item.output_ref); if (!source || source.command !== item.command || source.result !== item.result) { return { ok: false, error: `verdict verification reference ${JSON.stringify(item.output_ref)} does not bind matching candidate evidence` }; } } if (verdict.review.required !== authority.reviewRequired) { return { ok: false, error: "verdict review requirement does not match the protocol/class authority fact" }; } if (authority.reviewRequired) { const reviewCheck = validateReviewShape(review); if (!reviewCheck.ok) return { ok: false, error: `required review invalid: ${reviewCheck.error}` }; if (!reviewValidForCandidate(review, evidence.candidate_ref)) { return { ok: false, error: "required review is stale for the current candidate" }; } if (review.reviewer_id === evidence.writer_id || review.reviewer_assignment_id === authority.assignmentId) { return { ok: false, error: "required review is not independent from the candidate writer/assignment" }; } if (review.correction_classifications?.some((item) => item.classification === "open")) { return { ok: false, error: "required correction review still has an open finding" }; } if (verdict.review.review_result_id !== review.review_result_id || verdict.review.candidate_ref !== review.candidate_ref || verdict.review.outcome !== review.outcome || !sameList(verdict.review.open_findings, review.findings.map((finding) => finding.finding_id))) { return { ok: false, error: "verdict review state does not exactly bind the required review" }; } } else if (review !== null && review !== undefined) { return { ok: false, error: "no review document is valid when the protocol/class says review is not required" }; } const facts = await candidateGitFacts({ candidateRef: evidence.candidate_ref, repoRoot, grantedBase: auth.grant.base, scope: auth.scope, exclusions: auth.exclusions, }); if (!facts.ok) return { ok: false, error: facts.error }; if (!sameList(evidence.changed_paths, facts.cumulativePaths)) { return { ok: false, error: "candidate evidence changed_paths do not match the current cumulative Git diff" }; } if (evidence.cumulative_diff.diff !== facts.diff) { return { ok: false, error: "candidate evidence cumulative_diff does not match the exact Git objects" }; } return { ok: true }; } // ─── Workspace Protocol ────────────────────────────────────────────────────── // One canonical repository-wide protocol at the repository root; v0.1 has no // overlays. This slice is read/validate/pin/guard only: workflow consumption // of the protocol (classification, routing) is a later slice. The protocol can // narrow workflow, but it cannot grant a Capability or override the Role // Profile / Task Authority Envelope — the pin is advisory-only for authority // and checkToolCall never consults it. // Required core section headings, normalized (lowercase, whitespace-collapsed). // Optional sections are limited to the closed set below; their presence grants // no capability. const REQUIRED_CORE_SECTIONS = [ "decision matrix", "task classes and routing", "ownership and isolation", "candidate, verification, review, and acceptance", "reopen, dependency, and blocked handling", "evolution", ]; const OPTIONAL_PROTOCOL_SECTIONS = new Set([ "project criticality", "review and council rules", "review and council", "anti-patterns", "supervisor hints", ]); export function protocolPath(repoRoot) { return join(repoRoot, ".orchestration", "workspace-protocol.md"); } // Line-based YAML frontmatter subset — no parser framework: the protocol is // markdown with a small `key: value` header, so a closed line reader is // enough. Rejects missing, malformed, duplicate, and non-canonical metadata // with the exact reason. function parseFrontmatter(text) { const lines = String(text).split(/\r?\n/); if (lines[0] !== "---") { return { ok: false, error: "protocol must start with a --- frontmatter block" }; } let end = -1; for (let i = 1; i < lines.length; i++) { if (lines[i].trimEnd() === "---") { end = i; break; } } if (end === -1) return { ok: false, error: "protocol frontmatter has no closing --- line" }; const meta = {}; for (let i = 1; i < end; i++) { const line = lines[i].trim(); if (line === "" || line.startsWith("#")) continue; const colon = line.indexOf(":"); if (colon === -1) { return { ok: false, error: `malformed frontmatter line ${i + 1}: expected "key: value"` }; } const key = line.slice(0, colon).trim(); const raw = line.slice(colon + 1).trim(); if (key === "") return { ok: false, error: `malformed frontmatter line ${i + 1}: empty key` }; if (Object.prototype.hasOwnProperty.call(meta, key)) { return { ok: false, error: `duplicate metadata key ${JSON.stringify(key)}` }; } const quoted = raw.length >= 2 && ((raw[0] === '"' && raw[raw.length - 1] === '"') || (raw[0] === "'" && raw[raw.length - 1] === "'")); meta[key] = quoted ? raw.slice(1, -1) : raw; } const required = ["status", "version", "last_reviewed", "project_id", "repository_root"]; for (const key of required) { if (!Object.prototype.hasOwnProperty.call(meta, key)) { return { ok: false, error: `metadata ${key} is missing` }; } } const extra = Object.keys(meta).find((key) => !required.includes(key)); if (extra !== undefined) return { ok: false, error: `unknown metadata key ${JSON.stringify(extra)}` }; if (meta["status"] === "") return { ok: false, error: "metadata status must be a nonempty string" }; if (!/^\d+$/.test(meta["version"]) || !Number.isSafeInteger(Number(meta["version"])) || Number(meta["version"]) < 1) { return { ok: false, error: "metadata version must be a positive integer" }; } // Real calendar check: Date.parse rolls over (2025-02-30 → Mar 2), so the // parsed components must round-trip exactly. const dateMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(meta["last_reviewed"]); const dateValid = dateMatch !== null && (() => { const date = new Date(Date.UTC(Number(dateMatch[1]), Number(dateMatch[2]) - 1, Number(dateMatch[3]))); return ( date.getUTCFullYear() === Number(dateMatch[1]) && date.getUTCMonth() === Number(dateMatch[2]) - 1 && date.getUTCDate() === Number(dateMatch[3]) ); })(); if (!dateValid) { return { ok: false, error: "metadata last_reviewed must be a YYYY-MM-DD date" }; } if (meta["project_id"] === "") return { ok: false, error: "metadata project_id must be a nonempty string" }; if (meta["repository_root"] !== ".") { return { ok: false, error: 'metadata repository_root must be "." (repository-root applicability)' }; } return { ok: true, meta, bodyStart: end + 1 }; } function normalizedHeading(line) { const match = /^#{1,6}\s+(.+?)\s*$/.exec(line); return match === null ? null : match[1].toLowerCase().replace(/\s+/g, " ").trim(); } // Required core sections per the spec; a missing section fails closed with its // exact normalized name. The decision matrix must include must-ask boundaries. function checkCoreSections(text, bodyStart) { const lines = String(text).split(/\r?\n/); const bodies = new Map(); let current = null; for (let i = bodyStart; i < lines.length; i++) { const heading = normalizedHeading(lines[i]); if (heading !== null) { if (/^#\s+/.test(lines[i])) continue; if (bodies.has(heading)) { return { ok: false, error: `duplicate protocol section ${JSON.stringify(heading)}` }; } current = heading; bodies.set(heading, []); } else if (current !== null) { bodies.get(current).push(lines[i]); } } for (const required of REQUIRED_CORE_SECTIONS) { if (!bodies.has(required)) { return { ok: false, error: `missing required core section "${required}"` }; } if (bodies.get(required).join("\n").trim() === "") { return { ok: false, error: `required protocol section "${required}" must be nonempty` }; } } for (const heading of bodies.keys()) { if (heading === "workspace protocol" || REQUIRED_CORE_SECTIONS.includes(heading) || OPTIONAL_PROTOCOL_SECTIONS.has(heading)) continue; return { ok: false, error: `unknown protocol section ${JSON.stringify(heading)}` }; } const matrix = (bodies.get("decision matrix") ?? []).join("\n"); if (!/must-ask|must_ask/i.test(matrix)) { return { ok: false, error: "the decision matrix core section must include must-ask boundaries" }; } const routing = (bodies.get("task classes and routing") ?? []).join("\n"); if (!/(?:tiny\/bounded|cross-module\/lifecycle|architecture-sensitive)/i.test(routing)) { return { ok: false, error: "task classes and routing must name tiny/bounded, cross-module/lifecycle, and architecture-sensitive classes" }; } return { ok: true, allowsLeadTiny: /lead self-work\s+(?:is\s+)?(?:allowed|permitted)/i.test(routing) }; } // Validates protocol text: nonempty, frontmatter metadata, required core // sections. Returns { ok: true, meta, digest } or { ok: false, error }; digest // is the canonical sha256 of the raw bytes (utf8 of the text). export function validateProtocol(text) { if (typeof text !== "string" || text.trim() === "") { return { ok: false, error: "workspace protocol must be nonempty" }; } const frontmatter = parseFrontmatter(text); if (!frontmatter.ok) return { ok: false, error: frontmatter.error }; const sections = checkCoreSections(text, frontmatter.bodyStart); if (!sections.ok) return { ok: false, error: sections.error }; return { ok: true, meta: { status: frontmatter.meta["status"], version: Number(frontmatter.meta["version"]), last_reviewed: frontmatter.meta["last_reviewed"], project_id: frontmatter.meta["project_id"], repository_root: frontmatter.meta["repository_root"], }, digest: createHash("sha256").update(text).digest("hex"), allowsLeadTiny: sections.allowsLeadTiny, }; } // Reads and validates the canonical protocol file. The digest is computed over // the raw file bytes; missing, empty, malformed, and core-incomplete files // fail closed with the exact reason. export async function readAndValidateProtocol(repoRoot) { const path = protocolPath(repoRoot); let buffer; try { buffer = await readFile(path); } catch (err) { if (err.code === "ENOENT") { return { ok: false, error: `workspace protocol file is missing at ${path}` }; } return { ok: false, error: `workspace protocol read failed: ${err.message}` }; } if (buffer.length === 0) { return { ok: false, error: "workspace protocol file must be nonempty" }; } const digest = createHash("sha256").update(buffer).digest("hex"); const check = validateProtocol(buffer.toString("utf8")); if (!check.ok) return { ok: false, error: check.error }; return { ok: true, protocol: { repoRoot, path, digest, meta: check.meta, allowsLeadTiny: check.allowsLeadTiny } }; } // Protocol pin (process-latched like the role latch): the Lead pins // { repoRoot, version, projectId, digest } on first successful read+validate, // and every later gate (input / before_agent_start / tool_call) re-reads, // re-validates, and compares. Drift or identity mismatch blocks permanently; // the pin is per repoRoot and re-pins when the resolved root changes. Peer and // Supervisor roles never pin: the protocol is advisory-only for authority, so // authority checks never consult it. async function ensureProtocolPin() { if (latch === null || latch.role !== "lead") return { ok: true }; const repoRoot = await findRepoRoot(); if (repoRoot === null) { return { ok: false, error: "no git repository root is observable for workspace protocol pinning" }; } if (protocolPin === null || protocolPin.repoRoot !== repoRoot) { const read = await readAndValidateProtocol(repoRoot); if (!read.ok) return { ok: false, error: read.error }; protocolPin = { repoRoot, version: read.protocol.meta.version, projectId: read.protocol.meta.project_id, digest: read.protocol.digest, allowsLeadTiny: read.protocol.allowsLeadTiny, }; return { ok: true }; } const read = await readAndValidateProtocol(repoRoot); if (!read.ok) return { ok: false, error: read.error }; if (read.protocol.meta.project_id !== protocolPin.projectId) { return { ok: false, error: "workspace protocol project identity changed from the pinned project_id; a fresh process is required" }; } if (read.protocol.meta.version !== protocolPin.version) { return { ok: false, error: "workspace protocol version changed from the pinned version; a fresh process is required" }; } if (read.protocol.digest !== protocolPin.digest) { return { ok: false, error: "workspace protocol bytes drifted from the pinned digest; a fresh process is required" }; } if (read.protocol.allowsLeadTiny !== protocolPin.allowsLeadTiny) { return { ok: false, error: "workspace protocol Lead self-work allowance drifted; a fresh process is required" }; } return { ok: true }; } // Route binding: the direct Human task message is the only authority route // for Peer grants. tiny Lead and Supervisor recovery grants are issued only // by their idle governed slash-command flows (route "command"); an envelope of // those kinds pasted into a direct message has no route and grants nothing. // supervisor_recovery binds a supervisor process but targets a Lead, so its // role check applies on the command route only; on the direct route every // non-peer kind is rejected by the route check below regardless of process. async function activateEnvelope(envelope, route = "direct") { if (envelope.grant_kind === "supervisor_recovery") { if (route === "command" && latch.role !== "supervisor") { return { ok: false, error: "supervisor_recovery requires a supervisor process" }; } } else if (envelope.role !== latch.role) { return { ok: false, error: `envelope role ${JSON.stringify(envelope.role)} does not match the ${latch.role} process` }; } if (envelope.agent_id !== latch.agentId) { return { ok: false, error: `envelope agent_id ${JSON.stringify(envelope.agent_id)} does not match the latched Paseo agent ${JSON.stringify(latch.agentId)}` }; } // lead_tiny binds the currently pinned protocol digest; a mismatch fails // closed whether the envelope arrived by command or by direct message. if (envelope.grant_kind === "lead_tiny") { if (protocolPin === null) { return { ok: false, error: "lead_tiny requires a pinned workspace protocol digest" }; } if (envelope.protocol_digest !== protocolPin.digest) { return { ok: false, error: "protocol_digest does not match the pinned workspace protocol digest" }; } if (!protocolPin.allowsLeadTiny) { return { ok: false, error: "workspace protocol does not allow Lead tiny self-work" }; } } if (envelope.grant_kind !== "peer") { if (route !== "command") { return { ok: false, error: `grant_kind ${envelope.grant_kind} has no route in this slice for direct messages: it is issued only by its idle slash-command flow after Human confirmation` }; } } if (envelope.grant_kind === "supervisor_recovery") { // Recovery grants carry no edit/commit capability and no writable scope. return { ok: true, authority: { envelope, repoRoot: null, scope: null, exclusions: [] } }; } const repoRoot = await findRepoRoot(); if (repoRoot === null) { return { ok: false, error: "no git repository root is observable for scope validation" }; } const check = await validateScope(repoRoot, envelope.scope, envelope.exclusions); if (!check.ok) return { ok: false, error: check.error }; return { ok: true, authority: { envelope, repoRoot, scope: check.scope, exclusions: check.exclusions } }; } /** * Supervisor Notebook * * This is deliberately a small append-only file store. The manifest and * entries are evidence, not a control plane: none of the helpers below touch * a repository, Paseo state, authority, or lifecycle state. */ export const NOTEBOOK_CONTRACT = "pi-paseo-supervisor-notebook"; export const NOTEBOOK_ENTRY_CONTRACT = "pi-paseo-supervisor-notebook-entry"; export const NOTEBOOK_CONTRACT_VERSION = "v1"; export const NOTEBOOK_STORAGE_VERSION = "v1"; export const NOTEBOOK_INIT_COMMAND = "ppo:notebook-init"; export const NOTEBOOK_APPEND_TOOL = "supervisor_notebook_append"; const NOTEBOOK_MANIFEST_FIELDS = [ "contract", "contract_version", "manifest_schema", "notebook_id", "protocol_project_id", "paseo_project_id_at_creation", "repository_root_at_creation", "project_key", "created_at", "created_by", "creation_route", "manifest_digest", ]; const NOTEBOOK_CREATED_BY_FIELDS = ["supervisor_agent_id", "pi_session_id"]; const NOTEBOOK_ENTRY_FIELDS = [ "contract", "schema_version", "entry_id", "notebook_id", "protocol_project_id", "recorded_at", "observed_at", "writer", "context", "observation", "evidence", "suspected_mechanism", "impact", "question", "recommendation", "escalation", "history", "sensitivity", "entry_digest", ]; const NOTEBOOK_WRITER_FIELDS = ["supervisor_agent_id", "pi_session_id"]; const NOTEBOOK_CONTEXT_FIELDS = [ "paseo_project_id", "repository_root", "paseo_workspace_id", "lead_agent_id", "binding_source", "protocol_pin", ]; const NOTEBOOK_PROTOCOL_PIN_FIELDS = ["version", "digest"]; const NOTEBOOK_MECHANISM_FIELDS = ["hypothesis", "uncertainty", "confidence"]; const NOTEBOOK_ESCALATION_FIELDS = ["needed", "owner", "reason", "relay_target"]; const NOTEBOOK_HISTORY_FIELDS = ["relation", "references", "reason"]; const NOTEBOOK_REFERENCE_FIELDS = ["entry_id", "entry_digest"]; const NOTEBOOK_SENSITIVITY_FIELDS = ["redactions", "contains_secret"]; const NOTEBOOK_EVIDENCE_FIELDS = [ "item_id", "observed_at", "kind", "source", "selected", "source_digest", "retained_digest", "redaction_notes", "truncated", ]; const NOTEBOOK_MAX_ID = 128; const NOTEBOOK_MAX_PROJECT_ID = 512; const NOTEBOOK_MAX_TEXT = 4000; const NOTEBOOK_MAX_SOURCE = 512; const NOTEBOOK_MAX_EVIDENCE = 64; const NOTEBOOK_MAX_REDACTION_NOTES = 32; const NOTEBOOK_DIGEST = /^sha256:[0-9a-f]{64}$/; const NOTEBOOK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/; const NOTEBOOK_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; function notebookStorageRoot(configRoot) { return join(configRoot, "pi-paseo-orchestration", "supervisor-notebooks", NOTEBOOK_STORAGE_VERSION); } export function deriveNotebookProjectKey(projectId) { if (typeof projectId !== "string" || projectId.length === 0 || projectId.length > NOTEBOOK_MAX_PROJECT_ID || projectId.includes("\0")) { throw new Error("protocol project_id must be a nonempty bounded UTF-8 string without NUL"); } return createHash("sha256").update(Buffer.from(projectId, "utf8")).digest("hex").toLowerCase(); } function notebookPaths(configRoot, projectId) { const root = notebookStorageRoot(configRoot); const key = deriveNotebookProjectKey(projectId); const projectRoot = join(root, "projects", key); return { configRoot, storageRoot: root, projectsRoot: join(root, "projects"), projectRoot, manifestPath: join(projectRoot, "manifest.json"), entriesRoot: join(projectRoot, "entries"), stagingRoot: join(root, ".staging"), projectKey: key, }; } function canonicalNotebookValue(value) { if (Array.isArray(value)) return value.map(canonicalNotebookValue); if (isRecord(value)) { return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalNotebookValue(value[key])])); } if (typeof value === "number" && !Number.isFinite(value)) throw new Error("canonical JSON cannot contain a non-finite number"); if (value === undefined) throw new Error("canonical JSON cannot contain undefined"); return value; } export function canonicalNotebookJson(value) { const text = JSON.stringify(canonicalNotebookValue(value)); if (typeof text !== "string") throw new Error("canonical JSON value is not serializable"); return text; } function notebookBytes(value) { return Buffer.from(`${canonicalNotebookJson(value)}\n`, "utf8"); } function notebookDigest(value, field) { const copy = structuredClone(value); delete copy[field]; return `sha256:${createHash("sha256").update(Buffer.from(canonicalNotebookJson(copy), "utf8")).digest("hex")}`; } function rawDigest(bytes) { return `sha256:${createHash("sha256").update(bytes).digest("hex")}`; } function notebookClosed(value, fields, label) { if (!isRecord(value)) return { ok: false, error: `${label} must be a single object` }; const extra = Object.keys(value).find((field) => !fields.includes(field)); if (extra !== undefined) return { ok: false, error: `unknown field ${JSON.stringify(extra)} in ${label}` }; return { ok: true }; } function notebookText(value, label, max = NOTEBOOK_MAX_TEXT) { if (typeof value !== "string" || value.trim() === "") return { ok: false, error: `${label} must be a nonempty string` }; if (value.length > max) return { ok: false, error: `${label} exceeds the ${max}-character bound` }; if (value.includes("\0")) return { ok: false, error: `${label} must not contain NUL` }; return { ok: true }; } function notebookId(value, label) { if (typeof value !== "string" || !NOTEBOOK_ID.test(value) || value === "." || value === "..") { return { ok: false, error: `${label} must be one safe filename component` }; } return { ok: true }; } function notebookDigestField(value, label) { return NOTEBOOK_DIGEST.test(value) ? { ok: true } : { ok: false, error: `${label} must be a sha256:<64 lowercase hex> digest` }; } function notebookTimestamp(value, label) { if (typeof value !== "string" || !NOTEBOOK_TIMESTAMP.test(value) || Number.isNaN(Date.parse(value))) { return { ok: false, error: `${label} must be an RFC3339 UTC timestamp with milliseconds` }; } return { ok: true }; } function notebookPathLocator(value, label, { allowUnknown = false } = {}) { if (allowUnknown && value === "unknown") return { ok: true }; if (typeof value !== "string" || value.trim() === "" || value.includes("\0")) { return { ok: false, error: `${label} must be a nonempty path locator` }; } if (!isAbsolute(value)) return { ok: false, error: `${label} must be an absolute canonical path` }; if (posix.normalize(value) !== value || (value.length > 1 && value.endsWith("/"))) { return { ok: false, error: `${label} must be canonical without traversal or normalization aliases` }; } return { ok: true }; } export function validateNotebookManifest(manifest, options = {}) { const rawText = options["rawText"]; let check = notebookClosed(manifest, NOTEBOOK_MANIFEST_FIELDS, "notebook manifest"); if (!check.ok) return { ok: false, error: check.error }; for (const [field, expected] of [ ["contract", NOTEBOOK_CONTRACT], ["contract_version", NOTEBOOK_CONTRACT_VERSION], ["manifest_schema", NOTEBOOK_CONTRACT_VERSION], ]) { if (manifest[field] !== expected) return { ok: false, error: `manifest.${field} must be exactly ${JSON.stringify(expected)}` }; } for (const field of ["notebook_id"]) { check = notebookId(manifest[field], `manifest.${field}`); if (!check.ok) return { ok: false, error: check.error }; } check = notebookText(manifest.protocol_project_id, "manifest.protocol_project_id", NOTEBOOK_MAX_PROJECT_ID); if (!check.ok) return { ok: false, error: check.error }; try { if (deriveNotebookProjectKey(manifest.protocol_project_id) !== manifest.project_key) { return { ok: false, error: "manifest.project_key does not equal lowercase sha256(protocol_project_id UTF-8 bytes)" }; } } catch (err) { return { ok: false, error: err.message }; } check = notebookPathLocator(manifest.repository_root_at_creation, "manifest.repository_root_at_creation"); if (!check.ok) return { ok: false, error: check.error }; check = notebookText(manifest.paseo_project_id_at_creation, "manifest.paseo_project_id_at_creation", 512); if (!check.ok) return { ok: false, error: check.error }; check = notebookTimestamp(manifest.created_at, "manifest.created_at"); if (!check.ok) return { ok: false, error: check.error }; check = notebookClosed(manifest.created_by, NOTEBOOK_CREATED_BY_FIELDS, "manifest.created_by"); if (!check.ok) return { ok: false, error: check.error }; for (const field of NOTEBOOK_CREATED_BY_FIELDS) { check = notebookText(manifest.created_by[field], `manifest.created_by.${field}`, 512); if (!check.ok) return { ok: false, error: check.error }; } if (manifest.creation_route !== "human_confirmed") { return { ok: false, error: "manifest.creation_route must be exactly human_confirmed" }; } check = notebookDigestField(manifest.manifest_digest, "manifest.manifest_digest"); if (!check.ok) return { ok: false, error: check.error }; if (manifest.manifest_digest !== notebookDigest(manifest, "manifest_digest")) { return { ok: false, error: "manifest.manifest_digest does not match canonical manifest bytes" }; } if (rawText !== undefined) { let expected; try { expected = notebookBytes(manifest).toString("utf8"); } catch (err) { return { ok: false, error: err.message }; } if (rawText !== expected && rawText !== expected.slice(0, -1)) return { ok: false, error: "notebook manifest bytes are not canonical JSON" }; } return { ok: true, manifest }; } export function parseNotebookManifest(text) { if (typeof text !== "string") return { ok: false, error: "notebook manifest must be a string" }; const duplicate = findDuplicateKey(text); if (duplicate !== null) return { ok: false, error: `notebook manifest contains duplicate field ${JSON.stringify(duplicate)}` }; let manifest; try { manifest = JSON.parse(text); } catch { return { ok: false, error: "notebook manifest is not valid JSON" }; } const check = validateNotebookManifest(manifest, { rawText: text }); return check.ok ? { ok: true, manifest } : check; } function validateNotebookEvidenceItem(item, index) { let check = notebookClosed(item, NOTEBOOK_EVIDENCE_FIELDS, `notebook evidence[${index}]`); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["item_id", "kind", "source", "selected", "retained_digest"]) { check = notebookText(item[field], `notebook evidence[${index}].${field}`, field === "source" ? NOTEBOOK_MAX_SOURCE : NOTEBOOK_MAX_TEXT); if (!check.ok) return { ok: false, error: check.error }; } check = notebookTimestamp(item.observed_at, `notebook evidence[${index}].observed_at`); if (!check.ok) return { ok: false, error: check.error }; if (item.source_digest !== null) { check = notebookDigestField(item.source_digest, `notebook evidence[${index}].source_digest`); if (!check.ok) return { ok: false, error: check.error }; } check = notebookDigestField(item.retained_digest, `notebook evidence[${index}].retained_digest`); if (!check.ok) return { ok: false, error: check.error }; if (item.retained_digest !== rawDigest(Buffer.from(item.selected, "utf8"))) { return { ok: false, error: `notebook evidence[${index}].retained_digest does not match the retained redacted representation` }; } if (!Array.isArray(item.redaction_notes) || item.redaction_notes.length > NOTEBOOK_MAX_REDACTION_NOTES || item.redaction_notes.some((note) => typeof note !== "string" || note.trim() === "" || note.length > 512)) { return { ok: false, error: `notebook evidence[${index}].redaction_notes must be a bounded array of nonempty strings` }; } if (typeof item.truncated !== "boolean") return { ok: false, error: `notebook evidence[${index}].truncated must be a boolean` }; return { ok: true }; } function validateNotebookContext(context) { let check = notebookClosed(context, NOTEBOOK_CONTEXT_FIELDS, "notebook entry context"); if (!check.ok) return { ok: false, error: check.error }; check = notebookText(context.paseo_project_id, "context.paseo_project_id", 512); if (!check.ok || context.paseo_project_id === "unknown") return { ok: false, error: "context.paseo_project_id must be an exact current Paseo project identity" }; check = notebookPathLocator(context.repository_root, "context.repository_root"); if (!check.ok) return { ok: false, error: check.error }; check = notebookText(context.paseo_workspace_id, "context.paseo_workspace_id", 512); if (!check.ok) return { ok: false, error: check.error }; check = notebookText(context.lead_agent_id, "context.lead_agent_id", 512); if (!check.ok) return { ok: false, error: check.error }; check = notebookId(context.binding_source, "context.binding_source"); if (!check.ok && context.binding_source !== "manifest") return check; if (context.protocol_pin !== null) { check = notebookClosed(context.protocol_pin, NOTEBOOK_PROTOCOL_PIN_FIELDS, "context.protocol_pin"); if (!check.ok) return { ok: false, error: check.error }; if (!Number.isSafeInteger(context.protocol_pin.version) || context.protocol_pin.version < 1) { return { ok: false, error: "context.protocol_pin.version must be a positive integer" }; } check = notebookDigestField(context.protocol_pin.digest, "context.protocol_pin.digest"); if (!check.ok) return { ok: false, error: check.error }; } return { ok: true }; } export function validateNotebookEntry(entry, options = {}) { const manifest = options["manifest"]; const rawText = options["rawText"]; let check = notebookClosed(entry, NOTEBOOK_ENTRY_FIELDS, "notebook entry"); if (!check.ok) return { ok: false, error: check.error }; if (entry.contract !== NOTEBOOK_ENTRY_CONTRACT || entry.schema_version !== NOTEBOOK_CONTRACT_VERSION) { return { ok: false, error: "notebook entry contract and schema_version must be exactly v1" }; } check = notebookId(entry.entry_id, "entry.entry_id"); if (!check.ok) return { ok: false, error: check.error }; check = notebookId(entry.notebook_id, "entry.notebook_id"); if (!check.ok) return { ok: false, error: check.error }; check = notebookText(entry.protocol_project_id, "entry.protocol_project_id", NOTEBOOK_MAX_PROJECT_ID); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["recorded_at", "observed_at"]) { check = notebookTimestamp(entry[field], `entry.${field}`); if (!check.ok) return { ok: false, error: check.error }; } check = notebookClosed(entry.writer, NOTEBOOK_WRITER_FIELDS, "notebook entry writer"); if (!check.ok) return { ok: false, error: check.error }; for (const field of NOTEBOOK_WRITER_FIELDS) { check = notebookText(entry.writer[field], `entry.writer.${field}`, 512); if (!check.ok) return { ok: false, error: check.error }; } check = validateNotebookContext(entry.context); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["observation", "impact", "question", "recommendation"]) { check = notebookText(entry[field], `entry.${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!Array.isArray(entry.evidence) || entry.evidence.length === 0 || entry.evidence.length > NOTEBOOK_MAX_EVIDENCE) { return { ok: false, error: `entry.evidence must contain 1-${NOTEBOOK_MAX_EVIDENCE} items` }; } for (const [index, item] of entry.evidence.entries()) { check = validateNotebookEvidenceItem(item, index); if (!check.ok) return { ok: false, error: check.error }; } check = notebookClosed(entry.suspected_mechanism, NOTEBOOK_MECHANISM_FIELDS, "notebook suspected_mechanism"); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["hypothesis", "uncertainty"]) { check = notebookText(entry.suspected_mechanism[field], `suspected_mechanism.${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!["low", "medium", "high"].includes(entry.suspected_mechanism.confidence)) { return { ok: false, error: "suspected_mechanism.confidence must be low|medium|high" }; } check = notebookClosed(entry.escalation, NOTEBOOK_ESCALATION_FIELDS, "notebook escalation"); if (!check.ok) return { ok: false, error: check.error }; if (typeof entry.escalation.needed !== "boolean") return { ok: false, error: "escalation.needed must be a boolean" }; if (!["lead", "human", "none"].includes(entry.escalation.owner)) return { ok: false, error: "escalation.owner must be lead|human|none" }; check = notebookText(entry.escalation.reason, "escalation.reason"); if (!check.ok) return { ok: false, error: check.error }; if (entry.escalation.relay_target !== null) { check = notebookText(entry.escalation.relay_target, "escalation.relay_target", 512); if (!check.ok) return { ok: false, error: check.error }; } check = notebookClosed(entry.history, NOTEBOOK_HISTORY_FIELDS, "notebook history"); if (!check.ok) return { ok: false, error: check.error }; if (!["original", "correction", "supersession", "rebind"].includes(entry.history.relation)) { return { ok: false, error: "history.relation must be original|correction|supersession|rebind" }; } if (!Array.isArray(entry.history.references) || entry.history.references.length > NOTEBOOK_MAX_EVIDENCE) { return { ok: false, error: "history.references must be a bounded array" }; } for (const [index, reference] of entry.history.references.entries()) { check = notebookClosed(reference, NOTEBOOK_REFERENCE_FIELDS, `history.references[${index}]`); if (!check.ok) return { ok: false, error: check.error }; check = notebookId(reference.entry_id, `history.references[${index}].entry_id`); if (!check.ok) return { ok: false, error: check.error }; check = notebookDigestField(reference.entry_digest, `history.references[${index}].entry_digest`); if (!check.ok) return { ok: false, error: check.error }; } if (["correction", "supersession"].includes(entry.history.relation) && entry.history.references.length === 0) { return { ok: false, error: `history.${entry.history.relation} requires a prior entry reference` }; } check = notebookText(entry.history.reason, "history.reason"); if (!check.ok) return { ok: false, error: check.error }; check = notebookClosed(entry.sensitivity, NOTEBOOK_SENSITIVITY_FIELDS, "notebook sensitivity"); if (!check.ok) return { ok: false, error: check.error }; if (!Array.isArray(entry.sensitivity.redactions) || entry.sensitivity.redactions.length > NOTEBOOK_MAX_REDACTION_NOTES || entry.sensitivity.redactions.some((item) => typeof item !== "string" || item.trim() === "" || item.length > 512)) { return { ok: false, error: "sensitivity.redactions must be a bounded array of nonempty strings" }; } if (entry.sensitivity.contains_secret !== false) return { ok: false, error: "sensitivity.contains_secret must be exactly false" }; check = notebookDigestField(entry.entry_digest, "entry.entry_digest"); if (!check.ok) return { ok: false, error: check.error }; if (manifest !== undefined) { if (!manifest || entry.notebook_id !== manifest.notebook_id) return { ok: false, error: "entry.notebook_id does not match the manifest" }; if (entry.protocol_project_id !== manifest.protocol_project_id) return { ok: false, error: "entry.protocol_project_id does not match the manifest" }; } if (entry.entry_digest !== notebookDigest(entry, "entry_digest")) { return { ok: false, error: "entry.entry_digest does not match canonical entry bytes" }; } if (rawText !== undefined) { const expected = notebookBytes(entry).toString("utf8"); if (rawText !== expected && rawText !== expected.slice(0, -1)) return { ok: false, error: "notebook entry bytes are not canonical JSON" }; } return { ok: true, entry }; } export function parseNotebookEntry(text, options = {}) { const manifest = options["manifest"]; if (typeof text !== "string") return { ok: false, error: "notebook entry must be a string" }; const duplicate = findDuplicateKey(text); if (duplicate !== null) return { ok: false, error: `notebook entry contains duplicate field ${JSON.stringify(duplicate)}` }; let entry; try { entry = JSON.parse(text); } catch { return { ok: false, error: "notebook entry is not valid JSON" }; } const check = validateNotebookEntry(entry, { manifest, rawText: text }); return check.ok ? { ok: true, entry } : check; } function safeNotebookComponentPath(root, target, label) { const rel = relative(root, target); if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return { ok: false, error: `${label} escapes the Pi config root` }; return { ok: true }; } async function canonicalConfigRoot(raw, create = false) { const candidate = isAbsolute(raw) ? raw : join(process.cwd(), raw); try { const real = await realpath(candidate); if (!(await stat(real)).isDirectory()) return { ok: false, error: "effective Pi config directory is not a directory" }; return { ok: true, path: real }; } catch (err) { if (err.code !== "ENOENT" || !create) return { ok: false, error: "effective Pi config directory is not readable" }; try { await mkdir(candidate, { recursive: true, mode: 0o700 }); const real = await realpath(candidate); return { ok: true, path: real }; } catch (createErr) { return { ok: false, error: `effective Pi config directory cannot be created: ${createErr.message}` }; } } } async function assertNoSymlinkComponents(root, target, label, { allowMissing = false } = {}) { const containment = safeNotebookComponentPath(root, target, label); if (!containment.ok) return { ok: false, error: containment.error }; const rel = relative(root, target); let current = root; for (const part of rel.split(/[\\/]/).filter(Boolean)) { current = join(current, part); let entry; try { entry = await lstat(current); } catch (err) { if (err.code === "ENOENT" && allowMissing) return { ok: true }; return { ok: false, error: `${label} is not readable` }; } if (entry.isSymbolicLink()) return { ok: false, error: `${label} contains a symlink component` }; } return { ok: true }; } async function makePrivateDirectory(path, root, label) { const safe = await assertNoSymlinkComponents(root, path, label, { allowMissing: true }); if (!safe.ok) return { ok: false, error: safe.error }; try { await mkdir(path, { recursive: true, mode: 0o700 }); } catch (err) { return { ok: false, error: `${label} cannot be created: ${err.message}` }; } return assertNoSymlinkComponents(root, path, label); } async function prepareNotebookPaths(env, projectId, create = false) { let root = await canonicalConfigRoot(configDir(env), create); if (!root.ok) return { ok: false, error: root.error }; let paths; try { paths = notebookPaths(root.path, projectId); } catch (err) { return { ok: false, error: err.message }; } if (create) { for (const [path, label] of [[paths.storageRoot, "notebook storage root"], [paths.projectsRoot, "notebook projects root"], [paths.projectRoot, "notebook project directory"], [paths.entriesRoot, "notebook entries directory"], [paths.stagingRoot, "notebook staging directory"]]) { const made = await makePrivateDirectory(path, root.path, label); if (!made.ok) return { ok: false, error: made.error }; } } else { for (const [path, label] of [[paths.storageRoot, "notebook storage root"], [paths.projectsRoot, "notebook projects root"], [paths.projectRoot, "notebook project directory"], [paths.entriesRoot, "notebook entries directory"]]) { const check = await assertNoSymlinkComponents(root.path, path, label); if (!check.ok) return { ok: false, error: check.error }; } } return { ok: true, root: root.path, paths }; } async function parseNotebookJson(bytes, label) { const text = bytes.toString("utf8"); const duplicate = findDuplicateKey(text); if (duplicate !== null) return { ok: false, error: `${label} contains duplicate field ${JSON.stringify(duplicate)}` }; try { return { ok: true, value: JSON.parse(text), text }; } catch { return { ok: false, error: `${label} is not valid JSON` }; } } async function readManifestForPaths(prepared) { const safe = await assertNoSymlinkComponents(prepared.root, prepared.paths.manifestPath, "notebook manifest path"); if (!safe.ok) return { ok: false, error: safe.error }; let bytes; try { bytes = await readFile(prepared.paths.manifestPath); } catch (err) { if (err.code === "ENOENT") return { ok: false, error: "notebook manifest is missing" }; return { ok: false, error: `notebook manifest read failed: ${err.message}` }; } const parsed = await parseNotebookJson(bytes, "notebook manifest"); if (!parsed.ok) return { ok: false, error: parsed.error }; const check = validateNotebookManifest(parsed.value, { rawText: parsed.text }); if (!check.ok) return { ok: false, error: check.error }; if (parsed.value.project_key !== prepared.paths.projectKey) return { ok: false, error: "manifest project_key does not match its derived notebook directory" }; return { ok: true, manifest: parsed.value, bytes, rawDigest: rawDigest(bytes) }; } async function readValidNotebookEntries(prepared, manifest) { let names; try { names = await readdir(prepared.paths.entriesRoot); } catch (err) { return { ok: false, error: `notebook entries directory read failed: ${err.message}` }; } const files = []; const valid = []; const invalid = []; for (const name of names.sort()) { const path = join(prepared.paths.entriesRoot, name); let st; try { st = await lstat(path); } catch (err) { return { ok: false, error: `notebook entry ${name} cannot be inspected: ${err.message}` }; } if (!st.isFile()) return { ok: false, error: `notebook entry ${name} is not a regular direct-child file` }; const bytes = await readFile(path); const digest = rawDigest(bytes); files.push({ filename: name, raw_digest: digest }); const parsed = await parseNotebookJson(bytes, `notebook entry ${name}`); let check = parsed.ok ? validateNotebookEntry(parsed.value, { manifest, rawText: parsed.text }) : { ok: false, error: parsed.error }; if (check.ok) { if (name !== `${check.entry.entry_id}.json`) check = { ok: false, error: "entry filename does not match entry_id" }; } if (check.ok) valid.push(check.entry); else invalid.push({ filename: name, raw_digest: digest, error: check.error }); } return { ok: true, files, valid, invalid }; } async function syncNotebookDirectory(path) { let handle; let failure = null; try { handle = await open(path, "r"); await handle.sync(); } catch (err) { failure = new Error(`directory durability sync failed for ${path}: ${err.message}`); } if (handle !== undefined) { try { await handle.close(); } catch (err) { failure ??= new Error(`directory durability close failed for ${path}: ${err.message}`); } } if (failure !== null) throw failure; } // One package-private publication primitive: complete private staging, fsync, // atomic hard-link to an absent final name, parent fsync, staging cleanup. export async function publishNotebookCreateOnly({ storageRoot, finalParent, finalName, bytes }) { if (!Buffer.isBuffer(bytes)) bytes = Buffer.from(bytes); const safeParent = await assertNoSymlinkComponents(storageRoot, finalParent, "notebook final parent"); if (!safeParent.ok) return { ok: false, error: safeParent.error }; if (dirname(join(finalParent, finalName)) !== finalParent || finalName.includes("/") || finalName.includes("\\") || finalName === "." || finalName === "..") { return { ok: false, error: "notebook final name must be a direct child of the exact final parent" }; } const stagingRoot = join(storageRoot, ".staging"); const safeStage = await assertNoSymlinkComponents(storageRoot, stagingRoot, "notebook staging root"); if (!safeStage.ok) return { ok: false, error: safeStage.error }; const finalPath = join(finalParent, finalName); let before = null; let inspectError = null; try { before = await lstat(finalPath); } catch (err) { if (err.code !== "ENOENT") inspectError = err; } if (inspectError) return { ok: false, error: `notebook final path cannot be inspected: ${inspectError.message}` }; if (before && before.isSymbolicLink()) return { ok: false, error: "notebook final path must not be a symlink" }; if (before && !before.isFile()) return { ok: false, error: "notebook final path is not a regular file" }; if (before) { const existing = await readFile(finalPath); const incomingDigest = rawDigest(bytes); const existingDigest = rawDigest(existing); if (existingDigest === incomingDigest) { try { await syncNotebookDirectory(finalParent); } catch (err) { return { ok: false, error: err.message }; } return { ok: true, status: "idempotent", path: finalPath, digest: existingDigest }; } return { ok: false, status: "conflict", error: "notebook final path already contains different bytes", path: finalPath, existing_digest: existingDigest, incoming_digest: incomingDigest }; } const stagePath = join(stagingRoot, `.stage-${randomUUID()}`); let handle; let stageCreated = false; let result; let cleanupError = null; try { handle = await open(stagePath, "wx", 0o600); stageCreated = true; let offset = 0; while (offset < bytes.length) { const written = await handle.write(bytes, offset, bytes.length - offset); if (!written.bytesWritten) throw new Error("staging write made no progress"); offset += written.bytesWritten; } await handle.sync(); await handle.close(); handle = null; const finalSafe = await assertNoSymlinkComponents(storageRoot, finalParent, "notebook final parent"); if (!finalSafe.ok) { result = finalSafe; } else { try { await link(stagePath, finalPath); await syncNotebookDirectory(finalParent); result = { ok: true, status: "created", path: finalPath, digest: rawDigest(bytes) }; } catch (err) { if (err.code === "EEXIST") { const existing = await readFile(finalPath); const incomingDigest = rawDigest(bytes); const existingDigest = rawDigest(existing); result = existingDigest === incomingDigest ? { ok: true, status: "idempotent", path: finalPath, digest: existingDigest } : { ok: false, status: "conflict", error: "notebook final path already contains different bytes", path: finalPath, existing_digest: existingDigest, incoming_digest: incomingDigest }; } else if (["EXDEV", "EOPNOTSUPP", "ENOTSUP", "EPERM"].includes(err.code)) { result = { ok: false, error: `no-replace notebook publication is unsupported: ${err.message}` }; } else { result = { ok: false, error: `notebook publication link failed: ${err.message}` }; } } } } catch (err) { result = { ok: false, error: `notebook publication failed: ${err.message}` }; } finally { try { await handle?.close(); } catch (err) { cleanupError ??= `staging handle close failed: ${err.message}`; } // A linked inode is already durable evidence; only the private staging name // is cleaned. Cleanup failures are surfaced rather than silently accepted. try { await unlink(stagePath); } catch (err) { if (err.code !== "ENOENT" || stageCreated) cleanupError ??= `staging cleanup failed: ${err.message}`; } try { await syncNotebookDirectory(stagingRoot); } catch (err) { cleanupError ??= err.message; } } if (cleanupError !== null) return { ok: false, error: cleanupError }; return result ?? { ok: false, error: "notebook publication produced no result" }; } async function findNotebookIdElsewhere(prepared, notebookId) { let names; try { names = await readdir(prepared.paths.projectsRoot); } catch (err) { return { ok: false, error: `notebook projects directory read failed: ${err.message}` }; } for (const name of names) { if (name === prepared.paths.projectKey) continue; if (!NOTEBOOK_ID.test(name)) continue; const candidateRoot = join(prepared.paths.projectsRoot, name); const candidateSafety = await assertNoSymlinkComponents(prepared.root, candidateRoot, "notebook project scan path"); if (!candidateSafety.ok) return { ok: false, error: candidateSafety.error }; const candidate = { ...prepared, paths: notebookPaths(prepared.root, "x") }; candidate.paths.projectRoot = candidateRoot; candidate.paths.manifestPath = join(candidate.paths.projectRoot, "manifest.json"); let bytes; try { bytes = await readFile(candidate.paths.manifestPath); } catch (err) { if (err.code === "ENOENT") continue; return { ok: false, error: `notebook manifest scan failed: ${err.message}` }; } const parsed = await parseNotebookJson(bytes, "notebook manifest"); if (!parsed.ok) return { ok: false, error: parsed.error }; const check = validateNotebookManifest(parsed.value, { rawText: parsed.text }); if (!check.ok) return { ok: false, error: `another notebook is malformed: ${check.error}` }; if (parsed.value.notebook_id === notebookId) return { ok: false, error: "notebook_id already exists under another project key" }; } return { ok: true }; } async function canonicalLocator(value, label, allowUnknown = true) { if (allowUnknown && value === "unknown") return { ok: true, value }; if (typeof value !== "string" || value.trim() === "") return { ok: false, error: `${label} must be nonempty` }; const candidate = isAbsolute(value) ? value : join(process.cwd(), value); try { const real = await realpath(candidate); if (!(await stat(real)).isDirectory()) return { ok: false, error: `${label} must name a directory` }; return { ok: true, value: real }; } catch (err) { return { ok: false, error: `${label} is not readable: ${err.message}` }; } } export async function initializeNotebook(options = {}) { const env = options["env"] ?? process.env; const projectId = options["projectId"]; const protocolProjectId = options["protocolProjectId"]; const paseoProjectId = options["paseoProjectId"]; const repositoryRoot = options["repositoryRoot"] ?? "unknown"; const supervisorAgentId = options["supervisorAgentId"]; const piSessionId = options["piSessionId"]; const createdAt = options["createdAt"] ?? new Date().toISOString(); const humanProjectId = projectId ?? protocolProjectId; if (typeof humanProjectId !== "string" || humanProjectId.length === 0) return { ok: false, error: "protocol project_id is required" }; const paseo = typeof paseoProjectId === "string" && paseoProjectId.trim() !== "" && paseoProjectId !== "unknown" ? paseoProjectId : null; if (paseo === null) return { ok: false, error: "paseo_project_id_at_creation is required" }; const agent = notebookText(supervisorAgentId, "supervisor_agent_id", 512); if (!agent.ok) return { ok: false, error: agent.error }; const session = notebookText(piSessionId, "pi_session_id", 512); if (!session.ok) return { ok: false, error: session.error }; const repo = await canonicalLocator(repositoryRoot, "repository_root_at_creation", true); if (!repo.ok) return { ok: false, error: repo.error }; const prepared = await prepareNotebookPaths(env, humanProjectId, true); if (!prepared.ok) return { ok: false, error: prepared.error }; let notebookId; try { notebookId = `nb-${randomUUID()}`; } catch (err) { return { ok: false, error: `notebook identity generation failed: ${err.message}` }; } const duplicate = await findNotebookIdElsewhere(prepared, notebookId); if (!duplicate.ok) return { ok: false, error: duplicate.error }; let existing; try { existing = await lstat(prepared.paths.manifestPath); } catch (err) { if (err.code !== "ENOENT") return { ok: false, error: `notebook manifest cannot be inspected: ${err.message}` }; } if (existing) return { ok: false, error: "notebook manifest already exists; initialization is create-once" }; const manifest = { contract: NOTEBOOK_CONTRACT, contract_version: NOTEBOOK_CONTRACT_VERSION, manifest_schema: NOTEBOOK_CONTRACT_VERSION, notebook_id: notebookId, protocol_project_id: humanProjectId, paseo_project_id_at_creation: paseo, repository_root_at_creation: repo.value, project_key: prepared.paths.projectKey, created_at: createdAt, created_by: { supervisor_agent_id: supervisorAgentId, pi_session_id: piSessionId }, creation_route: "human_confirmed", manifest_digest: "", }; manifest.manifest_digest = notebookDigest(manifest, "manifest_digest"); const check = validateNotebookManifest(manifest); if (!check.ok) return { ok: false, error: check.error }; const published = await publishNotebookCreateOnly({ storageRoot: prepared.paths.storageRoot, finalParent: prepared.paths.projectRoot, finalName: "manifest.json", bytes: notebookBytes(manifest), }); if (!published.ok) return { ok: false, error: published.error }; if (published.status !== "created") return { ok: false, error: "notebook manifest already exists; initialization is create-once" }; return { ok: true, manifest, paths: prepared.paths, status: "created" }; } async function loadNotebook(prepared) { const manifest = await readManifestForPaths(prepared); if (!manifest.ok) return { ok: false, error: manifest.error }; const entries = await readValidNotebookEntries(prepared, manifest.manifest); if (!entries.ok) return { ok: false, error: entries.error }; return { ok: true, manifest: manifest.manifest, bytes: manifest.bytes, rawDigest: manifest.rawDigest, entries: { files: entries.files, valid: entries.valid, invalid: entries.invalid } }; } function contextForNotebook(options = {}) { const context = options["context"] ?? {}; return { paseo_project_id: context["paseo_project_id"] ?? context["paseoProjectId"] ?? options["paseoProjectId"] ?? "unknown", repository_root: context["repository_root"] ?? context["repositoryRoot"] ?? options["repositoryRoot"] ?? "unknown", paseo_workspace_id: context["paseo_workspace_id"] ?? context["paseoWorkspaceId"] ?? options["paseoWorkspaceId"] ?? "unknown", lead_agent_id: context["lead_agent_id"] ?? context["leadAgentId"] ?? options["leadAgentId"] ?? "unknown", binding_source: context["binding_source"] ?? context["bindingSource"] ?? options["bindingSource"] ?? "manifest", protocol_pin: context["protocol_pin"] ?? context["protocolPin"] ?? options["protocolPin"] ?? null, }; } function currentNotebookBinding(loaded) { let binding = { paseo_project_id: loaded.manifest.paseo_project_id_at_creation, repository_root: loaded.manifest.repository_root_at_creation, source: "manifest", }; for (const entry of loaded.entries.valid.sort((a, b) => a.entry_id.localeCompare(b.entry_id))) { if (entry.history.relation === "rebind" && entry.context.binding_source === entry.entry_id) { binding = { paseo_project_id: entry.context.paseo_project_id, repository_root: entry.context.repository_root, source: entry.entry_id }; } } return binding; } export function classifyNotebookBinding(manifestOrLoaded, context) { const loaded = manifestOrLoaded?.manifest ? manifestOrLoaded : { manifest: manifestOrLoaded, entries: { valid: [] } }; if (!loaded.manifest) return { ok: false, classification: "invalid", error: "notebook manifest is required" }; const binding = currentNotebookBinding(loaded); const actual = contextForNotebook({ context }); const sameProject = actual.paseo_project_id === binding.paseo_project_id; const sameRepository = actual.repository_root === binding.repository_root; if (sameProject && sameRepository) return { ok: true, classification: "same", binding_source: binding.source, context: actual }; return { ok: false, classification: "move_or_copy", error: "notebook project membership or repository locator changed; Human must classify move versus copy", binding, context: actual, }; } function normalizeNotebookEvidence(item, index) { const value = structuredClone(item); if (value.selected === undefined) value.selected = value.selected_facts ?? value.excerpt; if (value.source === undefined) value.source = value.source_locator; if (value.redaction_notes === undefined) value.redaction_notes = value.redactions ?? []; if (value.source_digest === undefined) value.source_digest = null; if (value.truncated === undefined) value.truncated = false; if (value.retained_digest === undefined && typeof value.selected === "string") value.retained_digest = rawDigest(Buffer.from(value.selected, "utf8")); return value; } function redactNotebookText(text, path, redactions) { if (typeof text !== "string") return text; let result = text; const secretPattern = /((?:password|passwd|secret|token|credential|api[_-]?key|private[_-]?key|authorization)\s*[:=]\s*)([^\s,;]+)/gi; result = result.replace(secretPattern, (_whole, prefix) => { redactions.push(`${path}:secret`); return `${prefix}[REDACTED]`; }); if (result.length > NOTEBOOK_MAX_TEXT) { redactions.push(`${path}:truncated`); result = `${result.slice(0, NOTEBOOK_MAX_TEXT - 32)}…[TRUNCATED]`; } return result; } function redactNotebookEntry(entry) { const value = structuredClone(entry); const redactions = Array.isArray(value.sensitivity?.redactions) ? [...value.sensitivity.redactions] : []; for (const field of ["observation", "impact", "question", "recommendation", "history"]) { if (field === "history") { if (typeof value.history?.reason === "string") value.history.reason = redactNotebookText(value.history.reason, "history.reason", redactions); } else if (typeof value[field] === "string") value[field] = redactNotebookText(value[field], field, redactions); } if (value.suspected_mechanism) { for (const field of ["hypothesis", "uncertainty"]) value.suspected_mechanism[field] = redactNotebookText(value.suspected_mechanism[field], `suspected_mechanism.${field}`, redactions); } if (value.escalation?.reason) value.escalation.reason = redactNotebookText(value.escalation.reason, "escalation.reason", redactions); for (const [index, item] of (value.evidence ?? []).entries()) { if (typeof item.selected === "string") item.selected = redactNotebookText(item.selected, `evidence[${index}].selected`, redactions); if (Array.isArray(item.redaction_notes)) item.redaction_notes = item.redaction_notes.map((note) => redactNotebookText(note, `evidence[${index}].redaction_notes`, redactions)); if (item.selected !== undefined) item.retained_digest = rawDigest(Buffer.from(item.selected, "utf8")); } if (value.sensitivity?.contains_secret === true) throw new Error("notebook entry declares contains_secret=true; redact it before appending"); if (value.sensitivity) value.sensitivity = { ...value.sensitivity, redactions: [...new Set(redactions)], contains_secret: false }; return value; } export async function appendNotebookEntry(options = {}) { const env = options["env"] ?? process.env; const projectId = options["projectId"]; const protocolProjectId = options["protocolProjectId"]; const entry = options["entry"]; const context = options["context"]; const allowRebind = options["allowRebind"] ?? false; const supervisorAgentId = options["supervisorAgentId"]; const piSessionId = options["piSessionId"]; const humanProjectId = projectId ?? protocolProjectId ?? entry?.protocol_project_id; if (typeof humanProjectId !== "string" || humanProjectId.length === 0) return { ok: false, error: "protocol project_id is required" }; const prepared = await prepareNotebookPaths(env, humanProjectId, false); if (!prepared.ok) return { ok: false, error: prepared.error }; const loaded = await loadNotebook(prepared); if (!loaded.ok) return { ok: false, error: loaded.error }; const binding = classifyNotebookBinding(loaded, context); if (!binding.ok && !allowRebind) return { ok: false, error: binding.error, classification: binding.classification, binding: binding.binding, context: binding.context }; let candidate; try { candidate = redactNotebookEntry(structuredClone(entry)); } catch (err) { return { ok: false, error: err.message }; } if (!isRecord(candidate)) return { ok: false, error: "notebook append entry must be an object" }; if (context !== undefined) { const actual = contextForNotebook({ context }); for (const field of ["paseo_project_id", "repository_root", "paseo_workspace_id", "lead_agent_id"]) { if (candidate.context?.[field] !== actual[field]) return { ok: false, error: `entry.context.${field} does not match the current live Notebook binding` }; } } if (binding.ok) { candidate.context = { ...candidate.context, binding_source: binding.binding_source }; } else { candidate.context = { ...candidate.context, binding_source: candidate.entry_id }; candidate.history = { ...candidate.history, relation: "rebind", reason: `Human-confirmed move: ${candidate.history?.reason ?? "rebind current project locator"}` }; } candidate.notebook_id = loaded.manifest.notebook_id; candidate.protocol_project_id = loaded.manifest.protocol_project_id; // The binding-source/history normalization above changes bytes (rebind path), // so the canonical digest is recomputed over the exact published entry — the // entry as written is always self-consistent; prior entries are never touched. candidate.entry_digest = notebookDigest(candidate, "entry_digest"); const check = validateNotebookEntry(candidate, { manifest: loaded.manifest }); if (!check.ok) return { ok: false, error: check.error }; if (supervisorAgentId !== undefined && candidate.writer.supervisor_agent_id !== supervisorAgentId) { return { ok: false, error: "entry.writer.supervisor_agent_id does not match the current Supervisor identity" }; } if (piSessionId !== undefined && candidate.writer.pi_session_id !== piSessionId) { return { ok: false, error: "entry.writer.pi_session_id does not match the current Pi session identity" }; } for (const reference of candidate.history.references) { if (["correction", "supersession"].includes(candidate.history.relation)) { const prior = loaded.entries.valid.find((item) => item.entry_id === reference.entry_id); if (!prior || prior.entry_digest !== reference.entry_digest) { return { ok: false, error: `history reference ${reference.entry_id} does not match a preserved prior entry digest` }; } } } const published = await publishNotebookCreateOnly({ storageRoot: prepared.paths.storageRoot, finalParent: prepared.paths.entriesRoot, finalName: `${candidate.entry_id}.json`, bytes: notebookBytes(candidate), }); if (!published.ok) return { ok: false, error: published.error, status: published.status, path: published.path, existing_digest: published.existing_digest, incoming_digest: published.incoming_digest }; return { ok: true, status: published.status, entry: candidate, paths: prepared.paths }; } export async function snapshotNotebook(options = {}) { const env = options["env"] ?? process.env; const projectId = options["projectId"]; const protocolProjectId = options["protocolProjectId"]; const humanProjectId = projectId ?? protocolProjectId; if (typeof humanProjectId !== "string" || humanProjectId.length === 0) return { ok: false, error: "protocol project_id is required" }; const prepared = await prepareNotebookPaths(env, humanProjectId, false); if (!prepared.ok) return { ok: false, error: prepared.error }; const loaded = await loadNotebook(prepared); if (!loaded.ok) return { ok: false, error: loaded.error }; const physical = loaded.entries.files.sort((a, b) => a.filename.localeCompare(b.filename)); const snapshotDigest = `sha256:${createHash("sha256").update(canonicalNotebookJson({ manifest_digest: loaded.rawDigest, entries: physical })).digest("hex")}`; const projection = loaded.entries.valid.sort((a, b) => a.entry_id.localeCompare(b.entry_id)); return { ok: true, snapshot: { manifest_digest: loaded.rawDigest, manifest_canonical_digest: loaded.manifest.manifest_digest, physical_entries: physical, snapshot_digest: snapshotDigest, valid_causal_projection: projection, invalid_entries: loaded.entries.invalid, }, }; } async function notebookContextFromPi(ctx, env) { const cwd = ctx?.cwd ?? process.cwd(); const repoRoot = await findRepoRoot(cwd); const paseoProjectId = ctx?.paseoProjectId ?? ctx?.paseo_project_id ?? ctx?.workspace?.projectId ?? env.PASEO_PROJECT_ID ?? "unknown"; const workspaceId = ctx?.workspaceId ?? ctx?.paseoWorkspaceId ?? ctx?.paseo_workspace_id ?? env.PASEO_WORKSPACE_ID ?? "unknown"; const leadId = ctx?.leadAgentId ?? ctx?.lead_agent_id ?? env.PASEO_LEAD_AGENT_ID ?? "unknown"; let protocolPinValue = null; if (repoRoot) { const protocol = await readAndValidateProtocol(repoRoot); if (protocol.ok) protocolPinValue = { version: protocol.protocol.meta.version, digest: `sha256:${protocol.protocol.digest}` }; } return { paseoProjectId, workspaceId, leadId, repositoryRoot: repoRoot ?? "unknown", protocolPin: protocolPinValue, piSessionId: ctx?.sessionId ?? ctx?.piSessionId ?? ctx?.session?.id ?? "unknown", }; } async function runNotebookInit(_args, ctx) { const notify = (message, level) => ctx.ui?.notify?.(message, level); const env = envOf(ctx); const role = parseRole(env).role; if (role !== null && role !== "supervisor") { notify("pi-paseo-orchestration: notebook-init is available only to a Human session or active supervisor process", "error"); return { ok: false, error: "Human session or supervisor role required" }; } if (role === "supervisor") { if (latch === null || latch.role !== "supervisor") { notify("pi-paseo-orchestration: notebook-init is available only to an active supervisor process", "error"); return { ok: false, error: "supervisor role required" }; } if (blockedReason !== null) { notify(`pi-paseo-orchestration blocked: ${blockedReason}`, "error"); return { ok: false, error: blockedReason }; } if (!(await verifyOrBlock(ctx, configDir(env), null, { runtime: false }))) return { ok: false, error: blockedReason }; } const ui = ctx.ui ?? {}; if (typeof ui.input !== "function" || typeof ui.confirm !== "function") { const error = "interactive input is unavailable; notebook initialization did not write"; notify(error, "error"); return { ok: false, error }; } const projectId = await ui.input("Protocol project_id for the Supervisor Notebook:", ""); if (!projectId) { notify("Cancelled; no notebook manifest written.", "info"); return { ok: false, error: "cancelled" }; } const facts = await notebookContextFromPi(ctx, envOf(ctx)); if (facts.repositoryRoot !== "unknown") { const protocol = await readAndValidateProtocol(facts.repositoryRoot); if (!protocol.ok) { notify(`Notebook initialization blocked: ${protocol.error}`, "error"); return { ok: false, error: protocol.error }; } if (protocol.protocol.meta.project_id !== projectId) { const error = "notebook project_id must exactly match the repository Workspace Protocol project_id"; notify(error, "error"); return { ok: false, error }; } } const draft = { protocol_project_id: projectId, paseo_project_id_at_creation: facts.paseoProjectId, repository_root_at_creation: facts.repositoryRoot, supervisor_agent_id: latch?.agentId ?? "human", pi_session_id: facts.piSessionId }; const confirmed = await ui.confirm("Create this immutable Supervisor Notebook manifest?", JSON.stringify(draft, null, 2)); if (!confirmed) { notify("Not written; notebook manifest unchanged.", "info"); return { ok: false, error: "cancelled" }; } const result = await initializeNotebook({ env: envOf(ctx), projectId, paseoProjectId: facts.paseoProjectId, repositoryRoot: facts.repositoryRoot, supervisorAgentId: latch?.agentId ?? "human", piSessionId: facts.piSessionId, }); notify(result.ok ? `Supervisor Notebook initialized at ${result.paths.manifestPath}` : `Notebook initialization failed: ${result.error}`, result.ok ? "info" : "error"); return result; } async function runNotebookAppend(args, ctx) { const notify = (message, level) => ctx.ui?.notify?.(message, level); if (latch === null || latch.role !== "supervisor") { const error = "supervisor role required"; notify(error, "error"); return { ok: false, error }; } if (blockedReason !== null) { notify(`pi-paseo-orchestration blocked: ${blockedReason}`, "error"); return { ok: false, error: blockedReason }; } if (!(await verifyOrBlock(ctx, configDir(envOf(ctx)), null, { runtime: false }))) return { ok: false, error: blockedReason }; let input = args; if (typeof input === "string") { try { input = JSON.parse(input); } catch { const error = "notebook-append arguments must be a JSON object"; notify(error, "error"); return { ok: false, error }; } } if (!isRecord(input) || Object.keys(input).some((key) => ["path", "file_path", "filesystem_path"].includes(key))) { const error = "notebook-append accepts contract fields only and no filesystem path"; notify(error, "error"); return { ok: false, error }; } const facts = await notebookContextFromPi(ctx, envOf(ctx)); const projectId = input.project_id ?? input.protocol_project_id; const suppliedEntry = input.entry ?? Object.fromEntries(Object.entries(input).filter(([key]) => key !== "project_id")); const liveContext = { paseo_project_id: facts.paseoProjectId, repository_root: facts.repositoryRoot, paseo_workspace_id: facts.workspaceId, lead_agent_id: facts.leadId, protocol_pin: facts.protocolPin, }; const entry = { ...suppliedEntry, writer: { ...suppliedEntry.writer, supervisor_agent_id: latch.agentId, pi_session_id: facts.piSessionId }, context: { ...suppliedEntry.context, ...liveContext } }; const result = await appendNotebookEntry({ env: envOf(ctx), projectId, entry, supervisorAgentId: latch.agentId, piSessionId: facts.piSessionId, context: liveContext, }); if (!result.ok && result.classification === "move_or_copy") { const moved = typeof ctx.ui?.confirm === "function" && await ctx.ui.confirm( "Notebook binding changed. Is this a Human-confirmed project move (not a copy)?", JSON.stringify({ previous: result.binding, current: result.context }, null, 2), ); if (!moved) { const error = "Notebook write stopped: classify as copy and create a new project identity and notebook"; notify(error, "error"); return { ok: false, error, classification: "copy" }; } const reboundEntry = { ...entry, context: { ...entry.context, binding_source: entry.entry_id } }; const rebound = await appendNotebookEntry({ env: envOf(ctx), projectId, entry: reboundEntry, supervisorAgentId: latch.agentId, piSessionId: facts.piSessionId, context: { ...liveContext, binding_source: entry.entry_id }, allowRebind: true, }); notify(rebound.ok ? "Notebook rebind evidence appended; prior bytes remain immutable." : `Notebook rebind failed: ${rebound.error}`, rebound.ok ? "info" : "error"); return rebound; } notify(result.ok ? `Notebook entry ${result.status}.` : `Notebook append failed: ${result.error}`, result.ok ? "info" : "error"); return result; } // ─── Doctor ────────────────────────────────────────────────────────────────── export const DOCTOR_REPORT_BEGIN = ''; export const DOCTOR_REPORT_END = ""; const DOCTOR_STATUSES = ["PASS", "WARN", "BLOCKED"]; const DOCTOR_STATUS_RANK = { PASS: 0, WARN: 1, BLOCKED: 2 }; const DOCTOR_CHECK_CODES = [ "CONTEXT_CWD", "GIT_REPOSITORY", "GIT_WORKTREE", "PI_CAPABILITIES", "PACKAGE_PROVENANCE", "PASEO_IDENTITY", "ADAPTER_OBSERVER", "OBSERVER_ATTESTATION", "ROLE_ACTIVATION", "ROLE_SETTINGS", "ROLE_PROFILE", "WORKSPACE_PROTOCOL", "TOOL_POLICY", "AUTHORITY_STATE", ]; function doctorRemediation(status, owner, action) { if (status === "PASS") return { owner: null, action: null, commands: [], rerun_required: false }; return { owner: owner ?? "human", action: action ?? "Re-observe the reported fact and rerun doctor.", commands: [], rerun_required: true }; } function doctorCheck(code, subject, status, expected, observed, evidence = [], remediation = {}) { const owner = remediation["owner"] ?? (status === "BLOCKED" ? "operator" : "human"); return { code, subject, applicable: remediation["applicable"] !== false, required: remediation["required"] !== false, status, expected: redactDoctorText(expected), observed: redactDoctorText(observed), evidence: evidence.map((item) => ({ kind: item.kind ?? "memory", source: redactDoctorText(item.source ?? "doctor"), digest: typeof item.digest === "string" && SHA256_HEX.test(item.digest) ? `sha256:${item.digest}` : (item.digest ?? null), exit_code: item.exit_code ?? null, output: item.output === null || item.output === undefined ? null : redactDoctorText(item.output, 500), })), remediation: doctorRemediation(status, owner, remediation["action"]), }; } function redactDoctorText(value, max = 300) { if (typeof value !== "string") return value; let result = value.replace(/((?:password|passwd|secret|token|credential|api[_-]?key|authorization)\s*[:=]\s*)([^\s,;]+)/gi, "$1[REDACTED]"); if (result.length > max) result = `${result.slice(0, Math.max(0, max - 30))}…[TRUNCATED sha256:${createHash("sha256").update(value).digest("hex")}]`; return result; } function doctorNow(value) { return value instanceof Date ? value.toISOString() : (typeof value === "string" ? value : new Date().toISOString()); } function doctorMode(ctx) { const output = ctx?.outputMode ?? ctx?.mode; if (output === "print" || output === "json" || output === "stdout" || ctx?.json === true || ctx?.print === true) return null; if (ctx?.rpc === true || (ctx?.rpc && typeof ctx.rpc === "object") || ctx?.mode === "rpc" || ctx?.outputMode === "rpc" || ctx?.ui?.mode === "rpc") return "rpc"; if (ctx?.ui && typeof ctx.ui.notify === "function") return "tui"; return null; } export function doctorOutputMode(ctx) { const mode = doctorMode(ctx); return mode ?? "OUTPUT_CHANNEL_UNAVAILABLE"; } function doctorPackageSource() { const url = import.meta.url; const fileSource = url.startsWith("file:") ? fileURLToPath(url) : "data-url"; return { scope: url.startsWith("file:") ? "project" : "temporary", origin: "package", source: fileSource, digest: `sha256:${createHash("sha256").update(url).digest("hex")}`, }; } function validatePaseoObservation(observation, agentId) { if (!isRecord(observation)) return { ok: false, error: "observer result must be an object" }; if (agentId === "" || observation.agent_id !== agentId) return { ok: false, error: "observer agent_id does not match the current Paseo agent" }; for (const [field, value] of [["daemon_id", observation.daemon_id ?? observation.daemon?.id], ["status", observation.status], ["cwd", observation.cwd ?? observation.agent_cwd], ["provider", observation.provider]]) { if (typeof value !== "string" || value.trim() === "") return { ok: false, error: `observer ${field} is missing` }; } if (observation.provider !== "pi") return { ok: false, error: "observer provider must be pi" }; const workspace = isRecord(observation.workspace) ? observation.workspace : {}; const workspaceId = observation.workspace_id ?? workspace.id; const projectId = observation.project_id ?? workspace.project_id; if (typeof workspaceId !== "string" || workspaceId.trim() === "") return { ok: false, error: "observer typed workspace_id is missing" }; if (typeof projectId !== "string" || projectId.trim() === "") return { ok: false, error: "observer workspace project_id is missing" }; if (observation.workspace_typed !== true && workspace.typed !== true) return { ok: false, error: "observer typed workspace binding is not attested" }; if (!Object.prototype.hasOwnProperty.call(observation, "parent_id") && !Object.prototype.hasOwnProperty.call(observation, "parent")) return { ok: false, error: "observer parentage is missing" }; const parent = isRecord(observation.parent) ? observation.parent : {}; const parentId = Object.prototype.hasOwnProperty.call(observation, "parent_id") ? observation.parent_id : (parent.id ?? null); if (parentId !== null && typeof parentId !== "string") return { ok: false, error: "observer parent_id is malformed" }; if (parentId !== null && observation.parent_resolvable !== true && parent.resolvable !== true) return { ok: false, error: "observer parent resolvability is not attested" }; const runtime = observation.runtimeInfo ?? observation.runtime_info; if (!isRecord(runtime)) return { ok: false, error: "observer runtimeInfo is missing" }; const model = runtime.model; if (!(typeof model === "string" && model.trim() !== "") && !(isRecord(model) && typeof model.id === "string" && model.id.trim() !== "")) return { ok: false, error: "observer runtimeInfo.model is missing" }; if (typeof runtime.thinkingOptionId !== "string" || runtime.thinkingOptionId.trim() === "") return { ok: false, error: "observer runtimeInfo.thinkingOptionId is missing" }; if (observation.mcp_configuration_attested !== true && observation.mcpConfigurationAttested !== true) return { ok: false, error: "observer MCP configuration attestation is missing" }; return { ok: true }; } async function doctorPaseoObservation(ctx, env, role) { const agentId = (env[AGENT_ENV] ?? "").trim(); const observer = ctx?.observeCurrentAgent ?? ctx?.paseoObserver?.observeCurrentAgent ?? ctx?.paseo?.observeCurrentAgent; if (typeof observer === "function") { let observation; try { // One bounded read-only observation. There is intentionally no retry or // alternate target when this capability is unavailable. const result = Promise.resolve(observer({ agent_id: agentId })); observation = await Promise.race([ result, new Promise((resolve) => setTimeout(() => resolve({ __timeout: true }), 1500)), ]); } catch (err) { return { status: role === "lead" || role === "supervisor" ? "BLOCKED" : "WARN", reason: `adapter observer unavailable: ${err.message}`, observation: null, agentId }; } if (!observation || observation.__timeout) { return { status: role === "lead" || role === "supervisor" ? "BLOCKED" : "WARN", reason: "adapter observer timed out", observation: null, agentId }; } const shape = validatePaseoObservation(observation, agentId); if (!shape.ok) { return { status: role === "lead" || role === "supervisor" ? "BLOCKED" : "WARN", reason: shape.error, observation, agentId }; } return { status: "PASS", reason: "public current-agent observer returned the complete current-agent tuple", observation, agentId }; } // No adapter-provided observer is loaded (pi-mcp-adapter v2.23.0 has no // Paseo integration). Fall back to the independently installed Paseo CLI — // read-only, fixed agent identity, provenance-checkable. This proves // identity/model/thinking/parent/cwd; workspace binding and MCP-config // attestation are not observable through the CLI and are reported // separately by OBSERVER_ATTESTATION (never claimed as proven). if (agentId === "") { return { status: role === "lead" || role === "supervisor" ? "BLOCKED" : "WARN", reason: "no Paseo agent identity to observe", observation: null, agentId }; } const observed = await observePaseoCurrentAgent(agentId, { env }); if (!observed.ok) { return { status: role === "lead" || role === "supervisor" ? "BLOCKED" : "WARN", reason: observed.error, observation: null, agentId }; } const runtime = observed.observation.runtimeInfo; if (typeof runtime.model !== "string" || runtime.model === "" || typeof runtime.thinkingOptionId !== "string" || runtime.thinkingOptionId === "") { return { status: role === "lead" || role === "supervisor" ? "BLOCKED" : "WARN", reason: "paseo CLI observer cannot prove model/thinking for the current agent", observation: observed.observation, agentId }; } return { status: "PASS", reason: "paseo CLI current-agent observation proved identity, model, thinking, parent, and cwd", observation: observed.observation, agentId }; } // Reads the exact current-agent tuple through the installed Paseo CLI // (`paseo inspect --json`). One bounded read-only call, fixed identity, // no retry, no alternate target, no mutation. export async function observePaseoCurrentAgent(agentId, { env = process.env, timeoutMs = 15000 } = {}) { const id = (agentId ?? "").trim(); if (id === "") return { ok: false, error: "no Paseo agent identity to observe" }; const output = await execFileAsync("paseo", ["inspect", id, "--json"], { env, timeout: timeoutMs }) .then(({ stdout }) => ({ stdout: stdout.trim(), error: null })) .catch((err) => ({ stdout: "", error: `paseo inspect failed: ${err.message}` })); if (output.error) return { ok: false, error: output.error }; if (output.stdout === "") return { ok: false, error: "paseo inspect returned no output" }; let raw; try { raw = JSON.parse(output.stdout); } catch { return { ok: false, error: "paseo inspect returned non-JSON output" }; } if (!isRecord(raw)) return { ok: false, error: "paseo inspect returned a non-object payload" }; if (raw.Id !== id) return { ok: false, error: `paseo inspect returned identity ${JSON.stringify(raw.Id)} instead of the requested agent ${JSON.stringify(id)}` }; const observation = { agent_id: raw.Id, provider: typeof raw.Provider === "string" ? raw.Provider : null, status: typeof raw.Status === "string" ? raw.Status : null, cwd: typeof raw.Cwd === "string" ? raw.Cwd : null, parent_agent_id: typeof raw.ParentAgentId === "string" && raw.ParentAgentId !== "" ? raw.ParentAgentId : null, runtimeInfo: { model: typeof raw.Model === "string" ? raw.Model : null, thinkingOptionId: typeof raw.Thinking === "string" ? raw.Thinking : null, }, workspace_id: null, mcp_configuration_attested: false, source: "paseo-cli", }; return { ok: true, observation }; } function doctorActivation(roleCheck) { if (!roleCheck.ok) return "blocked"; if (roleCheck.role === null) return "ungoverned"; if (blockedReason !== null) return "blocked"; return "governed"; } function doctorPiCapabilities(pi) { const required = ["getActiveTools", "setActiveTools", "setModel", "setThinkingLevel", "getThinkingLevel"]; const missing = required.filter((name) => typeof pi?.[name] !== "function"); return { missing, observed: required.filter((name) => typeof pi?.[name] === "function") }; } function doctorAuthorityState() { if (currentAuthority !== null) return "valid"; if (authorityReason !== null) return "rejected"; return "none"; } function doctorEffectiveToolReport(pi, role) { const observed = readActiveTools(pi); const actual = observed.ok ? observed.tools : []; const base = baseline ?? []; const authority = currentAuthority; const expected = role ? effectiveTools(base, role, authority) : []; const requested = authority?.envelope?.capabilities ?? []; const names = [...new Set([...base, ...CEILINGS[role] ?? [], "mcp_script", "write", "edit"])].sort(); const effective = names.map((name) => { const active = actual.includes(name); const allowed = expected.includes(name); return { name, source: base.includes(name) ? "session_baseline" : "role_ceiling_or_authority", state: active ? "active" : (base.includes(name) || allowed ? "inactive" : "unavailable"), reason: active ? (allowed ? "effective_policy" : "policy_drift") : (base.includes(name) ? "human_disabled_or_not_applied" : "outside_role_ceiling"), }; }); return { actual, base, expected, requested, effective }; } export async function buildDoctorReport(options = {}) { const ctx = options["ctx"] ?? {}; const pi = options["pi"] ?? {}; const now = options["now"]; const reportId = options["reportId"]; const startedAt = doctorNow(now); const env = envOf(ctx); const roleCheck = parseRole(env); const role = roleCheck.ok ? roleCheck.role : null; let activation = doctorActivation(roleCheck); const cwdValue = typeof ctx.cwd === "string" && ctx.cwd !== "" ? ctx.cwd : null; let cwd = null; if (cwdValue !== null) { try { cwd = await realpath(cwdValue); } catch { cwd = null; } } let repoRoot = null; if (cwd !== null) { const found = await findRepoRoot(cwd); if (found !== null) { try { repoRoot = await realpath(found); } catch { repoRoot = found; } } } const statusOutput = repoRoot === null ? null : await gitOut(repoRoot, ["status", "--porcelain=v1", "--untracked-files=all"], false); const head = repoRoot === null ? null : await gitOut(repoRoot, ["rev-parse", "HEAD"]); const branch = repoRoot === null ? null : await gitOut(repoRoot, ["branch", "--show-current"]); const statusLines = statusOutput === null ? [] : statusOutput.split(/\r?\n/).filter(Boolean); const untracked = statusLines.filter((line) => line.startsWith("??")).length; const staged = statusLines.filter((line) => !line.startsWith("??") && line[0] !== " ").length; const unstaged = statusLines.filter((line) => !line.startsWith("??") && line[1] !== " ").length; const dirty = statusLines.length; const cwdCheckStatus = cwd === null ? (role ? "BLOCKED" : "WARN") : "PASS"; const repoStatus = repoRoot === null ? (role ? "BLOCKED" : "WARN") : "PASS"; const checks = []; checks.push(doctorCheck("CONTEXT_CWD", "current Pi cwd", cwdCheckStatus, "ctx.cwd resolves to a readable canonical directory", cwd ?? "unavailable", [{ kind: "memory", source: "ctx.cwd", output: cwd }], { owner: "operator", action: "Start doctor with the current readable Pi cwd." })); checks.push(doctorCheck("GIT_REPOSITORY", "containing Git repository", repoStatus, "one canonical Git repository root contains cwd", repoRoot ?? "not found", [{ kind: "command", source: "git rev-parse --show-toplevel", output: repoRoot }], { owner: "operator", action: "Open the intended Git repository and rerun doctor." })); checks.push(doctorCheck("GIT_WORKTREE", "Git clean and dirty counts", repoRoot === null ? (role ? "BLOCKED" : "WARN") : "PASS", "read-only Git status is observable", repoRoot === null ? "unavailable" : JSON.stringify({ head, branch, staged, unstaged, untracked, dirty }), [{ kind: "command", source: "git status --porcelain=v1 --untracked-files=all", output: repoRoot === null ? null : JSON.stringify({ head, branch, staged, unstaged, untracked, dirty }) }], { owner: "human", action: "Review reported dirty files manually; doctor does not clean or stash them." })); const piFacts = doctorPiCapabilities(pi); checks.push(doctorCheck("PI_CAPABILITIES", "Pi read-only API and hook surface", piFacts.missing.length === 0 ? "PASS" : (role ? "BLOCKED" : "WARN"), "required Pi APIs are present", piFacts.missing.length === 0 ? "all required APIs present" : `missing ${piFacts.missing.join(", ")}`, [{ kind: "api", source: "Pi extension API", output: piFacts.observed.join(", ") }], { owner: "operator", action: "Use a Pi process exposing the required extension APIs." })); const packageSource = doctorPackageSource(); const packageStatus = packageSource.scope === "temporary" ? "WARN" : "PASS"; checks.push(doctorCheck("PACKAGE_PROVENANCE", "loaded extension provenance", packageStatus, "one canonical package source is observable", packageSource.source, [{ kind: "api", source: "import.meta.url", digest: packageSource.digest, output: packageSource.source }], { owner: "operator", action: "Load the reviewed package source once and rerun doctor." })); const paseo = await doctorPaseoObservation(ctx, env, role); const paseoIdentityStatus = role && (env[AGENT_ENV] ?? "").trim() === "" ? "BLOCKED" : role ? (paseo.status === "PASS" && paseo.observation?.agent_id === (env[AGENT_ENV] ?? "").trim() ? "PASS" : "BLOCKED") : "WARN"; checks.push(doctorCheck("PASEO_IDENTITY", "Paseo current-agent identity", paseoIdentityStatus, role ? "PASEO_AGENT_ID is nonempty" : "identity is not required for passive mode", (env[AGENT_ENV] ?? "").trim() || "absent", [{ kind: "env", source: AGENT_ENV, output: (env[AGENT_ENV] ?? "").trim() ? "present" : "absent" }], { owner: "operator", action: "Set the exact Paseo agent identity before governed work." })); const observerOwner = role === "supervisor" ? "supervisor" : role === "lead" ? "lead" : "operator"; checks.push(doctorCheck("ADAPTER_OBSERVER", "public current-agent observation capability", paseo.status, "the already-loaded adapter or Paseo CLI proves exact current-agent observation", paseo.reason, [{ kind: "api", source: paseo.observation?.source ?? "public current-agent observer", output: paseo.observation ? "verified" : "unavailable" }], { owner: observerOwner, action: paseo.status === "BLOCKED" ? "Start the Paseo daemon and verify the exact agent identity, then rerun doctor." : "Use a configured observer when governed live facts are needed." })); // Workspace binding and MCP-configuration attestation are required // observations that neither the installed pi-mcp-adapter (no Paseo // integration) nor the Paseo CLI currently proves. Capability-first: never // claimed as proven, surfaced as an explicit WARN with the exact pieces. const unverified = []; if (!paseo.observation?.workspace_id) unverified.push("workspace_binding"); if (paseo.observation?.mcp_configuration_attested !== true) unverified.push("mcp_configuration_attestation"); const attestationStatus = unverified.length === 0 ? "PASS" : "WARN"; checks.push(doctorCheck("OBSERVER_ATTESTATION", "workspace binding and MCP-configuration attestation", attestationStatus, "the observation proves the typed workspace binding and MCP-configuration attestation", unverified.length === 0 ? "all attested" : `unverified: ${unverified.join(", ")}`, [{ kind: "api", source: "current observation tuple", output: unverified.length === 0 ? "attested" : unverified.join(", ") }], { owner: "operator", action: "Provide an observer that proves the typed workspace binding and MCP-configuration attestation, or accept the WARN as the environment ceiling.", applicable: role !== null, required: role !== null })); if (!roleCheck.ok) { checks.push(doctorCheck("ROLE_ACTIVATION", "role activation", "BLOCKED", "PI_PASEO_ORCHESTRATION_ROLE is supervisor|lead|peer or empty", roleCheck.error, [{ kind: "env", source: ROLE_ENV, output: redactDoctorText(env[ROLE_ENV] ?? "absent") }], { owner: "human", action: "Correct the role environment and start a fresh process." })); } else if (role === null) { checks.push(doctorCheck("ROLE_ACTIVATION", "role activation", "WARN", "an explicit governed role is optional", "UNGOVERNED", [{ kind: "env", source: ROLE_ENV, output: "absent" }], { owner: "human", action: "Set an explicit role only when governed orchestration is intended." })); } else if (latch === null) { checks.push(doctorCheck("ROLE_ACTIVATION", "role activation snapshot", "BLOCKED", "first successful activation snapshot is latched", "governed role has no activation snapshot", [], { owner: "operator", action: "Start a fresh governed Pi process and rerun doctor." })); } else { checks.push(doctorCheck("ROLE_ACTIVATION", "role activation snapshot", blockedReason ? "BLOCKED" : "PASS", "latched role and Paseo identity remain current", blockedReason ?? `${latch.role}/${latch.agentId}`, [{ kind: "memory", source: "process activation latch", output: `${latch.role}/${latch.agentId}` }], { owner: "operator", action: "Start a fresh process after correcting activation drift." })); } let settingsStatus = "PASS"; let settingsObserved = "not applicable"; let profileStatus = "PASS"; let profileObserved = "not applicable"; let latchVerification = null; if (role !== null) { if (latch === null) { settingsStatus = profileStatus = "BLOCKED"; settingsObserved = profileObserved = "activation snapshot unavailable"; } else { latchVerification = await verifyLatch(latch, env, configDir(env), ctx); try { const currentSettings = await readSettings(configDir(env)); settingsObserved = currentSettings === null ? "missing" : (JSON.stringify(currentSettings) === JSON.stringify(latch.settings) ? "matches activation snapshot" : "drifted"); if (settingsObserved !== "matches activation snapshot") settingsStatus = "BLOCKED"; } catch (err) { settingsStatus = "BLOCKED"; settingsObserved = err.message; } try { const currentProfile = await readProfile(latch.profileDir, latch.role); profileObserved = profileDigest(currentProfile) === latch.profileDigest ? "matches activation snapshot" : "drifted"; if (profileObserved !== "matches activation snapshot") profileStatus = "BLOCKED"; } catch (err) { profileStatus = "BLOCKED"; profileObserved = err.message; } if (!latchVerification.ok) { activation = "blocked"; if (/profile/.test(latchVerification.error)) profileStatus = "BLOCKED"; if (/settings|runtime model|thinking level/.test(latchVerification.error)) { settingsStatus = "BLOCKED"; settingsObserved = latchVerification.error; } const activationCheck = checks.find((check) => check.code === "ROLE_ACTIVATION"); if (activationCheck) { activationCheck.status = "BLOCKED"; activationCheck.observed = latchVerification.error; } } } } checks.push(doctorCheck("ROLE_SETTINGS", "role model settings snapshot", settingsStatus, role ? "current settings equal the latched closed document" : "not applicable in passive mode", settingsObserved, [{ kind: "file", source: settingsPath(configDir(env)), output: role ? settingsObserved : null }], { owner: "human", action: "Restore the latched settings or start a fresh process; do not hot-switch a governed role.", applicable: role !== null, required: role !== null })); checks.push(doctorCheck("ROLE_PROFILE", "selected Role Profile snapshot", profileStatus, role ? "selected profile bytes equal the latched digest" : "not applicable in passive mode", profileObserved, [{ kind: "file", source: role ? latch?.profileDir ?? "unavailable" : "not applicable", digest: role ? latch?.profileDigest ? `sha256:${latch.profileDigest}` : null : null, output: role ? profileObserved : null }], { owner: "human", action: "Restore the selected profile or start a fresh process; doctor does not fall back.", applicable: role !== null, required: role !== null })); let protocol = null; if (repoRoot !== null) protocol = await readAndValidateProtocol(repoRoot); const protocolRequired = role !== null; let protocolStatus; let protocolObserved; if (protocol === null) { protocolStatus = protocolRequired ? "BLOCKED" : "WARN"; protocolObserved = "repository root unavailable"; } else if (!protocol.ok) { protocolStatus = protocolRequired ? "BLOCKED" : "WARN"; protocolObserved = protocol.error; } else { protocolObserved = JSON.stringify({ project_id: protocol.protocol.meta.project_id, version: protocol.protocol.meta.version, digest: protocol.protocol.digest }); protocolStatus = "PASS"; if (role === "lead" && protocolPin !== null && (protocol.protocol.digest !== protocolPin.digest || protocol.protocol.meta.version !== protocolPin.version || protocol.protocol.meta.project_id !== protocolPin.projectId)) { protocolStatus = "BLOCKED"; protocolObserved = `pinned protocol drift: ${protocolObserved}`; } } checks.push(doctorCheck("WORKSPACE_PROTOCOL", "repository-root Workspace Protocol", protocolStatus, protocolRequired ? "strict protocol is valid and matches any current Lead pin" : "current protocol is informative in passive mode", protocolObserved, [{ kind: "file", source: repoRoot ? protocolPath(repoRoot) : "unavailable", digest: protocol?.ok ? protocol.protocol.digest : null, output: protocolObserved }], { owner: "lead", action: "Re-read the exact repository-root protocol, resolve drift with the Human, and rerun doctor.", applicable: repoRoot !== null, required: protocolRequired })); const toolReport = doctorEffectiveToolReport(pi, role); const missingCore = role ? requireBaselineTools(baseline, role) : { ok: true }; const expectedActiveTools = baseline === null ? null : (lastAppliedTools ?? baseline); const toolDrift = role ? expectedActiveTools === null || !sameList(toolReport.actual, expectedActiveTools) : false; const toolStatus = !missingCore.ok || toolDrift || toolReport.actual.includes("mcp_script") ? (role ? "BLOCKED" : "WARN") : "PASS"; checks.push(doctorCheck("TOOL_POLICY", "baseline, ceiling, authority, and effective tools", toolStatus, role ? "actual tools equal baseline ∩ role policy ∩ current authority" : "passive mode does not shape tools", JSON.stringify({ baseline: toolReport.base, ceiling: CEILINGS[role] ?? [], requested: toolReport.requested, effective: toolReport.actual }), [{ kind: "memory", source: "Pi active-tool API", output: JSON.stringify(toolReport.effective) }], { owner: "human", action: "Restore the Human-selected baseline and rerun the governed process; doctor never re-enables tools.", applicable: role !== null, required: role !== null })); const authorityState = doctorAuthorityState(); const authorityStatus = blockedReason !== null ? "BLOCKED" : authorityState === "rejected" ? "WARN" : "PASS"; checks.push(doctorCheck("AUTHORITY_STATE", "current-run Task Authority Envelope", authorityStatus, "doctor reports internal authority only; no authority is minted", authorityState, [{ kind: "memory", source: "extension authority state", output: currentAuthority ? JSON.stringify({ grant_kind: currentAuthority.envelope.grant_kind, task_id: currentAuthority.envelope.task_id, capabilities: currentAuthority.envelope.capabilities }) : authorityReason ?? "none" }], { owner: "human", action: "Submit a fresh direct Human grant only if the current run actually needs exceptional capability." })); checks.sort((left, right) => left.code.localeCompare(right.code)); const overall = checks.reduce((worst, check) => DOCTOR_STATUS_RANK[check.status] > DOCTOR_STATUS_RANK[worst] ? check.status : worst, role === null ? "WARN" : "PASS"); const paseoObservation = paseo.observation; const target = { cwd, repository_root: repoRoot, pi_session_id: ctx.sessionId ?? ctx.piSessionId ?? ctx.session?.id ?? null, paseo_agent_id: (env[AGENT_ENV] ?? "").trim() || null, workspace_id: paseoObservation?.workspace_id ?? paseoObservation?.workspace?.id ?? ctx.workspaceId ?? ctx.paseoWorkspaceId ?? null, paseo_project_id: paseoObservation?.project_id ?? paseoObservation?.workspace?.project_id ?? ctx.paseoProjectId ?? ctx.paseo_project_id ?? null, protocol_project_id: protocol?.ok ? protocol.protocol.meta.project_id : null, role, }; const authorityEnvelope = currentAuthority?.envelope; const actualToolPolicy = { session_baseline: [...toolReport.base], role_ceiling: [...(CEILINGS[role] ?? [])], authority_state: authorityState, requested_capabilities: [...toolReport.requested], effective_tools: toolReport.effective, }; const report = { report_id: reportId ?? `doctor-${randomUUID()}`, started_at: startedAt, finished_at: doctorNow(now), doctor: { contract_version: "v1", package_version: "unknown", source: packageSource }, overall_status: overall, activation, target, compatibility: [ { component: "adapter", version: null, strategy: "capability", required_capabilities: ["public-current-agent-observer"], missing_capabilities: paseo.status === "PASS" ? [] : ["public-current-agent-observer"], floor: null, status: paseo.status }, { component: "paseo-client", version: null, strategy: "capability", required_capabilities: ["current-agent-observer"], missing_capabilities: paseo.status === "PASS" ? [] : ["current-agent-observer"], floor: null, status: paseo.status }, { component: "paseo-daemon", version: null, strategy: "capability", required_capabilities: ["current-agent-observer"], missing_capabilities: paseo.status === "PASS" ? [] : ["current-agent-observer"], floor: null, status: paseo.status }, { component: "pi", version: null, strategy: "capability", required_capabilities: piFacts.observed, missing_capabilities: piFacts.missing, floor: null, status: piFacts.missing.length === 0 ? "PASS" : (role ? "BLOCKED" : "WARN") }, ], checks, policy: actualToolPolicy, mutations: { attempted: false, performed: false }, limitations: [ "not acceptance or authority", "not a sandbox, authentication, authorization, or security guarantee", "not current task/lifecycle truth; notebook evidence is historical only", "Human/profile/protocol semantics are not cryptographically proven", ], }; // Keep the authority variable intentionally local to the observation block; // it is not included in raw prompt form and does not alter current authority. void authorityEnvelope; if (paseo.status === "PASS") { const finalPaseo = await doctorPaseoObservation(ctx, env, role); if (finalPaseo.status !== "PASS" || canonicalNotebookJson(finalPaseo.observation) !== canonicalNotebookJson(paseo.observation)) { checks.push(doctorCheck("OBSERVATION_DRIFT", "critical Paseo identity recheck", "BLOCKED", "the bounded observation remained identical through output", finalPaseo.reason, [{ kind: "api", source: "public current-agent observer", output: finalPaseo.status }], { owner: "operator", action: "Start a fresh governed process and rerun doctor." })); checks.sort((left, right) => left.code.localeCompare(right.code)); report.checks = checks; report.overall_status = "BLOCKED"; } } return report; } export function formatDoctorReport(report) { return `${DOCTOR_REPORT_BEGIN}\n${canonicalNotebookJson(report)}\n${DOCTOR_REPORT_END}`; } export function formatDoctorTable(report) { const lines = [ `Doctor ${report.overall_status} | target=${report.target.cwd ?? "unavailable"} | repo=${report.target.repository_root ?? "unavailable"}`, "STATUS | CODE | OBSERVED | REMEDIATION", ]; for (const check of report.checks) { lines.push(`${check.status} | ${check.code} | ${check.observed} | ${check.remediation.action ?? "none"}`); } lines.push(`authority=${report.policy.authority_state} tools=${report.policy.effective_tools.filter((tool) => tool.state === "active").map((tool) => tool.name).join(",") || "none"}`); lines.push(`limitations=${report.limitations.join("; ")}`); return lines.join("\n"); } function validateDoctorNullableString(value, label) { return value === null || (typeof value === "string" && value.trim() !== "") ? { ok: true } : { ok: false, error: `${label} must be null or a nonempty string` }; } function validateDoctorStringArray(value, label) { return Array.isArray(value) && value.every((item) => typeof item === "string" && item.trim() !== "") ? { ok: true } : { ok: false, error: `${label} must be an array of nonempty strings` }; } function validateDoctorEvidence(item) { if (!isRecord(item)) return { ok: false, error: "doctor evidence must be an object" }; const fields = ["kind", "source", "digest", "exit_code", "output"]; const closed = notebookClosed(item, fields, "doctor evidence"); if (!closed.ok) return { ok: false, error: closed.error }; if (typeof item.kind !== "string" || item.kind.trim() === "") return { ok: false, error: "doctor evidence.kind must be nonempty" }; if (typeof item.source !== "string" || item.source.trim() === "") return { ok: false, error: "doctor evidence.source must be nonempty" }; if (item.digest !== null && !NOTEBOOK_DIGEST.test(item.digest)) return { ok: false, error: "doctor evidence.digest must be null or sha256 digest" }; if (item.exit_code !== null && !Number.isInteger(item.exit_code)) return { ok: false, error: "doctor evidence.exit_code must be null or an integer" }; if (item.output !== null && typeof item.output !== "string") return { ok: false, error: "doctor evidence.output must be null or a string" }; return { ok: true }; } export function parseDoctorReport(text) { if (typeof text !== "string") return { ok: false, error: "doctor report must be a string" }; const stripped = text.trim(); if (!stripped.startsWith(`${DOCTOR_REPORT_BEGIN}\n`) || !stripped.endsWith(`\n${DOCTOR_REPORT_END}`)) return { ok: false, error: "doctor report markers are malformed" }; const body = stripped.slice(DOCTOR_REPORT_BEGIN.length + 1, -DOCTOR_REPORT_END.length - 1); const duplicate = findDuplicateKey(body); if (duplicate !== null) return { ok: false, error: `duplicate field ${JSON.stringify(duplicate)} in doctor report` }; let report; try { report = JSON.parse(body); } catch { return { ok: false, error: "doctor report body is not valid JSON" }; } const fields = ["report_id", "started_at", "finished_at", "doctor", "overall_status", "activation", "target", "compatibility", "checks", "policy", "mutations", "limitations"]; let check = notebookClosed(report, fields, "doctor report"); if (!check.ok) return { ok: false, error: check.error }; check = notebookId(report.report_id, "doctor report.report_id"); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["started_at", "finished_at"]) { check = notebookTimestamp(report[field], `doctor report.${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!DOCTOR_STATUSES.includes(report.overall_status)) return { ok: false, error: "doctor report.overall_status is invalid" }; if (!["governed", "ungoverned", "blocked"].includes(report.activation)) return { ok: false, error: "doctor report.activation is invalid" }; check = notebookClosed(report.doctor, ["contract_version", "package_version", "source"], "doctor report.doctor"); if (!check.ok) return { ok: false, error: check.error }; if (report.doctor.contract_version !== "v1" || typeof report.doctor.package_version !== "string") return { ok: false, error: "doctor report doctor metadata is malformed" }; check = notebookClosed(report.doctor.source, ["scope", "origin", "source", "digest"], "doctor report source"); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["scope", "origin", "source"]) { check = notebookText(report.doctor.source[field], `doctor report source.${field}`, 2000); if (!check.ok) return { ok: false, error: check.error }; } check = notebookDigestField(report.doctor.source.digest, "doctor report source.digest"); if (!check.ok) return { ok: false, error: check.error }; check = notebookClosed(report.target, ["cwd", "repository_root", "pi_session_id", "paseo_agent_id", "workspace_id", "paseo_project_id", "protocol_project_id", "role"], "doctor report target"); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["cwd", "repository_root", "pi_session_id", "paseo_agent_id", "workspace_id", "paseo_project_id", "protocol_project_id"]) { check = validateDoctorNullableString(report.target[field], `doctor report target.${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (report.target.role !== null && !ROLES.includes(report.target.role)) return { ok: false, error: "doctor report target.role is invalid" }; if (!Array.isArray(report.compatibility) || !Array.isArray(report.checks) || !Array.isArray(report.limitations)) return { ok: false, error: "doctor report arrays are malformed" }; let previous = ""; for (const component of report.compatibility) { check = notebookClosed(component, ["component", "version", "strategy", "required_capabilities", "missing_capabilities", "floor", "status"], "doctor compatibility"); if (!check.ok) return { ok: false, error: check.error }; if (typeof component.component !== "string" || component.component <= previous) return { ok: false, error: "doctor compatibility must be deterministically ordered" }; previous = component.component; if (!DOCTOR_STATUSES.includes(component.status) || !["capability", "floor"].includes(component.strategy) || !Array.isArray(component.required_capabilities) || !Array.isArray(component.missing_capabilities) || !component.required_capabilities.every((item) => typeof item === "string") || !component.missing_capabilities.every((item) => typeof item === "string")) return { ok: false, error: "doctor compatibility item is malformed" }; check = validateDoctorNullableString(component.version, "doctor compatibility.version"); if (!check.ok) return { ok: false, error: check.error }; check = validateDoctorNullableString(component.floor, "doctor compatibility.floor"); if (!check.ok) return { ok: false, error: check.error }; } const codes = new Set(); let previousCode = ""; for (const item of report.checks) { check = notebookClosed(item, ["code", "subject", "applicable", "required", "status", "expected", "observed", "evidence", "remediation"], "doctor check"); if (!check.ok) return { ok: false, error: check.error }; if (codes.has(item.code)) return { ok: false, error: "doctor check codes must be unique" }; codes.add(item.code); if (typeof item.code !== "string" || item.code.trim() === "" || item.code <= previousCode) return { ok: false, error: "doctor checks must be deterministically ordered" }; previousCode = item.code; if (DOCTOR_STATUSES.includes(item.status) === false || typeof item.applicable !== "boolean" || typeof item.required !== "boolean" || !Array.isArray(item.evidence)) return { ok: false, error: "doctor check is malformed" }; for (const field of ["subject", "expected", "observed"]) { check = notebookText(item[field], `doctor check.${field}`, 4000); if (!check.ok) return { ok: false, error: check.error }; } for (const evidence of item.evidence) { check = validateDoctorEvidence(evidence); if (!check.ok) return { ok: false, error: check.error }; } check = notebookClosed(item.remediation, ["owner", "action", "commands", "rerun_required"], "doctor remediation"); if (!check.ok) return { ok: false, error: check.error }; if (item.remediation.owner !== null && !["human", "operator", "lead", "supervisor"].includes(item.remediation.owner)) return { ok: false, error: "doctor remediation.owner is invalid" }; check = validateDoctorNullableString(item.remediation.action, "doctor remediation.action"); if (!check.ok) return { ok: false, error: check.error }; if (!Array.isArray(item.remediation.commands) || typeof item.remediation.rerun_required !== "boolean") return { ok: false, error: "doctor remediation is malformed" }; for (const command of item.remediation.commands) { check = notebookClosed(command, ["command", "mutates"], "doctor remediation command"); if (!check.ok) return { ok: false, error: check.error }; if (typeof command.command !== "string" || typeof command.mutates !== "boolean") return { ok: false, error: "doctor remediation command is malformed" }; } } check = notebookClosed(report.policy, ["session_baseline", "role_ceiling", "authority_state", "requested_capabilities", "effective_tools"], "doctor policy"); if (!check.ok) return { ok: false, error: check.error }; for (const field of ["session_baseline", "role_ceiling", "requested_capabilities"]) { check = validateDoctorStringArray(report.policy[field], `doctor policy.${field}`); if (!check.ok) return { ok: false, error: check.error }; } if (!["none", "valid", "rejected", "stale_inactive"].includes(report.policy.authority_state)) return { ok: false, error: "doctor policy authority_state is invalid" }; if (!Array.isArray(report.policy.effective_tools)) return { ok: false, error: "doctor policy.effective_tools must be an array" }; for (const tool of report.policy.effective_tools) { check = notebookClosed(tool, ["name", "source", "state", "reason"], "doctor policy tool"); if (!check.ok) return { ok: false, error: check.error }; if (typeof tool.name !== "string" || typeof tool.source !== "string" || !["active", "inactive", "unavailable"].includes(tool.state) || typeof tool.reason !== "string") return { ok: false, error: "doctor policy tool is malformed" }; } check = notebookClosed(report.mutations, ["attempted", "performed"], "doctor mutations"); if (!check.ok) return { ok: false, error: check.error }; if (report.mutations.attempted !== false || report.mutations.performed !== false) return { ok: false, error: "doctor report must assert no mutation" }; if (report.limitations.some((item) => typeof item !== "string" || item.trim() === "")) return { ok: false, error: "doctor limitations must be nonempty strings" }; return { ok: true, report }; } export async function runDoctor(args, ctx, pi) { const hasAlternateTarget = (typeof args === "string" && args.trim() !== "") || (isRecord(args) && Object.keys(args).length > 0); if (hasAlternateTarget) return { ok: false, error: "doctor does not accept an alternate target" }; const mode = doctorMode(ctx); const notify = ctx?.ui?.notify ?? ctx?.rpc?.notify ?? ctx?.emit; if (mode === null || typeof notify !== "function") { const error = "OUTPUT_CHANNEL_UNAVAILABLE"; if (typeof notify === "function" && ctx.outputMode !== "print" && ctx.outputMode !== "json") notify(error, "error"); return { ok: false, error }; } if (latch !== null) { const tools = await ensureToolPolicy(pi); if (!tools.ok) return { ok: false, error: "error" in tools ? tools.error : "active-tool policy cannot be re-applied" }; } const report = await buildDoctorReport({ ctx, pi }); const block = formatDoctorReport(report); const table = formatDoctorTable(report); // Both outputs are ephemeral command-channel messages; nothing is written to // transcript/session state by this extension. notify(block, "info"); notify(table, "info"); return { ok: true, mode, report, block, table }; } // ─── Slash-command authority routes ────────────────────────────────────────── // Both routes are idle-only: they run only when the process is latched to the // right role, is not blocked, and has no agent run in flight. Each collects // the grant fields, shows the complete draft, requires explicit Human // confirmation, and stores the envelope as a pending authority that the next // input event activates through the same activateEnvelope path as every other // grant. Cancel, incomplete, or invalid drafts preserve and store nothing. function processIsIdle(ctx) { if (typeof ctx.isIdle === "function") return ctx.isIdle(); return false; // idle is not observable → fail closed } async function runLeadTiny(_args, ctx) { const notify = (message, level) => ctx.ui?.notify?.(message, level); if (latch === null || latch.role !== "lead") { notify("pi-paseo-orchestration: lead-tiny is available only to an active lead process", "error"); return; } if (blockedReason !== null) { notify(`pi-paseo-orchestration blocked: ${blockedReason}`, "error"); return; } if (!processIsIdle(ctx)) { notify("pi-paseo-orchestration: lead-tiny requires an idle process; wait for the current run to settle", "error"); return; } const env = envOf(ctx); if (!(await verifyOrBlock(ctx, configDir(env)))) return; const pin = await ensureProtocolPin(); if (!pin.ok) { blockWith(ctx, pin.error); return; } const ui = ctx.ui ?? {}; if (typeof ui.input !== "function" || typeof ui.select !== "function" || typeof ui.confirm !== "function") { notify("pi-paseo-orchestration: interactive input is unavailable in this mode; no pending authority stored", "error"); return; } const cancelled = () => { notify("Cancelled; no pending authority stored.", "info"); return null; }; const taskId = await ui.input("Task ID for the tiny Lead grant:", ""); if (!taskId) return cancelled(); const objective = await ui.input("Bounded objective for the tiny Lead run:", ""); if (!objective) return cancelled(); const capabilityChoice = await ui.select("Capabilities for this run:", ["edit", "local_commit", "edit,local_commit"]); if (!capabilityChoice) return cancelled(); const capabilities = capabilityChoice.split(","); const scope = await ui.input("Repository-relative writable scope:", ""); if (!scope) return cancelled(); const exclusionsRaw = await ui.input("In-scope exclusions (comma-separated; optional):", ""); if (exclusionsRaw === undefined) return cancelled(); const exclusions = exclusionsRaw.split(",").map((s) => s.trim()).filter((s) => s !== ""); let base; if (capabilities.includes("local_commit")) { const head = await gitOut(protocolPin.repoRoot, ["rev-parse", "HEAD"]); if (head === null) { notify("Cannot resolve a full HEAD commit as the candidate base; no pending authority stored.", "error"); return; } base = head; } const draft = { version: 1, grant_kind: "lead_tiny", role: "lead", issuer: "human", agent_id: latch.agentId, task_id: taskId, objective, capabilities, scope, exclusions, protocol_digest: protocolPin.digest, ...(base !== undefined ? { base } : {}), }; const check = validateEnvelopeShape(draft); if (!check.ok) { notify(`Invalid grant draft (${check.error}); no pending authority stored.`, "error"); return; } const scopeCheck = await validateScope(protocolPin.repoRoot, check.envelope.scope, check.envelope.exclusions); if (!scopeCheck.ok) { notify(`Invalid grant scope (${scopeCheck.error}); no pending authority stored.`, "error"); return; } const confirmed = await ui.confirm("Store this grant as a pending authority for the next run?", JSON.stringify(check.envelope, null, 2)); if (!confirmed) { notify("Not stored; no pending authority.", "info"); return; } pendingAuthority = check.envelope; notify("Pending authority stored; it activates on the next input event.", "info"); } async function runSupervisorRecovery(_args, ctx) { const notify = (message, level) => ctx.ui?.notify?.(message, level); if (latch === null || latch.role !== "supervisor") { notify("pi-paseo-orchestration: supervisor-recovery is available only to an active supervisor process", "error"); return; } if (blockedReason !== null) { notify(`pi-paseo-orchestration blocked: ${blockedReason}`, "error"); return; } if (!processIsIdle(ctx)) { notify("pi-paseo-orchestration: supervisor-recovery requires an idle process; wait for the current run to settle", "error"); return; } if (!(await verifyOrBlock(ctx, configDir(envOf(ctx))))) return; const ui = ctx.ui ?? {}; if (typeof ui.input !== "function" || typeof ui.select !== "function" || typeof ui.confirm !== "function") { notify("pi-paseo-orchestration: interactive input is unavailable in this mode; no pending authority stored", "error"); return; } const cancelled = () => { notify("Cancelled; no pending authority stored.", "info"); return null; }; const taskId = await ui.input("Task ID for the recovery grant:", ""); if (!taskId) return cancelled(); const objective = await ui.input("Bounded objective for the replacement Lead:", ""); if (!objective) return cancelled(); const provider = await ui.input("Human-attested Paseo provider alias for the replacement Lead:", ""); if (!provider) return cancelled(); const workspaceId = await ui.input("Paseo workspace ID for the replacement Lead:", ""); if (!workspaceId) return cancelled(); const handoffId = await ui.input("Handoff ID:", ""); if (!handoffId) return cancelled(); const draft = { version: 1, grant_kind: "supervisor_recovery", role: "lead", issuer: "human", agent_id: latch.agentId, task_id: taskId, objective, provider, workspace_id: workspaceId, handoff_id: handoffId, }; const check = validateEnvelopeShape(draft); if (!check.ok) { notify(`Invalid grant draft (${check.error}); no pending authority stored.`, "error"); return; } const confirmed = await ui.confirm("Store this recovery grant as a pending authority for the next run?", JSON.stringify(check.envelope, null, 2)); if (!confirmed) { notify("Not stored; no pending authority.", "info"); return; } pendingAuthority = check.envelope; notify("Pending authority stored; it activates on the next input event.", "info"); } // Thinking levels the closed settings document may store. The picker filters // these per selected model via Model.thinkingLevelMap (null marks unsupported); // without a map the full closed set is offered and activation fails closed if // the runtime clamps. export function thinkingLevelsFor(model) { if (!model?.reasoning) return ["off"]; return THINKING_LEVELS.filter((level) => { const mapped = model.thinkingLevelMap?.[level]; if (mapped === null) return false; return level !== "xhigh" && level !== "max" || mapped !== undefined; }); } // ctx.ui.custom exposes raw terminal input but this local package cannot resolve // pi-tui from its source directory. These are the standard sequences needed by // the small settings controls; tests without custom UI retain the built-in // select fallback. function key(data, name, keybindings) { const action = { up: "tui.select.up", down: "tui.select.down", left: "tui.editor.cursorLeft", right: "tui.editor.cursorRight", tab: "tui.input.tab", enter: "tui.select.confirm", escape: "tui.select.cancel", ctrlC: "tui.select.cancel", }[name]; if (typeof keybindings?.matches === "function" && keybindings.matches(data, action)) return true; return ({ up: "\u001b[A", down: "\u001b[B", right: "\u001b[C", left: "\u001b[D", tab: "\t", enter: "\r", escape: "\u001b", ctrlC: "\u0003" })[name] === data || (name === "enter" && data === "\n"); } async function selectWizard(ctx, title, options, initial = null) { if (typeof ctx.ui?.custom !== "function") return { fallback: true }; let index = Math.max(0, options.indexOf(initial)); const value = await ctx.ui.custom((tui, _theme, keybindings, done) => ({ render: (width) => { const lines = [title, ""]; options.forEach((option, i) => lines.push(`${i === index ? "→ " : " "}${option}`)); lines.push("", "↑↓ navigate · enter select · esc back (esc at the start cancels)"); return lines.map((line) => line.slice(0, width)); }, invalidate: () => {}, handleInput: (data) => { if (key(data, "up", keybindings)) { index = (index - 1 + options.length) % options.length; tui.requestRender(); } else if (key(data, "down", keybindings)) { index = (index + 1) % options.length; tui.requestRender(); } else if (key(data, "enter", keybindings)) done(options[index]); else if (key(data, "escape", keybindings) || key(data, "ctrlC", keybindings)) done(null); }, })); return { value }; } export async function confirmSettings(ctx, title, body) { if (typeof ctx.ui?.custom !== "function") return ctx.ui.confirm(title, body); const lines = body.split("\n"); let offset = 0; const pageSize = 14; return ctx.ui.custom((tui, _theme, keybindings, done) => ({ render: (width) => [ title, `showing lines ${offset + 1}-${Math.min(offset + pageSize, lines.length)} of ${lines.length}`, "", ...lines.slice(offset, offset + pageSize), "", "↑↓ scroll · enter confirm · esc cancel", ].map((line) => line.slice(0, width)), invalidate: () => {}, handleInput: (data) => { if (key(data, "up", keybindings)) { offset = Math.max(0, offset - 1); tui.requestRender(); } else if (key(data, "down", keybindings)) { offset = Math.min(Math.max(0, lines.length - pageSize), offset + 1); tui.requestRender(); } else if (key(data, "enter", keybindings)) done(true); else if (key(data, "escape", keybindings) || key(data, "ctrlC", keybindings)) done(false); }, })); } async function pickModelSelection(ctx, models, providers, label, cancelAtStart = false, includeThinking = true, initial = null, restart = false) { const known = models.some((entry) => entry.provider === initial?.provider && entry.id === initial?.model); let provider = known && !restart ? initial.provider : null; let model = provider ? initial.model : null; while (true) { if (provider === null) { const providerOptions = providers; const res = await selectWizard(ctx, `Provider for ${label}:`, providerOptions, known ? initial.provider : null); const choice = res.fallback ? await ctx.ui.select(`Provider for ${label}:`, providerOptions) : res.value; if (choice === null || choice === undefined) return cancelAtStart ? null : { back: true }; provider = choice; } if (model === null) { const ids = models.filter((entry) => entry.provider === provider).map((entry) => entry.id).sort(); const res = await selectWizard(ctx, `Model for ${label}:`, ids, provider === initial?.provider ? initial.model : null); const choice = res.fallback ? await ctx.ui.select(`Model for ${label}:`, ids) : res.value; if (choice === null || choice === undefined) { provider = null; continue; } model = choice; } const modelEntry = models.find((entry) => entry.provider === provider && entry.id === model); if (!includeThinking) return { provider, model }; const levels = thinkingLevelsFor(modelEntry); const res = await selectWizard(ctx, `Thinking level for ${label}:`, levels, initial?.thinking); const thinking = res.fallback ? await ctx.ui.select(`Thinking level for ${label}:`, levels) : res.value; if (thinking === null || thinking === undefined) { model = null; continue; } return { provider, model, thinking }; } } export async function pickPeerRouteSelections(ctx, models, providers, initialRoutes = {}) { const routeNames = [...new Set([...Object.keys(DEFAULT_PEER_ROUTES), ...Object.keys(initialRoutes)])]; if (typeof ctx.ui?.custom !== "function") { const result = []; for (const route of routeNames) { const selection = await pickModelSelection(ctx, models, providers, `Peer route ${route}`, false, true, initialRoutes[route]); if (selection?.back) return null; result.push({ route, ...selection }); } return result; } const rows = routeNames.map((route) => { const initial = initialRoutes[route]; const known = models.some((entry) => entry.provider === initial?.provider && entry.id === initial?.model); const provider = known ? initial.provider : providers[0]; const model = known ? initial.model : models.filter((entry) => entry.provider === provider).map((entry) => entry.id).sort()[0]; const levels = thinkingLevelsFor(models.find((entry) => entry.provider === provider && entry.id === model)); return { route, provider, model, thinking: levels.includes(initial?.thinking) ? initial.thinking : levels[0] }; }); const fields = ["provider", "model", "thinking"]; let rowIndex = 0; let fieldIndex = 0; const values = (row, field) => field === "provider" ? providers : field === "model" ? models.filter((entry) => entry.provider === row.provider).map((entry) => entry.id).sort() : thinkingLevelsFor(models.find((entry) => entry.provider === row.provider && entry.id === row.model)); return ctx.ui.custom((tui, _theme, keybindings, done) => ({ render: (width) => { const lines = ["Peer route models", "↑↓ route · tab field · ←→ value · enter apply all · esc back", ""]; for (const [i, row] of rows.entries()) { const cells = fields.map((field, j) => j === fieldIndex && i === rowIndex ? `[${row[field]}]` : row[field]); lines.push(`${i === rowIndex ? "→ " : " "}${row.route.padEnd(13)} ${cells.join(" ")}`); } return lines.map((line) => line.slice(0, width)); }, invalidate: () => {}, handleInput: (data) => { if (key(data, "up", keybindings)) rowIndex = (rowIndex - 1 + rows.length) % rows.length; else if (key(data, "down", keybindings)) rowIndex = (rowIndex + 1) % rows.length; else if (key(data, "tab", keybindings)) fieldIndex = (fieldIndex + 1) % fields.length; else if (key(data, "left", keybindings) || key(data, "right", keybindings)) { const row = rows[rowIndex]; const field = fields[fieldIndex]; const options = values(row, field); const step = key(data, "left", keybindings) ? -1 : 1; row[field] = options[(options.indexOf(row[field]) + step + options.length) % options.length]; if (field === "provider") row.model = values(row, "model")[0]; if (field !== "thinking") row.thinking = values(row, "thinking")[0]; } else if (key(data, "enter", keybindings)) { done(rows); return; } else if (key(data, "escape", keybindings) || key(data, "ctrlC", keybindings)) { done(null); return; } tui.requestRender(); }, })); } export function paseoConfigPath(env = process.env, home = homedir()) { return join(env.PASEO_HOME || join(home, ".paseo"), "config.json"); } export async function installPaseoProfiles(env = process.env) { const target = paseoConfigPath(env); let config; try { config = JSON.parse(await readFile(target, "utf8")); } catch (err) { throw new Error(`Paseo config read failed at ${target}: ${err.message}`); } if (!isRecord(config)) throw new Error("Paseo config must be a JSON object"); const providers = isRecord(config.agents?.providers) ? config.agents.providers : {}; const next = { ...config, agents: { ...(isRecord(config.agents) ? config.agents : {}), providers: { ...providers, "ppo-supervisor": { extends: "pi", label: "PPO Supervisor", enabled: true, env: { PI_PASEO_ORCHESTRATION_ROLE: "supervisor" } }, "ppo-lead": { extends: "pi", label: "PPO Lead", enabled: true, env: { PI_PASEO_ORCHESTRATION_ROLE: "lead", PI_PASEO_ORCHESTRATION_PEER_ALIAS: "ppo-peer" } }, "ppo-peer": { extends: "pi", label: "PPO Peer", enabled: true, env: { PI_PASEO_ORCHESTRATION_ROLE: "peer" } }, }, }, }; const tmp = `${target}.tmp`; try { await writeFile(tmp, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 }); await rename(tmp, target); } catch (err) { await unlink(tmp).catch(() => {}); throw new Error(`Paseo config write failed: ${err.message}`); } return target; } async function runSettings(_args, ctx) { const notify = (message, level) => ctx.ui?.notify?.(message, level); const env = ctx.env ?? process.env; const action = await ctx.ui.select("PPO settings:", ["Paseo profiles", "Role models"]); if (action === null || action === undefined) { notify("Cancelled; settings unchanged.", "info"); return; } if (action === "Paseo profiles") { const confirmed = await ctx.ui.confirm("Install or update PPO Paseo profiles?", "Only agents.providers.ppo-supervisor, ppo-lead, and ppo-peer will be replaced."); if (!confirmed) { notify("Not written; Paseo config unchanged.", "info"); return; } try { const path = await installPaseoProfiles(env); notify(`PPO Paseo profiles written to ${path}. Restart Paseo before creating new agents.`, "info"); } catch (err) { notify(err.message, "error"); } return; } const dir = configDir(env); let prior; try { prior = await readSettings(dir); } catch (err) { notify(err.message, "error"); return; } const models = ctx.modelRegistry?.getAvailable?.() ?? []; const providers = [...new Set(models.map((entry) => entry.provider))].sort(); if (providers.length === 0) { notify("No models available in the current model registry; settings unchanged.", "error"); return; } const cancelNote = prior ? "Cancelled; settings unchanged." : "Cancelled; no settings written."; let roles; let peerRoutes; if (prior) { roles = structuredClone(prior.roles); peerRoutes = structuredClone(prior.peer_routes); if (!peerRoutes.reviewer) peerRoutes.reviewer = { ...structuredClone(peerRoutes.general ?? peerRoutes.fast), description: DEFAULT_PEER_ROUTES.reviewer }; const target = await ctx.ui.select("Edit saved model settings:", ["Configure all settings", "Supervisor", "Lead", "Peer routes", "Add custom route", "Review without changes"]); if (target === null || target === undefined) { notify(cancelNote, "info"); return; } if (target === "Configure all settings") { roles = {}; for (const role of ["supervisor", "lead"]) { const selection = await pickModelSelection(ctx, models, providers, role, role === "supervisor", true, prior.roles[role], true); if (!selection || selection.back) { notify(cancelNote, "info"); return; } roles[role] = selection; } const peerMode = await ctx.ui.select("Peer routes:", ["Use one model for all built-in routes", "Edit routes in one table"]); if (peerMode === null || peerMode === undefined) { notify(cancelNote, "info"); return; } if (peerMode === "Use one model for all built-in routes") { const selection = await pickModelSelection(ctx, models, providers, "all built-in Peer routes", true, true, peerRoutes.fast, true); if (!selection || selection.back) { notify(cancelNote, "info"); return; } for (const [route, description] of Object.entries(DEFAULT_PEER_ROUTES)) peerRoutes[route] = { description, ...selection }; } else { const selections = await pickPeerRouteSelections(ctx, models, providers, peerRoutes); if (!selections) { notify(cancelNote, "info"); return; } for (const { route, provider, model, thinking } of selections) peerRoutes[route] = { description: peerRoutes[route]?.description ?? DEFAULT_PEER_ROUTES[route], provider, model, thinking }; } } else if (target === "Supervisor" || target === "Lead") { const role = target.toLowerCase(); const selection = await pickModelSelection(ctx, models, providers, role, true, true, roles[role], true); if (!selection || selection.back) { notify(cancelNote, "info"); return; } roles[role] = selection; } else if (target === "Peer routes") { const peerMode = await ctx.ui.select("Peer routes:", ["Use one model for all built-in routes", "Edit routes in one table"]); if (peerMode === null || peerMode === undefined) { notify(cancelNote, "info"); return; } if (peerMode === "Use one model for all built-in routes") { const selection = await pickModelSelection(ctx, models, providers, "all built-in Peer routes", true, true, peerRoutes.fast, true); if (!selection || selection.back) { notify(cancelNote, "info"); return; } for (const [route, description] of Object.entries(DEFAULT_PEER_ROUTES)) peerRoutes[route] = { description, ...selection }; } else { const selections = await pickPeerRouteSelections(ctx, models, providers, peerRoutes); if (!selections) { notify(cancelNote, "info"); return; } for (const { route, provider, model, thinking } of selections) { peerRoutes[route] = { description: peerRoutes[route]?.description ?? DEFAULT_PEER_ROUTES[route], provider, model, thinking }; } } } else if (target === "Add custom route") { const route = (await ctx.ui.input("Custom route ID (lowercase letters, numbers, _ or -):"))?.trim(); if (!ROUTE_ID.test(route ?? "") || Object.prototype.hasOwnProperty.call(peerRoutes, route)) { notify("Custom route ID is invalid or already used.", "error"); return; } const description = (await ctx.ui.input(`Description for ${route}:`))?.trim(); if (!description || description.length > 240) { notify("Description must be 1-240 characters.", "error"); return; } const selection = await pickModelSelection(ctx, models, providers, `Peer route ${route}`, true); if (!selection || selection.back) { notify(cancelNote, "info"); return; } peerRoutes[route] = { description, ...selection }; } } else { roles = {}; for (const role of ["supervisor", "lead"]) { const selection = await pickModelSelection(ctx, models, providers, role, role === "supervisor"); if (!selection || selection.back) { notify(cancelNote, "info"); return; } roles[role] = selection; } peerRoutes = {}; const peerMode = await ctx.ui.select("Peer routes:", ["Use one model for all routes", "Configure each route individually"]); if (peerMode === null || peerMode === undefined) { notify(cancelNote, "info"); return; } if (peerMode === "Use one model for all routes") { const selection = await pickModelSelection(ctx, models, providers, "all default Peer routes"); if (!selection || selection.back) { notify(cancelNote, "info"); return; } for (const [route, description] of Object.entries(DEFAULT_PEER_ROUTES)) peerRoutes[route] = { description, ...selection }; } else { const selections = await pickPeerRouteSelections(ctx, models, providers); if (!selections) { notify(cancelNote, "info"); return; } for (const { route, provider, model, thinking } of selections) peerRoutes[route] = { description: DEFAULT_PEER_ROUTES[route], provider, model, thinking }; } while (true) { const next = await ctx.ui.select("Custom Peer routes:", ["Finish", "Add custom route"]); if (next === null || next === undefined) { notify(cancelNote, "info"); return; } if (next === "Finish") break; const route = (await ctx.ui.input("Custom route ID (lowercase letters, numbers, _ or -):"))?.trim(); if (!ROUTE_ID.test(route ?? "") || Object.prototype.hasOwnProperty.call(peerRoutes, route)) { notify("Custom route ID is invalid or already used.", "error"); continue; } const description = (await ctx.ui.input(`Description for ${route}:`))?.trim(); if (!description || description.length > 240) { notify("Description must be 1-240 characters.", "error"); continue; } const selection = await pickModelSelection(ctx, models, providers, `Peer route ${route}`); if (selection?.back) continue; peerRoutes[route] = { description, ...selection }; } } const doc = { version: 2, roles, peer_routes: peerRoutes }; const path = settingsPath(dir); const confirmed = await confirmSettings(ctx, "Apply PPO model-routing document?", `${path}\n\n${JSON.stringify(doc, null, 2)}`); if (!confirmed) { notify(`Not written; settings unchanged. Path: ${path}`, "info"); return; } try { await writeSettings(dir, doc); notify(`Success: PPO model routes written to ${path}. Start fresh governed agents to apply them.`, "info"); } catch (err) { notify(`Failed to write PPO model routes at ${path}: ${err.message}`, "error"); } } // Process-latched governed state. Once set, blockedReason never clears in this // process: drift or missing prerequisites require a fresh Paseo process. let latch = null; let blockedReason = null; let baseline = null; // The last tool set applied by this extension. It distinguishes intentional // policy transitions between runs from an external active-tool drift. let lastAppliedTools = null; // Protocol pin: { repoRoot, version, projectId, digest } for the Lead role, // process-latched like the role latch. Advisory-only for authority. let protocolPin = null; // Current-run authority record: { envelope, repoRoot, scope, exclusions } or // null when the run carries no valid grant. Replaced on every input event. let currentAuthority = null; // Last validated terminal Peer Report / acceptance are process-local evidence only; // no mailbox, registry, or durable workflow state is created. let lastPeerReport = null; let lastAcceptance = null; // Last explicit no-authority reason (diagnostics; doctor reads it later). let authorityReason = null; export function getAuthority() { if (currentAuthority === null) return null; return { envelope: { ...currentAuthority.envelope }, repoRoot: currentAuthority.repoRoot }; } export function getAuthorityReason() { return authorityReason; } export function getPeerReport() { return lastPeerReport === null ? null : structuredClone(lastPeerReport); } export function getLastAcceptance() { return lastAcceptance === null ? null : structuredClone(lastAcceptance); } // Pending authority from a confirmed idle slash-command (lead_tiny / // supervisor_recovery): stored by the command handler, consumed by the next // input event (one-shot), and cleared by any new/resumed/forked session. let pendingAuthority = null; export function getPendingAuthority() { return pendingAuthority === null ? null : { ...pendingAuthority }; } export function getProtocolPin() { return protocolPin === null ? null : { ...protocolPin }; } // ─── Slice 8: package verification and release gate ──────────────────────────── // Canonical package resources: the manifest-declared extension and skill, // the skill's required companion guide, and the three private profiles. // Everything resolves from loaded-module/package provenance (the module URL // argument, defaulting to import.meta.url) // — never from cwd, repository root, Pi config root, Paseo workspace, or // parent-directory search. Expected resources must be regular, readable, // nonempty, direct descendants without symlink escape (the realpath- // containment pattern from validateProfileDir). const BUNDLED_PROFILE_FILES = ["supervisor.md", "lead.md", "peer.md"]; const BUNDLED_SKILL_GUIDE_FILE = "AUTHORING-GUIDE.md"; const MANIFEST_DEPENDENCY_FIELDS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; const MANIFEST_INSTALL_SCRIPTS = ["preinstall", "install", "postinstall"]; export async function resolvePackageResources(moduleUrl = import.meta.url) { let url; try { url = new URL(moduleUrl); } catch { return { ok: false, error: "package module URL is not a valid URL" }; } if (url.protocol !== "file:") { return { ok: false, error: "package module must load from a canonical file URL (loaded-module provenance is unavailable)" }; } const modulePath = fileURLToPath(url); let realRoot; try { realRoot = await realpath(join(dirname(modulePath), "..")); if (!(await stat(realRoot)).isDirectory()) return { ok: false, error: "package root is not a directory" }; } catch { return { ok: false, error: "package root is not readable" }; } let manifest; try { manifest = JSON.parse(await readFile(join(realRoot, "package.json"), "utf8")); } catch (err) { return { ok: false, error: `package manifest is not readable JSON: ${err.message}` }; } if (!isRecord(manifest) || !isRecord(manifest.pi)) return { ok: false, error: "package manifest has no pi declaration" }; const extraSurfaces = Object.keys(manifest.pi).filter((key) => key !== "extensions" && key !== "skills"); if (extraSurfaces.length > 0) { return { ok: false, error: `package manifest declares unsupported pi surfaces: ${extraSurfaces.join(", ")}` }; } if (!Array.isArray(manifest.pi.extensions) || manifest.pi.extensions.length !== 1 || typeof manifest.pi.extensions[0] !== "string" || manifest.pi.extensions[0] === "") { return { ok: false, error: "package manifest must declare exactly one extension" }; } if (!Array.isArray(manifest.pi.skills) || manifest.pi.skills.length !== 2 || manifest.pi.skills.some((skill) => typeof skill !== "string" || skill === "")) { return { ok: false, error: "package manifest must declare exactly two skills" }; } for (const field of MANIFEST_DEPENDENCY_FIELDS) { const deps = manifest[field]; if (deps === undefined || deps === null) continue; if (!isRecord(deps)) return { ok: false, error: `package manifest ${field} must be an object` }; if (Object.prototype.hasOwnProperty.call(deps, "pi-mcp-adapter")) { return { ok: false, error: "package manifest must not declare an adapter dependency" }; } } if (isRecord(manifest.scripts)) { const install = MANIFEST_INSTALL_SCRIPTS.filter((name) => typeof manifest.scripts[name] === "string"); if (install.length > 0) { return { ok: false, error: `package manifest must not declare install lifecycle scripts (${install.join(", ")})` }; } } const declared = [ ["extension", manifest.pi.extensions[0]], ["skill", manifest.pi.skills[0]], ["guide", join(dirname(manifest.pi.skills[0]), BUNDLED_SKILL_GUIDE_FILE)], ["orchestration skill", manifest.pi.skills[1]], ...BUNDLED_PROFILE_FILES.map((file) => [`profile ${file}`, join("profiles", file)]), ]; const resources = { package_root: realRoot, profiles: {}, extension: null, skill: null, guide: null, orchestration_skill: null }; for (const [label, rel] of declared) { if (isAbsolute(rel)) return { ok: false, error: `${label} must be a direct descendant of the package root (absolute path)` }; const full = join(realRoot, rel); const relPath = relative(realRoot, full); if (relPath === "" || relPath === ".." || relPath.startsWith(`..${sep}`)) { return { ok: false, error: `${label} must be a direct descendant of the package root (${rel})` }; } let real; try { real = await realpath(full); } catch (err) { return { ok: false, error: err.code === "ENOENT" ? `${label} must exist (${rel})` : `${label} must be readable (${rel})` }; } if (real !== join(realRoot, rel)) { return { ok: false, error: `${label} must be a direct descendant without symlink escape (${rel})` }; } if (!(await stat(real)).isFile()) return { ok: false, error: `${label} must be a regular file (${rel})` }; if ((await readFile(real, "utf8")).trim() === "") return { ok: false, error: `${label} must be nonempty (${rel})` }; if (label === "extension" || label === "skill" || label === "guide") resources[label] = real; else if (label === "orchestration skill") resources.orchestration_skill = real; else resources.profiles[label.slice("profile ".length).replace(/\.md$/, "")] = real; } // The loaded module must be the manifest-declared extension. try { if ((await realpath(modulePath)) !== resources.extension) { return { ok: false, error: "loaded module is not the manifest-declared extension" }; } } catch { return { ok: false, error: "loaded module is not readable" }; } return { ok: true, resources }; } // Release gate: a pure fail-closed function over release facts. Every required // fact must be exactly proven; missing, failed, or unknown values become // concrete blockers naming the responsible party. The public current-agent // observer is a REQUIRED capability: this package does not implement or vendor // the adapter, so while pi-mcp-adapter does not expose it the gate must list it // as a blocker. There is no fallback. const RELEASE_FACTS = [ { fact: "install_pinned", condition: "fresh pinned npm-version install proven (configured npm source, installed name/version/integrity, and extension digest)", owner: "operator", action: "Install npm:pi-paseo-orchestration@ with Pi and verify the installed package identity, registry integrity, and loaded extension digest in a fresh process." }, { fact: "relocation", condition: "package resources resolve identically from a fresh npm-tarball install", owner: "operator", action: "Pack and install the candidate npm tarball in a fresh root, then verify every declared resource resolves with identical bytes." }, { fact: "doctor_tui_rpc_equivalence", condition: "doctor produces equivalent non-persistent TUI and RPC output", owner: "maintainer", action: "Run doctor in TUI and RPC modes and confirm the canonical reports match." }, { fact: "settings_exact", condition: "model-routing settings apply exactly (fixed Supervisor/Lead selections and an allowed Peer route)", owner: "maintainer", action: "Confirm one complete settings document applies fixed role selections and the exact chosen Peer route." }, { fact: "notebook_primitives", condition: "Notebook publication primitives pass concurrency, crash, durability, and containment tests", owner: "maintainer", action: "Run the Notebook publication tests and fix any fail-closed violation." }, { fact: "hermetic_tests", condition: "hermetic package tests pass", owner: "maintainer", action: "Run npm test and fix every failure." }, { fact: "release_smoke", condition: "release smoke passes on the exact npm package candidate", owner: "maintainer", action: "Run npm run release:smoke on the exact npm package candidate and resolve its printed blocker." }, { fact: "mutation_boundaries", condition: "mutation-boundary tests prove settings and Notebook writes stay inside their exact surfaces", owner: "maintainer", action: "Run the mutation-boundary tests and confirm no project, package, Git, or Paseo mutation." }, ]; const RELEASE_CAPABILITIES = [ { capability: "pi_api", condition: "required Pi extension APIs (getActiveTools, setActiveTools, setModel, setThinkingLevel, getThinkingLevel) are present", owner: "operator", action: "Use a Pi process exposing the required extension APIs." }, { capability: "paseo_live", condition: "live Paseo daemon/client identity, cwd, and typed workspace binding are observable", owner: "operator", action: "Start the Paseo daemon and client and rerun doctor until live facts are PASS." }, { capability: "adapter_current_agent_observer", condition: "the public current-agent observer (adapter-provided or the independently installed Paseo CLI) proves exact current-agent identity, model, thinking, parent, and cwd", owner: "operator", action: "Start the Paseo daemon, verify the exact agent identity through the installed observer, and rerun the release smoke on the exact npm package candidate." }, ]; function releaseFactState(value) { if (value === true) return "proven"; if (value === false) return "failed"; return "unknown"; // null, undefined, and non-boolean values are unproven } export function releaseGate(facts) { if (!isRecord(facts)) { return { ok: false, blockers: [{ fact: "facts", condition: "release facts must be a single object with every required fact proven", status: "missing", observed: facts === null || facts === undefined ? null : typeof facts, owner: "maintainer", action: "Pass one facts object with every required fact exactly proven." }], }; } const blockers = []; const knownFacts = new Set(RELEASE_FACTS.map((spec) => spec.fact)); for (const key of Object.keys(facts)) { if (key !== "capabilities" && !knownFacts.has(key)) { blockers.push({ fact: key, condition: "unknown release fact", status: "unknown", observed: facts[key], owner: "maintainer", action: "Remove or prove the unrecognized release fact." }); } } for (const spec of RELEASE_FACTS) { const present = Object.prototype.hasOwnProperty.call(facts, spec.fact); const value = facts[spec.fact]; const state = !present || value === undefined || value === null ? "missing" : releaseFactState(value); if (state !== "proven") { blockers.push({ fact: spec.fact, condition: spec.condition, status: state, observed: present && value !== undefined && value !== null ? value : null, owner: spec.owner, action: spec.action }); } } const caps = facts.capabilities; if (!isRecord(caps)) { blockers.push({ fact: "capabilities", condition: "all required Pi/Paseo/adapter capabilities are proven", status: "missing", observed: caps === undefined || caps === null ? null : typeof caps, owner: "operator", action: "Prove each required capability; the adapter current-agent observer is mandatory." }); } else { const knownCaps = new Set(RELEASE_CAPABILITIES.map((spec) => spec.capability)); for (const key of Object.keys(caps)) { if (!knownCaps.has(key)) { blockers.push({ fact: `capabilities.${key}`, condition: "unknown release capability", status: "unknown", observed: caps[key], owner: "operator", action: "Remove or prove the unrecognized release capability." }); } } for (const spec of RELEASE_CAPABILITIES) { const present = Object.prototype.hasOwnProperty.call(caps, spec.capability); const value = caps[spec.capability]; const state = !present || value === undefined || value === null ? "missing" : releaseFactState(value); if (state !== "proven") { blockers.push({ fact: `capabilities.${spec.capability}`, condition: spec.condition, status: state, observed: present && value !== undefined && value !== null ? value : null, owner: spec.owner, action: spec.action }); } } } blockers.sort((left, right) => (left.fact < right.fact ? -1 : left.fact > right.fact ? 1 : 0)); return blockers.length === 0 ? { ok: true } : { ok: false, blockers }; } const bundledDir = (() => { try { const url = new URL("../profiles/", import.meta.url); return url.protocol === "file:" ? fileURLToPath(url) : null; } catch { return null; } })(); function envOf(ctx) { return ctx?.env ?? process.env; } function readActiveTools(pi) { if (typeof pi?.getActiveTools !== "function") return { ok: false, error: "active tools are not observable" }; let actual; try { actual = pi.getActiveTools(); } catch { return { ok: false, error: "active tools are not observable" }; } if (!Array.isArray(actual) || actual.some((tool) => typeof tool !== "string" || tool === "")) { return { ok: false, error: "active tools are not an array of tool names" }; } return { ok: true, tools: [...actual] }; } // Cooperative active-tool policy: the active tool set is shared with other // extensions and tool loaders, so a drift from the latched policy is healed by // re-applying the ceiling rather than hard-blocking the session. The ceiling // itself is still enforced per call in checkToolCall; this function only keeps // the prompt surface in line with the policy. async function ensureToolPolicy(pi) { if (baseline === null) return { ok: false, error: "active-tool baseline is not observable" }; const observed = readActiveTools(pi); if (!observed.ok) return observed; const expected = lastAppliedTools ?? baseline; if (sameList(observed.tools, expected)) return { ok: true }; const allowed = effectiveTools(baseline, latch?.role ?? null, currentAuthority); if (typeof pi.setActiveTools !== "function") return { ok: false, error: "active-tool policy cannot be re-applied" }; try { pi.setActiveTools(allowed); } catch { return { ok: false, error: "active-tool policy cannot be re-applied" }; } const applied = readActiveTools(pi); if (!applied.ok || !sameList(applied.tools, allowed)) { return { ok: false, error: "active tools drifted while re-applying the session policy" }; } lastAppliedTools = [...allowed]; return { ok: true }; } async function verifyOrBlock(ctx, dir, pi = null, { runtime = true } = {}) { const check = await verifyLatch(latch, envOf(ctx), dir, ctx, { runtime }); if (!check.ok) { blockedReason = check.error; ctx.ui?.notify?.(`pi-paseo-orchestration blocked: ${check.error}`, "error"); return false; } if (pi !== null) { const tools = await ensureToolPolicy(pi); if (tools.ok !== true) { const error = "error" in tools ? tools.error : "active tools are not observable"; blockedReason = error; ctx.ui?.notify?.(`pi-paseo-orchestration blocked: ${error}`, "error"); return false; } } return true; } // Spec: governed activation requires the read tool, and Supervisor/Lead also // require an active outer mcp tool. Not observable = fail closed. function requireBaselineTools(baseline, role) { if (baseline === null) return { ok: false, error: "active-tool baseline is not observable" }; if (!baseline.includes("read")) return { ok: false, error: "read tool is not active in the session" }; if (role !== "peer" && !baseline.includes("mcp")) { return { ok: false, error: `outer mcp tool is not active for the ${role} role` }; } return { ok: true }; } function blockWith(ctx, reason) { blockedReason = reason; ctx.ui?.notify?.(`pi-paseo-orchestration blocked: ${reason}`, "error"); } function createdAgentIdFromResult(event) { const direct = event?.details?.structuredContent?.agentId ?? event?.details?.agentId; if (typeof direct === "string" && direct.trim() !== "") return direct; const blocks = Array.isArray(event?.content) ? event.content : []; for (const block of blocks) { const text = typeof block === "string" ? block : block?.text; if (typeof text !== "string") continue; const match = /"agentId"\s*:\s*"([^"]+)"/.exec(text); if (match) return match[1]; } return null; } function registerCommand(pi, name, definition) { pi.registerCommand(`ppo:${name}`, definition); } function bootstrapPrompt(task, cwd, settings, env) { const supervisorAlias = env[SUPERVISOR_ALIAS_ENV] || "ppo-supervisor"; const leadAlias = env[LEAD_ALIAS_ENV] || "ppo-lead"; if (!validProviderAlias(supervisorAlias) || !validProviderAlias(leadAlias)) throw new Error("PPO Supervisor/Lead aliases must be nonblank names without slashes"); return [ "Load the ppo-orchestrate skill and execute its Bootstrap coordinator workflow.", "PPO_BOOTSTRAP_V1", JSON.stringify({ version: 1, task_key: createHash("sha256").update(`${cwd}\0${task}`).digest("hex"), cwd, task, supervisor_alias: supervisorAlias, lead_alias: leadAlias, supervisor: settings.roles.supervisor, lead: settings.roles.lead }), "Create the governed Lead and Supervisor now. Do not implement the task in this coordinator session.", ].join("\n"); } export async function runBootstrap(args, ctx, pi) { const task = String(args ?? "").trim(); if (task === "" || task.length > OBJECTIVE_MAX) { ctx.ui?.notify?.(`Usage: /ppo:bootstrap (1-${OBJECTIVE_MAX} characters)`, "warning"); return { ok: false, error: "task must be nonempty and bounded" }; } if (!processIsIdle(ctx)) { ctx.ui?.notify?.("pi-paseo-orchestration: bootstrap requires an idle process", "error"); return { ok: false, error: "process is busy" }; } const role = parseRole(envOf(ctx)); if (!role.ok || role.role !== null || latch !== null || blockedReason !== null) { ctx.ui?.notify?.("pi-paseo-orchestration: bootstrap must run from a healthy ungoverned coordinator process", "error"); return { ok: false, error: "governed or blocked process cannot bootstrap" }; } const tools = readActiveTools(pi); if (!tools.ok || !tools.tools.includes("mcp")) { ctx.ui?.notify?.("pi-paseo-orchestration: bootstrap requires the active outer mcp tool", "error"); return { ok: false, error: "outer mcp tool is unavailable" }; } let settings; try { settings = await readSettings(configDir(envOf(ctx))); } catch (err) { ctx.ui?.notify?.(`pi-paseo-orchestration: ${err.message}`, "error"); return { ok: false, error: err.message }; } if (settings === null) { ctx.ui?.notify?.("pi-paseo-orchestration: run /ppo:settings before bootstrap", "error"); return { ok: false, error: "settings are missing" }; } const repoRoot = await findRepoRoot(ctx.cwd); if (repoRoot === null) { ctx.ui?.notify?.("pi-paseo-orchestration: bootstrap requires a Git repository", "error"); return { ok: false, error: "repository is unavailable" }; } const protocol = await readAndValidateProtocol(repoRoot); if (!protocol.ok) { ctx.ui?.notify?.(`pi-paseo-orchestration: ${protocol.error}`, "error"); return { ok: false, error: protocol.error }; } let prompt; try { prompt = bootstrapPrompt(task, repoRoot, settings, envOf(ctx)); } catch (err) { ctx.ui?.notify?.(`pi-paseo-orchestration: ${err.message}`, "error"); return { ok: false, error: err.message }; } if (typeof pi.sendUserMessage !== "function") { ctx.ui?.notify?.("pi-paseo-orchestration: Pi sendUserMessage API is unavailable", "error"); return { ok: false, error: "sendUserMessage API is unavailable" }; } pi.sendUserMessage(prompt); return { ok: true }; } export default function (pi) { registerCommand(pi, "bootstrap", { description: "Create a governed Supervisor and Lead for one task in the current Paseo workspace", handler: (args, ctx) => runBootstrap(args, ctx, pi), }); registerCommand(pi, "settings", { description: "Choose the exact provider, model, and thinking level for Supervisor, Lead, and Peer roles", handler: runSettings, }); registerCommand(pi, "lead-tiny", { description: "Store a Human-confirmed tiny Lead edit/local-commit grant as a pending authority (idle lead process only)", handler: runLeadTiny, }); registerCommand(pi, "supervisor-recovery", { description: "Store a Human-confirmed Supervisor recovery grant binding provider, workspace, and handoff (idle supervisor process only)", handler: runSupervisorRecovery, }); registerCommand(pi, NOTEBOOK_INIT_COMMAND.replace("ppo:", ""), { description: "Create a Human-confirmed immutable Supervisor Notebook manifest (Supervisor only)", handler: runNotebookInit, }); registerCommand(pi, "doctor", { description: "Report bounded observation-only readiness for the current Pi/Paseo context", handler: (args, ctx) => runDoctor(args, ctx, pi), }); if (typeof pi.registerTool === "function") { // Current Pi API: one definition object (name, label, description, // parameters, execute). The old (name, definition) two-argument form is // gone — it yields a tool whose .name is undefined and corrupts the // provider payload. pi.registerTool({ name: NOTEBOOK_APPEND_TOOL, label: "Supervisor Notebook Append", description: "Supervisor-only typed append of one immutable causal Notebook entry; no filesystem path is accepted", parameters: { type: "object", additionalProperties: false, required: ["project_id", "entry"], properties: { project_id: { type: "string" }, entry: { type: "object", additionalProperties: false, properties: Object.fromEntries(NOTEBOOK_ENTRY_FIELDS.filter((field) => field !== "entry_digest").map((field) => [field, {}])), }, }, }, isEnabled: () => latch?.role === "supervisor" && blockedReason === null, execute: async (toolCallId, params, signal, onUpdate, ctx) => { const result = await runNotebookAppend(params, ctx ?? {}); // Execute failure must throw (Pi reports the error); returning a result // object with ok:false here would be surfaced as a successful tool // call whose content is ignored. if (result.ok !== true) { const error = new Error("error" in result && typeof result.error === "string" ? result.error : "notebook append failed"); throw error; } return { content: [{ type: "text", text: "status" in result ? `Notebook entry ${result.status}.` : "Notebook entry appended." }], details: {} }; }, }); } pi.on("session_start", async (_event, ctx) => { currentAuthority = null; // new/resumed/forked sessions inherit no authority pendingAuthority = null; // ...and clear any pending slash-command authority createdPeerIds.clear(); lastPeerReport = null; lastAcceptance = null; authorityReason = null; baseline = null; lastAppliedTools = null; const env = envOf(ctx); const dir = configDir(env); const roleCheck = parseRole(env); if (!roleCheck.ok) { blockWith(ctx, roleCheck.error); return; } if (roleCheck.role === null) return; // passive / ungoverned // Capture the Human-selected active surface once, before this extension // applies any run policy. A missing observation is never replaced by the // current post-policy active set. const observed = readActiveTools(pi); baseline = observed.ok ? observed.tools : null; if (latch !== null) { const tools = requireBaselineTools(baseline, latch.role); if (!tools.ok) { blockWith(ctx, tools.error); return; } if (!(await verifyOrBlock(ctx, dir, pi))) return; if (latch.role === "lead") { const pin = await ensureProtocolPin(); if (!pin.ok) blockWith(ctx, pin.error); } return; } const source = await resolveProfileSource(env, bundledDir); if (!source.ok) { blockWith(ctx, source.error); return; } const result = await activate({ env, dir, profileDir: source.dir, models: ctx.modelRegistry, setModel: pi.setModel, setThinkingLevel: pi.setThinkingLevel, getThinkingLevel: pi.getThinkingLevel, currentModel: ctx.model, currentThinking: ctx.thinkingLevel, }); if (!result.ok) { blockWith(ctx, result.error); return; } latch = result.latch; const tools = requireBaselineTools(baseline, latch.role); if (!tools.ok) { blockWith(ctx, tools.error); return; } if (latch.role === "lead") { const pin = await ensureProtocolPin(); if (!pin.ok) blockWith(ctx, pin.error); } }); pi.on("input", async (event, ctx) => { if (latch === null && blockedReason === null) return { action: "continue" }; if (blockedReason !== null) { ctx.ui?.notify?.(`pi-paseo-orchestration blocked: ${blockedReason}`, "error"); return { action: "handled" }; } if (!(await verifyOrBlock(ctx, configDir(envOf(ctx)), pi))) return { action: "handled" }; // Governed orchestration requires a valid pinned protocol for the Lead: // re-read and re-validate at every gate; drift blocks permanently. if (latch.role === "lead") { const pin = await ensureProtocolPin(); if (!pin.ok) { blockWith(ctx, pin.error); return { action: "handled" }; } } const acceptance = parseAcceptance(event.text ?? "", event.source); if (acceptance.ok && acceptance.acceptance !== null) { const chain = ctx.acceptanceChain; if (!chain || typeof chain !== "object") { authorityReason = "local acceptance evidence chain is unavailable"; ctx.ui?.notify?.(`pi-paseo-orchestration: local acceptance blocked (${authorityReason})`, "error"); return { action: "handled" }; } const checked = await validateAcceptance({ ...chain, acceptance: acceptance.acceptance }); if (!checked.ok) { authorityReason = checked.error; ctx.ui?.notify?.(`pi-paseo-orchestration: local acceptance blocked (${checked.error})`, "error"); return { action: "handled" }; } lastAcceptance = acceptance.acceptance; ctx.ui?.notify?.("pi-paseo-orchestration: LOCAL_ACCEPT validated for the exact candidate", "info"); return { action: "handled" }; } if (!acceptance.ok && String(event.text ?? "").includes(ACCEPTANCE_BEGIN)) { authorityReason = acceptance.error; ctx.ui?.notify?.(`pi-paseo-orchestration: local acceptance blocked (${acceptance.error})`, "error"); return { action: "handled" }; } // Authority lifetime: every run (input) replaces the internal current-run // authority record — including replacement with NO authority when the // message carries no valid envelope. A Human-confirmed slash-command grant // (lead_tiny / supervisor_recovery) is stored as a pending authority and // activates here on the NEXT input event through the same activation path // as every other grant; the pending slot is one-shot (this run consumes it // whether activation succeeds or fails). Direct messages can never // activate those kinds — the route is bound at issuance. New/resumed/ // forked sessions inherit nothing. currentAuthority = null; authorityReason = null; if (pendingAuthority !== null) { const pending = pendingAuthority; pendingAuthority = null; if (event.source === "extension") { authorityReason = "authority envelope route must be a direct Human message, not an extension relay"; ctx.ui?.notify?.(`pi-paseo-orchestration: no authority granted (${authorityReason})`, "error"); } else { const activated = await activateEnvelope(pending, "command"); if (!activated.ok) { authorityReason = activated.error; ctx.ui?.notify?.(`pi-paseo-orchestration: no authority granted (${activated.error})`, "error"); } else { currentAuthority = activated.authority; } } } else if (event.source === "extension") { authorityReason = "authority envelope route must be a direct Human message, not an extension relay"; ctx.ui?.notify?.(`pi-paseo-orchestration: no authority granted (${authorityReason})`, "error"); } else { const parsed = parseEnvelope(event.text ?? ""); if (!parsed.ok) { authorityReason = parsed.error; ctx.ui?.notify?.(`pi-paseo-orchestration: no authority granted (${parsed.error})`, "error"); } else if (parsed.envelope !== null) { const activated = await activateEnvelope(parsed.envelope, "direct"); if (!activated.ok) { authorityReason = activated.error; ctx.ui?.notify?.(`pi-paseo-orchestration: no authority granted (${activated.error})`, "error"); } else { currentAuthority = activated.authority; } } } return { action: "continue" }; }); pi.on("tool_result", (event) => { if (latch?.role !== "lead" || event?.toolName !== "mcp" || event?.input?.server !== "paseo" || event?.input?.tool !== "paseo_create_agent" || event?.isError === true) return undefined; const childId = createdAgentIdFromResult(event); if (childId !== null) createdPeerIds.add(childId); return undefined; }); pi.on("agent_end", async (event, ctx) => { if (latch?.role !== "peer") return undefined; const messages = Array.isArray(event?.messages) ? event.messages : []; let text = ""; for (const message of [...messages].reverse()) { if (message?.role && message.role !== "assistant") continue; const content = message?.content ?? message?.text; if (typeof content === "string") { text = content; break; } if (Array.isArray(content)) { const parts = content.map((item) => typeof item === "string" ? item : item?.text ?? "").filter(Boolean); if (parts.length > 0) { text = parts.join("\n"); break; } } } const parsed = parseReport(text); if (!parsed.ok || parsed.report === null) { lastPeerReport = null; ctx.ui?.notify?.(`pi-paseo-orchestration: Peer run ended without a valid terminal report (${parsed.error ?? "missing report"})`, "error"); return undefined; } const known = ctx.peerReportContext; if (known !== undefined) { const correlated = correlateReport(parsed.report, known); if (!correlated.ok) { lastPeerReport = null; ctx.ui?.notify?.(`pi-paseo-orchestration: Peer report rejected (${correlated.error})`, "error"); return undefined; } } lastPeerReport = parsed.report; return undefined; }); pi.on("before_agent_start", async (event, ctx) => { if (latch === null) return undefined; if (blockedReason !== null) { ctx.ui?.notify?.(`pi-paseo-orchestration blocked: ${blockedReason}`, "error"); return undefined; } if (!(await verifyOrBlock(ctx, configDir(envOf(ctx)), pi))) return undefined; if (latch.role === "lead") { const pin = await ensureProtocolPin(); if (!pin.ok) { blockWith(ctx, pin.error); return undefined; } } const tools = requireBaselineTools(baseline, latch.role); if (!tools.ok) { blockWith(ctx, tools.error); return undefined; } const allowed = effectiveTools(baseline, latch.role, currentAuthority); if (typeof pi.setActiveTools !== "function") { blockWith(ctx, "active-tool policy cannot be applied"); return undefined; } try { pi.setActiveTools(allowed); } catch { blockWith(ctx, "active-tool policy cannot be applied"); return undefined; } const applied = readActiveTools(pi); if (!applied.ok || !sameList(applied.tools, allowed)) { blockWith(ctx, "active tools drifted while applying the session policy"); return undefined; } lastAppliedTools = [...allowed]; return { systemPrompt: `${event.systemPrompt}\n\n${PROFILE_MARKER(latch.role, latch.profileDigest)}\n${latch.profileText}\n\n${createAgentPolicyPrompt(latch, currentAuthority)}\n`, }; }); pi.on("tool_call", async (event, ctx) => { if (latch === null) return undefined; if (blockedReason !== null) { return { block: true, reason: `pi-paseo-orchestration blocked: ${blockedReason}` }; } if (!(await verifyOrBlock(ctx, configDir(envOf(ctx)), pi))) { return { block: true, reason: `pi-paseo-orchestration blocked: ${blockedReason}` }; } if (latch.role === "lead") { const pin = await ensureProtocolPin(); if (!pin.ok) { blockWith(ctx, pin.error); return { block: true, reason: `pi-paseo-orchestration blocked: ${pin.error}` }; } } // Resolve the repository root for the peer read gate when no envelope // carries one; the gate needs a root to resolve read targets. let repoRoot = currentAuthority?.repoRoot ?? null; if (repoRoot === null && latch.role === "peer" && PROTOCOL_READ_TOOLS.includes(event.toolName)) { repoRoot = await findRepoRoot(); } const allowed = new Set(effectiveTools(baseline ?? [], latch.role, currentAuthority)); const decision = checkToolCall(event.toolName, event.input, { role: latch.role, allowed, mcpTargets: MCP_TARGETS[latch.role] ?? {}, roleSettings: latch.settings.roles, peerRoutes: latch.settings.peer_routes, peerProviderAlias: latch.peerProviderAlias, currentAgentId: latch.agentId, envelope: currentAuthority?.envelope ?? null, repoRoot, createdPeerIds, }); if (decision?.block) { ctx.ui?.notify?.(`Blocked ${event.toolName}: ${decision.reason}`, "error"); return decision; } // The commit gate is the async continuation of the same bash check: the // static layer admits `git commit` only under a local_commit grant, and // this layer re-checks HEAD and diff scope against the granted base. if (event.toolName === "bash" && currentAuthority !== null && GIT_COMMIT.test(event.input?.command ?? "")) { const gate = await checkCommitGate(event.input?.command ?? "", currentAuthority); if (gate?.block) { ctx.ui?.notify?.(`Blocked ${event.toolName}: ${gate.reason}`, "error"); return gate; } } return undefined; }); // Governed processes keep Pi-native lifecycle for the Paseo control plane. for (const name of ["session_before_switch", "session_before_fork"]) { pi.on(name, (_event, ctx) => { if (latch === null) return undefined; ctx.ui?.notify?.("pi-paseo-orchestration: use Paseo lifecycle operations in governed processes", "info"); return { cancel: true }; }); } }