import type { GovernancePolicy, SchemaTable, QueryResult } from './types.js'; import type { ActorAssurance } from './tokens.js'; import { type SqlDialect } from './sql-gate/dispatch.js'; export interface GovernanceMetadata { accessedTables: string[]; accessedFunctions: string[]; rewrittenSql?: string; appliedRowCap?: number; maskedFields: string[]; maskedCount: number; /** Detector / custom-rule name → hit count. Names only; no pattern text. */ byClass?: Record; truncated?: boolean; denied: boolean; denyReason?: string; /** Receipt `flags` extras. Names only — never values. */ flags?: string[]; } export interface GuardedQuery { sql: string; aliases: Record; metadata: GovernanceMetadata; } export interface GovernedQueryResult extends QueryResult { governance: GovernanceMetadata; } export declare class PolicyError extends Error { metadata?: GovernanceMetadata; constructor(message: string, metadata?: GovernanceMetadata); } /** Column-name heuristic shared by nested JSON and flat query rows (G18). */ export declare function maskByColumnName(column: string): '[MASKED_PII]' | '[MASKED_SECRET]' | null; export declare class Governance { private policy; private profileName; private customPatterns; constructor(policy?: GovernancePolicy, profileName?: string | null); /** Operator-declared SQL gate. Omitted dialect is postgres. */ dialect(): SqlDialect; /** * Shipped `query` path. Picks the gate from `policy.dialect`. * Does not inspect the SQL to choose a parser. */ guardSql(sql: string): GuardedQuery; /** Name of the masking profile in force, or null for the base policy. */ appliedProfile(): string | null; /** * Resolve the policy for a specific person. * * Masking that fits an AI agent does not fit the data controller: the owner * asking "which customer owes the most" needs the name. Rather than a global * switch that would turn the guarantee off for everyone, a named profile * overlays `maskColumns`/`maxRows` for one identified person, and the receipt * records which profile was in force (`policy.id`). * * Fail-closed at every step — anything unexpected falls back to the BASE * policy, never to a wider one: * - no actor, or actor authenticated with a SHARED token → base. A shared * token means "whoever holds this string", and handing unmasked PII to a * bearer is the failure mode the product exists to prevent. * - actor not listed in `actorProfiles` → base * - profile name not found in `profiles` → base */ forActor(actor?: { id: string; assurance: ActorAssurance; }): Governance; allowsTable(qualified: string): boolean; filterTables(tables: SchemaTable[]): SchemaTable[]; /** * Tool permission — `allowTools` / `denyTools`. * * 🔴 2026-08-12: these fields were editable in the CONSOLE, documented in `types.ts` * and had a test, but the check lived only inside `ApiGovernance`, which nothing * called — so an operator saying "I turned that tool off" changed nothing. * It was not a hole (writes are already refused at the connector; reads go through * `allowsTable`), but it was a FALSE CLAIM: the UI promised something the engine * did not do. This product's only claim is "actually do what you said", so a dead * policy field is more expensive than a working gap. * * ⚠️ Deliberately fail-OPEN — unlike `allowsTable`/`allowsConnector`. If * `allowTools` is undefined every tool passes, because the real door is already * table policy and that is fail-closed. Making this fail-closed too would silently * break every existing install that never wrote `allowTools`; that would be the * same class of error we are closing. This field is not a protection layer, it is * a NARROWING layer. */ allowsTool(toolName: string): boolean; /** * Fail-closed, like `allowsTable`. Previously an unset `allowConnectors` * meant "every connector is reachable", while an unset `allowTables` meant * "no table is readable" — two opposite defaults in the same policy object. * A gateway whose whole claim is fail-closed cannot ship that asymmetry. * * BREAKING: a config with connectors but no `allowConnectors` now reaches * nothing. That is deliberate — see `bootDeps`, which refuses to start with * a named error rather than silently serving zero connectors. */ allowsConnector(connectorName: string): boolean; guardQuery(sql: string): GuardedQuery; maxRows(): number; /** * `protectedColumns` is also a mask list. When the field is absent or empty * this returns `maskColumns` unchanged so an omitted field is bit-identical. */ private maskPatterns; redact(result: QueryResult, aliases?: Record, metadata?: GovernanceMetadata): GovernedQueryResult; /** True when `key` of `row` is masked by policy (or by upstream query analysis). */ private masksColumn; maskPII(obj: unknown, ctx?: { column?: string; }): { masked: unknown; count: number; byClass: Record; }; private createAnalysisState; private metadataFrom; private metadataFromMetadata; private deny; private isSchemaQualifiedTable; private normalizeIdentifier; private qualifiedTable; private analyzeRead; private analyzeSelect; private collectFrom; private addSource; private enforceTableAccess; private collectOrderBy; private collectExpr; private inspectFunction; private functionId; private expressionReferencesMasked; private refReferencesMasked; private matchesMaskedColumn; private outputNamesForColumn; private applyRowCap; private limitTarget; } export declare class ApiGovernance { private policy; constructor(policy?: GovernancePolicy); allowsTool(toolName: string): boolean; redactResponse(data: any): any; private maskPii; private applyRegexMasks; } //# sourceMappingURL=governance.d.ts.map