import type { OrgBus } from './bus.js'; import type { RolePolicy } from './types.js'; export { globToRegExp, webDomainMatches } from './policy-paths.js'; export { redactSecrets, summarizeToolInput, summarizeToolOutput } from './policy-secrets.js'; export type Decision = { behavior: 'allow'; updatedInput: Record; } | { behavior: 'deny'; message: string; }; /** ADR-O001 D1: the four token quantities an Anthropic response bills for. * They are SIBLINGS, not subsets — `input` is the uncached remainder only, * while `cacheRead` (~0.1x input) and `cacheCreation` (~1.25x input) are * billed on top of it. Summing only `input + output`, as this engine used * to, reports ~0.3% of real consumption on a well-cached run. */ export interface TokenUsage { input: number; output: number; cacheRead: number; cacheCreation: number; } /** Extra per-role context the daemon wires into a PolicyEngine (M1). */ export interface PolicyToolContext { /** `__` of every tool provider of the role — exempt from allowTools * like `mcp__org__` (fence/Vercel runners pass bare provider tool names). */ providerPrefixes?: () => string[]; /** The role's current chain trace; copied onto every `tool` event. */ trace?: () => { chain_id: string; hop: number; } | undefined; } export declare class PolicyEngine { readonly role: string; policy: RolePolicy; private bus; private cwd; /** #303: extra roots the file tools may touch beyond cwd — the role's * temp dir, the org root, and any `policy.sandbox.allowWrite` entries * (file-roots.ts's `fileToolRoots()`, computed by the daemon). Deny * lists (file-roots.ts's `fileToolDenied()`) still apply inside every * root, cwd included — see the deny pass below. */ private roots; /** #498: the org root, whose `.monomind/orgs/` holds the authority files * (org-authority-files.ts). Without it, cwd and every root count as one. */ private orgRoot?; /** ADR-O001 D1: the four billable quantities, tracked separately. */ private tokens; /** ORG-7: accumulated USD cost for this role, mirrors `used` (tokens). */ private usedUsd; private toolContext; private osSandboxed; /** #550: this role's runner refuses to start an exec below the budget * floor, so a role under it is as good as out of budget. Set per session. */ budgetFloorGated: boolean; /** #492: real paths of the non-glob fileWrite/fileRead entries, taken from * the operator's config before the role runs (and again on `org reload`). */ private scopeSnapshots; constructor(role: string, policy: RolePolicy, bus: OrgBus, cwd: string, /** #303: extra roots the file tools may touch beyond cwd — the role's * temp dir, the org root, and any `policy.sandbox.allowWrite` entries * (file-roots.ts's `fileToolRoots()`, computed by the daemon). Deny * lists (file-roots.ts's `fileToolDenied()`) still apply inside every * root, cwd included — see the deny pass below. */ roots?: string[], /** #498: the org root, whose `.monomind/orgs/` holds the authority files * (org-authority-files.ts). Without it, cwd and every root count as one. */ orgRoot?: string | undefined); /** Whether this role's current session runs Bash inside the SDK's OS * sandbox — set by session.ts from the runtime result of * resolveRoleGitEnforcement, so it is false for mode 'off', an unavailable * sandbox, push roles and non-Claude runtimes. Relaxes only checkGitPolicy's * fail-closed rule for commands it can't read (policy-git.ts). */ setOsSandboxed(on: boolean): void; /** Wire provider prefixes and trace source (daemon, M1). */ setToolContext(ctx: PolicyToolContext): void; /** Hot reload (`org reload`): replace the role's policy. Budget ceilings the * daemon derived at spawn (maxTokens/maxUsd) are kept unless the new policy * sets them itself. */ updatePolicy(next: RolePolicy): void; /** #343 hot reload of budget_tokens / budget_usd: replace the ceilings and * keep what has been spent — the new cap applies to total spend. */ setBudgetCaps(caps: { maxTokens?: number; maxUsd?: number; }): void; /** Set by lowerTokenCap: the ceiling before the reduction, and the reduction itself. */ private configuredCap?; private capReduction; /** Lower the token ceiling by `by` tokens already spent by earlier incarnations of the role, * keeping the configured total cap (a context reset starts this counter at zero). No-op * without a token ceiling. */ lowerTokenCap(by: number): void; /** The token ceiling as configured, before any reset reduction (= `policy.maxTokens` otherwise). * Reload compares and re-derives caps against this, never against the reduced ceiling. */ get configuredTokenCap(): number | undefined; /** Legacy scalar accumulator. A caller with no breakdown to give (a * pre-ADR-O001 checkpoint, a runner that reports one number) lands on the * uncached `input` bucket — the basis such a number has always been on — * so it counts toward both `usage` and `budgetedUsage` and nothing that * used to bind stops binding. */ addUsage(tokens: number): void; /** ADR-O001 D1: add one turn's real usage, per quantity. */ addTokenUsage(u: Partial): void; /** The honest meter: every billable token this role has consumed, cache * reads and cache writes included. Reporting, checkpoints and dashboards * read this. */ get usage(): number; /** The four quantities, for per-role persistence (checkpoint.ts). */ get tokenUsage(): TokenUsage; /** ADR-O001 D1: what `maxTokens` (role.budget_tokens / run_config.budget_tokens) * is compared against. * * It deliberately is NOT `usage`. Counting cache tokens multiplies the * observed volume by ~100x on a well-cached run, so an existing * `budget_tokens` — including the schema's 1M default, which every org * gets whether or not it asked for one — would exhaust within a couple of * turns and close every mailbox. The meter therefore becomes honest while * the budget keeps the basis it was written against, unless a config opts * in via `run_config.budget_tokens_basis: 'billable'`. USD budgets * (`budget_usd`) are unaffected and are the control ADR-O001 recommends. */ get budgetedUsage(): number; /** Set usage counter directly for checkpoint/resume - Pattern 3. * Scalar form: a pre-ADR-O001 checkpoint has no breakdown to restore, so * the value lands on the legacy (uncached) basis it was recorded on. */ setUsage(tokens: number): void; /** Restore a persisted breakdown (checkpoint.ts's `tokenUsage`). */ setTokenUsage(u: TokenUsage): void; get overBudget(): boolean; /** ORG-7: accumulate real USD cost (from 'usage' bus events' data.cost_usd). */ addUsageUsd(costUsd: number): void; get usageUsd(): number; /** Set USD usage counter directly for checkpoint/resume, mirrors setUsage(). */ setUsageUsd(costUsd: number): void; /** ORG-7: parallel to overBudget (token), but for the role's USD spend cap * (policy.maxUsd, from OrgRole.budget_usd). Unset maxUsd means no USD * enforcement for this role — only overBudget (tokens) applies. */ get overBudgetUsd(): boolean; /** @param callId #289: the harness's tool-use id for THIS call, stamped onto * the emitted 'tool' event as `call_id` so the later 'tool_result' event can * be joined back to it — by id, since a role can run the same tool twice * concurrently and the tool name alone does not identify a call. */ decide(tool: string, input: Record, callId?: string): Promise; /** When the run ends (run_config.deadline_seconds), and the role's usual Bash ceiling: a Bash call is * capped to a fraction of the time left (bash-timeout.ts capBashTimeoutMs). */ private runDeadlineAt?; private bashMaxMs; setRunDeadline(atMs: number | undefined, bashMaxMs?: number): void; /** `input` for a Bash call with the timeout this run's deadline allows (the same object when it changes nothing). */ private withDeadlineTimeout; /** Session ids this role's runner has reported (and resumed). The runner saves a tool result that is * too large under `/projects///tool-results/` and tells the model to * Read it; those files, of these sessions only, are readable (see isOwnToolOutput). */ private ownSessions; noteSessionId(id: string | undefined): void; /** `real` is a path under `/projects///tool-results`. Nothing else of a * session directory (the transcript, other files) and no other session's directory qualifies. */ private isOwnToolOutput; /** The directory a sections org keeps the full text of this role's long messages in * (`/mail//`, documents/mail-isolation.ts). The inline copy is cut at 1 KB and points * there, so the role's own directory is readable whatever its fileRead scope is; no other role's is. */ private ownMailDir; noteOwnMailDir(dir: string | undefined): void; private isOwnMailDigest; /** The directory the runner spills this role's large tool results into * (`/tool-results//`, tool-spill.ts). The digest in the transcript points there, so * the role's own folder is readable whatever its fileRead scope is; no other role's is. */ private ownToolSpillDir; noteOwnToolSpillDir(dir: string | undefined): void; private isOwnToolSpill; /** Why a file-tool call on `p` is refused, or null when it may proceed. */ private filePathDenial; } //# sourceMappingURL=policy.d.ts.map