import { type DenialActorSummary, type DenialBucket, type DenialFilter, type DenialGroupBy, type DenialOperationBucket, type DenialOperationSummary, type PermissionDenial } from '@substrat-run/contracts'; /** The raw row shape, as either adapter hands it back. */ export interface DenialRow { id: string; actor: string; permission: string; tenant_id: string; scope_id: string | null; operation: string | null; /** K-42: the staff actor + session, as JSON, when the refusal was under one. */ impersonation: string | null; /** #1525: the invocation the refusal happened during. NULL = none was carried. */ invocation_id: string | null; at: string; drained_at: string | null; } /** * The stored spelling of an actor. The writer persists `JSON.stringify(actor)`, so a * principal is stored WITH its quotes (`"01J…"`) while a system or connection actor is * stored as an object (`{"system":"invoicing"}`). A caller filtering by actor holds the * logical form, not that encoding, so normalize rather than making every call site know: * text that already parses as JSON is passed through, anything else is a bare id and is * stringified. Round-trips exactly what `recordDenial` wrote in both adapters. */ export declare function storedActor(input: string): string; /** * Turn a stored row into the contract shape — TOLERANTLY, and saying so (#1636). * * Every read of this log is a LIST, and the decode used to `JSON.parse` while mapping: one * row whose `actor` or `impersonation` would not parse threw for the page, which for the * only witness to a refused check is the worst read to lose. Each field is now decoded * against its own contract field and whatever did not decode is named in `decodeError`, * on `rowDecoder`'s rules — an undecodable actor reads as {@link UNDECODED_ACTOR}, and a * row whose id, tenant or time does not decode still throws, naming them. * * `permission` is the exception to that last rule, deliberately. A malformed key is not * only a dump's doing: nothing validates a checked key at runtime, so a module that casts * one is refused and the refusal is recorded with it — and this log is where someone comes * to find out why. Throwing would hide the evidence along with the list. So it reads as * {@link UNDECODED_PERMISSION}, and `decodeError` quotes the stored key verbatim. */ export declare function mapDenialRow(row: DenialRow): PermissionDenial; /** A bounded page of raw denial rows, newest first. */ export declare function denialListQuery(filter?: DenialFilter): { sql: string; params: (string | number)[]; }; /** * K-35's rate-buckets: one row per (actor, permission), busiest first — or, with * `groupBy: 'operation'` (#1456), one row per operation, ordered the same way. * * Busiest-first rather than newest-first on purpose — this view exists BECAUSE the * volume is attacker-influenceable, and ordering by recency would let whoever wrote * the last hundred rows push everyone else off the page, which is the exact failure * the bucketing is there to prevent. Ties break on `MAX(id)` so the order is total. * * The grouping is returned beside the SQL so the adapter maps the rows it gets back * with the matching mapper (`mapDenialSummaryBuckets`) rather than re-deriving which * query it ran from the filter. */ export declare function denialSummaryQuery(filter?: DenialFilter): { sql: string; params: (string | number)[]; groupBy: DenialGroupBy; }; export interface DenialBucketRow { actor: string; permission: string; count: number; operations: number; first_at: string; last_at: string; } /** * One (actor, permission) bucket, tolerantly (#1636). The buckets are `GROUP BY actor`, so a * stored actor that would not parse is its own bucket — and used to throw the whole summary, * the read a console opens first. It reads as {@link UNDECODED_ACTOR} now, still counted, * with `decodeError` saying why. A malformed permission key is its own bucket too, and reads * as {@link UNDECODED_PERMISSION} with the stored key quoted — `mapDenialRow` says why. */ export declare function mapDenialBucketRow(row: DenialBucketRow): DenialBucket; export interface DenialOperationBucketRow { operation: string | null; count: number; first_at: string; last_at: string; } /** * One operation bucket, tolerantly (#1643) — the sibling `mapDenialBucketRow` already was. * * `operation` is the only column with an empty value its schema accepts: one that is not * text reads as `null`, and `decodeError` says so, which is what keeps it from reading as * the bucket for a refusal that ran outside an operation. `count` and the two times are * required scalars: a bucket that breaks one throws, naming them, rather than being * returned typed as valid. */ export declare function mapDenialOperationBucketRow(row: DenialOperationBucketRow): DenialOperationBucket; /** The half of a `DenialSummary` the grouped query answers; the facts are the other half. */ export type DenialSummaryBuckets = Pick | Pick; /** * Map the rows `denialSummaryQuery` came back with, under the grouping it reported — * and carry that grouping onto the answer, so a caller who asked one question can see * which one was answered. Both adapters spread this into the summary they return. */ export declare function mapDenialSummaryBuckets(groupBy: DenialGroupBy, rows: unknown[]): DenialSummaryBuckets; /** Totals for the FILTERED set — what the capped bucket list is a page of. */ export declare function denialTotalsQuery(filter?: DenialFilter): { sql: string; params: (string | number)[]; }; /** * Facts about the WINDOW, filter ignored — deliberately. * * These describe the log, not the query. A caller reading an empty filtered result * needs to know whether the log reaches back past the interval it asked about, because * rows here drain rather than expire (K-24's split) and until a Tier-2 sink exists the * window simply IS the retention. Reporting the floor is what stops absence being read * as "this never happened" — K-35 calls that a stated limitation, so the surface states it. */ export declare const DENIAL_WINDOW_QUERY: string; export interface DenialWindowRow { oldest_at: string | null; newest_at: string | null; drained: number | null; } //# sourceMappingURL=denial-query.d.ts.map