auth/audit_log_schema.ts

Audit log types and client-safe Zod schemas.

Records auth mutations (login, logout, grant, revoke, etc.) for security monitoring and operational visibility.

Table DDL and indexes live in auth/audit_log_ddl.ts.

view source

Declarations
#

20 declarations

AdminSessionJson
#

auth/audit_log_schema.ts view source

ZodObject<{ id: $ZodBranded<ZodString, "SessionId", "out">; account_id: $ZodBranded<ZodUUID, "Uuid", "out">; created_at: ZodString; expires_at: ZodString; username: ZodString; }, $strict> import type {AdminSessionJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for admin session listing (session + username).

AUDIT_EVENT_TYPE_NAME_REGEX
#

auth/audit_log_schema.ts view source

RegExp import {AUDIT_EVENT_TYPE_NAME_REGEX} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Letter start, then letters, digits, _, ., /, -. Accepts snake_case, dotted, and namespaced consumer conventions; rejects empty strings, leading separators, whitespace, and control characters.

AUDIT_EVENT_TYPES
#

auth/audit_log_schema.ts view source

readonly ["login", "logout", "bootstrap", "signup", "password_change", "session_revoke", "session_revoke_all", "token_create", "token_revoke", "token_revoke_all", "role_grant_create", ... 16 more ..., "db_admin_row_delete"] import {AUDIT_EVENT_TYPES} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

All tracked auth event types. Frozen to convert accidental in-process mutation (test cross-contamination, cast escapes) into loud TypeErrors. Not a security boundary — in-process code has many other paths to subvert audit logging.

AUDIT_LOG_DEFAULT_LIMIT
#

audit_metadata_schemas
#

auth/audit_log_schema.ts view source

Readonly<{ login: ZodNullable<ZodObject<{ username: ZodString; }, $loose>>; logout: ZodNull; bootstrap: ZodNullable<ZodObject<{ error: ZodString; }, $loose>>; ... 24 more ...; db_admin_row_delete: ZodObject<...>; }> import {audit_metadata_schemas} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Per-event-type metadata Zod schemas. z.looseObject so consumers can add fields while known ones are validated. The record is frozen to catch mutation bugs at the key level (e.g. tests that try to swap in a stub schema); the Zod schemas themselves are reachable and mutable — freeze isn't a security boundary.

AuditEventType
#

auth/audit_log_schema.ts view source

ZodEnum<{ invite_create: "invite_create"; invite_delete: "invite_delete"; account_delete: "account_delete"; account_purge: "account_purge"; account_undelete: "account_undelete"; app_settings_update: "app_settings_update"; ... 21 more ...; db_admin_row_delete: "db_admin_row_delete"; }> import type {AuditEventType} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for audit event types.

AuditEventTypeName
#

auth/audit_log_schema.ts view source

ZodString import type {AuditEventTypeName} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for valid audit event-type name strings.

AuditLogConfig
#

auth/audit_log_schema.ts view source

AuditLogConfig import type {AuditLogConfig} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Configuration bundle for audit-log event types and metadata schemas.

Lets consumers extend the closed AUDIT_EVENT_TYPES enum with their own event strings (and metadata Zod schemas) without forking. Pass to create_audit_emitter (or query_audit_log for in-tx call sites) as the optional config argument; both default to builtin_audit_log_config.

The DB column is TEXT NOT NULL and never enforced an enum, so consumer event types round-trip through query_audit_log_list and SSE identically to builtins.

Constructed configs are deep-frozen (wrapper, event_types, metadata_schemas) to catch accidental mutation bugs early. Not a security boundary against in-process code, which can subvert audit logging through other paths.

event_types

All recognized event-type strings — fuz_app builtins plus consumer extras.

type ReadonlyArray<string>

readonly

metadata_schemas

Per-event-type metadata schemas. Missing entries skip metadata validation for that type (row still written; metadata stored as raw JSONB).

type Readonly<Record<string, z.ZodType>>

readonly

AuditLogEvent
#

auth/audit_log_schema.ts view source

AuditLogEvent import type {AuditLogEvent} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Audit log row from the database. See AuditLogEventJson for event_type widening rationale.

id

type Uuid

seq

type number

event_type

type AuditEventTypeName

outcome

type AuditOutcome

actor_id

Operator (the actor that initiated the event) — populated when the request resolved an acting actor.

Resolution is driven per-request by the route-spec wrapper / RPC dispatcher; a route gets an acting actor when its input schema declares acting?: ActingActor or its auth requires role_grants (role / keeper). Account-grain operations declare neither, so no actor is resolved and actor_id is null: login (also pre-credential), logout, signup, bootstrap, password_change, session/token revoke, app_settings_update, invite events. Role grant events, admin actions, and actor-targeted offers populate this with the initiator's actor.

type Uuid | null

account_id

type Uuid | null

target_account_id

type Uuid | null

target_actor_id

Actor-grain target — populated when the event subject is bound to a specific actor.

Concretely:

  • Always populated: role_grant_revoke and role_grant_create (admin direct-grant, self-service toggle, and in-tx role_grant_offer_accept all populate both target columns — the role_grant's grantee is the actor-grain subject regardless of who initiated the grant), role_grant_offer_accept on accept (the accept binds the actor deterministically), role_grant_offer_decline (the grantor actor — decline is *to* the offering actor).
  • Conditionally populated: offer-shape events (role_grant_offer_create, _expire, _retract, _supersede) carry the actor when the offer was actor-targeted at create time (role_grant_offer.to_actor_id set), null when the offer was account-grain (any actor on to_account_id may accept).
  • Not populated: admin actions, account-shape events (login, logout, signup, bootstrap, password_change, session/token revoke, app_settings_update, invite events) — subject is the account or no specific resource, not an actor-bound role_grant.
  • Not populated: events whose principal isn't an actor-bound resource (e.g. consumer events that name a non-actor scope in metadata).

Multi-actor invariants this column relies on: when both target_actor_id and target_account_id are populated they refer to the same account (actor.account_id-derivable). The invariant holds uniformly across every populated event including decline (the grantor's account is joined into the decline RETURNING) and the supersede cascade (the recipient account is known on role_grant_offer.to_account_id). target_account_id stays the SSE/WS socket-close key because sessions remain account-grain after multi-actor lands.

type Uuid | null

ip

type string | null

created_at

type string

metadata

type Record<string, unknown> | null

AuditLogEventJson
#

auth/audit_log_schema.ts view source

ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; seq: ZodNumber; event_type: ZodString; outcome: ZodEnum<{ success: "success"; failure: "failure"; }>; ... 6 more ...; metadata: ZodNullable<...>; }, $strict> import type {AuditLogEventJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for client-safe audit log event.

event_type is AuditEventTypeName (regex-validated string) — matches the AuditLogEvent row and the DB's TEXT NOT NULL column. Consumer types registered via create_audit_log_config({extra_events}) round-trip through queries, on_audit_event callbacks, and JSON-RPC responses identically to builtins. AuditLogInput<T> stays parameterized on the write side so AuditMetadataMap narrowing via get_audit_metadata works.

AuditLogEventWithUsernamesJson
#

auth/audit_log_schema.ts view source

ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; seq: ZodNumber; event_type: ZodString; outcome: ZodEnum<{ success: "success"; failure: "failure"; }>; ... 8 more ...; target_username: ZodNullable<...>; }, $strict> import type {AuditLogEventWithUsernamesJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for audit log events with resolved usernames.

AuditLogInput
#

auth/audit_log_schema.ts view source

AuditLogInput<T> import type {AuditLogInput} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Input for creating an audit log entry.

generics

AuditLogInput<T extends string = AuditEventType>
T
constraint string

event_type

type T

outcome?

type AuditOutcome

actor_id?

type Uuid | null

account_id?

type Uuid | null

target_account_id?

type Uuid | null

target_actor_id?

type Uuid | null

ip?

type string | null

metadata?

Per-event-type metadata. Builtin T narrows to AuditMetadataMap[T]; consumer strings widen to a generic record (validation runs against AuditLogConfig.metadata_schemas at insert time).

type T extends AuditEventType ? (AuditMetadataMap[T] & Record<string, unknown>) | null : Record<string, unknown> | null

AuditLogListOptions
#

auth/audit_log_schema.ts view source

AuditLogListOptions import type {AuditLogListOptions} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Options for listing audit log entries.

limit?

type number

offset?

type number

event_type?

Event-type filter. Accepts any string — builtins or consumer-registered via create_audit_log_config({extra_events}). The DB column is TEXT NOT NULL with no CHECK, so unknown strings simply match nothing.

type string

event_type_in?

type Array<string>

account_id?

type Uuid

outcome?

type AuditOutcome

since_seq?

When set, only return events with seq greater than this value. Enables SSE reconnection gap fill.

type number

AuditMetadataMap
#

auth/audit_log_schema.ts view source

AuditMetadataMap import type {AuditMetadataMap} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Mapped type of metadata shapes per event type, derived from Zod schemas.

invite_create

type { [x: string]: unknown; invite_id: string & $brand<"Uuid">; email: string | null; username: string | null; }

invite_delete

type { [x: string]: unknown; invite_id: string & $brand<"Uuid">; }

account_delete

type { [x: string]: unknown; username?: string | undefined; email?: string | null | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; }

account_purge

type { [x: string]: unknown; username?: string | undefined; email?: string | null | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; }

account_undelete

type { [x: string]: unknown; username?: string | undefined; email?: string | null | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; }

app_settings_update

type { [x: string]: unknown; setting: string; old_value: unknown; new_value: unknown; }

login

type { [x: string]: unknown; username: string; } | null

logout

type null

bootstrap

type { [x: string]: unknown; error: string; } | null

signup

type { [x: string]: unknown; username: string; invite_id?: (string & $brand<"Uuid">) | undefined; open_signup?: boolean | undefined; reason?: string | undefined; email?: string | undefined; }

password_change

type { [x: string]: unknown; sessions_revoked?: number | undefined; tokens_revoked?: number | undefined; reason?: "concurrent_change" | undefined; credential_type?: "session" | "api_token" | "daemon_token" | undefined; } | null

session_revoke

type { [x: string]: unknown; session_id: string & $brand<"SessionId">; credential_type?: "session" | "api_token" | "daemon_token" | undefined; }

session_revoke_all

type { [x: string]: unknown; count?: number | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; credential_type?: "session" | "api_token" | "daemon_token" | undefined; }

token_create

type { [x: string]: unknown; token_id: string; name: string; credential_type?: "session" | "api_token" | "daemon_token" | undefined; }

token_revoke

type { [x: string]: unknown; token_id: string; credential_type?: "session" | "api_token" | "daemon_token" | undefined; }

token_revoke_all

type { [x: string]: unknown; count?: number | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; }

role_grant_create

type { [x: string]: unknown; role: string; role_grant_id?: (string & $brand<"Uuid">) | undefined; scope_id?: (string & $brand<"Uuid">) | null | undefined; source_offer_id?: (string & $brand<...>) | undefined; self_service?: boolean | undefined; }

role_grant_revoke

type { [x: string]: unknown; role: string; role_grant_id: string & $brand<"Uuid">; scope_id?: (string & $brand<"Uuid">) | null | undefined; reason?: string | undefined; self_service?: boolean | undefined; }

role_grant_offer_create

type { [x: string]: unknown; role: string; to_account_id: string & $brand<"Uuid">; offer_id?: (string & $brand<"Uuid">) | undefined; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

role_grant_offer_accept

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

role_grant_offer_decline

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; reason?: string | undefined; }

role_grant_offer_retract

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

role_grant_offer_expire

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

role_grant_offer_supersede

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; reason: "sibling_accepted" | "role_grant_revoked" | "scope_destroyed"; cause_id: string & $brand<"Uuid">; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; }

actor_delete

type { [x: string]: unknown; name?: string | undefined; }

actor_purge

type { [x: string]: unknown; name?: string | undefined; }

actor_undelete

type { [x: string]: unknown; name?: string | undefined; }

db_admin_row_delete

type { [x: string]: unknown; table: string; pk_column: string; id: string; }

AuditOutcome
#

auth/audit_log_schema.ts view source

ZodEnum<{ success: "success"; failure: "failure"; }> import type {AuditOutcome} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for audit event outcomes.

builtin_audit_log_config
#

auth/audit_log_schema.ts view source

AuditLogConfig import {builtin_audit_log_config} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Builtin fuz_app audit-log config — every existing event type and its metadata schema.

create_audit_log_config
#

auth/audit_log_schema.ts view source

(options?: CreateAuditLogConfigOptions | undefined): AuditLogConfig import {create_audit_log_config} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Build an AuditLogConfig by merging fuz_app builtins with consumer extras.

Throws when an extra_events key collides with a builtin event type, or fails AuditEventTypeName format validation.

Call once at startup; pass the result into the consumer's audit_factory body — typically `({db, log}) => create_audit_emitter({db, log, audit_log_config, ...})` — so it gets captured inside the bound AppDeps.audit emitter. Builtin handlers omit the audit_log_config slot and pick up builtin_audit_log_config.

options?

type CreateAuditLogConfigOptions | undefined
optional

returns

AuditLogConfig

throws

  • Error - when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation

CreateAuditLogConfigOptions
#

auth/audit_log_schema.ts view source

CreateAuditLogConfigOptions import type {CreateAuditLogConfigOptions} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

extra_events?

Extra event types keyed by event-type string. Value is a Zod metadata schema, or null to register the type without validation (row still written, metadata stored as raw JSONB).

Collisions with builtin event-type strings throw at construction. Schemas are run via safeParse at insert time; mismatches log + count but never throw (fail-open — see the drift counters in auth/audit_log_queries.ts).

type Readonly<Record<string, z.ZodType | null>>

get_audit_metadata
#

auth/audit_log_schema.ts view source

<T extends AuditEventType>(event: AuditLogEvent & { event_type: T; }): AuditMetadataMap[T] | null import {get_audit_metadata} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Narrow metadata type for a known event type.

Use after checking event_type to get typed metadata access.

event

type AuditLogEvent & { event_type: T; }

returns

AuditMetadataMap[T] | null

generics

get_audit_metadata<T extends AuditEventType>
T
constraint AuditEventType

RoleGrantHistoryEventJson
#

auth/audit_log_schema.ts view source

ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; seq: ZodNumber; event_type: ZodString; outcome: ZodEnum<{ success: "success"; failure: "failure"; }>; ... 8 more ...; target_username: ZodNullable<...>; }, $strict> import type {RoleGrantHistoryEventJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for role_grant history events with resolved usernames.

Depends on
#

Imported by
#