import type { OwnershipModel, SchemaSnapshot } from "../types.js"; /** * The authorization model, reduced to the facts a proof was made about. * * Everything in here is something that decides who may read which row: whether * row-level security is on and forced, the policy expressions verbatim, the * grants, and which roles can execute a `SECURITY DEFINER` function. Nothing * else is in here — not columns, not indexes, not defaults, not constraints — * and that omission is the design, not an oversight. Drift has to mean drift * that matters, or the first `add column` after a passing run turns this into * another thing that cries wolf. */ export interface ModelFingerprint { /** Bumped when the shape below changes in a way an older lock cannot be read as. */ formatVersion: 1; /** The Crossline that recorded it. */ recordedBy: string; recordedAt: string; /** Which schemas the proof covered. A lock says nothing about the rest. */ schemas: string[]; /** The roles the proof impersonated. Different roles, different proof. */ roles: { anonymous: string | null; authenticated: string | null; }; userTableId: string | null; tables: TableFingerprint[]; definerFunctions: FunctionFingerprint[]; /** sha256 over everything above except the two `recorded*` fields. */ digest: string; } export interface TableFingerprint { id: string; rlsEnabled: boolean; rlsForced: boolean; /** * What the run took the table to hold, carried so the lock reads as * something rather than as a hash. Deliberately *not* compared: it is an * inference over the whole schema, so a column added anywhere could move it, * and a moved inference is not a changed authorization model. */ holds: string; policies: PolicyFingerprint[]; /** `grantee:PRIVILEGE`, sorted. */ grants: string[]; } export interface PolicyFingerprint { name: string; command: string; permissive: boolean; roles: string[]; using: string | null; withCheck: string | null; } export interface FunctionFingerprint { signature: string; /** Of the roles a request runs as, the ones that may EXECUTE it. */ executableBy: string[]; } /** Take the fingerprint of a schema as it stands. */ export declare function fingerprint(snapshot: SchemaSnapshot, model: OwnershipModel, opts?: { schemas: string[]; recordedBy: string; }): ModelFingerprint; export type DriftKind = "table_appeared" | "table_gone" | "rls_disabled" | "rls_enabled" | "rls_unforced" | "rls_forced" | "policy_dropped" | "policy_added" | "policy_changed" | "grant_added" | "grant_removed" | "function_appeared" | "function_gone" | "function_reach_widened" | "function_reach_narrowed" | "roles_changed" | "schemas_changed" | "unnamed_difference"; export interface Drift { kind: DriftKind; /** Table id, function signature, or the whole run. */ subject: string; /** One line, naming exactly what moved. */ what: string; /** * True when the change could admit more than the proof covered. * * Both directions are reported, because both mean the running database is * not the one that was proved. Only this flag decides the exit code — a * *removed* grant is drift worth naming and is not a reason to fail a * deploy. */ weakening: boolean; } /** * Name every difference between the model that was proved and the one running. * * The comparison is over the recorded facts and only those, so an added column, * a new index, a changed default and a renamed constraint all produce nothing * here — by construction, because none of them is in the fingerprint at all. */ export declare function compareFingerprints(recorded: ModelFingerprint, live: ModelFingerprint): Drift[];