/** * MCP policies: the preview matrix and the approvals (DES-MCP-TOOLS-001 §4.5, §4.7, §8; slice S6). * * - **Preview.** `preview` answers what a call to each tool WOULD get, per phase role × seat × run * mode, under the policies in the store right now. It is the engine's own evaluation * (`Core.previewMcpCalls`, the same `evaluate` the broker's `evaluateMcpCall` runs) over * synthetic unit cells, and nothing is recorded. `previewUnsaved` does the same for a server * that is being added, judged as if it were saved now. * - **Approvals.** An approval is an audited policy edit, never a grant. It adds a token to the * `excludes` of the ledger rules the `mcp-defaults` pack ships: `MCP-FIRST-USE` (the first use * of a server asks in every mode) and `MCP-POSTURE-WRITE` (balanced mode asks for a write until * the tool is approved). Approving a SERVER (`mcp:`) approves its first use only, so its * write tools still ask in balanced mode, one by one (§4.5: a server-level grant would hide the * destructive tools behind the one read the operator tried). Approving a TOOL * (`mcp:/`) approves that tool's first use and its writes. Revoking removes the * token from both. An approval never lifts an engine gate (D-1, D-5) or an explicit deny: * deny dominates in the engine. * - **Withdrawal.** A saved tool whose schema changed, and a removed server, lose their approvals, * so the next call asks again (operator decision 1: first use asks, "and again when a tool's * schema changes"). A server-wide approval cannot exclude one tool, so a changed tool withdraws * the server's approval too: the server asks again on its next first use. * * Ledger edits are read-modify-write of one rule, so they are serialized here. * * - **One server, one writer (crew#993).** Approving, revoking, and the routes that withdraw then * change a registration (a save, a removal) run one at a time per server, through * {@link McpPolicies.exclusive}. An approval that arrives while a save is withdrawing and * committing waits for the commit, so it is judged against the registration the save left (a * tool the save dropped is a 404), never against the one the save is replacing. Different * servers do not wait on each other. */ import type { ConformanceRule, McpAuthConfig, McpApprovalsResponse, McpPhaseRole, McpPolicyPreviewResponse, McpPreviewTool, McpRunMode, McpUpstreamKind } from '../core/types.js'; import { type HashedRestTarget } from './classify.js'; import { McpRegistryError, type McpRegistry } from './registry.js'; /** * A ledger edit that failed part-way. `changed` names the rules still changed after the rollback * (empty when the rollback restored them all); the route audits those before answering 502. */ export declare class McpLedgerEditError extends McpRegistryError { readonly changed: string[]; constructor(changed: string[], message: string); } /** The first-use approvals ledger (the engine reads its `excludes`, active or retired). */ export declare const FIRST_USE_LEDGER = "MCP-FIRST-USE"; /** The balanced-mode write approvals ledger. */ export declare const WRITE_LEDGER = "MCP-POSTURE-WRITE"; /** The six seats a governed unit can run on; the matrix has one column per seat. */ export declare const MCP_PREVIEW_SEATS: readonly ["claude", "codex", "opencode", "copilot", "pi", "agy"]; export declare const MCP_PREVIEW_ROLES: readonly McpPhaseRole[]; export declare const MCP_RUN_MODES: readonly McpRunMode[]; /** The engine seam this module needs: the rule store and the preview binding. */ export interface McpPolicyEngine { listRules(): Promise; upsertRule(rule: ConformanceRule): Promise; /** `Core.previewMcpCalls`: `{calls, cells}` → `[{subject, cells}]` as JSON. */ previewCalls(requestJson: string): Promise; } export interface McpPolicyQuery { subject?: string | undefined; server?: string | undefined; phaseRole?: McpPhaseRole | undefined; seat?: string | undefined; mode?: McpRunMode | undefined; phaseId?: string | undefined; } /** `mcp:` or `mcp:/` → its parts, or `null`. */ export declare function parseApprovalSubject(subject: string): { server: string; tool: string | null; } | null; export declare class McpPolicies { private readonly registry; private readonly engine; private chain; /** Per server: the tail of the work queued by {@link exclusive}. Never rejects. */ private readonly serverTails; constructor(registry: McpRegistry, engine: McpPolicyEngine); preview(query: McpPolicyQuery): Promise; /** * The matrix for a server that is being added (`POST /mcp/servers/preview`): every tool judged * as if the preview were saved now, so the operator sees what would run, ask or be denied. */ previewUnsaved(server: { name: string; kind: McpUpstreamKind; url: string | null; auth: McpAuthConfig | null; }, tools: McpPreviewTool[]): Promise; approvals(): Promise; /** Approve a subject (a server: first use; a tool: first use and writes). Answers the rule ids that changed. */ approve(subject: string): Promise; /** Revoke a subject's approval from both ledgers. 404 when neither holds it. */ revoke(subject: string): Promise; /** * Run `fn` alone for `server` (crew#993): after every earlier `exclusive` call for the same * server has settled, and before any later one starts. `approve` and `revoke` take it * themselves; a route that withdraws approvals and then changes the registration (save, remove) * wraps the whole sequence in it, so no approval lands between the withdrawal and the change. * Not re-entrant: inside `fn`, call {@link withdraw}, never `approve` or `revoke`, for the same * server. Other servers are not blocked. */ exclusive(server: string, fn: () => Promise): Promise; /** * Remove every listed token from both ledgers (revoke, a changed schema, a removed server). * Callers outside this class hold {@link exclusive} for the server the tokens name. */ withdraw(tokens: readonly string[]): Promise; /** * The approval tokens saving `next` over the registered server of the same name withdraws: each * tool whose saved schema changed, and the server's own token when any did (a server-wide * approval cannot leave one tool out). Empty for a new server or an unchanged one. * * (G2) A `rest` server whose target (`next.target`: origin, base path, auth header) differs from * the registered one's withdraws EVERY approval naming the server, even when no tool name is in * both saves: an approval covered the API it was given for, not the server's name. So does * re-saving a registration whose saved hashes never covered its target (saved before G2). */ withdrawnBySave(next: { name: string; tools: ReadonlyArray<{ name: string; schemaHash: string; }>; target: HashedRestTarget | null; }): Promise; /** Every approval token that names `server` (the server's own and each of its tools'). */ tokensOf(server: string): Promise; private cells; private axes; private judge; private ledgers; private approvalOf; /** * Apply `next` to both ledgers' `excludes`, serialized. `next` answers the new list or `null` for * no change. A missing ledger refuses the edit: the pack is seeded at boot, so its absence means * the store is not the engine's (fail closed, nothing half-written). */ private edit; } //# sourceMappingURL=policies.d.ts.map