/** * Field-level classification and fail-closed redaction (plan 027 Task 8). * * A dependency-free, JSON-like structured-value walker that evaluates an * explicit host policy against every field of a value destined to cross a * data boundary (provider prompt egress, tool dispatch/result persistence, * artifact write/read/export, audit hashing, telemetry attributes/events, * lifecycle export). The policy classifies fields by label, path, destination, * and tenant; unknown fields fail closed under the protected default. * * Non-goals (frozen): no automatic sensitive-data discovery (labels come from * an explicit `labelFor` hint function supplied by the boundary owner), no * global registry, no decorator framework, no second policy language. Existing * hardcoded secret redaction (`createSecretRedactor`) stays in place as * defense in depth and runs *after* policy transformation at egress seams. */ export type FieldPolicyAction = "allow" | "redact" | "tokenize" | "deny"; export interface FieldPolicyDecision { readonly action: FieldPolicyAction; /** Bounded reason recorded in audit redaction lists; never includes values. */ readonly reason?: string; } export interface FieldPolicyInput { /** Dot/bracket path from the value root, e.g. `user.credentials[2].apiKey`. */ readonly path: string; /** Boundary destination: prompt, tool, artifact, audit, telemetry, persistence, export, ... */ readonly destination: string; /** Explicit caller-assigned label from `labelFor`; undefined = unknown. */ readonly label?: string; readonly kind: "string" | "number" | "boolean" | "null" | "array" | "object"; readonly tenantId?: string; readonly direction: "inbound" | "outbound"; readonly purpose?: string; } export type FieldPolicy = (input: FieldPolicyInput) => FieldPolicyDecision; export interface ApplyFieldPolicyOptions { readonly destination: string; readonly direction?: "inbound" | "outbound"; readonly tenantId?: string; readonly purpose?: string; /** Explicit key→label hints owned by the boundary; no automatic discovery. */ readonly labelFor?: (key: string, path: string) => string | undefined; /** Called for every non-allow decision with {path, reason}; used by audit adapters. */ readonly onRedact?: (path: string, reason: string) => void; readonly maxDepth?: number; readonly maxKeys?: number; readonly maxChars?: number; /** Wall-clock budget for the whole walk; a slow policy trips it (fail closed). */ readonly maxPolicyMs?: number; /** Deterministic token prefix (stable across runs; safe for audit chains). */ readonly tokenPrefix?: string; } export declare class FieldPolicyError extends Error { readonly code = "ERR_PRISM_FIELD_POLICY"; readonly path: string; constructor(path: string, message: string); } export declare const FIELD_POLICY_LIMITS: Readonly<{ readonly maxDepth: 32; readonly maxKeys: 10000; readonly maxChars: 1000000; readonly maxPolicyMs: 5000; readonly tokenPrefix: "tok_"; }>; /** * Applies `policy` to every field of a JSON-like value. * * Decisions: `allow` keeps the value; `redact` replaces string leaves with * `[REDACTED]` while preserving container shape; `tokenize` replaces string * leaves with a deterministic token; `deny` replaces the value with `[DENIED]`. * Unknown labels fail closed under the protected default. Hostile values * (cycles, unsupported types, over-budget walks) throw `FieldPolicyError` * instead of stringifying guesses. The input is never mutated; only fields the * policy changed are allocated. */ export declare function applyFieldPolicy(value: T, policy: FieldPolicy, options: ApplyFieldPolicyOptions): T; export interface ProtectedFieldPolicyOptions { /** Labels allowed to cross outbound/persisted boundaries unchanged. */ readonly publicLabels?: readonly string[]; /** Labels denied with the given reason; default `secret` and `financial`. */ readonly deniedLabels?: Readonly>; /** Labels redacted with the given reason; default `personal`. */ readonly redactedLabels?: Readonly>; /** Labels tokenized; default none. */ readonly tokenizedLabels?: Readonly>; } /** * Fail-closed default for the protected profile (frozen behavior): * unknown labels are denied on outbound boundaries and allowed inbound; * `secret`/`financial` denied, `personal` redacted, listed public labels * allowed. Labels are never auto-discovered — `labelFor` decides them. */ export declare function createProtectedFieldPolicy(options?: ProtectedFieldPolicyOptions): FieldPolicy; /** Identity policy: every field allowed (explicit override for trusted marks). */ export declare const ALLOW_FIELD_POLICY: FieldPolicy; export interface AuditFieldRedaction { readonly path: string; readonly reason: string; } /** Structural match for `AuditRedactionPolicy` in @arnilo/prism-policy. */ export interface AuditFieldRedactorLike { apply(record: Readonly>): { record: Readonly>; redactions?: readonly AuditFieldRedaction[]; }; } export interface AuditFieldRedactorOptions { readonly tenantId?: string; readonly purpose?: string; readonly labelFor?: (key: string, path: string) => string | undefined; /** Default true; denial instead would fail export (protected profile fails closed). */ readonly collectProvenance?: boolean; } /** * Adapts a `FieldPolicy` to the audit-export redaction hook. Transformation * runs BEFORE canonical hashing so the exported chain verifies exactly. * Only `{path, reason}` provenance survives; values never echo into the list. */ export declare function createAuditFieldRedactor(policy: FieldPolicy, options?: AuditFieldRedactorOptions): AuditFieldRedactorLike;