/** * The rules an impersonated session is held to (K-42, #868) — kernel-side, so * both adapters enforce the same ones. * * An adapter supplies storage and a clock. Everything that decides whether a * session may be opened, whether it may still be used, and what it may do lives * here: a rule implemented twice is a rule that will eventually be two rules, * and this is not a surface where the two versions may drift. * * What is deliberately NOT here: the permission evaluation. An impersonated * operation checks as the impersonated principal through the ordinary * `PermissionChecker`, with no override branch anywhere — the same discipline * that made `system:` a subject rather than a bypass (#383). A door * that grants authority by opening is a door nobody can audit. */ import { SubstratError, type BeginImpersonationInput, type ImpersonationFilter, type ImpersonationSession, type ImpersonationSessionId, type ImpersonationStamp, type Instant, type PlatformActorId, type ScopeId, type TenantId } from '@substrat-run/contracts'; /** * A session refused: expired, ended, pointed at the wrong scope, or asked to do * something a `read-only` session may not. * * `forbidden` rather than `permission_denied`, and the distinction is worth the * separate class: the principal being impersonated may well hold the permission, * and the check may well have passed. What refused is the SESSION. Recording * this in the denial log would put a row there about a permission nobody was * actually denied. */ export declare class ImpersonationRefused extends SubstratError { constructor(message: string); } /** * Mint a session from a staff request. Pure: the caller supplies the id and the * instant, so a frozen clock produces a deterministic session (#812) and the * adapter keeps its one source of ULIDs. * * `minutes` is CAPPED BY THE SCHEMA rather than clamped here — a caller asking * for a day is refused with a parse error naming the ceiling, because silently * handing back a shorter session than was asked for is how somebody comes to * believe they have one that is still open. */ export declare function newImpersonationSession(id: string, actor: PlatformActorId, input: BeginImpersonationInput, now: Instant): ImpersonationSession; /** * Is this session usable, right now, for this scope? * * Called at the DOOR and again on EVERY INVOKE. Twice deliberately: a stub is a * capability and nothing forces a caller to drop it, so a session checked only * when the stub was minted would be a session whose expiry meant nothing to the * one caller holding it — the exact shape of a time-box that is not one. * * Expiry is compared as ISO text, which sorts lexicographically, so neither * adapter parses a date to answer it. */ export declare function assertSessionUsable(session: ImpersonationSession, now: Instant, at?: { tenantId: TenantId; scopeId: ScopeId; }): void; /** * The effecting verbs a `read-only` session refuses, by name. * * Refusing at the verb is the half a person notices; the transaction being * rolled back rather than committed is the half that is actually load-bearing, * since `ctx.sql.exec` can write a row without going through any of these. Both * are needed: without the rollback the guarantee is a promise about which * functions module code happens to call, and without the refusal a support * engineer watches a save appear to succeed and silently vanish. */ export declare function assertImpersonationWrites(session: ImpersonationSession | undefined, verb: string): void; /** What the kernel stamps on a record raised under this session. */ export declare function impersonationStampOf(session: ImpersonationSession): ImpersonationStamp; /** Every column of `_substrat_impersonations`, in the order `mapImpersonationRow` expects. */ export declare const IMPERSONATION_COLUMNS = "id, actor, principal, tenant_id, scope_id, reason, mode, started_at, expires_at, ended_at"; /** The raw row shape, as either adapter hands it back. */ export interface ImpersonationRow { id: string; actor: string; principal: string; tenant_id: string; scope_id: string; reason: string; mode: string; started_at: string; expires_at: string; ended_at: string | null; } export declare function mapImpersonationRow(row: ImpersonationRow): ImpersonationSession; export declare function impersonationRowValues(session: ImpersonationSession): (string | null)[]; /** Read one session by id — the door's own lookup, shared so both adapters spell it once. */ export declare function impersonationByIdQuery(id: ImpersonationSessionId | string): { sql: string; params: string[]; }; /** * A bounded page of sessions, newest first. `active` is resolved against the * caller's `now` rather than a stored flag: a session becomes inactive by the * clock moving, and a flag would need somebody to come and set it. */ export declare function impersonationListQuery(filter: ImpersonationFilter | undefined, now: Instant): { sql: string; params: (string | number)[]; }; /** The DDL both adapters create the session store from — one spelling, one shape. */ export declare const IMPERSONATION_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_impersonations (\n id TEXT PRIMARY KEY,\n -- The REAL actor. Never a principal: a platform actor is branded apart from\n -- one precisely so a staff member can never read as a person in a trail.\n actor TEXT NOT NULL,\n principal TEXT NOT NULL,\n tenant_id TEXT NOT NULL,\n scope_id TEXT NOT NULL,\n reason TEXT NOT NULL,\n mode TEXT NOT NULL,\n started_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n -- Explicitly closed, as distinct from expired. Never deleted (K-21): a\n -- session that once existed is why some rows carry the stamp they do.\n ended_at TEXT\n );\n CREATE INDEX IF NOT EXISTS _substrat_impersonations_tenant ON _substrat_impersonations (tenant_id, id);\n CREATE INDEX IF NOT EXISTS _substrat_impersonations_actor ON _substrat_impersonations (actor, id);\n"; //# sourceMappingURL=impersonation.d.ts.map