import { Limit, type PathSpec, type Refusal } from '../types.ts'; import type { Policy } from './base.ts'; import { VALIDITY, type Ask, type CommandContext, type Deny, type ExecuteResultContext, type OpsContext, type OpsResultContext, type Pending, type SessionContext } from './types.ts'; type Hook = keyof typeof VALIDITY; /** * The command plane's rendering of a refusal: stderr and exit code. The * one place the outcome table for that plane is written down, so a * document rule and a coded policy print alike: a whole-command Deny is * bash's own `: Permission denied` at 126, with the reason on * the result's `refusal` record rather than on stderr; an operand Deny * keeps the GNU voice `: ` at the command's * operand-refusal code (1, tar 2), because there the reason is the * diagnostic. */ export declare function renderDeny(subject: string, deny: Deny): [Uint8Array, number]; /** * The command plane's rendering of an unanswered ask: refused for now * at 126 in the same words as a deny, so stderr never tells an agent * whether a retry might pass; the ask id it should quote rides the * `refusal` record. */ export declare function renderPending(subject: string, pending: Pending): [Uint8Array, number]; /** The record a refused result carries beside its bash-voiced stderr. */ export declare function refusalOf(action: Deny | Pending): Refusal; /** * One line saying why, for a surface that hands the agent text rather * than a record; the agent adapters append it after stderr. */ export declare function describeRefusal(refusal: Refusal): string; /** * Whether `text` already carries the line that says why the command was * refused. Only an operand-scoped denial has one: its GNU diagnostic * `: ` is the reason, wherever a redirect landed it, so * a surface that describes the record after the text looks for that * line rather than for the scope (`2>/dev/null` takes the line away and * the record is the only reason left, `2>&1` moves it onto stdout and * nothing needs repeating) and rather than for the reason as a * substring, since output that happens to quote the words has refused * nothing. A command-scoped refusal's stderr is bash's bare * `Permission denied`, which never says why. An empty reason says * nothing, so no text can already have said it. */ export declare function saysWhy(text: string, refusal: Refusal): boolean; /** * Fire preOps at the op door; a Deny becomes a PolicyDenied (EACCES). * The one seam helper the dispatcher calls, so a refusal is identical * however the mount is reached: shell internals, programmatic access, * FUSE, and the warm cache all pass through it. */ export declare function preOpsGate(policies: Policies, op: string, path: PathSpec, write: boolean, prefix: string, sessionId?: string, issuer?: symbol): Promise; /** * Fire postOps at the op door; a Deny suppresses the result. Returns * the merged Limit bound (tightest per field across every opining * policy) for the door to apply to a byte-producing result, or null * when no policy bounds this op. */ export declare function postOpsGate(policies: Policies, op: string, path: PathSpec, write: boolean, prefix: string, result: unknown): Promise; /** * Fire postExecute at the workspace boundary. Returns the fail-closed * Deny (a throwing policy) if any, and the merged Limit bound for the * boundary to enforce on the line's output stream. */ export declare function postExecuteGate(policies: Policies, ctx: ExecuteResultContext): Promise<[Deny | null, Limit | null]>; /** * Fire preSession on the session plane; a Deny becomes a PolicyDenied. * The one seam helper the session plane's writers call, so a refusal * is identical however the state is reached: shell builtin, command * view, or a later tier. Null policies (a view constructed outside a * workspace) gate nothing. */ export declare function preSessionGate(policies: Policies | null, ctx: SessionContext): Promise; /** * Ordered policies; on a pre hook the first Deny wins. * * Built-ins are seeded first (MountRegistry registers * MountRootPolicy), then the document's deny rules compiled by the * workspace, then user policies in registration order * (`Workspace({policies})`, then anything added later through * `add`). There is no allow arm, so adding a policy can only tighten * the workspace, never loosen it; order decides which refusal message * is shown, never whether a refusal holds. * * A policy that throws fails closed: the command is refused with a * whole-command Deny naming the policy. A policy that returns something the hook may * not return (VALIDITY) throws PolicyError: that is a programming * error, not a refusal. */ export declare class Policies { private readonly policies; private wanted; constructor(policies?: readonly Policy[]); /** * True when any policy defines `hook`. O(1); the op seam gates on it * so a workspace with no op policies pays nothing per VFS op. */ wants(hook: Hook): boolean; /** * True when some policy will speak at `hook` for this session. The * per-session refinement of `wants`: a policy that defines the hook * counts, unless it speaks per session (`SessionScoped`) and says this * is not one of its. For a seam that pays ahead for a hook rather than * gating on it: the secret fill drops its masks under a session-write * gate, and a profile's policy at that door is one profile's, not * every session's. */ wantsFor(hook: Hook, sessionId: string): Promise; private rescan; /** * Register a policy after the existing ones. Code only: a * declarative rule belongs in the permissions document * (`commands.deny`), which the workspace compiles. */ add(entry: Policy): void; /** * One loop for every hook: the first Deny wins (limits are moot once * the result is suppressed), Limit actions accumulate and merge * to the tightest value per field. An Ask is remembered and the * loop goes on looking for a Deny, so a later policy's refusal * outranks an earlier policy's question and an approval can never * re-open a deny; the first Ask is returned when nothing refused. */ private fire; /** Fire preCommand across the policies; the first Deny wins, else the first Ask. */ preCommand(ctx: CommandContext): Promise; /** Fire preOps across the policies; the first Deny wins. */ preOps(ctx: OpsContext): Promise; /** Fire postOps; a Deny suppresses the result, Limits merge. */ postOps(ctx: OpsResultContext): Promise<[Deny | null, Limit | null]>; /** Fire postExecute; Limits merge to the boundary bound. */ postExecute(ctx: ExecuteResultContext): Promise<[Deny | null, Limit | null]>; /** Fire preSession across the policies; the first Deny wins. */ preSession(ctx: SessionContext): Promise; } export {}; //# sourceMappingURL=policies.d.ts.map