/** * Server-read permission mode. * * `observe` vs `enforce` is no longer a local, per-machine setting — it is a * **permission the plugin reads from Keto** before gating a tool call, so an * admin controls the deny posture centrally from the Ory project (Ory Console / * Agent Security), and every session inherits it. There is no local override * and no `ORY_PERMISSION_MODE` env var. * * The mode lives in its own `PermissionMode` namespace — it is not a tool, so it * is not a tool object (see the naming-conventions note in `opl.ts`). It is * resolved by `checkPermission` against the `mode` anchor, at two scopes: * * project: PermissionMode:mode#enforcedSubjects@(PermissionMode:project#enforcedSubjects) * applicable: PermissionMode:mode#enforcedSubjects@ * allowed ⇒ enforce denied ⇒ observe * * The **project** grant is checked first and is the posture an admin normally * sets: one tuple governs every principal, including ones that do not exist * yet. That matters because a per-principal grant cannot cover a future * principal — before the project scope existed, an agent connecting after an * admin turned on enforce resolved `observe` and silently escaped enforcement * until someone re-applied the grant for it. A **per-principal** grant remains * meaningful as an exception: enforce one identity, agent harness, or sub-agent * type while the project still observes. Type grants are inherited through the * server-managed AgentType/SubAgentType membership relations. * Neither scope can be set by the runtime — the plugin only ever reads, riding * its own OAuth2 access token like every other Keto read. * * Resolution precedence, applied by {@link resolvePermissionMode}: * * 1. **In-memory TTL cache** (per scope) — avoids a Keto round-trip on * literally every tool call while still re-reading the server every `ttlMs()`. * The project scope caches under one key shared by every subject, so the * common "whole project enforced" case costs a single read per TTL window. * 2. **Server check** — project scope, then identity and acting machine scopes * only if the project is not enforcing. On a clean result, refresh the in-memory cache * and return it (`source: "server"`). * 3. **Persistent cache** — when the server is unreachable, fall back to the * last value read from the server, persisted in `config.json` * (`source: "cache"`). * 4. **`observe`** — nothing was ever cached (`source: "default"`). * * The persistent cache is written by {@link warmPermissionModeCache} at session * start and when a gate first discovers a new acting-machine scope. It only * writes when a cached mode changes, so normal per-tool checks do not churn the * shared config while child-only posture still survives subprocess boundaries. */ import type { OryAgentClient } from "./client.js"; import { type PermissionMode } from "./config.js"; import type { PermissionCheck } from "./types.js"; /** Where {@link resolvePermissionMode} sourced the value it returned. */ export type PermissionModeSource = "server" | "cache" | "default"; /** The subject a mode check is evaluated for — the same shape as a tool check. */ export type ModeSubject = Pick; export interface ResolvedPermissionMode { mode: PermissionMode; source: PermissionModeSource; } export interface AdditionalModeSubject { subject: ModeSubject; scope: "agent" | "subagent"; } export interface PermissionModeBatchPlan { checks: PermissionCheck[]; resolve(results: Array<{ allowed: boolean; error?: string; }>): ResolvedPermissionMode; } /** Drop all in-memory caches. Test-only (reassigns the WeakMap). */ export declare function resetPermissionModeCache(): void; export declare function preparePermissionModeBatch(client: OryAgentClient, subject: ModeSubject, opts?: { now?: number; additionalSubjects?: AdditionalModeSubject[]; }): PermissionModeBatchPlan; /** * Resolve the current permission mode for `subject`. Never throws — a Keto * error falls back to the persistent cache, then `observe`. See the module * doc for the precedence. Does **not** persist (that is * {@link warmPermissionModeCache}'s job); `opts.now` is injectable for tests. */ export declare function resolvePermissionMode(client: OryAgentClient, subject: ModeSubject, opts?: { now?: number; additionalSubjects?: AdditionalModeSubject[]; }): Promise; /** * Resolve the mode and, when it came from a live server read, persist it to * `config.json` so a subprocess harness's later hook processes (and the * synchronous MCP `applyPermissionMode` path) see the current posture, and so a * subsequent offline session has a cached value to fall back to. Called once at * session start. Best-effort; never throws. Only writes when the value changed, * so it doesn't churn the config lockfile. */ export declare function warmPermissionModeCache(client: OryAgentClient, subject: ModeSubject, opts?: { now?: number; additionalSubjects?: AdditionalModeSubject[]; }): Promise;