// contracts §13 — Permission Contracts // File: permission.ts — PathPolicy, PermissionRequestContext, PermissionAction, // PermissionGrantScope, PermissionDecision, PermissionRecordResult, PermissionEngine import type { SessionID, TaskID, AgentID, EvidenceRefID } from './ids.js' import type { AirError } from './error.js' // Re-export PathPolicy and ToolPermissionSpec from tool.ts so they are also // available from this module per the DD §3 file map (PathPolicy canonical // assignment: permission.ts) and downstream import expectations. export type { PathPolicy, ToolPermissionSpec } from './tool.js' // ============================================================================= // §13 — Permission Contracts // ============================================================================= /** * Context provided when the ToolRegistry requests a permission evaluation. * Constructed from ToolExecutionContext + ToolDefinition.permissions + input * paths/commands per DD §9.1. */ export interface PermissionRequestContext { session_id: SessionID task_id?: TaskID agent_id?: AgentID tool_name?: string command?: string paths?: string[] network?: boolean requested_action: string reason: string } /** * Actions the PermissionEngine can return. * ToolRegistry.call branches on these per DD §9.3: * * allow — execute; create backup first if backup_required * announce_then_run — emit visible notice, then execute unless interrupted; * bounded by grant_scope * ask_user — suspend; emit permission.prompt.requested; * resume on permission.prompt.resolved * deny — do not execute; return ToolResultEnvelope{status:"error"}; * caller may pick safe path * block — return blocked outcome → task.blocked upstream * refuse — return AirError{kind:"policy_error"}; no execution */ export type PermissionAction = | 'allow' | 'announce_then_run' | 'ask_user' | 'deny' | 'block' | 'refuse' /** * Scope of a permission grant. Determines how long the granted action * remains valid before re-evaluation is required. * * none — no grant (decision is informational only) * once — valid for this single invocation * session — valid for the remainder of the session * project — valid across sessions for this project * global — valid across all projects for this user */ export type PermissionGrantScope = | 'none' | 'once' | 'session' | 'project' | 'global' /** * Risk levels assigned by the PermissionEngine during evaluation. * Used by downstream branching logic and UI presentation. */ export type PermissionRiskLevel = 'low' | 'medium' | 'high' | 'critical' /** * Decision returned by PermissionEngine.evaluate. * Matches DD §22.1 specification and DD §9.3 branching table. * * Layered evaluation order (contracts §13, runtime-semantics §8, overview §12): * 1. tool capability declaration * 2. permission profile (permission_template) * 3. TaskSpec scope allowed/denied paths * 4. path/command/network risk classification (PathClassifier + CommandRiskAnalyzer) * 5. credential/system-sensitive override * 6. user prompt workflow if required */ export interface PermissionDecision { action: PermissionAction grant_scope: PermissionGrantScope risk_level: PermissionRiskLevel reason: string required_confirmation?: boolean backup_required?: boolean evidence_ref_ids?: EvidenceRefID[] } /** * Result of recording a permission decision. * PermissionEngine.record writes a permission.decision.recorded durable event * and returns this result; on write failure it returns {ok:false, error}. */ export interface PermissionRecordResult { ok: boolean error?: AirError } /** * Core permission evaluation engine interface. * Implements contracts §13, referenced by ToolRegistry (DD §9.1, §9.3). * * Invariants (DD §9.2): * - project-level allow never overrides task scope * - credential/system-sensitive overrides broad allows * - paths normalized via realpath before prefix checks * - .git/ internals protected */ export interface PermissionEngine { evaluate(context: PermissionRequestContext): Promise record(decision: PermissionDecision, context: PermissionRequestContext): Promise }