api #

fullstack app library

401 modules · 2523 declarations

Modules
#

AcceptOfferInput
#

auth/role_grant_offer_queries.ts view source

AcceptOfferInput import type {AcceptOfferInput} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

offer_id

type Uuid

to_account_id

Account of the accepting recipient — IDOR guard against another account accepting the offer.

type Uuid

actor_id

Accepting actor — the actor that will hold the resulting role_grant. Must belong to to_account_id; the query verifies and throws if not (defense-in-depth — the action handler passes auth.actor.id which is session-bound, but the query enforces the invariant for all callers including tests and future direct consumers).

Required because under multi-actor an account may host many actors; the resulting role_grant must bind to the actor that actually accepted, not "an" actor on the account picked by query order.

type Uuid

ip?

Optional IP to stamp on the audit events.

type string | null

AcceptOfferResult
#

auth/role_grant_offer_queries.ts view source

AcceptOfferResult import type {AcceptOfferResult} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Result of query_accept_offer — the role_grant produced (new or pre-existing on race), plus the (now-accepted) offer.

role_grant

type RoleGrant

offer

type RoleGrantOffer

created

true if this call is the one that accepted the offer (new role_grant inserted); false on a replay (or a race loser) returning the already-created, still-held role_grant — the caller then audits and notifies nothing.

type boolean

superseded_offers

Sibling offers superseded by this accept — empty on the race-loser path. Each entry carries its grantor's from_account_id so the caller can fan out role_grant_offer_supersede notifications without a second round-trip.

type SupersededOffer[]

audit_events

Audit events emitted in-transaction — fed back through audit.notify by the caller, which fans out to the registered listeners. Includes one role_grant_offer_supersede per superseded sibling.

type AuditLogEvent[]

Account
#

auth/account_schema.ts view source

Account import type {Account} from '@fuzdev/fuz_app/auth/account_schema.js';

Account — authentication identity. You log in as an account.

id

type Uuid

username

type string

email

type string | null

email_verified

type boolean

password_hash

type string

created_at

type string

created_by

type Uuid | null

updated_at

type string

updated_by

type Uuid | null

deleted_at

Soft-delete tombstone. Non-null means the account is deleted (delete = soft); auth resolution treats it as absent. A hard purge removes the row entirely. See auth/account_queries.ts.

type string | null

deleted_by

Actor that performed the soft-delete (initiator: self / admin / keeper). Paired with deleted_at, mirroring role_grant's revoked_at / revoked_by. Plain UUID (no FK, like created_by / updated_by on this table).

type Uuid | null

ACCOUNT_COLUMNS
#

auth/account_queries.ts view source

readonly ["id", "username", "email", "email_verified", "password_hash", "created_at", "created_by", "updated_at", "updated_by", "deleted_at", "deleted_by"] import {ACCOUNT_COLUMNS} from '@fuzdev/fuz_app/auth/account_queries.js';

The full account column set, named explicitly so a row read fails loud on schema drift.

SELECT * silently omits a dropped column, which the login lookups then misread: query_account_by_username_or_email filters its result with account.deleted_at === null, so a missing deleted_at column reads back as undefined, undefined === null is false, and *every* login resolves to "not found" (401) — a silent, total auth outage instead of an error. Selecting named columns turns that drift into a hard Postgres column "..." does not exist. Mirrors the Rust side (fuz_auth/src/account_queries.rs), which selects named columns and decodes them positionally. Keep in sync with Account and the account DDL in auth/auth_ddl.ts.

account_delete_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodDefault<ZodObject<{ account_id: ZodOptional<ZodNullable<$ZodBranded<ZodUUID, "Uuid", "out">>>; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; async: tru... import {account_delete_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Soft-delete an account (reversible tombstone). Self-or-admin: the caller may delete their own account; deleting another requires the admin role (handler-enforced elevation). The self path is session-only, also handler-enforced — the spec declares no credential_types so an admin deleting another account keeps any credential, and a self-delete over an API or daemon token gets the dispatcher's own 403 credential_type_required (required_credential_types: ['session']), ahead of the keeper / last-admin guards. No admin_ prefix — the privilege lives in the auth check, not the name, so self-service deletion stays open (delete = soft, purge = hard).

ACCOUNT_EMAIL_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE UNIQUE INDEX IF NOT EXISTS idx_account_email ON account (LOWER(email)) WHERE email IS NOT NULL" import {ACCOUNT_EMAIL_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

account_purge_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; credential_types: string[]; }; side_effects: true; input: ZodObject<{ account_id: $ZodBranded<...>; confirm: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<.... import {account_purge_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Hard-purge an account (keeper-gated, irreversible). Keeper credential (daemon_token) + keeper role + explicit confirm: true. Not admin-reachable and not self-service — the most dangerous operation is the most restricted. purge = hard; the word + gating + WARN flag the danger (fail-loud).

ACCOUNT_ROUTER_PATHS
#

testing/cross_backend/rust_spine_consumer_surface.ts view source

readonly ["/status", "/login", "/logout", "/password"] import {ACCOUNT_ROUTER_PATHS} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_consumer_surface.js';

The routes fuz_auth::account_router mounts, relative to /api/account. An allowlist rather than every account route shape: /verify is a TS-only route the Rust router doesn't serve, so declaring it would have the suites probe a 404. A shape fuz_app renames or drops fails the surface build; one it adds stays off the surface until the Rust router mounts it and this list names it.

ACCOUNT_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS account (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n username TEXT UNIQUE NOT NULL,\n email TEXT,\n email_verified BOOLEAN NOT NULL DEFAULT false,\n password_hash TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n created_by UUID,\n updated_at TIMESTAMPTZ ... import {ACCOUNT_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

account_session_list_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodVoid; output: ZodObject<{ sessions: ZodArray<ZodObject<{ id: $ZodBranded<ZodString, "SessionId", "out">; account_id: $ZodBranded<...>; created_at: ZodString; expires_at: Zo... import {account_session_list_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_session_revoke_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ session_id: $ZodBranded<ZodString, "SessionId", "out">; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {account_session_revoke_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_session_revoke_all_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; credential_types: string[]; }; side_effects: true; input: ZodVoid; output: ZodObject<...>; async: true; description: string; } import {account_session_revoke_all_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_sessions_rpc_context
#

ui/account_sessions_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AccountSessionsRpc; get_maybe: () => (() => AccountSessionsRpc) | undefined; set: (value: () => AccountSessionsRpc) => () => AccountSessionsRpc; } import {account_sessions_rpc_context} from '@fuzdev/fuz_app/ui/account_sessions_state.svelte.js';

Svelte context carrying the reactive AccountSessionsRpc accessor. Mirrors the admin-side RPC contexts. get() throws when no provisioner ran above the component — the adapter is required.

account_status_route_shape
#

auth/account_route_schema.ts view source

{ method: "GET"; path: string; auth: { account: "none"; actor: "none"; }; reads_credential: true; description: string; input: ZodNull; output: ZodObject<{ account: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; username: ZodPipe<...>; email: ZodNullable<...>; email_verified: ZodBoolean; created_at: ZodString; ... import {account_status_route_shape} from '@fuzdev/fuz_app/auth/account_route_schema.js';

The GET /status route shape minus its handler — pure hono-free data. create_account_status_route_spec spreads this and attaches the live handler (which reads the account id off the request context); surface generation spreads it with a stub handler.

The path is relative like the sibling account shapes (/login, /verify), so it composes under prefix_route_specs('/api/account', …) into /api/account/status. create_account_route_specs bundles it (so every account surface serves /status, matching the Rust account_router); mirror Rust by mounting it as part of the account family, not separately.

account_token_create_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ name: ZodDefault<ZodString>; scope: ZodDiscriminatedUnion<...>; lifetime: ZodDiscriminatedUnion<...>; }, $strict>; output: ZodObject<...... import {account_token_create_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

credential_types: ['session'] — see docs/security.md §Credential-channel gating.

rate_limit: 'account' bounds the burn rate of API-token creates. The outstanding-token count is already capped by max_tokens (via query_api_token_enforce_limit), but the per-account *rate* of churn is not — without this cap, a caller could rotate tokens in a tight loop to amplify token_create audit churn or attempt to provoke downstream rate-limit hot spots.

account_token_list_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodVoid; output: ZodObject<{ tokens: ZodArray<ZodObject<{ id: ZodString; ... 6 more ...; scope: ZodString; }, $strict>>; }, $strict>; async: true; description: string; } import {account_token_list_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_token_revoke_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ token_id: ZodString; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {account_token_revoke_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_undelete_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ account_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: strin... import {account_undelete_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Reactivate a soft-deleted account (clears the tombstone). Admin-only — there is no self path because a soft-deleted account can't authenticate (auth resolution excludes it and its sessions are revoked), so reactivation is always an admin acting on another account. The inverse of account_delete; does not restore revoked sessions/tokens (delete = soft, purge = hard).

ACCOUNT_USERNAME_CI_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE UNIQUE INDEX IF NOT EXISTS idx_account_username_ci ON account (LOWER(username))" import {ACCOUNT_USERNAME_CI_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

account_verify_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodVoid; output: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; username: ZodPipe<...>; email: ZodNullable<...>; email_verified: ZodBoolean; created_at: ZodString; }, $s... import {account_verify_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

AccountActionOptions
#

AccountDeleteInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id?: (string & $brand<"Uuid">) | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {AccountDeleteInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for account_delete (soft delete). account_id is optional — omitted (or equal to the caller's own account) is a self-delete; a different account requires the admin role (handler-enforced elevation, like role_grant_offer_list).

account_id?

type (string & $brand<"Uuid">) | null

acting?

type string & $brand<"Uuid">

AccountDeleteOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; deleted: boolean; } import {AccountDeleteOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for account_delete.

ok

type true

deleted

type boolean

AccountIdentitySnapshot
#

auth/account_queries.ts view source

AccountIdentitySnapshot import type {AccountIdentitySnapshot} from '@fuzdev/fuz_app/auth/account_queries.js';

Identifying values snapshotted into a deletion/purge audit event so the identity behind a now-orphaned audit_log id isn't lost. Mirrors the Rust AccountIdentitySnapshot.

username

type string

email

type string | null

AccountLifecycleCrossTestOptions
#

testing/cross_backend/account_lifecycle.ts view source

RpcPathCapabilityGatedCrossSuiteOptions import type {AccountLifecycleCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/account_lifecycle.js';

Options for the account-lifecycle parity suite. The standard capability-gating RPC-dispatched cross-suite shape (setup_test / capabilities / rpc_path); aliases RpcPathCapabilityGatedCrossSuiteOptions rather than duplicating.

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability declarations — each suite gates on its own flag.

type BackendCapabilities

readonly

AccountPurgeInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id: string & $brand<"Uuid">; confirm?: boolean | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {AccountPurgeInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for account_purge (hard, irreversible delete). Keeper-only.

account_id

type string & $brand<"Uuid">

confirm?

type boolean

acting?

type string & $brand<"Uuid">

AccountPurgeOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; purged: boolean; } import {AccountPurgeOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for account_purge.

ok

type true

purged

type boolean

AccountRevocation
#

testing/cross_backend/revocation_drivers.ts view source

AccountRevocation import type {AccountRevocation} from '@fuzdev/fuz_app/testing/cross_backend/revocation_drivers.js';

One account-wide revocation, as a row both transport suites iterate.

label

Names the revocation in a case name and a failure message.

type string

readonly

slug

Distinguishes the target account's username — unique in the table, and short enough that a suite's prefix keeps the username within Username.

type string

readonly

gate

Whether the backend serves the revocation (beyond rpc_path, which every row needs).

type boolean

readonly

revoke

Revoke target's credentials and assert the revocation succeeded. target must have been created with DEFAULT_TEST_PASSWORD, which the password-change row presents.

type (fixture: TestFixture, target: TestAccount): Promise<void>

readonly

fixture

target

returns Promise<void>

AccountRevocationsOptions
#

testing/cross_backend/revocation_drivers.ts view source

AccountRevocationsOptions import type {AccountRevocationsOptions} from '@fuzdev/fuz_app/testing/cross_backend/revocation_drivers.js';

Configuration for create_account_revocations.

capabilities

Backend capability flags; account_purge gates on account_lifecycle.

type BackendCapabilities

readonly

rpc_path

RPC endpoint path the admin and keeper revocations are sent to.

type string

readonly

logout_path

REST logout route path.

type string

readonly

password_path

REST password-change route path.

type string

readonly

AccountRouteOptions
#

auth/account_routes.ts view source

AccountRouteOptions import type {AccountRouteOptions} from '@fuzdev/fuz_app/auth/account_routes.js';

Per-factory configuration for account route specs.

inheritance

login_ip_rate_limiter

Rate limiter for login + password-change attempts, keyed by client IP. Pass null to disable. The distributed-spray backstop: login is the one surface that guesses *other* accounts' credentials, so this bucket is never refunded on success (see RateLimiter.reset). Password change shares it — it is password-bearing on the same account grain, and the Rust spine shares the same instance across both.

type RateLimiter | null

login_account_rate_limiter

Rate limiter for login attempts, keyed by submitted username. Pass null to disable.

type RateLimiter | null

max_sessions?

Max active sessions per account. Evicts oldest on login. Default 5, null disables.

type number | null

login_fail_floor_ms?

Minimum wall-clock time (ms) for login 401 responses. Set to 0 or a negative number to disable (e.g., in tests) — announced once on stderr (LOGIN_FAIL_FLOOR_DISABLED_BANNER). Default DEFAULT_LOGIN_FAIL_FLOOR_MS.

type number

login_fail_jitter_ms?

Uniform jitter window (±ms) layered on the floor. Set to 0 to disable jitter while keeping the floor. Default DEFAULT_LOGIN_FAIL_JITTER_MS.

type number

bootstrap_status?

Runtime bootstrap status for the bundled GET /status route — when available, its unauthenticated 401 carries bootstrap_available: true so a fresh frontend can route to the bootstrap flow. Pass ctx.bootstrap_status (the live BootstrapStatus ref) so the flag tracks the one-shot bootstrap completing. Omit when no bootstrap flow is wired — /status is still served, just without the flag.

type { available: boolean; }

AccountRouteShapeOptions
#

auth/account_route_schema.ts view source

AccountRouteShapeOptions import type {AccountRouteShapeOptions} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Option inputs that shape the account route metadata (not its handlers).

login_account_rate_limited

Whether a per-account login rate limiter is wired — toggles /password's rate_limit.

type boolean

AccountSessions
#

AccountSessionsRpc
#

ui/account_sessions_state.svelte.ts view source

AccountSessionsRpc import type {AccountSessionsRpc} from '@fuzdev/fuz_app/ui/account_sessions_state.svelte.js';

Narrow RPC surface consumed by AccountSessionsState. Consumers adapt their typed RPC client to this shape. Mirrors the other per-domain *Rpc interfaces (AdminAccountsRpc, AuditLogRpc, AdminInvitesRpc): each method takes its action's input as one params object typed from the action spec, or nothing for an input-less one.

The three methods wrap the corresponding action specs on auth/account_actions.ts:

  • list → account_session_list
  • revoke → account_session_revoke (IDOR-guarded by account_id server-side)
  • revoke_all → account_session_revoke_all

list

type (): Promise<{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; is_current: boolean; }[]; }>

returns Promise<SessionListOutput>

revoke

type (params: { session_id: string & $brand<"SessionId">; }): Promise<{ ok: true; revoked: boolean; }>

params

returns Promise<SessionRevokeOutput>

revoke_all

type (): Promise<{ ok: true; count: number; }>

returns Promise<SessionRevokeAllOutput>

AccountSessionsState
#

ui/account_sessions_state.svelte.ts view source

import {AccountSessionsState} from '@fuzdev/fuz_app/ui/account_sessions_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

revoke

type KeyedAsyncSlot<string, void, string>

readonly

revoke_all

type AsyncSlot<void, string>

readonly

sessions

type AuthSessionJson[]

$state.raw

active_count

type number

readonly $derived

constructor

type new (options: AccountSessionsStateOptions): AccountSessionsState

options

fetch

type (): Promise<void>

returns Promise<void>

submit_revoke

type (id: string & $brand<"SessionId">): Promise<void>

id

returns Promise<void>

submit_revoke_all

type (): Promise<void>

returns Promise<void>

AccountSessionsStateOptions
#

ui/account_sessions_state.svelte.ts view source

AccountSessionsStateOptions import type {AccountSessionsStateOptions} from '@fuzdev/fuz_app/ui/account_sessions_state.svelte.js';

get_rpc

Reactive accessor for the RPC adapter. Matches the get_rpc pattern on the admin state classes.

type (): AccountSessionsRpc

AccountStatusInput
#

auth/account_route_schema.ts view source

value + type

null import {AccountStatusInput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Input for GET /api/account/status. No parameters — caller is the subject.

AccountStatusOptions
#

auth/account_routes.ts view source

AccountStatusOptions import type {AccountStatusOptions} from '@fuzdev/fuz_app/auth/account_routes.js';

Options for the account status route spec.

path?

Override the default path (/api/account/status).

type string

bootstrap_status?

Runtime bootstrap status — when available, 401 responses include bootstrap_available.

type { available: boolean; }

AccountStatusOutput
#

auth/account_route_schema.ts view source

value + type

{ account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; }; actor: { id: string & $brand<"Uuid">; name: string; } | null; role_grants: { ...; }[]; } import {AccountStatusOutput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Output for GET /api/account/status.

account

type SessionAccountJson

actor

type ActorSummaryJson | null

role_grants

type RoleGrantSummaryJson[]

AccountStatusUnauthenticatedError
#

auth/account_route_schema.ts view source

value + type

{ [x: string]: unknown; error: "authentication_required"; bootstrap_available?: boolean | undefined; } import {AccountStatusUnauthenticatedError} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Error body for GET /api/account/status on the unauthenticated path.

error

type "authentication_required"

bootstrap_available?

type boolean

[key: string]

type unknown

AccountUndeleteInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {AccountUndeleteInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for account_undelete (reactivation). account_id is required — unlike account_delete there is no self path: a soft-deleted account can't authenticate (auth resolution excludes it, sessions are revoked), so reactivation is always an admin acting on another account.

account_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

AccountUndeleteOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; undeleted: boolean; } import {AccountUndeleteOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for account_undelete.

ok

type true

undeleted

type boolean

AccountVanishedError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "account_vanished"; } import {AccountVanishedError} from '@fuzdev/fuz_app/http/error_schemas.js';

error

type "account_vanished"

[key: string]

type unknown

ActingActor
#

http/auth_shape.ts view source

value + type

(string & $brand<"Uuid">) | undefined

type (string & $brand<"Uuid">) | undefined

import {ActingActor} from '@fuzdev/fuz_app/http/auth_shape.js';

acting field shared by every input that needs the caller's acting actor. Declaring acting: ActingActor on a route or action input signals to the dispatcher's authorization phase to resolve an actor against the authenticated account: it runs resolve_acting_actor, builds the actor-bound RequestContext, and loads role_grants before auth guards fire.

Resolution rules: omitted + 1 actor → use it; omitted + multiple actors → actor_required with the available list; supplied + on the account → use it; supplied + foreign actor → actor_not_on_account.

Account-grain routes — input doesn't declare acting and auth doesn't require role_grants — skip resolution entirely; their RequestContext.actor is null and the audit envelope's actor_id stays null.

Lives next to RouteAuth because the two are paired by registry-time invariant 2: auth.actor !== 'none' ⟺ input (or query, on REST GETs) declares acting?: ActingActor. Keeping the contract in one module removes the http/ → auth/ import that an earlier split forced.

ActingSlots
#

http/auth_shape.ts view source

ActingSlots import type {ActingSlots} from '@fuzdev/fuz_app/http/auth_shape.js';

Slots where a spec may declare the acting?: ActingActor field — input for both REST + actions; query for REST GETs that bi-locate acting on the query schema (actions have no query shape, so the field is omitted on action call sites).

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

query?

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

Action
#

actions/action_types.ts view source

Action<TSpec> import type {Action} from '@fuzdev/fuz_app/actions/action_types.js';

A spec paired with its optional handler — the composable unit passed to register_action_ws and create_rpc_client. The server uses both fields; the client reads only spec (the handler is ignored, harmless). Shared fuz_app primitives (e.g. heartbeat_action) export a complete tuple so consumers spread them into both sides' actions arrays without inventing per-repo ping plumbing.

Polymorphic on kind: request_response specs require a handler for dispatch; remote_notification specs are dispatcher-handled (e.g. cancel, which has no handler); local_call specs never reach a network dispatcher. The WS dispatcher only invokes handlers on request_response actions; everything else is registry-only.

generics

Action<TSpec extends ActionSpecUnion = ActionSpecUnion>
TSpec
constraint ActionSpecUnion

spec

type TSpec

handler?

Server-side handler — invoked by dispatchers on request_response actions. Ignored for client-only specs and dispatcher-handled notifications.

type (input: any, ctx: ActionContext): any

input

type any

ctx

returns any

action_event_phase_by_kind
#

actions/action_event_types.ts view source

Record<"request_response" | "remote_notification" | "local_call", readonly ("send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute")[]>

type Record<ActionKind, readonly ActionEventPhase[]>

import {action_event_phase_by_kind} from '@fuzdev/fuz_app/actions/action_event_types.js';

action_event_phase_transitions
#

actions/action_event_types.ts view source

Record<"send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "send_request" | "receive_request" | ... 7 more ... | null>

type Record<ActionEventPhase, ActionEventPhase | null>

import {action_event_phase_transitions} from '@fuzdev/fuz_app/actions/action_event_types.js';

action_event_step_transitions
#

action_input_is_optional
#

actions/action_codegen.ts view source

(spec: Pick<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<...>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, "input">): boolean import {action_input_is_optional} from '@fuzdev/fuz_app/actions/action_codegen.js';

Whether a caller may omit an action's input: its schema accepts undefined, or accepts {} (what an omitted input is validated as) and is not marked empty_input_handler_guarded (SchemaFieldMeta, read by is_empty_input_handler_guarded). The rule codegen emits input?: by and the input-shape invariant tests read.

spec

the action spec

type Pick<ActionSpecUnion, "input">

returns

boolean

whether the typed surface makes the input optional

action_manifest_entry
#

testing/cross_backend/action_manifest.ts view source

(spec: { readonly method: string; readonly input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; readonly auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }; readonly side_effects: boolean; readonly rate_limit?: "both" | ... 2 more ... | undefined; }): { ...; } import {action_manifest_entry} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest.js';

Normalize one spec's input shape + auth + side-effects into a manifest entry. Reads the input shape off is_void_schema(spec.input), pulls the four auth axes off RouteAuth (the same shape the Rust AuthSpec mirrors), and flattens optional roles / credential_types to sorted arrays.

spec

type { readonly method: string; readonly input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; readonly auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: str...

returns

ActionManifestEntry

ACTION_RATE_LIMIT_ENABLED_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_ACTION_RATE_LIMIT_ENABLED" import {ACTION_RATE_LIMIT_ENABLED_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var both spine binaries read to enable their action rate limiters ('true' on / unset off) — one per-IP and one per-account limiter, shared by the RPC and WS endpoints. The cross-language contract for the action rate-limit cross suite (ws_action_rate_limit.ts); same delivery as LOGIN_RATE_LIMIT_ENABLED_ENV. Off, neither spine builds an action limiter.

action_rate_limit_env
#

testing/cross_backend/default_backend_configs.ts view source

(options: ActionRateLimitEnvOptions): Record<string, string> import {action_rate_limit_env} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

The extra_env entries carrying the action rate-limit toggles, shared by the TS and Rust backend configs so one option pair drives both impls. action_rate_limit_max_attempts is passed through even without the enable flag, so the binary refuses the combination instead of the harness hiding it.

options

returns

Record<string, string>

ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS" import {ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var replacing both action limiters' max_attempts (a positive integer; the windows stay default_action_ip_rate_limit's / default_action_account_rate_limit's). Both spine binaries refuse to boot when it is set without ACTION_RATE_LIMIT_ENABLED_ENV or is not a positive integer, so a suite never asserts a throttle the backend didn't build.

ActionActorContext
#

actions/action_types.ts view source

ActionActorContext import type {ActionActorContext} from '@fuzdev/fuz_app/actions/action_types.js';

ActionContext narrowed to a resolved acting actor.

Used by handlers whose spec declares auth.actor === 'required' — the dispatcher's authorization phase resolves an actor (per registry-time invariant 2 the input declares acting?: ActingActor), so ctx.auth.actor is non-null. Selected automatically by rpc_action's conditional return type for the actor-implying tier.

inheritance

extends: Omit<ActionContext, 'auth'>

auth

type RequestActorContext

ActionAuthContext
#

actions/action_types.ts view source

ActionAuthContext import type {ActionAuthContext} from '@fuzdev/fuz_app/actions/action_types.js';

ActionContext narrowed to a non-null RequestContext.

Used by handlers whose spec declares auth.account === 'required' (with auth.actor === 'none') — the dispatcher's pre-authorization 401 gate guarantees request_context is populated before the handler runs, but the actor slot stays null because no acting resolution happened. Selected automatically by rpc_action's conditional return type for the account-grain tier.

inheritance

extends: Omit<ActionContext, 'auth'>

auth

type RequestContext

ActionContext
#

actions/action_types.ts view source

ActionContext import type {ActionContext} from '@fuzdev/fuz_app/actions/action_types.js';

Per-request context provided to action handlers across every transport (HTTP RPC and WebSocket). Built once per dispatched action by perform_action and threaded into the handler.

auth is RequestContext | null — handlers for authenticated actions can narrow via the dispatcher's authorization-phase guarantee.

Single handler context shape across every transport. Consumers inject domain deps via factory closures the same way HTTP RPC factories do.

auth

The authenticated identity, or null for public routes.

type RequestContext | null

request_id

The JSON-RPC request ID from the envelope.

type JsonrpcRequestId

connection_id?

Stable per-socket connection id on WebSocket transport; undefined on HTTP RPC. Consumers key per-connection domain state on this directly; HTTP handlers ignore it.

type Uuid

request_client?

Initiate a JSON-RPC request to the originating client and await its typed reply — the server→client direction of ActionPeer. Present only on the WebSocket transport (it targets the originating socket); undefined on HTTP RPC, where there is no return socket. Handlers that depend on it must handle its absence — e.g. peer/ping surfaces peer_no_transport.

type (method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<PeerRequestOutcome>

method

type string

params

type JsonrpcRequestParams | undefined

options?

optional
returns Promise<PeerRequestOutcome>

db

Transaction-scoped when spec.side_effects is true (the dispatcher wraps in db.transaction); pool-level otherwise. deps.audit.emit(ctx, input) writes a success row in this transaction and a failure row on the pool the emitter closes over.

type Db

pending_effects

Eager fire-and-forget queue — push the in-flight Promise<void> for pool writes already running (audit emits, api-token usage tracking). Drained via flush_pending_effects after the handler returns.

type Promise<void>[]

post_commit_effects

Deferred post-commit thunks — do not push directly; reach for emit_after_commit(ctx, fn) from http/pending_effects.ts. The flush site invokes each thunk after the handler (and any wrapping db.transaction) returns.

type (() => void | Promise<void>)[]

client_ip

Resolved client IP from the trusted-proxy middleware — 'unknown' if the middleware wasn't in the stack (e.g. WS dispatch) or couldn't resolve. Thread into deps.audit.emit as ip: ctx.client_ip for every user-initiated action so RPC audit rows match the REST convention. Pass null only for rows written outside a request (e.g. the role_grant_offer_expire cleanup sweep in auth/cleanup.ts).

type string

credential_type

Credential channel the request arrived on ('session' | 'api_token' | 'daemon_token'), or null for anonymous requests. Same value the dispatcher's credential_types gate consumed at step 3 — exposed here so handlers can record it in audit metadata (defense in depth: the gate may be loosened or bypassed in a future refactor, but the audit row preserves what actually authenticated the request).

type BuiltinCredentialType | null

session_hash

Blake3 hash of the session token the request authenticated with — the auth_session.id of the caller's own session — or null for any other credential and for anonymous requests.

type string | null

log

Logger instance.

type Logger

notify

Send a request-scoped JSON-RPC notification to the originator.

On streaming transports (WebSocket) this routes to the originating connection only. On the HTTP RPC transport this is a no-op with a DEV-mode warn — non-streaming transports have no channel for mid- request notifications. The streams field on an ActionSpec names the notification method this handler is expected to emit.

type (method: string, params: unknown): void

method

type string

params

type unknown
returns void

signal

AbortSignal that fires when the originating request is cancelled (client disconnect on HTTP, socket close or per-request cancel notification on WebSocket). Streaming handlers should check this for early termination.

type AbortSignal

ActionDispatcher
#

actions/action_dispatcher.ts view source

import {ActionDispatcher} from '@fuzdev/fuz_app/actions/action_dispatcher.js';

environment

type ActionEventEnvironment

readonly

transports

type Transports

readonly

default_send_options

type Omit<ActionDispatcherSendOptions, "signal">

constructor

type new (options: ActionDispatcherOptions): ActionDispatcher

options

send

Resolve a transport (per-call name → default name → registry default) and forward the message. Catches unexpected throws and converts them to JSON-RPC error responses — this method never throws.

A request's queue (per call, else default_send_options.queue) is resolved once and drives both the transport choice and the send: with it set, a disconnected transport that queues is chosen and the request waits in its durable queue for the reconnect — or until its signal aborts; the queue is size-bounded, not timed. A WebSocket transport queues only while its connection will reopen on its own (will_reconnect); after a close that schedules no reconnect, the request goes to a ready transport (HTTP) instead. A notification never queues.

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: ActionDispatcherSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

the response envelope for requests, or null for successful notifications (JsonrpcErrorResponse if the notification's transport send failed)

receive

Dispatch an inbound JSON-RPC message — request, notification, or malformed envelope. Never throws; unexpected failures become JSON-RPC error responses.

type (message: unknown): Promise<{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | ... 4 more ... | (number & $brand<...>); message: string; data?: unknown; }; } | { ...; } | null>

message

type unknown
returns Promise<JsonrpcMessageFromServerToClient | null>

response message for requests, null for notifications, or an invalid_request error for malformed input

ActionDispatcherOptions
#

ActionDispatcherSendOptions
#

actions/action_dispatcher.ts view source

ActionDispatcherSendOptions import type {ActionDispatcherSendOptions} from '@fuzdev/fuz_app/actions/action_dispatcher.js';

Per-call options for ActionDispatcher.send. Extends TransportSendOptions with transport_name for per-call transport selection. The peer-wide default for any field lives on ActionDispatcherOptions.default_send_options — set queue: true there once for client-authoritative peers and override per-call for exceptions (e.g. high-frequency position sync where stale replays are wrong).

inheritance

transport_name?

type string

ActionEvent
#

actions/action_event.ts view source

import {ActionEvent} from '@fuzdev/fuz_app/actions/action_event.js';

Action event that manages the lifecycle of an action through its state machine.

generics

ActionEvent<TMethod extends string = string, TPhase extends ActionEventPhase = ActionEventPhase, TStep extends ActionEventStep = ActionEventStep>
TMethod
constraint string
default string
TPhase
constraint ActionEventPhase
TStep
constraint ActionEventStep

environment

type ActionEventEnvironment

readonly

spec

method narrows to TMethod so consumers passing a typed TApi to create_rpc_client get event.spec.method typed as the union of their API's method names rather than plain string. The runtime value comes from lookup_action_spec(method) keyed off the Proxy get trap, so the narrowing matches the dispatched method.

type (RequestResponseActionSpec & { method: TMethod; }) | (RemoteNotificationActionSpec & { method: TMethod; }) | (LocalCallActionSpec & { method: TMethod; })

readonly

constructor

type new <TMethod extends string = string, TPhase extends ActionEventPhase = "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", TStep extends ActionEventStep = "initial" | ... 3 more ... | "failed">(environment: ActionEventEnvironment, spec: { ...; } | ... 1 more ... | { ...; }, data: ActionEventDataUnion<...>): ActionEvent<...>

environment

spec

data

toJSON

type (): ActionEventDataUnion<TMethod>

observe

Subscribe a listener fired on every data transition.

type (listener: ActionEventChangeObserver<TMethod>): () => void

listener

called with (new_data, old_data, event) after each mutation

type ActionEventChangeObserver<TMethod>
returns () => void

unsubscribe function

mutates

  • this — adds `listener` to the internal observer set

set_data

Replace the event's data and notify observers.

type (new_data: ActionEventDataUnion<TMethod>): void

new_data

returns void

mutates

  • this — sets `data` and fires every registered observer in insertion order

parse

Parse the event's payload against the action's schemas — spec.input for outgoing and incoming calls, spec.output for a received response.

A received response's result is validated like any other inbound data, except that keys spec.output doesn't declare are dropped rather than refused (safe_parse_dropping_unknown_keys). On success output becomes the parsed value (brands, defaults, and transforms applied); on any other mismatch the event moves to receive_error with an internal_error whose data.reason is ERROR_RESPONSE_OUTPUT_INVALID (the remote broke its contract — not the caller's input), the same path an error response takes.

type (): this

returns this

this for chaining with handle_async / handle_sync

throws

  • Error - if called from a step other than `initial`

mutates

  • this — transitions step from `initial` to `parsed` (or to `failed` / `receive_error` on validation or response error)

handle_async

Execute the handler for the current phase.

type (): Promise<void>

returns Promise<void>

throws

  • Error - if called from a step other than `parsed` (or `failed`, which no-ops). Handler-thrown `ThrownJsonrpcError` is caught and routed through error phases; other throws are wrapped as `internal_error` carrying the thrown message (see `#to_handler_error`).

mutates

  • this — transitions step `parsed → handling → handled`, or to `failed` / `send_error` / `receive_error` on handler throw. No-op when already `failed`.

handle_sync

Execute handler synchronously (only for sync local_call actions).

type (): void

returns void

throws

  • Error - if the spec is not a sync `local_call`, or if called from a step other than `parsed` (or `failed`, which no-ops).

mutates

  • this — transitions step `parsed → handling → handled`, or to `failed` on handler throw. No-op when already `failed`.

transition

Transition to a new phase.

type (phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): void

phase

the next phase to transition into

returns void

throws

  • Error - if called from a step other than `handled` (or `failed`, which no-ops), or if the phase transition is illegal for the current phase

mutates

  • this — replaces `data` with a fresh phase-initial record, carrying forward `request` / `response` / `error` / `output` as appropriate for the kind/phase pair

is_complete

type (): boolean

returns boolean

update_progress

type (progress: unknown): void

progress

type unknown
returns void

set_request

type (request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }): void

request

returns void

set_response

type (response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; }): void

response

returns void

set_notification

type (notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }): void

notification

returns void

data

type ({ kind: "request_response"; phase: "send_request"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_request"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_request"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_request"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_request"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_error"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_error"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_error"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_error"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_error"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_error"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_error"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_error"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } | null; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; })

getter

ActionEventChangeObserver
#

actions/action_event.ts view source

ActionEventChangeObserver<TMethod> import type {ActionEventChangeObserver} from '@fuzdev/fuz_app/actions/action_event.js';

generics

ActionEventChangeObserver<TMethod extends string = string>
TMethod
constraint string
default string

(call)

type (new_data: ActionEventDataUnion<TMethod>, old_data: ActionEventDataUnion<TMethod>, event: ActionEvent<TMethod, "send_request" | ... 7 more ... | "execute", "initial" | ... 3 more ... | "failed">): void

new_data

old_data

event

type ActionEvent<TMethod, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">
returns void

ActionEventData
#

actions/action_event_data.ts view source

value + type

{ kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; } import {ActionEventData} from '@fuzdev/fuz_app/actions/action_event_data.js';

kind

type "request_response" | "remote_notification" | "local_call"

phase

type "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"

step

type "initial" | "parsed" | "handling" | "handled" | "failed"

method

type string

executor

type "frontend" | "backend"

input

type unknown

output

type unknown

error

type JsonrpcErrorObject | null

progress

type unknown

request

type JsonrpcRequest | null

response

type JsonrpcResponse | JsonrpcErrorResponse | null

notification

type JsonrpcNotification | null

ActionEventDataUnion
#

ActionEventEnvironment
#

actions/action_event_types.ts view source

ActionEventEnvironment import type {ActionEventEnvironment} from '@fuzdev/fuz_app/actions/action_event_types.js';

executor

type ActionExecutor

readonly

lookup_action_handler

type (method: string, phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): ((event: any) => any) | undefined

method

type string

phase

returns ((event: any) => any) | undefined

lookup_action_spec

type (method: string): { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; } | undefined

method

type string
returns ActionSpecUnion | undefined

log?

Where dispatch diagnostics go — failed sends, rejected or failing inbound notifications and requests. Unset falls back to console_action_log; null silences them.

type ActionLog | null

readonly

ActionEventLocalCallData
#

actions/action_event_data.ts view source

ActionEventLocalCallData<TMethod, TInput, TOutput>

type { kind: "local_call"; phase: "execute"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "local_call"; phase: "execute"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "local_call"; phase: "execute"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: null; } | { kind: "local_call"; phase: "execute"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: null; response: null; notification: null; } | { kind: "local_call"; phase: "execute"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: null; }

import type {ActionEventLocalCallData} from '@fuzdev/fuz_app/actions/action_event_data.js';

generics

ActionEventLocalCallData<TMethod extends string = string, TInput = unknown, TOutput = unknown>
TMethod
constraint string
default string
TInput
default unknown
TOutput
default unknown

ActionEventOptions
#

ActionEventPhase
#

actions/action_spec.ts view source

value + type

"send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"

type "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"

import {ActionEventPhase} from '@fuzdev/fuz_app/actions/action_spec.js';

ActionEventRemoteNotificationData
#

actions/action_event_data.ts view source

ActionEventRemoteNotificationData<TMethod, TInput>

type { kind: "remote_notification"; phase: "send"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "remote_notification"; phase: "send"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "remote_notification"; phase: "send"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "send"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "send"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } | null; } | { kind: "remote_notification"; phase: "receive"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "receive"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "receive"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "receive"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "receive"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; }

import type {ActionEventRemoteNotificationData} from '@fuzdev/fuz_app/actions/action_event_data.js';

generics

ActionEventRemoteNotificationData<TMethod extends string = string, TInput = unknown>
TMethod
constraint string
default string
TInput
default unknown

ActionEventRequestResponseData
#

actions/action_event_data.ts view source

ActionEventRequestResponseData<TMethod, TInput, TOutput>

type { kind: "request_response"; phase: "send_request"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "request_response"; phase: "send_request"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "request_response"; phase: "send_request"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "send_request"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "send_request"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_error"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "send_error"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "send_error"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "send_error"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "receive_error"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_error"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_error"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_error"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; }

import type {ActionEventRequestResponseData} from '@fuzdev/fuz_app/actions/action_event_data.js';

generics

ActionEventRequestResponseData<TMethod extends string = string, TInput = unknown, TOutput = unknown>
TMethod
constraint string
default string
TInput
default unknown
TOutput
default unknown

ActionEventStep
#

actions/action_event_types.ts view source

value + type

"initial" | "parsed" | "handling" | "handled" | "failed"

type "initial" | "parsed" | "handling" | "handled" | "failed"

import {ActionEventStep} from '@fuzdev/fuz_app/actions/action_event_types.js';

ActionExecutor
#

ActionFactoryDeps
#

auth/deps.ts view source

ActionFactoryDeps import type {ActionFactoryDeps} from '@fuzdev/fuz_app/auth/deps.js';

Capabilities for action-spec factories — the "Action caps" shape.

The minimal slice every create_*_actions factory needs: a log for RPC-internal error logging and the bound audit emitter for audit writes. RouteFactoryDeps (and AppDeps) satisfy it structurally, so consumers pass their fuller deps bundle straight through.

log

Structured logger instance.

type Logger

audit

Bound audit emitter for audit writes.

type AuditEmitter

ActionHandler
#

actions/action_types.ts view source

ActionHandler<TInput, TOutput> import type {ActionHandler} from '@fuzdev/fuz_app/actions/action_types.js';

Handler function for an RPC action.

Receives validated input and an ActionContext with per-request deps. Returns the output value (serialized to JSON by the wrapper).

generics

ActionHandler<TInput = any, TOutput = any>
TInput
default any
TOutput
default any

(call)

type (input: TInput, ctx: ActionContext): TOutput | Promise<TOutput>

input

type TInput

ctx

returns TOutput | Promise<TOutput>

ActionInitiator
#

actions/action_spec.ts view source

value + type

"frontend" | "backend" | "both"

type "frontend" | "backend" | "both"

import {ActionInitiator} from '@fuzdev/fuz_app/actions/action_spec.js';

ActionKind
#

actions/action_spec.ts view source

value + type

"request_response" | "remote_notification" | "local_call"

type "request_response" | "remote_notification" | "local_call"

import {ActionKind} from '@fuzdev/fuz_app/actions/action_spec.js';

ActionLog
#

actions/action_event_types.ts view source

ActionLog import type {ActionLog} from '@fuzdev/fuz_app/actions/action_event_types.js';

The logging surface the action dispatch path uses.

external types

Pick<Logger, 'error' | 'warn' | 'debug'>

ActionManifest
#

testing/cross_backend/action_manifest.ts view source

value + type

{ methods: { method: string; input: "object" | "void"; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; }[]; } import {ActionManifest} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest.js';

The full action manifest — every entry, sorted by method.

methods

type ActionManifestEntry[]

ActionManifestDiff
#

testing/cross_backend/action_manifest_parity.ts view source

ActionManifestDiff

type { readonly kind: "method_only_in"; readonly where: "a" | "b"; readonly method: string; } | { readonly kind: "input_differs"; readonly method: string; readonly a: "object" | "void"; readonly b: "object" | "void"; } | { readonly kind: "side_effects_differ"; readonly method: string; readonly a: boolean; readonly b: boolean; } | { readonly kind: "rate_limit_differs"; readonly method: string; readonly a: "both" | "account" | "ip" | null; readonly b: "both" | "account" | "ip" | null; } | { readonly kind: "auth_field_differs"; readonly method: string; readonly field: "account" | "actor" | "roles" | "credential_types"; readonly a: unknown; readonly b: unknown; }

import type {ActionManifestDiff} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest_parity.js';

Structured drift entry. where is the named source impl ('a' or 'b').

ActionManifestDiffLabels
#

testing/cross_backend/action_manifest_parity.ts view source

ActionManifestDiffLabels import type {ActionManifestDiffLabels} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest_parity.js';

Labels used in formatted output — defaults to 'a' and 'b'.

a?

type string

readonly

b?

type string

readonly

ActionManifestEntry
#

testing/cross_backend/action_manifest.ts view source

value + type

{ method: string; input: "object" | "void"; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; } import {ActionManifestEntry} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest.js';

One method's normalized RPC metadata — the cross-impl-comparable unit. roles / credential_types are always present + sorted (an absent gate and an empty list both serialize to []) so the diff never trips on a undefined-vs-[] or declaration-order difference between impls; the auth axes reuse the canonical AuthAxisState enum. rate_limit is nullable rather than optional for the same reason — an unthrottled action serializes an explicit null, so a missing key is drift, not a default.

method

RPC method name (spec.method).

type string

input

'void' when the input is z.void() (a parameterless method), else 'object' — the Rust spine's declared InputShape, which its dispatcher enforces. A z.strictObject({}) input is 'object'.

type "object" | "void"

side_effects

true when the action declares side_effects (mutation; POST-only on the RPC transport).

type boolean

account

The account auth axis.

type "optional" | "none" | "required"

actor

The actor auth axis.

type "optional" | "none" | "required"

roles

Required roles (any-of), sorted; [] when the action declares no role gate.

type string[]

credential_types

Permitted credential channels, sorted; [] when ungated (any credential).

type string[]

rate_limit

Per-action rate-limit class, or null when the action declares none.

type "both" | "account" | "ip" | null

ActionMethodEnumKind
#

actions/action_codegen.ts view source

ActionMethodEnumKind

type "all" | "request_response" | "remote_notification" | "local_call" | "frontend" | "backend" | "frontend_handled" | "backend_handled" | "broadcast"

import type {ActionMethodEnumKind} from '@fuzdev/fuz_app/actions/action_codegen.js';

Discriminator for generate_action_method_enums — which method-set enums to emit.

ActionRateLimitEnvOptions
#

ActionRegistry
#

actions/action_registry.ts view source

import {ActionRegistry} from '@fuzdev/fuz_app/actions/action_registry.js';

specs

type ActionSpecUnion[]

readonly

constructor

type new (specs: ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[]): ActionRegistry

specs

spec_by_method

type Map<string, ActionSpecUnion>

getter

methods

type string[]

getter

request_response_specs

type RequestResponseActionSpec[]

getter

remote_notification_specs

type RemoteNotificationActionSpec[]

getter

local_call_specs

type LocalCallActionSpec[]

getter

request_response_methods

type string[]

getter

remote_notification_methods

type string[]

getter

local_call_methods

type string[]

getter

specs_relevant_to_frontend

type ActionSpecUnion[]

getter

specs_relevant_to_backend

type ActionSpecUnion[]

getter

methods_relevant_to_frontend

type string[]

getter

methods_relevant_to_backend

type string[]

getter

frontend_handled_specs

type RequestResponseActionSpec[]

getter

backend_handled_specs

type RequestResponseActionSpec[]

getter

frontend_handled_methods

type string[]

getter

backend_handled_methods

type string[]

getter

broadcast_specs

type RemoteNotificationActionSpec[]

getter

broadcast_methods

type string[]

getter

backend_initiated_specs

type ActionSpecUnion[]

getter

backend_initiated_methods

type string[]

getter

backend_to_frontend_specs

type ActionSpecUnion[]

getter

frontend_to_backend_specs

type ActionSpecUnion[]

getter

frontend_to_backend_methods

type string[]

getter

backend_to_frontend_methods

type string[]

getter

public_specs

type ActionSpecUnion[]

getter

authenticated_specs

type ActionSpecUnion[]

getter

public_methods

type string[]

getter

authenticated_methods

type string[]

getter

ActionRegistryCompileResult
#

actions/compile_action_registry.ts view source

ActionRegistryCompileResult import type {ActionRegistryCompileResult} from '@fuzdev/fuz_app/actions/compile_action_registry.js';

Result returned by compile_action_registry.

action_map

Method → RpcAction lookup for dispatch. Only request_response specs with a handler land here — kind-polymorphic input arrays (the WebSocket dispatcher's actions: ReadonlyArray<Action>) pass remote_notification / handler-less specs through unchanged.

type Map<string, RpcAction>

ActionSideEffects
#

ActionSpec
#

actions/action_spec.ts view source

value + type

{ method: string; kind: "request_response" | "remote_notification" | "local_call"; initiator: "frontend" | "backend" | "both"; auth: { account: "optional" | "none" | "required"; actor: "optional" | ... 1 more ... | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; r... import {ActionSpec} from '@fuzdev/fuz_app/actions/action_spec.js';

method

type string

kind

type "request_response" | "remote_notification" | "local_call"

initiator

type "frontend" | "backend" | "both"

auth

The flat auth shape (canonical schema in http/auth_shape.ts). null for remote_notification and local_call — those don't dispatch through the request/response auth gate.

See http/auth_shape.ts for the design rationale (orthogonal authentication / account-resolution / actor-resolution / role-and- credential authorization axes). required_scope is the one field an action may not set — compile_action_registry rejects it, since nothing on the RPC path mounts a guard from it; the dispatcher derives the same rpc:<method> capability from spec.method instead.

type RouteAuth | null

side_effects

type boolean

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

output

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

async

type boolean

description

type string

streams?

Names the notification method this action emits as request-scoped progress. Forward-compatible handshake — transport-agnostic, does not imply a specific delivery mechanism. Registry-time validation (e.g., ensuring the named method is a remote_notification spec) is a consumer-side concern.

type string

error_reasons?

Error reason codes this action may surface via error.data.reason on failure. Declarative metadata mirroring the streams precedent — codegen, UI form-state matching, and docs read it off the spec instead of scanning handler implementations. Reuses the same as const string constants the handler throws (e.g. ERROR_ROLE_GRANT_OFFER_*) so call sites can import either side. Optional — actions that surface only standard transport errors leave it unset.

type readonly string[]

rate_limit?

Rate limit key the RPC dispatcher consults for this action. Optional — actions without it skip the rate-limit hook entirely.

  • 'ip' — keyed on the resolved client IP (get_client_ip(c)).
  • 'account' — keyed on the post-auth account id (request_context.account.id). Registration-time error if paired with auth.account !== 'required' (no account to key on).
  • 'both' — both checks run; either can block.

Throttle-requests semantics — every invocation records, regardless of outcome (different from REST login's throttle-failures, which resets on success). Suits admin mutation oracles (invite_create account- existence probe) where the *successful* invocation is the threat.

Today only RequestResponseActionSpec is consulted by the RPC dispatcher. The field is on the base for shape symmetry with streams and error_reasons; remote_notification / local_call dispatchers don't read it.

type "both" | "account" | "ip"

ActionSpecUnion
#

actions/action_spec.ts view source

value + type

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }

type RequestResponseActionSpec | RemoteNotificationActionSpec | LocalCallActionSpec

import {ActionSpecUnion} from '@fuzdev/fuz_app/actions/action_spec.js';

Actor
#

auth/account_schema.ts view source

Actor import type {Actor} from '@fuzdev/fuz_app/auth/account_schema.js';

Actor — the entity that acts. Owns cells, holds role_grants, appears in audit trails.

id

type Uuid

account_id

type Uuid

name

type string

created_at

type string

updated_at

type string | null

updated_by

type Uuid | null

deleted_at

Soft-delete tombstone — set alongside the owning account's soft-delete.

type string | null

deleted_by

Actor that performed the soft-delete. Paired with deleted_at.

type Uuid | null

ACTOR_COLUMNS
#

auth/account_queries.ts view source

readonly ["id", "account_id", "name", "created_at", "updated_at", "updated_by", "deleted_at", "deleted_by"] import {ACTOR_COLUMNS} from '@fuzdev/fuz_app/auth/account_queries.js';

The full actor column set — the same fail-loud discipline as ACCOUNT_COLUMNS (this module owns both tables). Keep in sync with Actor and the actor DDL in auth/auth_ddl.ts.

ACTOR_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS idx_actor_account ON actor(account_id)" import {ACTOR_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

actor_lookup_action_spec
#

auth/actor_lookup_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodObject<{ ids: ZodArray<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>; output: ZodObject<...>; async: true; rate_limit: "account"; description: string; } import {actor_lookup_action_spec} from '@fuzdev/fuz_app/auth/actor_lookup_action_specs.js';

ACTOR_LOOKUP_IDS_MAX
#

auth/actor_lookup_action_specs.ts view source

50 import {ACTOR_LOOKUP_IDS_MAX} from '@fuzdev/fuz_app/auth/actor_lookup_action_specs.js';

Hard cap on the number of ids resolvable in one call. Bounds the batched username-enumeration surface.

ACTOR_NAME_LOWER_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS idx_actor_name_lower ON actor (LOWER(name) text_pattern_ops)" import {ACTOR_NAME_LOWER_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

Functional index on LOWER(actor.name) supporting case-insensitive prefix search by actor_search (LOWER(name) LIKE LOWER(query) || '%'). text_pattern_ops keeps the LIKE-prefix pattern index-eligible — without it the planner falls back to a sequential scan once the table grows.

ACTOR_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS actor (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ,\n updated_by UUID REFERENCES actor(id) ON DELETE SET NULL,... import {ACTOR_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

actor_search_action_spec
#

auth/actor_search_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodObject<{ query: ZodString; scope_ids: ZodOptional<ZodArray<$ZodBranded<ZodUUID, "Uuid", "out">>>; limit: ZodOptional<...>; }, $strict>; ... 4 more ...; description: string; } import {actor_search_action_spec} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

ACTOR_SEARCH_LIMIT_DEFAULT
#

ACTOR_SEARCH_LIMIT_MAX
#

ACTOR_SEARCH_QUERY_LENGTH_MAX
#

auth/actor_search_action_specs.ts view source

50 import {ACTOR_SEARCH_QUERY_LENGTH_MAX} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

Hard cap on the query string length, in UTF-16 code units. Long inputs offer no extra search value once they exceed actor.name realistic lengths, and a low cap keeps the per-request work bounded for pathological inputs.

ActorActionHandler
#

actions/action_types.ts view source

ActorActionHandler<TInput, TOutput> import type {ActorActionHandler} from '@fuzdev/fuz_app/actions/action_types.js';

Handler signature for an actor-implying RPC action — auth.actor === 'required'. Mirrors ActionHandler but tightens the ctx.auth slot to the non-null RequestActorContext (with non-null actor).

generics

ActorActionHandler<TInput = any, TOutput = any>
TInput
default any
TOutput
default any

(call)

type (input: TInput, ctx: ActionActorContext): TOutput | Promise<TOutput>

input

type TInput

ctx

returns TOutput | Promise<TOutput>

ActorLookupActionDeps
#

ActorLookupCrossTestOptions
#

testing/cross_backend/actor_lookup.ts view source

RpcPathCrossSuiteOptions import type {ActorLookupCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/actor_lookup.js';

Options for the actor-lookup parity suite. The standard RPC-dispatched cross-suite shape (setup_test / rpc_path); aliases the shared RpcPathCrossSuiteOptions rather than minting a duplicate. No case here is capability-gated, so the flag bundle is not on the shape.

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

ActorLookupEntryJson
#

auth/actor_lookup_action_specs.ts view source

value + type

{ id: string & $brand<"Uuid">; username: string; display_name?: string | undefined; } import {ActorLookupEntryJson} from '@fuzdev/fuz_app/auth/actor_lookup_action_specs.js';

One resolved actor row. display_name omitted when blank.

id

type string & $brand<"Uuid">

username

type string

display_name?

type string

ActorLookupInput
#

ActorLookupOutput
#

ActorLookupRow
#

auth/actor_lookup_queries.ts view source

ActorLookupRow import type {ActorLookupRow} from '@fuzdev/fuz_app/auth/actor_lookup_queries.js';

Row shape returned to handlers — wire mapping happens at the action layer.

id

type Uuid

username

type string

display_name

type string | null

ActorNotOnAccountError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "actor_not_on_account"; } import {ActorNotOnAccountError} from '@fuzdev/fuz_app/http/error_schemas.js';

error

type "actor_not_on_account"

[key: string]

type unknown

ActorRequiredError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "actor_required"; available: { [x: string]: unknown; id: string; name: string; }[]; } import {ActorRequiredError} from '@fuzdev/fuz_app/http/error_schemas.js';

Authorization-phase failure shapes. Surfaced when the dispatcher's apply_authorization_phase rejects a request before the handler runs — the route is acting-aware (input declares acting?: ActingActor or auth requires role_grants), but actor resolution failed.

400: actor_required (with available[]) for unspecified-actor on a multi-actor account; actor_not_on_account for a supplied actor id that doesn't belong to the authenticated account.

500: no_actors_on_account for a signup-invariant violation (the actor list enumerated empty); account_vanished for a torn-read race (account/actor row deleted between credential validation and the dispatcher's follow-up read).

Used by derive_error_schemas when auth.actor !== 'none' so the merged error surface matches what the dispatcher actually emits.

error

type "actor_required"

available

type { [x: string]: unknown; id: string; name: string; }[]

[key: string]

type unknown

ActorSearchActionDeps
#

ActorSearchInput
#

auth/actor_search_action_specs.ts view source

value + type

{ query: string; scope_ids?: (string & $brand<"Uuid">)[] | undefined; limit?: number | undefined; } import {ActorSearchInput} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

query

type string

scope_ids?

type (string & $brand<"Uuid">)[]

limit?

type number

ActorSearchOutput
#

ActorSearchQueryInput
#

auth/actor_search_queries.ts view source

ActorSearchQueryInput import type {ActorSearchQueryInput} from '@fuzdev/fuz_app/auth/actor_search_queries.js';

Inputs for query_actor_search.

query

Case-insensitive prefix string. Must be non-empty (action layer enforces min(1)).

type string

scope_ids?

When non-empty, restrict to actors holding an active role_grant on one of these scope ids. When empty / omitted, no scope filter is applied — the handler is responsible for the admin gate.

type readonly Uuid[]

limit

Maximum rows to return. The handler clamps to ACTOR_SEARCH_LIMIT_MAX.

type number

ActorSummaryJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; name: string; } import {ActorSummaryJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for the actor summary returned in admin account listings.

id

type string & $brand<"Uuid">

name

type string

admin_account_list_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ acting: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; limit: ZodOptional<...>; offset: ZodOptional<...>; include_deleted: ZodOp... import {admin_account_list_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds admin-side scraping of the account table via (limit, offset) walking — admin trust is not a substitute for a read-rate cap when the listing is paginated and cross-account (yields every account + actor + active role_grant in the system).

ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT
#

ADMIN_ACCOUNT_LIST_LIMIT_MAX
#

admin_accounts_rpc_context
#

ui/admin_accounts_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AdminAccountsRpc; get_maybe: () => (() => AdminAccountsRpc) | undefined; set: (value: () => AdminAccountsRpc) => () => AdminAccountsRpc; } import {admin_accounts_rpc_context} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

Svelte context carrying the reactive AdminAccountsRpc accessor. The provisioner (typically the admin route shell) calls set(() => rpc); consumers read with const get_rpc = admin_accounts_rpc_context.get(); and either pass the accessor straight to AdminAccountsState/ AdminSessionsState or wrap it with const rpc = $derived(get_rpc()); for direct RPC calls. get() throws when no provisioner ran above the component — the adapter is required, not optional.

admin_invites_rpc_context
#

ui/admin_invites_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AdminInvitesRpc; get_maybe: () => (() => AdminInvitesRpc) | undefined; set: (value: () => AdminInvitesRpc) => () => AdminInvitesRpc; } import {admin_invites_rpc_context} from '@fuzdev/fuz_app/ui/admin_invites_state.svelte.js';

Svelte context carrying the reactive AdminInvitesRpc accessor. Mirrors admin_accounts_rpc_context. get() throws when no provisioner ran above the component — the adapter is required.

admin_only_field_blocklist
#

testing/integration_helpers.ts view source

readonly string[] import {admin_only_field_blocklist} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Field names that must not appear in non-admin HTTP response bodies.

admin_session_list_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ acting: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>>; output: ZodObject<...>; async: true; description: string; r... import {admin_session_list_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds cross-account scraping of every active auth_session row — no pagination, but the read is unbounded across accounts and reveals one row per live cookie globally.

admin_session_revoke_all_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ account_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: strin... import {admin_session_revoke_all_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

admin_token_revoke_all_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ account_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: strin... import {admin_token_revoke_all_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

AdminAccountEntryJson
#

auth/account_schema.ts view source

value + type

{ account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<"Uuid">) | null; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; } import {AdminAccountEntryJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for an admin account listing entry (account + actor + role_grants + pending offers).

account

type AdminAccountJson

actor

type ActorSummaryJson | null

role_grants

type RoleGrantSummaryJson[]

pending_offers

type PendingOfferSummaryJson[]

AdminAccountJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<"Uuid">) | null; deleted_at: string | null; } import {AdminAccountJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for admin-facing account data — extends SessionAccountJson with audit fields.

id

type string & $brand<"Uuid">

username

type string

email

type string | null

email_verified

type boolean

created_at

type string

updated_at

type string

updated_by

type (string & $brand<"Uuid">) | null

deleted_at

Soft-delete tombstone, non-null when the account is deleted. Surfaced so the admin UI can mark tombstoned rows (shown only when the listing is requested with include_deleted) and offer reactivation via account_undelete. Active listings always carry null here.

type string | null

AdminAccountListInput
#

auth/admin_action_specs.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; limit?: number | null | undefined; offset?: number | null | undefined; include_deleted?: boolean | null | undefined; } import {AdminAccountListInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for admin_account_list.

acting?

type string & $brand<"Uuid">

limit?

type number | null

offset?

type number | null

include_deleted?

type boolean | null

AdminAccountListOptions
#

auth/account_queries.ts view source

AdminAccountListOptions import type {AdminAccountListOptions} from '@fuzdev/fuz_app/auth/account_queries.js';

limit?

Max accounts to return. Defaults to ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT when omitted; pass null explicitly to disable the limit (unbounded fetch — for trusted internal callers / scripts; the RPC schema bounds wire callers to [1, ADMIN_ACCOUNT_LIST_LIMIT_MAX]).

type number | null

offset?

Pagination offset. Defaults to 0.

type number | null

include_deleted?

Include soft-deleted (tombstoned) accounts. Defaults to false — the listing shows active accounts only, matching auth resolution. Set true for the admin UI's "show deleted" view, which offers reactivation via account_undelete.

type boolean | null

AdminAccountListOutput
#

auth/admin_action_specs.ts view source

value + type

{ accounts: { account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<...>) | null; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }[]; gra... import {AdminAccountListOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for admin_account_list.

accounts

type AdminAccountEntryJson[]

grantable_roles

type string[]

AdminAccounts
#

AdminAccountsRpc
#

ui/admin_accounts_state.svelte.ts view source

AdminAccountsRpc import type {AdminAccountsRpc} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

Narrow RPC surface consumed by AdminAccountsState. Consumers adapt their typed RPC client (e.g. a create_rpc_client Proxy) to this shape — the state class stays decoupled from the client's Result return type so tests can inject plain-function stubs. Mirrors the RoleGrantOffersRpc pattern.

Every operation flows through RPC: the listing reuses admin_account_list, grant reuses role_grant_offer_create, revoke and retract have dedicated actions, and the session / token revoke-all mutations reuse admin_session_revoke_all and admin_token_revoke_all.

Every method takes its action's input as one params object, typed from the action specs (or nothing, for an input-less listing) — Uuid-branded ids propagate from the wire through the state class to the components. The adapter built by create_admin_rpc_adapters therefore needs zero casts to bridge to the typed throwing Proxy. delete_account is the one narrowing: account_delete's input defaults to {}, which deletes the caller, so the adapter requires account_id as a Uuid — never omitted, never null.

list_accounts

type (params?: { acting?: (string & $brand<"Uuid">) | undefined; limit?: number | null | undefined; offset?: number | null | undefined; include_deleted?: boolean | null | undefined; } | undefined): Promise<...>

params?

optional
returns Promise<AdminAccountListOutput>

delete_account

type (params: { account_id?: (string & $brand<"Uuid">) | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } & { account_id: string & $brand<"Uuid">; }): Promise<...>

params

type AccountDeleteInput & { account_id: string & $brand<"Uuid">; }
returns Promise<AccountDeleteOutput>

undelete_account

type (params: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; undeleted: boolean; }>

params

returns Promise<AccountUndeleteOutput>

list_sessions

type (params?: { acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; username: string; }[]; }>

params?

optional
returns Promise<AdminSessionListOutput>

create_role_grant

type (params: { to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; message?: string | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; }): Promise<...>

params

revoke_role_grant

type (params: { actor_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<...>

params

returns Promise<RoleGrantRevokeOutput>

retract_offer

type (params: { offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<...>; ... 13 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }>

params

session_revoke_all

type (params: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; count: number; }>

params

token_revoke_all

type (params: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; count: number; }>

params

returns Promise<AdminTokenRevokeAllOutput>

AdminAccountsState
#

ui/admin_accounts_state.svelte.ts view source

import {AdminAccountsState} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

grant

type KeyedAsyncSlot<string, RoleGrantOfferJson, string>

readonly

revoke

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

retract

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

soft_delete

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

undelete

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

accounts

type AdminAccountEntryJson[]

$state.raw

grantable_roles

type string[]

$state.raw

show_deleted

When true, fetch() includes soft-deleted (tombstoned) accounts so the admin can reactivate them. Toggled via set_show_deleted.

type boolean

$state

account_count

type number

readonly $derived

constructor

type new (options: AdminAccountsStateOptions): AdminAccountsState

options

fetch

type (): Promise<void>

returns Promise<void>

set_show_deleted

Toggle whether soft-deleted accounts appear in the listing, then re-fetch. Tombstoned rows are surfaced so an admin can reactivate them via submit_undelete.

type (value: boolean): Promise<void>

value

type boolean
returns Promise<void>

submit_delete

Soft-delete an account (reversible tombstone) via account_delete. Keyed by account_id so per-row spinners/errors stay independent. Refreshes the listing on success so the row drops out (active view) or flips to its tombstoned state (show_deleted view).

type (account_id: string & $brand<"Uuid">): Promise<void>

account_id

type Uuid
returns Promise<void>

submit_undelete

Reactivate a soft-deleted account via account_undelete (admin-only). Keyed by account_id; refreshes the listing on success so the row returns to active state.

type (account_id: string & $brand<"Uuid">): Promise<void>

account_id

type Uuid
returns Promise<void>

submit_grant

Offer the role to the recipient via the role_grant_offer_create RPC. Server returns the pending offer; the recipient must accept before the role_grant materializes. Returns the offer payload on success so callers can drive follow-up UX (e.g. seed RoleGrantOffersState.outgoing).

A re-offer from the same admin to the same (account, role) refreshes the existing pending row — the returned offer id is stable across those calls.

to_actor_id (optional) narrows the offer to a specific actor on account_id; the keyed-slot key stays at account_id:role for the account-grain default (so existing consumers keep working) and becomes account_id:role:to_actor_id when actor-targeted, so the two variants can be in flight without colliding on the per-row spinner.

type (account_id: string & $brand<"Uuid">, role: string, to_actor_id?: (string & $brand<"Uuid">) | null | undefined): Promise<{ id: string & $brand<"Uuid">; ... 14 more ...; resulting_role_grant_id: (string & $brand<...>) | null; } | undefined>

account_id

type Uuid

role

type string

to_actor_id?

type Uuid | null
optional
returns Promise<RoleGrantOfferJson | undefined>

submit_revoke

Revoke an active role_grant via the role_grant_revoke RPC.

actor_id is the natural key — role_grants are actor-scoped, and the admin UI reads row.actor.id straight from the listing, so the state class takes it directly rather than deriving it from account_id. The optional reason is stamped on role_grant.revoked_reason and surfaced on the revokee's WS notification.

type (actor_id: string & $brand<"Uuid">, role_grant_id: string & $brand<"Uuid">, reason?: string | null | undefined): Promise<void>

actor_id

type Uuid

role_grant_id

type Uuid

reason?

type string | null
optional
returns Promise<void>

submit_retract

Retract a pending offer the admin issued via the role_grant_offer_retract RPC. The action handles auth, audit, and the role_grant_offer_retracted WS notification.

After success, refetches the listing so pending_offers drops the row and the "+ {role}" button un-hides.

type (offer_id: string & $brand<"Uuid">): Promise<void>

offer_id

type Uuid
returns Promise<void>

AdminAccountsStateOptions
#

ui/admin_accounts_state.svelte.ts view source

AdminAccountsStateOptions import type {AdminAccountsStateOptions} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

get_rpc

Reactive accessor for the RPC adapter. Matches RoleGrantOffersStateOptions.account_id / actor_id pattern — lets the component pass a $props()-sourced rpc without tripping Svelte's state_referenced_locally warning.

type (): AdminAccountsRpc

AdminActionOptions
#

auth/admin_actions.ts view source

AdminActionOptions import type {AdminActionOptions} from '@fuzdev/fuz_app/auth/admin_actions.js';

roles?

Role schema result from create_role_schema(). Defaults to builtin roles only. Used to derive grantable_roles (the subset whose RoleSpec.grant_paths includes 'admin') returned by admin_account_list.

type RoleSchemaResult

AdminAuditLog
#

AdminInvites
#

AdminInvitesRpc
#

ui/admin_invites_state.svelte.ts view source

AdminInvitesRpc import type {AdminInvitesRpc} from '@fuzdev/fuz_app/ui/admin_invites_state.svelte.js';

Narrow RPC surface consumed by AdminInvitesState. Consumers adapt their typed RPC client to this shape. error.data.reason on thrown errors carries the ERROR_INVITE_* constant — handled by the caller when user-friendly messages are needed. Method signatures track the wire spec types directly so the adapter needs no casts.

list

type (params?: { acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ invites: { id: string & $brand<"Uuid">; email: string | null; username: string | null; ... 5 more ...; claimed_by_username: string | null; }[]; }>

params?

optional
returns Promise<InviteListOutput>

create

type (params: { email?: string | null | undefined; username?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; invite: { id: string & $brand<"Uuid">; ... 5 more ...; created_by: (string & $brand<...>) | null; }; }>

params

returns Promise<InviteCreateOutput>

delete

type (params: { invite_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; }>

params

returns Promise<InviteDeleteOutput>

AdminInvitesState
#

ui/admin_invites_state.svelte.ts view source

import {AdminInvitesState} from '@fuzdev/fuz_app/ui/admin_invites_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

create

type AsyncSlot<void, string>

readonly

remove

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

invites

type InviteWithUsernamesJson[]

$state.raw

invite_count

type number

readonly $derived

unclaimed_count

type number

readonly $derived

constructor

type new (options: AdminInvitesStateOptions): AdminInvitesState

options

fetch

type (): Promise<void>

returns Promise<void>

submit_create

type (email?: string | undefined, username?: string | undefined): Promise<boolean>

email?

type string
optional

username?

type string
optional
returns Promise<boolean>

submit_delete

type (id: string & $brand<"Uuid">): Promise<void>

id

type Uuid
returns Promise<void>

AdminInvitesStateOptions
#

AdminOverview
#

AdminRoleGrantHistory
#

AdminRpcAdapters
#

AdminRpcApi
#

ui/admin_rpc_adapters.ts view source

AdminRpcApi import type {AdminRpcApi} from '@fuzdev/fuz_app/ui/admin_rpc_adapters.js';

The wire-method surface this module needs from the typed throwing RPC client. Every method returns the unwrapped value or throws an Error carrying the JSON-RPC {code, message, data?} shape — i.e. the ThrowingApi<...> view of the corresponding action specs.

Consumers pass the typed throwing Proxy returned by create_frontend_rpc_client directly. Structural typing means any superset (e.g. the consumer's full ThrowingApi<ActionsApi>) is assignable as long as these methods are present at these signatures.

admin_account_list

type (input?: { acting?: (string & $brand<"Uuid">) | undefined; limit?: number | null | undefined; offset?: number | null | undefined; include_deleted?: boolean | null | undefined; } | undefined): Promise<...>

input?

optional
returns Promise<AdminAccountListOutput>

account_delete

type (input: { account_id?: (string & $brand<"Uuid">) | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; deleted: boolean; }>

input

returns Promise<AccountDeleteOutput>

account_undelete

type (input: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; undeleted: boolean; }>

input

returns Promise<AccountUndeleteOutput>

admin_session_list

type (input?: { acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; username: string; }[]; }>

input?

optional
returns Promise<AdminSessionListOutput>

admin_session_revoke_all

type (input: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; count: number; }>

input

admin_token_revoke_all

type (input: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; count: number; }>

input

returns Promise<AdminTokenRevokeAllOutput>

audit_log_list

type (input?: { event_type?: string | null | undefined; outcome?: "success" | "failure" | null | undefined; account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | ... 1 more ... | undefined; since_seq?: number | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } | undefined): Promise<...>

input?

optional
returns Promise<AuditLogListOutput>

audit_log_role_grant_history

type (input?: { limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ events: { id: string & $brand<...>; ... 11 more ...; target_username: string | null; }[]; }>

input?

optional

invite_list

type (input?: { acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ invites: { id: string & $brand<"Uuid">; email: string | null; username: string | null; ... 5 more ...; claimed_by_username: string | null; }[]; }>

input?

optional
returns Promise<InviteListOutput>

invite_create

type (input: { email?: string | null | undefined; username?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; invite: { id: string & $brand<"Uuid">; ... 5 more ...; created_by: (string & $brand<...>) | null; }; }>

input

returns Promise<InviteCreateOutput>

invite_delete

type (input: { invite_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; }>

input

returns Promise<InviteDeleteOutput>

app_settings_get

type (input?: { acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }; }>

input?

optional
returns Promise<AppSettingsGetOutput>

app_settings_update

type (input: { open_signup: boolean; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<...>) | null; updated_by_username: string | null; }; }>

input

returns Promise<AppSettingsUpdateOutput>

role_grant_offer_create

type (input: { to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; message?: string | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; }): Promise<...>

input

role_grant_offer_retract

type (input: { offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<...>; ... 13 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }>

input

role_grant_revoke

type (input: { actor_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<...>

input

returns Promise<RoleGrantRevokeOutput>

AdminSessionJson
#

auth/audit_log_schema.ts view source

value + type

{ id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; username: string; } import {AdminSessionJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for admin session listing (session + username, without is_current).

id

type string & $brand<"SessionId">

account_id

type string & $brand<"Uuid">

created_at

type string

expires_at

type string

username

type string

AdminSessionListInput
#

auth/admin_action_specs.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; } import {AdminSessionListInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for admin_session_list.

acting?

type string & $brand<"Uuid">

AdminSessionListOutput
#

auth/admin_action_specs.ts view source

value + type

{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; username: string; }[]; } import {AdminSessionListOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for admin_session_list. Cross-account listing; fan-out already scoped by role auth.

sessions

type AdminSessionJson[]

AdminSessionRevokeAllInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {AdminSessionRevokeAllInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for admin_session_revoke_all.

account_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

AdminSessionRevokeAllOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; count: number; } import {AdminSessionRevokeAllOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for admin_session_revoke_all.

ok

type true

count

type number

AdminSessions
#

AdminSessionsState
#

ui/admin_sessions_state.svelte.ts view source

import {AdminSessionsState} from '@fuzdev/fuz_app/ui/admin_sessions_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

revoke_sessions

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

revoke_tokens

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

sessions

type AdminSessionJson[]

$state.raw

active_count

type number

readonly $derived

constructor

type new (options: AdminSessionsStateOptions): AdminSessionsState

options

fetch

type (): Promise<void>

returns Promise<void>

submit_revoke_sessions

type (account_id: string & $brand<"Uuid">): Promise<void>

account_id

type Uuid
returns Promise<void>

submit_revoke_tokens

type (account_id: string & $brand<"Uuid">): Promise<void>

account_id

type Uuid
returns Promise<void>

AdminSessionsStateOptions
#

ui/admin_sessions_state.svelte.ts view source

AdminSessionsStateOptions import type {AdminSessionsStateOptions} from '@fuzdev/fuz_app/ui/admin_sessions_state.svelte.js';

Options for AdminSessionsState.

The RPC adapter drives every operation (listing + the two revoke-all mutations).

get_rpc

Reactive accessor for the RPC adapter. Mirrors AdminAccountsStateOptions.get_rpc so a single adapter instance backs both states without tripping Svelte's state_referenced_locally warning.

type (): AdminAccountsRpc

AdminSettings
#

AdminSurface
#

AdminTokenRevokeAllInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {AdminTokenRevokeAllInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for admin_token_revoke_all.

account_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

AdminTokenRevokeAllOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; count: number; } import {AdminTokenRevokeAllOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for admin_token_revoke_all.

ok

type true

count

type number

AdmissionRecheck
#

auth/resolved_auth.ts view source

AdmissionRecheck

type "admitted" | "credential_revoked" | "actor_inactive" | "role_revoked"

import type {AdmissionRecheck} from '@fuzdev/fuz_app/auth/resolved_auth.js';

What recheck_admission found: 'admitted', or the first check that refused — the credential, the acting actor, or the endpoint's role.

admit_origin_wildcards
#

http/origin_wildcard.ts view source

(origins: readonly RegExp[], allowed: boolean, origins_var?: string): Result<{ wildcards: OriginWildcardEntry[]; }, { message: string; }> import {admit_origin_wildcards} from '@fuzdev/fuz_app/http/origin_wildcard.js';

Admit origins' wildcard entries when allowed, returning what each admits for the caller to warn with; an exact list passes either way with none. Classifies the matchers parse_allowed_origins built (origin_pattern_of, read through a realm-global brand, so a second fuz_app copy's matchers classify too). A matcher built any other way can't be classified, so it is refused whatever allowed says — build the list with parse_allowed_origins.

origins

the allowlist's matchers

type readonly RegExp[]

allowed

whether the operator opted in (FUZ_ALLOW_ORIGIN_WILDCARDS=true)

type boolean

origins_var?

what the list is called in the refusal: the env var it was read from, or the option that carried it

type string
optional default ALLOWED_ORIGINS_VAR

returns

Result

the wildcard entries admitted, or the refusal — naming each unclassifiable matcher, or else each wildcard entry, what it admits, and the flag (the Rust spine's boot error, word for word)

AdversarialHeaderCase
#

testing/adversarial_headers.ts view source

AdversarialHeaderCase import type {AdversarialHeaderCase} from '@fuzdev/fuz_app/testing/adversarial_headers.js';

A header-level attack case for middleware stack testing.

name

type string

headers

type Record<string, string>

expected_status

type number

expected_error?

type string

expected_error_schema?

Zod schema to validate error response body against. Defaults to ApiError when expected_error is set.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

validate_expectation

Whether the request should reach token validation or be short-circuited by earlier middleware.

type "called" | "not_called"

AdversarialTestOptions
#

testing/attack_surface.ts view source

AdversarialTestOptions import type {AdversarialTestOptions} from '@fuzdev/fuz_app/testing/attack_surface.js';

Options for adversarial test runners (auth enforcement and input validation).

build

Build the app surface bundle (surface + route specs + middleware specs).

type (): AppSurfaceSpec

roles

All roles in the app (e.g. ['admin', 'keeper']).

type string[]

skip_routes?

Routes to skip, in 'METHOD /path' form (the surface key) — the escape hatch every sibling suite carries. Reach for it only when a route cannot be driven generically at all (a handler needing real seeded state, a path segment no schema can describe); a route whose params merely have a format is handled by the synthesizer.

type string[]

Alignment
#

ui/position_helpers.ts view source

Alignment

type "start" | "center" | "end"

import type {Alignment} from '@fuzdev/fuz_app/ui/position_helpers.js';

Alignment options for positioned elements.

all_account_action_specs
#

auth/account_action_specs.ts view source

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type RequestResponseActionSpec[]

import {all_account_action_specs} from '@fuzdev/fuz_app/auth/account_action_specs.js';

All self-service account action specs — a codegen-ready registry. Consumers spread this into their own action-spec array to include account methods in a typed client surface.

all_actor_lookup_action_specs
#

auth/actor_lookup_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodObject<{ ids: ZodArray<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>; output: ZodObject<...>; async: true; rate_limit: "account"; description: string; }] import {all_actor_lookup_action_specs} from '@fuzdev/fuz_app/auth/actor_lookup_action_specs.js';

All actor_lookup action specs — independent opt-in registry. Consumers spread alongside all_standard_action_specs if they want the labels arc; not folded into the standard bundle because consumers without a byline surface can skip it.

all_actor_search_action_specs
#

auth/actor_search_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodObject<{ query: ZodString; scope_ids: ZodOptional<ZodArray<$ZodBranded<...>>>; limit: ZodOptional<...>; }, $strict>; ... 4 more ...; description: string; }] import {all_actor_search_action_specs} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

All actor_search action specs — independent opt-in registry. Like all_actor_lookup_action_specs, not folded into all_standard_action_specs because consumers without a person-target picker can skip it.

all_admin_action_specs
#

auth/admin_action_specs.ts view source

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type RequestResponseActionSpec[]

import {all_admin_action_specs} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

All admin action specs — a codegen-ready registry. Consumers spread this into their own action-spec array to include admin methods in a typed client surface. Includes the two app-settings specs, whose handlers the runtime factory always wires.

all_cell_action_specs
#

auth/cell_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ data: ZodObject<{ label: ZodOptional<ZodString>; summary: ZodOptional<...>; }, $loose>; ... 5 more ...; acting: ZodOptional<...>; }, $strict>; output:... import {all_cell_action_specs} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

All cell-layer action specs — composed by app registries. Bundles the generic verbs (this module) with the cell_grant_*, cell_field_*, cell_item_*, and cell_audit_list specs so codegen + UI clients see a single cell namespace.

all_cell_audit_action_specs
#

auth/cell_audit_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; }] import {all_cell_audit_action_specs} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

Registry export to compose into all_cell_action_specs.

all_cell_field_action_specs
#

auth/cell_field_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ source_id: $ZodBranded<ZodUUID, "Uuid", "out">; name: $ZodBranded<...>; target_id: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: Zo... import {all_cell_field_action_specs} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

All cell_field action specs — composed into all_cell_action_specs.

all_cell_grant_action_specs
#

auth/cell_grant_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; level: ZodEnum<...>; principal: ZodDiscriminatedUnion<...>; acting: ZodOptional<...>; }, $strict>; outpu... import {all_cell_grant_action_specs} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

All cell_grant action specs — composed into all_cell_action_specs.

all_cell_item_action_specs
#

auth/cell_item_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ parent_id: $ZodBranded<ZodUUID, "Uuid", "out">; child_id: $ZodBranded<...>; position: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output:... import {all_cell_item_action_specs} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

All cell_item action specs — composed into all_cell_action_specs.

all_fuz_auth_action_spec_registries
#

auth/all_action_spec_registries.ts view source

readonly FuzAuthActionSpecRegistry[]

type readonly FuzAuthActionSpecRegistry[]

import {all_fuz_auth_action_spec_registries} from '@fuzdev/fuz_app/auth/all_action_spec_registries.js';

Every fuz_auth action-spec registry, in dependency-stable order.

Update this list when a new fuz_auth registry lands. The walker tests (action_spec_input_invariants.test.ts, all_action_spec_registries.acting_biconditional.test.ts) iterate over it — a missing entry silently skips coverage, which is the failure mode the registry-of-registries shape exists to prevent.

all_role_grant_offer_action_specs
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type RequestResponseActionSpec[]

import {all_role_grant_offer_action_specs} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

All role-grant-offer action specs — a codegen-ready registry. Consumers spread this into their own action-spec array to include offer lifecycle + revoke + assign methods in a typed client surface.

all_self_service_role_action_specs
#

auth/self_service_role_action_specs.ts view source

readonly { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type readonly RequestResponseActionSpec[]

import {all_self_service_role_action_specs} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

All self-service role action specs — a codegen-ready registry. Single-element post-unification, kept for symmetry with the other all_*_action_specs exports so codegen and frontend bundles import the same shape.

all_standard_action_specs
#

auth/standard_action_specs.ts view source

readonly { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type readonly RequestResponseActionSpec[]

import {all_standard_action_specs} from '@fuzdev/fuz_app/auth/standard_action_specs.js';

Combined spec registry for the standard RPC surface (admin + role_grant_offer + account). Symmetric with create_standard_rpc_actions.

Spec count is the sum of the three sub-registries. Adding a method to any sub-registry surfaces here automatically.

AllCellActionDeps
#

auth/all_cell_actions.ts view source

AllCellActionDeps

type ActionFactoryDeps & { validate_data?: ((kind: string | null, data: Readonly<{ [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }>) => void) | undefined; authorize_create?: CellCreateAuthorize | undefined; } & { authorize_grant_create?: CellGrantCreateAuthorize | undefined; authorize_grant_revoke?: CellGrantRevokeAuthorize | undefined; }

import type {AllCellActionDeps} from '@fuzdev/fuz_app/auth/all_cell_actions.js';

Dependencies for create_all_cell_actions — the CRUD factory's CellActionDeps plus the grant factory's optional authorize_grant_create and authorize_grant_revoke.

log

Structured logger instance.

type Logger

audit

Bound audit emitter for audit writes.

type AuditEmitter

validate_data?

type (kind: string | null, data: Readonly<{ [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }>): void

kind

type string | null

data

type Readonly<CellData>
returns void

authorize_create?

type CellCreateAuthorize

authorize_grant_create?

type (auth: RequestActorContext, input: CellGrantCreateAuthorizeInput, ctx: ActionContext): boolean | Promise<...>

auth

input

ctx

returns boolean | Promise<boolean>

authorize_grant_revoke?

type (auth: RequestActorContext, input: CellGrantRevokeAuthorizeInput, ctx: ActionContext): boolean | Promise<...>

auth

input

ctx

returns boolean | Promise<boolean>

AllCellActionsOptions
#

ALLOW_ORIGIN_WILDCARDS_VAR
#

http/origin_wildcard.ts view source

"FUZ_ALLOW_ORIGIN_WILDCARDS" import {ALLOW_ORIGIN_WILDCARDS_VAR} from '@fuzdev/fuz_app/http/origin_wildcard.js';

The env var that admits wildcard allowlist entries: exactly true admits them, exactly false (or unset) refuses them, and anything else fails the env schema (BaseServerEnv).

ALLOWED_ORIGINS_ERROR_MESSAGES
#

http/origin.ts view source

Readonly<Record<"empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | ... 7 more ... | "no_origins", string>>

type Readonly<Record<AllowedOriginsErrorKind, string>>

import {ALLOWED_ORIGINS_ERROR_MESSAGES} from '@fuzdev/fuz_app/http/origin.js';

The operator-facing reason for each refusal kind.

ALLOWED_ORIGINS_VAR
#

AllowedOriginsError
#

http/origin.ts view source

AllowedOriginsError import type {AllowedOriginsError} from '@fuzdev/fuz_app/http/origin.js';

One reason parse_allowed_origins refused a value.

kind

type AllowedOriginsErrorKind

readonly

index?

The refused entry's 1-based position; absent for no_origins.

type number

readonly

entry?

The refused entry as given, trimmed; absent for no_origins.

type string

readonly

AllowedOriginsErrorKind
#

http/origin.ts view source

value + type

"empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | ... 7 more ... | "no_origins"

type "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port" | "no_origins"

import {AllowedOriginsErrorKind} from '@fuzdev/fuz_app/http/origin.js';

Why an allowlist value was refused: no_origins (a blank value) or an entry's kind.

announce_rate_limiters_disabled
#

rate_limiter.ts view source

(): void import {announce_rate_limiters_disabled} from '@fuzdev/fuz_app/rate_limiter.js';

Print RATE_LIMITERS_DISABLED_BANNER with console.error, once per module instance.

Deliberately on stderr as well as the surface warning, which goes through the server's logger: a deployment that reached this mode is unprotected on every password-bearing route, and that must show in its first lines of output even with the logger silenced. Once per module instance, so a module instance that assembles many in-process servers prints it once, while every spawned test binary prints it at startup — the twin of the Rust spine's RateLimiterMode::limiter.

returns

void

API_TOKEN_BODY_LENGTH
#

API_TOKEN_COLUMNS
#

auth/api_token_queries.ts view source

readonly ["id", "account_id", "name", "token_hash", "expires_at", "last_used_at", "last_used_ip", "created_at", "scope"] import {API_TOKEN_COLUMNS} from '@fuzdev/fuz_app/auth/api_token_queries.js';

The full api_token column set, named explicitly so a row read fails loud on schema drift (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class; the Rust twin names columns at every token site). Keep in sync with ApiToken and the migration chain's end state.

API_TOKEN_ENTROPY_BYTES
#

auth/api_token.ts view source

32 import {API_TOKEN_ENTROPY_BYTES} from '@fuzdev/fuz_app/auth/api_token.js';

Random bytes encoded into the raw token body (256 bits).

API_TOKEN_ID_REGEX
#

auth/api_token.ts view source

RegExp import {API_TOKEN_ID_REGEX} from '@fuzdev/fuz_app/auth/api_token.js';

Regex for the public API token id (e.g. tok_abC0_d-3xyzA). Twelve base64url characters after the tok_ prefix. Matches the format produced by generate_api_token.

API_TOKEN_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS idx_api_token_account ON api_token(account_id)" import {API_TOKEN_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

API_TOKEN_NAME_LENGTH_MAX
#

auth/account_action_specs.ts view source

255 import {API_TOKEN_NAME_LENGTH_MAX} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Maximum length of an API token's name, in UTF-16 code units (what String.length measures; enforced by bounded_string). The name is copied into the token_create audit row, so it is bounded where it enters. Same value as the Rust spine's API_TOKEN_NAME_LENGTH_MAX.

API_TOKEN_PREFIX
#

auth/api_token.ts view source

"secret_fuz_token_" import {API_TOKEN_PREFIX} from '@fuzdev/fuz_app/auth/api_token.js';

Prefix for all fuz API tokens (enables secret scanning).

API_TOKEN_REGEX
#

API_TOKEN_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS api_token (\n id TEXT PRIMARY KEY,\n account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n token_hash TEXT NOT NULL,\n expires_at TIMESTAMPTZ,\n last_used_at TIMESTAMPTZ,\n last_used_ip TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n)" import {API_TOKEN_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

ApiError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: string; } import {ApiError} from '@fuzdev/fuz_app/http/error_schemas.js';

Base API error — all JSON error responses have at least {error: string}.

error

type string

[key: string]

type unknown

ApiToken
#

auth/account_schema.ts view source

ApiToken import type {ApiToken} from '@fuzdev/fuz_app/auth/account_schema.js';

API token for CLI/programmatic access.

id

type string

account_id

type Uuid

name

type string

token_hash

type string

expires_at

type string | null

last_used_at

type string | null

last_used_ip

type string | null

created_at

type string

scope

The token's authority narrowing (api_token.scope, NOT NULL). Stored as JSONB; read back through parse_token_scope, which is fail-closed — an unreadable document refuses the credential rather than widening it.

type unknown

ApiTokenId
#

auth/api_token.ts view source

value + type

string import {ApiTokenId} from '@fuzdev/fuz_app/auth/api_token.js';

Zod schema for the public API token id.

ApiTokenQueryDeps
#

APP_SERVER_RATE_LIMITER_KEYS
#

server/app_server.ts view source

internal

readonly ("login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter")[]

type readonly ("login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter")[]

import {APP_SERVER_RATE_LIMITER_KEYS} from '@fuzdev/fuz_app/server/app_server.js';

Every AppServerOptions rate-limiter field, in build order.

APP_SETTINGS_COLUMNS
#

auth/app_settings_queries.ts view source

readonly ["id", "open_signup", "updated_at", "updated_by"] import {APP_SETTINGS_COLUMNS} from '@fuzdev/fuz_app/auth/app_settings_queries.js';

The full app_settings column set — the singleton id (always 1) plus the settings the row type carries — drift-guarded like every other *_COLUMNS. Reads project APP_SETTINGS_ROW_COLUMNS, which omits the constant id since AppSettings doesn't carry it. Keep in sync with AppSettings and the app_settings DDL in auth/auth_ddl.ts.

app_settings_get_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ acting: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>>; output: ZodObject<...>; async: true; description: string; } import {app_settings_get_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

app_settings_rpc_context
#

ui/app_settings_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AppSettingsRpc; get_maybe: () => (() => AppSettingsRpc) | undefined; set: (value: () => AppSettingsRpc) => () => AppSettingsRpc; } import {app_settings_rpc_context} from '@fuzdev/fuz_app/ui/app_settings_state.svelte.js';

Svelte context carrying the reactive AppSettingsRpc accessor. Mirrors admin_accounts_rpc_context. get() throws when no provisioner ran above the component — the adapter is required.

APP_SETTINGS_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS app_settings (\n id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),\n open_signup BOOLEAN NOT NULL DEFAULT false,\n updated_at TIMESTAMPTZ,\n updated_by UUID\n)" import {APP_SETTINGS_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

APP_SETTINGS_SEED
#

auth/auth_ddl.ts view source

"\nINSERT INTO app_settings (id) VALUES (1) ON CONFLICT DO NOTHING" import {APP_SETTINGS_SEED} from '@fuzdev/fuz_app/auth/auth_ddl.js';

app_settings_update_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ open_signup: ZodBoolean; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; rate_limit: "account"... import {app_settings_update_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

AppBackend
#

server/app_backend.ts view source

AppBackend import type {AppBackend} from '@fuzdev/fuz_app/server/app_backend.js';

Result of create_app_backend() — database metadata + deps bundle.

This is the initialized backend, not the HTTP server. Pass it to create_app_server() to assemble the Hono app.

deps

type AppDeps

db_type

type DbType

db_name

type string

migration_results

Migration results from create_app_backend — auth migrations plus any consumer namespaces passed via migration_namespaces.

type readonly MigrationResult[]

readonly

close

Close the database connection. Bound to the actual driver.

type (): Promise<void>

returns Promise<void>

AppDeps
#

auth/deps.ts view source

AppDeps import type {AppDeps} from '@fuzdev/fuz_app/auth/deps.js';

Stateless capabilities bundle for fuz_app backends.

Injectable and swappable per environment (production vs test). Does not contain config (static values) or runtime state (mutable refs).

read_secure_file

Hardened secret-file read — used for the bootstrap token (the file that mints the keeper account). Production wiring passes the runtime's read_secure_file (FsSecureReadDeps), which rejects symlinks, non-regular files, group/other-accessible modes, and oversized files; both the boot-time availability probe and the request-time read go through this one capability so the probe can never be laxer than the read it gates.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

delete_file

Delete a file.

type (path: string): Promise<void>

path

type string
returns Promise<void>

keyring

HMAC-SHA256 cookie signing keyring.

type Keyring

password

Password hashing operations. Use argon2_password_deps in production.

type PasswordHashDeps

db

Database instance.

type Db

log

Structured logger instance.

type Logger

audit

Bound audit emitter. Closes over the pool, its registered listeners, and the optional AuditLogConfig. Built once at backend assembly via create_audit_emitter. emit writes a success row in the request transaction when one is open and every other row on the captured pool (auth/audit_emitter.ts, "Where a row is written"). There is no pool slot on the handler context.

type AuditEmitter

connection_closer

Closes live connections — WebSocket and SSE — when a credential is revoked. Every revocation handler queues its close on this (queue_connection_close), so it must reach every registry of the backend: create_app_backend creates it empty, and create_app_server adds each WebSocket registry it mounts and its audit stream registry. A registry mounted by hand is added through the connection_closer option of register_action_ws / create_audit_log_sse.

type RealtimeCloser

fact_store?

Optional content-addressed byte store. Present only on backends that serve binary content (facts) — minimal consumers leave it unset. The consumer constructs a PgFactStore (db/fact_store.ts) over its facts directory (disk_root + fs) at its own backend assembly and assigns it here; create_app_backend stays facts-agnostic. Typed HeldFactStore, not the @fuzdev/fuz_util FactStore: its puts name a holder (HeldFactPutOptions.holder), the possession record cell ref admission reads.

type HeldFactStore

apply_authorization_phase
#

auth/request_context.ts view source

(deps: QueryDeps, account_id: string | null, auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }, acting_value: string | undefined): Promise<...> import {apply_authorization_phase} from '@fuzdev/fuz_app/auth/request_context.js';

Apply the dispatcher's authorization phase against the flat-record RouteAuth shape. Shared by the route-spec wrapper, the HTTP RPC dispatcher, and the per-message WS dispatcher. Phase order: pre-authorization 401 → authorization phase → post-authorization 403 → input validation 400.

Pure data — the function does not touch a Hono context. Each transport passes account_id (extracted from its own credential surface) and binds the returned AuthorizationResult to its wire shape. The REST pipeline additionally writes REQUEST_CONTEXT_KEY on c for downstream require_role / require_credential_types middleware that still reads the resolved context off the Hono context.

Branching by auth.account × auth.actor:

  • Both 'none' → {ok: true, request_context: null}. Public actions never see a RequestContext.
  • account_id == null on any non-public route → same null request_context. The 'required' callers were already rejected at the pre-authorization gate in the dispatcher; only genuine anonymous access on an 'optional' axis lands here.
  • actor === 'none' → builds account-only context via build_account_context. Null lookup → account_vanished 500 failure.
  • actor === 'required' → resolves the actor from acting_value (or single-actor account); failures map to 400 / 500.
  • actor === 'optional' → same as 'required' except multi-actor accounts without an acting value fall back to account-only context (no actor_required 400). Bad acting ids still 400.

500 branches stay distinct: ERROR_NO_ACTORS_ON_ACCOUNT (signup invariant violation), ERROR_ACCOUNT_VANISHED (torn read after resolve).

deps

account_id

type string | null

auth

acting_value

type string | undefined

returns

Promise<AuthorizationResult>

apply_middleware_specs
#

http/route_spec.ts view source

(app: Hono<BlankEnv, BlankSchema, "/">, specs: MiddlewareSpec[]): void import {apply_middleware_specs} from '@fuzdev/fuz_app/http/route_spec.js';

Apply named middleware specs to a Hono app.

app

type Hono<BlankEnv, BlankSchema, "/">

specs

returns

void

mutates

  • app

apply_route_specs
#

http/route_spec.ts view source

(app: Hono<BlankEnv, BlankSchema, "/">, specs: RouteSpec[], resolve_auth_guards: AuthGuardResolver, log: Logger, db: Db, authorize?: AuthorizationHandler | undefined): void import {apply_route_specs} from '@fuzdev/fuz_app/http/route_spec.js';

Apply route specs to a Hono app.

For each spec: resolves auth to guards via the provided resolver, adds input validation middleware (for routes with non-null input schemas), runs the optional authorization phase to resolve the acting actor + build the request context, wraps handler with DEV-only output and error validation, wraps with error catch layer (a thrown JSON-RPC error, recognized via to_thrown_jsonrpc_error, maps to its status; any other throw to a 500), and registers the route.

Per-route middleware order: content type (415, POST only) → params → query → pre-authorization auth guards (401 + rule-3 scope) → authorization phase → post-authorization auth guards (403) → input validation (400) → handler. The content type goes first because it says almost nothing about the route, and a forged request should be refused before any credential is read. Body validation runs behind every authority gate, so a caller any of them refuse never sees route-shape information from a parse failure — a 400 there would confirm the route exists and describe how to call it. Params and query still validate first (they address the route rather than carry its payload, and the authorization phase reads acting off the query on GETs).

Each handler receives a RouteContext with:

  • db: transaction-scoped when RouteSpec.transaction is true; pool-level otherwise
  • pending_effects: eager fire-and-forget pool-write queue
  • post_commit_effects: deferred-thunk queue (push via emit_after_commit)

Also enforces registry-time invariant 2 from the auth-shape design: auth.actor !== 'none' ⟺ input or query declares acting?: ActingActor. REST is bi-located (GETs declare acting on query, mutations on input), so the check passes both slots; the action-dispatcher registries (compile_action_registry) share the same helper with input only — ActionSpec has no query shape.

app

type Hono<BlankEnv, BlankSchema, "/">

specs

type RouteSpec[]

resolve_auth_guards

log

type Logger

db

used for transaction wrapping and RouteContext

type Db

authorize?

optional authorization phase; runs after the pre-authorization guards and before the post-authorization guards

optional

returns

void

throws

  • Error - if two specs share the same `method` + `path` (each combination must be unique), or if any spec violates the actor-acting biconditional

mutates

  • app

AppServer
#

server/app_server.ts view source

AppServer import type {AppServer} from '@fuzdev/fuz_app/server/app_server.js';

Result of create_app_server().

app

type Hono<BlankEnv, BlankSchema, "/">

surface_spec

Surface spec — serializable surface + raw specs that produced it.

type AppSurfaceSpec

bootstrap_status

type BootstrapStatus

migration_results

Migration results from create_app_backend (auth + any migration_namespaces passed there).

type readonly MigrationResult[]

audit_sse

Factory-managed audit log SSE. Non-null when the audit_log_sse option was passed to create_app_server, null when omitted. Use require_audit_sse(server) to assert the invariant.

type AuditLogSse | null

ws_endpoints

Path-keyed map of mounted WS endpoints. Each value is the ConnectionRegistry create_app_server registered connections against — supplied via WsEndpointSpec.registry or auto-created when omitted. Retain for broadcast / fan-out:

app_server.ws_endpoints['/api/ws'].send_to_account(account_id, msg);

Empty when no ws_endpoints were mounted.

type Readonly<Record<string, ConnectionRegistry>>

close

Shut the server's resources down, in order:

  1. stop the auth cleanup schedule when the auth_cleanup option started one, waiting for a pass in progress — that wait has no bound of its own, so a shutdown with a deadline races it
  2. close every live connection on deps.connection_closer (close_all_connections) — each WebSocket with WS_CLOSE_GOING_AWAY, each audit stream — including registries supplied through WsEndpointSpec.registry or added by hand, since they are registered on the backend's closer and the backend is going away; a close that throws is logged and the shutdown goes on. Each connection and SSE registry is left closing, so an upgrade or stream that registers afterward is born closed — refused at once rather than outliving the server — and a closed registry hosts no other server
  3. release what assembly acquired, last acquired first: remove the audit listeners create_app_server registered on deps.audit (the revocation guard, the audit-log SSE listener) and the closer members it added to deps.connection_closer (each mounted connection registry, the audit stream registry), and dispose the rate limiters it built for omitted options — never one passed in, which its caller owns. A release that throws is logged and the rest still run. The backend's emitter and closer are left as they were before assembly, so a failed assembly can be retried on the same backend, and a backend whose close you own (a test harness) can host another server
  4. close the database connection (AppBackend.close)

Idempotent: a second call settles with the first one's outcome (a failed close is not retried), and concurrent calls share one shutdown.

Stop accepting new HTTP connections first — without awaiting the listener's full drain, since live WebSockets hold it open until close ends them — so no new request reaches a closed database.

type (): Promise<void>

returns Promise<void>

AppServerContext
#

server/app_server_context.ts view source

AppServerContext import type {AppServerContext} from '@fuzdev/fuz_app/server/app_server_context.js';

Context passed to create_route_specs.

deps

type AppDeps

backend

type AppBackend

bootstrap_status

type BootstrapStatus

session_options

type SessionOptions<string>

login_ip_rate_limiter

Per-IP login + password-change rate limiter. One instance per auth surface — see AppServerOptions.login_ip_rate_limiter for why these aren't shared.

Each limiter here is what create_app_server resolved: the instance its option passed, else a default instance under rate_limiters: 'enforced' (the default). null when disabled — the option was an explicit null, or the mode is 'disabled_for_testing'.

type RateLimiter | null

signup_ip_rate_limiter

Per-IP signup rate limiter. null when disabled.

type RateLimiter | null

bootstrap_ip_rate_limiter

Per-IP bootstrap rate limiter. null when disabled.

type RateLimiter | null

login_account_rate_limiter

Per-account login rate limiter. null when disabled.

type RateLimiter | null

signup_account_rate_limiter

Per-account signup rate limiter. null when disabled.

type RateLimiter | null

action_ip_rate_limiter

Per-IP action-dispatcher rate limiter — shared across HTTP RPC + WS. null when disabled.

type RateLimiter | null

action_account_rate_limiter

Per-account action-dispatcher rate limiter, keyed on the authenticated account's id — shared across HTTP RPC + WS. null when disabled.

type RateLimiter | null

audit_sse

Factory-managed audit log SSE. Non-null when the audit_log_sse option was passed to create_app_server, null when omitted. Use require_audit_sse(ctx) to assert the invariant.

type AuditLogSse | null

AppServerOptions
#

server/app_server.ts view source

AppServerOptions import type {AppServerOptions} from '@fuzdev/fuz_app/server/app_server.js';

Configuration for create_app_server().

Requires a pre-initialized AppBackend from create_app_backend(). Two explicit steps: init backend then assemble server.

backend

Pre-initialized backend from create_app_backend().

type AppBackend

session_options

Session options for cookie-based auth.

type SessionOptions<string>

allowed_origins

Parsed allowed origin patterns, as parse_allowed_origins returns them (validate_server_env's allowed_origins); assembly throws on a matcher built any other way, since its wildcards can't be checked. Must not be empty — an empty list would refuse every browser request, so assembly throws.

type RegExp[]

allow_origin_wildcards?

Admit a wildcard allowed_origins entry (:*, a * label) — the operator's FUZ_ALLOW_ORIGIN_WILDCARDS=true, as validate_server_env reads it. Without it assembly throws on one, naming each entry and what it admits; with it each is logged at warn, on every assembly. Covers each ws_endpoints spec's own allowed_origins too. Classifies the matchers parse_allowed_origins built (http/origin_wildcard.ts); assembly throws on any other matcher, flag or not. Defaults to false.

type boolean

origin_carve_outs?

Paths the origin check skips, each with its reason. The check covers every path by default (ORIGIN_MIDDLEWARE_PATH) — a request with no Origin passes it anyway, so probes and CLI clients need no carve-out; one is for a path that refuses browser callers by other means. Copied at assembly, so a later change to this array changes nothing; assembly throws on a carve-out with no reason, or one that overlaps the auth middleware's scope (AUTH_MIDDLEWARE_PATH) — every route that reads or mints a credential, and every RPC, WS, and bootstrap endpoint, sits there (assert_origin_carve_outs_valid).

type MiddlewareCarveOut[]

proxy

Trusted proxy options.

type { trusted_proxies: string[]; get_connection_ip: (c: Context<any, any, {}>) => string | undefined; }

rate_limiters?

What an omitted rate limiter becomes — 'enforced' (the default) builds a default instance for each *_rate_limiter option left out, 'disabled_for_testing' leaves each of them null. An explicit option (an instance or null) always wins over the mode. The disabled mode adds one surface warning, and prints a stderr banner once per module instance (announce_rate_limiters_disabled). Never 'disabled_for_testing' in production — see RateLimiterMode.

type RateLimiterMode

login_ip_rate_limiter?

Per-IP rate limiter for login + password change — the distributed-spray backstop. Omit or undefined to use a default limiter (5 attempts per 15 minutes) under rate_limiters: 'enforced', none under 'disabled_for_testing'. Pass null to explicitly disable rate limiting. Also available on AppServerContext for route factory callbacks.

One instance per auth surface, not one shared across all four. These buckets are monotone within their window — a success never refunds them (see RateLimiter.reset) — so a shared instance let a failure on any surface spend the budget that bounds guessing on every other one, and let one caller's exhaustion deny four routes at once. Pass the same limiter to two of these fields to opt back into a shared budget.

type RateLimiter | null

signup_ip_rate_limiter?

Per-IP rate limiter for signup. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter because an open-signup deployment lets any unauthenticated caller spend it. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

bootstrap_ip_rate_limiter?

Per-IP rate limiter for bootstrap. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter so a fumbled bootstrap token can't spend the operator's login budget. Wired directly into the factory-managed bootstrap route; also on AppServerContext for symmetry.

type RateLimiter | null

login_account_rate_limiter?

Per-account rate limiter for login attempts. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

signup_account_rate_limiter?

Per-account rate limiter for signup attempts, keyed by submitted username. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

action_ip_rate_limiter?

Per-IP rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'ip' or 'both'. Same limiter applies across transports — one budget per action. Omit or undefined for the mode's default (600 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

action_account_rate_limiter?

Per-account rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on the authenticated account's id, so every actor and credential of one account shares a budget. Omit or undefined for the mode's default (1200 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

max_body_size?

Maximum allowed request body size in bytes, a safe integer 0 or more (anything else throws at assembly). Omit or undefined to use the default (1 MiB). Pass null to explicitly disable body size limiting, which adds a security warning to the surface's diagnostics (logged at startup).

type number | null

daemon_token_state?

Daemon token state for keeper auth. Omit to disable.

type DaemonTokenState

bootstrap?

Bootstrap options. Omit to skip bootstrap status check and routes.

type BootstrapServerOptions

surface_route?

Mount GET /api/surface, which serves this server's generated AppSurface (every route, its auth, and its schemas) to an admin UI such as AdminSurface.svelte. Off by default — the Rust spine serves no such route, and a map of the API is nothing to publish unasked.

When mounted the route is admin-only (roles: ['admin'], so it takes the ?acting= selector) and a rule-3 token surface (required_scope: 'surface:app_surface'): a narrowed API token is refused it whatever its method list says. See server/surface_route.ts.

type boolean

default false

create_route_specs

Build route specs from the initialized backend. Called after all middleware is ready.

type (context: AppServerContext): RouteSpec[]

context

returns RouteSpec[]

transform_middleware?

Optional: transform middleware specs before applying.

Receives shallow copies of the trusted-proxy spec followed by the auth stack, in a fresh array — mutating them leaves the originals untouched. Add layers anywhere (before the proxy spec, between it and the auth stack, between the auth layers, or after), but keep each of those specs — its handler, not just its name — mounted once at its original path ('*' for the proxy and the origin check, AUTH_MIDDLEWARE_PATH for the rest of the auth stack) in its original order: the endpoint auth-scope check (assert_endpoint_in_auth_scope) and client-IP rate limiting (get_client_ip) assume it, so assembly throws (assert_middleware_stack_mounted) if the transform moves, drops, duplicates, reorders, wraps, or replaces one. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access.

type (specs: MiddlewareSpec[]): MiddlewareSpec[]

specs

returns MiddlewareSpec[]

audit_log_sse?

Enable factory-managed audit log SSE.

When truthy, creates an AuditLogSse instance internally, registers the SSE listener via backend.deps.audit.add_listener (composing with the consumer's on_audit_event callback rather than rebuilding AppDeps), adds its registry to backend.deps.connection_closer so revocations close its streams, and auto-includes audit_log_event_specs in the surface. The result is exposed on AppServerContext (for route factories) and AppServer (for the caller), always typed as AuditLogSse | null — when this option is set, the field is non-null. Use require_audit_sse(ctx) to assert the invariant in route factories that depend on it.

Pass true for defaults (admin role), or {role: 'custom'} for a custom role; keep_alive_ms sets the streams' keep-alive interval (a test server shortens it; default SSE_KEEP_ALIVE_INTERVAL_MS). Omit to wire audit SSE manually.

type true | { role?: string | undefined; keep_alive_ms?: number | null | undefined; }

auth_cleanup?

Schedule the auth cleanup beside the server: start_auth_cleanup over backend.deps — a pass as assembly finishes, then one per interval — stopped by AppServer.close. Each pass deletes expired sessions and closes the connections they opened through deps.connection_closer (every WebSocket connection registry and audit stream this server mounts), and audits each newly expired role_grant offer once.

Off by default; a production server should turn it on. Without it nothing sweeps: expired session rows accumulate and an offer's expiry is never audited. Connections do not depend on it — each closes at its credential's deadline (auth/credential_deadline.ts), and the sweep's close repeats that one. The Rust spine's servers all schedule the twin (spawn_auth_cleanup).

Pass true for the default interval (DEFAULT_AUTH_CLEANUP_INTERVAL_MS), or {interval_ms} for another. Leave it off in an in-process test harness — a background pass deletes rows and writes audit rows mid-test — and in a process that runs run_auth_cleanup on a schedule of its own.

type boolean | AuthCleanupScheduleOptions

event_specs?

SSE event specs for surface generation. Defaults to [] (no SSE events).

type EventSpec[]

rpc_endpoints?

RPC endpoint specs — single source of truth for both surface generation *and* live dispatch. Each entry becomes route specs via create_rpc_endpoint, applied with the other routes, so consumers don't call create_rpc_endpoint themselves inside create_route_specs.

Accepts either an array (evaluated eagerly) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> (evaluated after the server context is assembled). Use the factory form when action lists depend on ctx.deps — e.g. create_standard_rpc_actions(ctx.deps).

Each path must sit inside the auth middleware's scope (AUTH_MIDDLEWARE_PATH, '/api/*') — assembly throws otherwise, since an endpoint outside it would see every caller as unauthenticated.

type RpcEndpointSpec[] | ((context: AppServerContext) => RpcEndpointSpec[])

create_upgrade_websocket?

Returns the Hono runtime adapter's upgradeWebSocket helper for the assembled app. create_app_server calls it once, with the app it builds (the same instance as AppServer.app), after creating the app and before mounting ws_endpoints — and only when ws_endpoints resolves non-empty, so it is never called for a server with no WebSocket endpoints. Required in that case: assembly throws without it. One helper services every mounted endpoint.

A factory rather than the helper itself because Node's adapter needs the app: @hono/node-ws has no module-level helper, only createNodeWebSocket({app}), and create_app_server is what builds the app. Deno and Bun ignore the argument:

import {upgradeWebSocket} from '@hono/deno'; // or '@hono/bun' create_upgrade_websocket: () => upgradeWebSocket,

Node keeps the adapter so it can attach to the server after serve():

let node_ws: NodeWebSocket | undefined; const server = await create_app_server({ ..., create_upgrade_websocket: (app) => (node_ws = createNodeWebSocket({app})).upgradeWebSocket, }); node_ws?.injectWebSocket(serve({fetch: server.app.fetch, port}));

Test harnesses pass () => stub.upgradeWebSocket with create_stub_upgrade from testing/ws_round_trip.ts.

type (app: Hono<BlankEnv, BlankSchema, "/">): UpgradeWebSocket<unknown, any, WSEvents<unknown>>

app

type Hono<BlankEnv, BlankSchema, "/">
returns UpgradeWebSocket<unknown, any, WSEvents<unknown>>

ws_endpoints?

WebSocket endpoint specs — single source of truth for both surface generation *and* live dispatch. Each entry is auto-mounted via register_action_ws against the assembled Hono app on every runtime, so consumers don't call register_action_ws themselves.

An entry that omits actions serves the actions of the sole rpc_endpoints entry (to_ws_actions: the protocol actions first, then the RPC set), so one action set runs on both transports; assembly throws unless exactly one RPC endpoint resolves.

Accepts either an array (evaluated eagerly) or a factory (ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec> (evaluated after the server context is assembled). Use the factory form when action lists depend on ctx.deps — e.g. when spreading create_standard_rpc_actions({...ctx.deps, notification_sender}) over WS.

When non-empty, create_upgrade_websocket must be supplied (throws otherwise). A factory returning [] does NOT trip the check — feature-flag gated WS surfaces stay safe.

Every mount gets what the HTTP surface gets: the auth middleware (a path outside AUTH_MIDDLEWARE_PATH throws at assembly, like rpc_endpoints), the server's allowed_origins unless the spec narrows them (a spec's own list gets the same wildcard check, allow_origin_wildcards), and the same action_ip_rate_limiter / action_account_rate_limiter instances the RPC endpoints charge — one budget per action across both transports. The upgrade routes register after the route specs and before post_route_middleware and static_serving, so neither runs ahead of an upgrade.

Duplicate path values across two WsEndpointSpecs, and a path a GET RouteSpec already claims, throw at assembly before any endpoint mounts (Hono would silently shadow them otherwise).

Every mounted connection registry is added to deps.connection_closer, so the revocation handlers' post-commit closes reach its sockets, and so does the one revocation guard create_app_server registers on that closer (create_revocation_guard) when it mounts a WS endpoint or the audit stream. AppServer.close removes it again.

type readonly WsEndpointSpec[] | ((context: AppServerContext) => readonly WsEndpointSpec[])

env_schema?

Env schema for surface generation. Defaults to BaseServerEnv — pass an extended schema (typically BaseServerEnv.extend({...})) when the consumer adds app-specific env vars.

type ZodObject<$ZodLooseShape, $strip>

post_route_middleware?

Middleware applied after routes, before static serving. Included in surface.

type MiddlewareSpec[]

static_serving?

Static file serving. Omit if not serving static files.

type { serve_static: ServeStaticFactory; root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; }

await_pending_effects?

Await all pending fire-and-forget effects before returning the response. Use in tests so audit log assertions don't need polling. Default false (production: true fire-and-forget).

type boolean

on_effect_error?

Called when an eager pending effect rejects, on HTTP requests and on the frames of every WS endpoint the server mounts, in either await_pending_effects mode. Use for monitoring, metrics, or alerting.

type (error: unknown, context: EffectErrorContext): void

error

type unknown

context

returns void

env_values?

Env values for startup summary logging.

type Record<string, unknown>

AppServerRateLimiterKey
#

server/app_server.ts view source

internal

"login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter"

type "login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter"

import type {AppServerRateLimiterKey} from '@fuzdev/fuz_app/server/app_server.js';

Name of an AppServerOptions rate-limiter field — also the AppServerContext field its resolved limiter lands on.

AppSettings
#

auth/app_settings_schema.ts view source

AppSettings import type {AppSettings} from '@fuzdev/fuz_app/auth/app_settings_schema.js';

App settings row from the database.

open_signup

type boolean

updated_at

type string | null

updated_by

type Uuid | null

AppSettingsCrossTestOptions
#

testing/cross_backend/app_settings.ts view source

RpcPathCrossSuiteOptions import type {AppSettingsCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/app_settings.js';

Options for the app-settings effect suite (the standard RPC-dispatched shape).

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

AppSettingsGetInput
#

auth/admin_action_specs.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; } import {AppSettingsGetInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for app_settings_get.

acting?

type string & $brand<"Uuid">

AppSettingsGetOutput
#

auth/admin_action_specs.ts view source

value + type

{ settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }; } import {AppSettingsGetOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for app_settings_get.

settings

type AppSettingsWithUsernameJson

AppSettingsJson
#

auth/app_settings_schema.ts view source

value + type

{ open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; } import {AppSettingsJson} from '@fuzdev/fuz_app/auth/app_settings_schema.js';

Zod schema for client-safe app settings data.

open_signup

type boolean

updated_at

type string | null

updated_by

type (string & $brand<"Uuid">) | null

AppSettingsRpc
#

ui/app_settings_state.svelte.ts view source

AppSettingsRpc import type {AppSettingsRpc} from '@fuzdev/fuz_app/ui/app_settings_state.svelte.js';

Narrow RPC surface consumed by AppSettingsState. Consumers adapt their typed RPC client to this shape. Method signatures track the wire spec inputs/outputs directly so the adapter needs no casts.

get

type (params?: { acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }; }>

params?

optional
returns Promise<AppSettingsGetOutput>

update

type (params: { open_signup: boolean; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<...>) | null; updated_by_username: string | null; }; }>

params

returns Promise<AppSettingsUpdateOutput>

AppSettingsState
#

ui/app_settings_state.svelte.ts view source

import {AppSettingsState} from '@fuzdev/fuz_app/ui/app_settings_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

update

type AsyncSlot<void, string>

readonly

settings

type AppSettingsWithUsernameJson | null

$state.raw

constructor

type new (options: AppSettingsStateOptions): AppSettingsState

options

fetch

type (): Promise<void>

returns Promise<void>

update_open_signup

type (value: boolean): Promise<void>

value

type boolean
returns Promise<void>

AppSettingsStateOptions
#

AppSettingsUpdateInput
#

auth/admin_action_specs.ts view source

value + type

{ open_signup: boolean; acting?: (string & $brand<"Uuid">) | undefined; } import {AppSettingsUpdateInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for app_settings_update.

open_signup

type boolean

acting?

type string & $brand<"Uuid">

AppSettingsUpdateOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }; } import {AppSettingsUpdateOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for app_settings_update.

ok

type true

settings

type AppSettingsWithUsernameJson

AppSettingsWithUsernameJson
#

auth/app_settings_schema.ts view source

value + type

{ open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; } import {AppSettingsWithUsernameJson} from '@fuzdev/fuz_app/auth/app_settings_schema.js';

Zod schema for admin app settings with resolved updater username.

open_signup

type boolean

updated_at

type string | null

updated_by

type (string & $brand<"Uuid">) | null

updated_by_username

type string | null

AppShell
#

ui/AppShell.svelte view source

accepts children

import AppShell from '@fuzdev/fuz_app/ui/AppShell.svelte';

children

type Snippet<[]>

sidebar

type Snippet<[]>

sidebar_width?

Sidebar width in pixels when shown; in narrow mode capped at 85vw.

type number
optional default 180

sidebar_label?

Accessible name for the sidebar while it's the narrow-mode overlay, announced as a modal dialog.

type string
optional default 'sidebar'

sidebar_state?

Optional pre-built SidebarState for sharing visibility across shells.

optional

keyboard_shortcut?

Single-key shortcut that toggles the sidebar (e.g. 'b') while it's enabled and no modal is open — the persistent sidebar in wide mode, the overlay in narrow mode. false disables.

type string | false
optional default false

show_toggle?

Whether to render the built-in (or custom) toggle button. It renders only while the sidebar is enabled either way.

type boolean
optional default true

toggle_button?

Custom toggle-button renderer; receives the title, visibility, toggle callback, and the sidebar element's id. Give the button class="app-shell-toggle" so it stacks above the sidebar and scrim (it renders as a direct child of the root) — the z-index applies only to a positioned button, so position it (e.g. position: fixed) — and aria-controls={sidebar_id} so closing can return focus to it. show_sidebar is viewport-dependent: the server renders it as in wide mode, so rendering it as visible content changes on hydration at narrow widths — attributes are repaired, but key visible content on CSS or the viewport-free fields instead.

type Snippet<[{ title: string; show_sidebar: boolean; toggle: () => void; sidebar_id: string; }]>
optional
snippet parameters
arg0 { title: string; show_sidebar: boolean; toggle: () => void; sidebar_id: string; }

class?

type ClassValue | null
optional

CSS custom properties

--sidebar_bg
fallback var(--shade_05)
--sidebar_z_index
fallback 200
--sidebar_scrim_bg
fallback var(--darken_40)

external types

SvelteHTMLElements['div']

AppSurface
#

AppSurfaceDiagnostic
#

http/surface.ts view source

AppSurfaceDiagnostic import type {AppSurfaceDiagnostic} from '@fuzdev/fuz_app/http/surface.js';

Assembly-time diagnostic collected during surface generation or server assembly.

level

type "warning" | "info"

category

type string

message

type string

source?

type string

AppSurfaceEnv
#

http/surface.ts view source

AppSurfaceEnv import type {AppSurfaceEnv} from '@fuzdev/fuz_app/http/surface.js';

An env var in the generated attack surface (JSON-serializable).

name

type string

description

type string

sensitivity

Sensitivity level from .meta({sensitivity}). null when not sensitive.

type Sensitivity | null

has_default

type boolean

optional

type boolean

AppSurfaceEvent
#

http/surface.ts view source

AppSurfaceEvent import type {AppSurfaceEvent} from '@fuzdev/fuz_app/http/surface.js';

An SSE event in the generated attack surface (JSON-serializable).

method

type string

description

type string

channel

type string | null

params_schema

type unknown

AppSurfaceMiddleware
#

http/surface.ts view source

AppSurfaceMiddleware import type {AppSurfaceMiddleware} from '@fuzdev/fuz_app/http/surface.js';

A middleware in the generated attack surface (JSON-serializable).

name

type string

path

type string

error_schemas

JSON Schema representations of error responses, keyed by HTTP status code. null when none.

type Record<string, unknown> | null

carve_outs?

Paths under path the layer does not run on, each with its reason. Absent when none.

type { path: string; reason: string; }[]

AppSurfaceRoute
#

http/surface.ts view source

AppSurfaceRoute import type {AppSurfaceRoute} from '@fuzdev/fuz_app/http/surface.js';

A route in the generated attack surface (JSON-serializable).

method

type string

path

type string

auth

type RouteAuth

applicable_middleware

type string[]

description

type string

is_mutation

Whether this route mutates state (POST, PUT, DELETE, PATCH).

type boolean

transaction

Whether this route's handler runs inside a database transaction.

type boolean

raw_body

Whether this route carries raw bytes / a streaming protocol rather than JSON (see RouteSpec.raw_body). When true, input_schema / output_schema being null means "raw bytes", not "no body".

type boolean

rate_limit_key

Rate limit key type declared on the route spec. null when not rate-limited.

type RateLimitKey | null

params_schema

JSON Schema representation of the URL path params schema. null when no params.

type unknown

query_schema

JSON Schema representation of the URL query params schema. null when no query schema.

type unknown

input_schema

JSON Schema representation of the request body schema. null for no-body routes.

type unknown

output_schema

JSON Schema representation of the success response schema.

type unknown

error_schemas

JSON Schema representations of error responses, keyed by HTTP status code. null when none.

type Record<string, unknown> | null

AppSurfaceRpcEndpoint
#

http/surface.ts view source

AppSurfaceRpcEndpoint import type {AppSurfaceRpcEndpoint} from '@fuzdev/fuz_app/http/surface.js';

An RPC endpoint in the generated attack surface (JSON-serializable).

path

type string

methods

type AppSurfaceRpcMethod[]

AppSurfaceRpcMethod
#

http/surface.ts view source

AppSurfaceRpcMethod import type {AppSurfaceRpcMethod} from '@fuzdev/fuz_app/http/surface.js';

A method within an RPC endpoint in the generated attack surface (JSON-serializable).

name

type string

auth

type RouteAuth

input_schema

JSON Schema representation of the input schema. null for null-input methods.

type unknown

output_schema

JSON Schema representation of the output schema.

type unknown

side_effects

type boolean

description

type string

rate_limit_key

Rate limit key declared on the action spec. null when not rate-limited.

type RateLimitKey | null

AppSurfaceSpec
#

http/surface.ts view source

AppSurfaceSpec import type {AppSurfaceSpec} from '@fuzdev/fuz_app/http/surface.js';

The surface bundled with the source specs that produced it.

AppSurface is JSON-serializable (snapshots, UI, startup logging) — it's the observability layer, written to disk by gro gen for human inspection + drift detection.

AppSurfaceSpec is runtime-only — tests, introspection, attack surface assertions. Both in-process and cross-process tests construct an AppSurfaceSpec in TS via create_test_app_surface_spec (or a consumer equivalent); the cross-process-ness lives in the transport + per-test fixture, not the schema source.

surface

type AppSurface

route_specs

type RouteSpec[]

middleware_specs

type MiddlewareSpec[]

rpc_endpoints

type RpcEndpointSpec[]

ws_endpoints

type ResolvedWsEndpointSpec[]

AppSurfaceWsEndpoint
#

http/surface.ts view source

AppSurfaceWsEndpoint import type {AppSurfaceWsEndpoint} from '@fuzdev/fuz_app/http/surface.js';

A WebSocket endpoint in the generated attack surface (JSON-serializable).

path

type string

allowed_origins

Upgrade-time origin allowlist — the spec's own allowed_origins, or the server's when it declares none — one entry per regex, stringified via RegExp.prototype.toString() ('/<source>/<flags>'). An empty array refuses every request that carries an Origin. Reviewers read this as the exact pattern matched at the upgrade gate, not a wildcard approximation. Reconstruct via new RegExp(source, flags) if needed.

type readonly string[]

required_roles

Upgrade-time role gate — empty array when no required_roles was declared (any-authenticated). Documents the coarse gate; per-action auth on each method covers per-message authorization.

type readonly string[]

methods

type AppSurfaceWsMethod[]

AppSurfaceWsMethod
#

http/surface.ts view source

AppSurfaceWsMethod import type {AppSurfaceWsMethod} from '@fuzdev/fuz_app/http/surface.js';

A method within a WebSocket endpoint in the generated attack surface (JSON-serializable).

name

type string

kind

request_response (inbound dispatch) or remote_notification (server → client).

type ActionKind

auth

Per-action auth shape. null for remote_notification (server → client) — notifications have no inbound dispatch and therefore no auth axis. request_response always carries a RouteAuth.

type RouteAuth | null

input_schema

JSON Schema of the input schema. null for nullary inputs.

type unknown

output_schema

JSON Schema of the output schema.

type unknown

description

type string

side_effects

type boolean

rate_limit_key

Rate limit key declared on the action spec. null when not rate-limited.

type RateLimitKey | null

argon2_password_deps
#

assert_404_schemas_use_specific_errors
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_404_schemas_use_specific_errors} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Routes declaring 404 error schemas should use specific z.literal() or z.enum() error codes, not generic z.string().

A generic 404 schema (ApiError with z.string()) means the error code is unconstrained — the handler could return any string, making client error handling fragile. Routes with params (:id) are the primary 404 producers; their error schemas should use specific constants like ERROR_ACCOUNT_NOT_FOUND.

Only flags routes that have params_schema (param-driven resource lookup) — routes declaring 404 for other reasons (e.g., bootstrap not configured) may legitimately use generic schemas.

surface

returns

void

assert_action_manifests_equal
#

testing/cross_backend/action_manifest_parity.ts view source

(a: { methods: { method: string; input: "object" | "void"; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; }[]; }, b: { ...; }, labels?: ActionManifestDiffLabels): void import {assert_action_manifests_equal} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest_parity.js';

Throw if the two manifests disagree. The error message names the impls (via labels) and lists every diff, so the failure is self-diagnosing.

a

b

labels?

optional default {}

returns

void

assert_audit_event_metadata
#

testing/audit_drift_guard.ts view source

(event: Pick<{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 4 more ...; metadata: Record<...> | null; }, "event_type" | ... 1 more ... | "metadata">, options?: AssertAuditEventMetadataOptions | undefined): void import {assert_audit_event_metadata} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Assert an audit row read off the wire carries the metadata its event type declares — parsed against audit_metadata_schemas[event_type] as declared, so a mistyped, null, or missing field fails, and with reject_unknown_keys so does a key the event doesn't declare. The wire schema (AuditLogEventJson.metadata) is a loose record, and only the TS spine validates metadata, at insert and fail-open, so this is the check that holds a spine's rows to their event's contract.

Builtin event types only: a consumer-registered type has no entry here, and passes unexamined.

event

an audit row from audit_log_list or an audit read like it

type Pick<AuditLogEventJson, "event_type" | "outcome" | "metadata">

options?

reject_unknown_keys for the closed-world check

optional

returns

void

throws

  • AssertionError - naming the event type and the schema issues

assert_audit_metadata_fields
#

testing/cross_backend/audit_cross_helpers.ts view source

(fixture: Pick<AuditReadFixture, "reject_unknown_audit_metadata_keys">, metadata: Record<string, unknown> | null, expected: Record<string, unknown>, absent?: readonly string[]): void import {assert_audit_metadata_fields} from '@fuzdev/fuz_app/testing/cross_backend/audit_cross_helpers.js';

Assert an audit row's metadata carries expected — each field compared on its own, so a consumer's added key passes — and none of absent. When the fixture's setup closed the world (reject_unknown_audit_metadata_keys), the whole object must equal expected, so an undeclared key fails too.

fixture

the per-test fixture, whose flag picks the comparison

type Pick<AuditReadFixture, "reject_unknown_audit_metadata_keys">

metadata

the row's metadata

type Record<string, unknown> | null

expected

the declared fields and their values

type Record<string, unknown>

absent?

keys the row must not carry, e.g. an optional field left unset

type readonly string[]
optional default []

returns

void

assert_close_call
#

testing/connection_closer_helpers.ts view source

(call: RecordedClose | undefined, expected: RecordedClose): void import {assert_close_call} from '@fuzdev/fuz_app/testing/connection_closer_helpers.js';

Pin a single recorded close call to expected (deep equality).

Throws via assert.ok if call is undefined — index a recorded calls array directly (calls[0]) and let this helper handle the missing-element case.

call

type RecordedClose | undefined

expected

returns

void

assert_columns_match_live
#

testing/db.ts view source

(db: Db, table: string, columns: readonly string[]): Promise<void> import {assert_columns_match_live} from '@fuzdev/fuz_app/testing/db.js';

Assert a *_COLUMNS projection const names exactly the live columns of table — the drift guard for an exported const (ACCOUNT_COLUMNS, CELL_COLUMNS, a consumer's own).

This is the reverse of the fail-loud read: a named projection makes a *dropped* column fail loud at query time, but a column *added* to the table and not to the projection would silently vanish from every row read. Both directions are covered by the equality here. It guards one const; it can't see a table that has *no* const — for that, keep a table → const registry and assert its key set against query_public_columns (fuz_app's own is src/test/db/column_projections.db.test.ts).

db

a bootstrapped test database

type Db

table

the public-schema table the const projects

type string

columns

the projection const's column names

type readonly string[]

returns

Promise<void>

assert_credential_routes_in_auth_scope
#

auth/middleware.ts view source

(route_specs: readonly RouteSpec[]): void import {assert_credential_routes_in_auth_scope} from '@fuzdev/fuz_app/auth/middleware.js';

Throw when a route that reads or mints a credential sits outside the auth middleware's scope (AUTH_MIDDLEWARE_PATH). A route reads one when its auth.account is not 'none', or when it is marked reads_credential (a handler that reads the resolved account behind no gate: account status) — outside the scope no session or bearer parsing runs, so it would answer every caller as unauthenticated. A route mints one when it is marked mints_credential (login, signup, bootstrap) — outside the scope an origin carve-out could cover it (assert_origin_carve_outs_valid refuses one only inside), and a forged cross-site request to it is login CSRF.

Account and signup route specs are relative and the consumer prefixes them, so this is what holds them inside the scope whatever prefix they get. create_app_server runs it over every route it registers, consumer and factory alike, and create_test_app_surface_spec does the same.

route_specs

every route the server registers

type readonly RouteSpec[]

returns

void

throws

  • Error - naming the first route that reads or mints a credential outside `AUTH_MIDDLEWARE_PATH`

assert_descriptions_present
#

assert_endpoint_in_auth_scope
#

auth/middleware.ts view source

(option: "rpc_endpoints" | "ws_endpoints" | "bootstrap.route_prefix", path: string): void import {assert_endpoint_in_auth_scope} from '@fuzdev/fuz_app/auth/middleware.js';

Throw when an endpoint path sits outside the auth middleware's scope (AUTH_MIDDLEWARE_PATH) — there it would get no session or bearer parsing, so every call or upgrade would be unauthenticated. middleware_applies matches the way Hono matches the middleware's pattern.

create_app_server calls it for every rpc_endpoints and ws_endpoints path and the bootstrap route at assembly, and create_test_app_surface_spec does the same, so a surface snapshot never records an endpoint the real server refuses.

option

the option the path came from, named in the error

type "rpc_endpoints" | "ws_endpoints" | "bootstrap.route_prefix"

path

the endpoint path to check

type string

returns

void

throws

  • Error - when `path` is outside `AUTH_MIDDLEWARE_PATH`

assert_error_code_status_consistency
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_error_code_status_consistency} from '@fuzdev/fuz_app/testing/surface_invariants.js';

The same z.literal() error code should not appear at different HTTP status codes across routes.

Extracts const values from error schema error properties (which correspond to z.literal() in the Zod source). Walks union branches (anyOf from z.union, oneOf from z.discriminatedUnion) so literal codes nested inside merged unions (e.g. validation 400 + actor-resolution 400) are still tracked. Flags when the same literal appears at different status codes — e.g., ERROR_INVALID_CREDENTIALS at both 401 and 403 would be a bug.

Only checks const values (literal schemas). Generic z.string() schemas (which produce {type: 'string'}) and z.enum() schemas are ignored — the literal-only narrow keeps the check unambiguous.

surface

returns

void

assert_error_coverage
#

testing/error_coverage.ts view source

(collector: ErrorCoverageCollector, route_specs: RouteSpec[], options?: ErrorCoverageOptions | undefined): void import {assert_error_coverage} from '@fuzdev/fuz_app/testing/error_coverage.js';

Assert error coverage meets a minimum threshold.

Computes the ratio of exercised error paths to total declared error paths. For routes whose status error schema names specific codes (z.literal or z.enum), each declared code counts as one coverage path; for schemas without declared codes (ApiError/z.string()), the status counts as one path. A status-only observation covers all declared codes for that status (the "any-code" rule).

When min_coverage is 0 (default), logs coverage info without failing. When > 0, fails if coverage is below the threshold.

collector

route_specs

type RouteSpec[]

options?

optional

returns

void

throws

  • AssertionError - if `min_coverage > 0` and the covered/total ratio falls below the threshold — the failure message lists every uncovered route + status (+ code).

assert_error_schema_tightness
#

testing/surface_invariants.ts view source

(surface: AppSurface, options?: ErrorSchemaTightnessOptions | undefined): void import {assert_error_schema_tightness} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Assert that all error schemas meet a minimum specificity threshold.

Calls audit_error_schema_tightness and fails on any entry below the configured threshold. Use allowlist and ignore_statuses to exclude known exceptions during progressive tightening.

surface

options?

optional

returns

void

throws

  • AssertionError - listing every route × status combination whose error schema specificity is below `min_specificity` (default `'enum'`) and is not in `allowlist` or `ignore_statuses`.

assert_error_schema_valid
#

testing/assertions.ts view source

(lookup: Map<string, Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>>, route: AppSurfaceRoute, status: number, body: unknown): void import {assert_error_schema_valid} from '@fuzdev/fuz_app/testing/assertions.js';

Assert that an error schema exists for a route+status and validate the body against it.

Protected routes should always have auto-derived error schemas (401 for authenticated, 403 for role-restricted). A missing schema indicates a gap in error schema derivation.

lookup

map from "METHOD /path" to merged error schemas

type Map<string, Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>>

route

the surface route to validate against

status

expected HTTP status code

type number

body

the parsed response body to validate

type unknown

returns

void

throws

  • AssertionError - if no schema is declared for the route+status pair.
  • ZodError - if the body does not satisfy the declared schema.

assert_error_schemas_structurally_valid
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_error_schemas_structurally_valid} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route's declared error schemas must have an error field at the top level (conforming to the ApiError base shape {error: string}).

Walks union branches (anyOf from z.union, oneOf from z.discriminatedUnion) so every emit shape inside a merged 400 / 404 is checked, not just the top-level wrapper.

Catches typos in error schema definitions and ensures consumers can always read .error from error responses.

surface

returns

void

assert_expected_headers
#

testing/cross_backend/conformance_table.ts view source

(headers: Record<string, string>, expected: Record<string, string | null>, label: string): void import {assert_expected_headers} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Assert each declared header expectation: a string value must be present and equal (header name matched case-insensitively), null must be absent. The negative-space twin for headers — expect.headers pins a header beyond the always-on no-fingerprint floor.

headers

type Record<string, string>

expected

type Record<string, string | null>

label

type string

returns

void

assert_full_middleware_stack
#

testing/assertions.ts view source

(surface: AppSurface, path_prefix: string, expected_middleware: string[]): void import {assert_full_middleware_stack} from '@fuzdev/fuz_app/testing/assertions.js';

Verify every route under a path prefix has the exact expected middleware stack.

surface

the app surface to check

path_prefix

prefix to filter routes (e.g. '/api/')

type string

expected_middleware

the exact middleware names in order

type string[]

returns

void

throws

  • AssertionError - if no routes match `path_prefix`, or if any matching route's `applicable_middleware` does not deep-equal `expected_middleware`.

assert_input_routes_declare_400
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_input_routes_declare_400} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route with non-null input_schema has 400 in error_schemas.

surface

returns

void

assert_iso8601_seconds
#

testing/cross_backend/wire_shapes.ts view source

(value: unknown, label: string): void import {assert_iso8601_seconds} from '@fuzdev/fuz_app/testing/cross_backend/wire_shapes.js';

Assert value is a timestamp in the spine's canonical wire shape: second-precision UTC, exactly 20 characters. The TS spine emits it from the iso8601_timestamp_column SQL projection (db/sql_columns.ts) and to_iso8601_seconds (timestamp.ts); the Rust spine from fuz_db::iso8601_timestamp_column and fuz_sys::rfc3339_now.

value

the field read off a wire response

type unknown

label

the field's name, for the failure message

type string

returns

void

assert_iso8601_seconds_nullable
#

testing/cross_backend/wire_shapes.ts view source

(value: unknown, label: string): void import {assert_iso8601_seconds_nullable} from '@fuzdev/fuz_app/testing/cross_backend/wire_shapes.js';

Assert value is either null or a canonical second-precision UTC timestamp — the shape of every nullable timestamp on the wire (expires_at, deleted_at, updated_at, …).

value

the field read off a wire response

type unknown

label

the field's name, for the failure message

type string

returns

void

assert_jsonrpc_error_response
#

testing/rpc_helpers.ts view source

(body: unknown, expected_code?: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">) | undefined): void import {assert_jsonrpc_error_response} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Assert that a response body is a valid JSON-RPC error response.

Validates the structure matches JsonrpcErrorResponse and optionally checks the error code.

body

type unknown

expected_code?

optional error code to assert

optional

returns

void

assert_jsonrpc_success_response
#

testing/rpc_helpers.ts view source

(body: unknown, output_schema?: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | undefined): void import {assert_jsonrpc_success_response} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Assert that a response body is a valid JSON-RPC success response.

Validates the structure matches JsonrpcResponse. When output_schema is provided, also validates the result field against the declared output schema — matching the REST round-trip's assert_response_matches_spec.

body

type unknown

output_schema?

optional Zod schema to validate the result field against

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>
optional

returns

void

assert_keeper_routes_under_prefix
#

testing/surface_invariants.ts view source

(surface: AppSurface, prefixes?: string[]): void import {assert_keeper_routes_under_prefix} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Keeper-protected routes must be under expected path prefixes.

Catches keeper routes accidentally placed outside the API namespace (e.g., a keeper route at /health or /admin/ instead of /api/...).

surface

prefixes?

type string[]
optional default DEFAULT_KEEPER_ROUTE_PREFIXES

returns

void

assert_middleware_errors_propagated
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_middleware_errors_propagated} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every applicable middleware that declares errors must have those status codes present in the route's error_schemas.

surface

returns

void

assert_middleware_stack_mounted
#

auth/middleware.ts view source

(stack_specs: readonly MiddlewareSpec[], middleware_specs: readonly MiddlewareSpec[]): void import {assert_middleware_stack_mounted} from '@fuzdev/fuz_app/auth/middleware.js';

Throw unless a middleware list still mounts every spec of the stack it was built from — the trusted-proxy spec and the auth middleware — exactly once, at its original path, in its original order.

create_app_server and create_test_app_surface_spec run it on the list a transform_middleware option returns, against the stack they handed it ([proxy, ...auth]). The endpoint auth-scope check (assert_endpoint_in_auth_scope) assumes the auth stack is mounted at AUTH_MIDDLEWARE_PATH, and every IP-keyed rate limiter assumes the proxy spec ran first — without it get_client_ip answers 'unknown' for every caller, collapsing each limiter into one shared bucket. So a transform that moves, drops, duplicates, reorders, wraps, or replaces a stack layer is refused. Other specs may sit anywhere, including before the proxy spec and between the auth layers. The check pins where the stack's layers sit, not what added layers do — a layer you add runs with full context access.

Each stack spec is matched by its handler — the closure its factory built — not by name, which any spec can carry, so a same-named replacement or a wrapped handler counts as missing. A spread copy ({...spec}) keeps the handler and still matches. Its path is compared with the original's, so pass specs the transform could not reach: the assemblies hand the transform shallow copies in a fresh array and keep the originals here.

stack_specs

the stack the transform was built from, in mount order

type readonly MiddlewareSpec[]

middleware_specs

the middleware list the transform returned

type readonly MiddlewareSpec[]

returns

void

throws

  • Error - when a stack spec is missing, mounted more than once, mounted at another path, or out of order

assert_migration_namespaces
#

db/migrate.ts view source

(namespaces: readonly MigrationNamespace[]): void import {assert_migration_namespaces} from '@fuzdev/fuz_app/db/migrate.js';

Refuse a namespace bearing a reserved_migration_namespaces name unless the spine built it (create_spine_migration_namespace). run_migrations and baseline run this before touching the database; a server assembly can call it to refuse a bad chain earlier.

namespaces

the chain to check

type readonly MigrationNamespace[]

returns

void

throws

  • MigrationError - with `kind` `namespace-reserved`, naming the first offending namespace

assert_migration_trackers_equal
#

testing/schema_parity.ts view source

(a: { entries: { namespace: string; name: string; sequence: number; }[]; }, b: { entries: { namespace: string; name: string; sequence: number; }[]; }, labels?: SchemaDiffLabels): void import {assert_migration_trackers_equal} from '@fuzdev/fuz_app/testing/schema_parity.js';

Throw if the two spines' schema_version trackers disagree — the gate for the swap-freely invariant (any consumer can swap TS↔Rust over one DB without re-bootstrapping). This catches what assert_schema_snapshots_equal is blind to by design: the snapshot excludes the tracker, so a migration-name or partitioning divergence that yields an identical *schema* (e.g. cell_v0 vs full_cell_schema, or a table bundled into one namespace vs isolated in its own) passes schema parity but breaks the runner's positional name-prefix check at boot (name-divergence-at-N). The error names the impls and lists every diff.

a

b

labels?

optional default {}

returns

void

assert_mutation_routes_use_post
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_mutation_routes_use_post} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Routes with non-null input schemas should use POST (or other mutation methods), not GET.

GET routes with request bodies are technically allowed by HTTP but semantically suspicious — they bypass browser security assumptions about GET being idempotent. Query-string-driven filtering (audit log, list endpoints) should use params schemas or query string parsing, not input schemas.

Note: RPC endpoints (create_rpc_endpoint) use input: z.null() on their route specs — the dispatcher handles body/query parsing internally. Real input schemas live in rpc_endpoints surface, not on routes; see assert_rpc_ws_surface_invariants for the parallel checks over RPC/WS method shapes.

surface

returns

void

assert_no_audit_drift
#

testing/audit_drift_guard.ts view source

(): void import {assert_no_audit_drift} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Assert the process-wide audit counters are zero — the check install_audit_drift_guard runs after each test. Exported for a test that manages the counters itself.

returns

void

throws

  • AssertionError - naming the first counter that is not zero

assert_no_duplicate_routes
#

assert_no_error_info_leakage
#

testing/integration_helpers.ts view source

(body: unknown, context: string): void import {assert_no_error_info_leakage} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Assert that an error response contains no leaky field values.

Checks both field names and string values for patterns indicating stack traces, SQL, or internal paths. Accepts unknown so callers pass response bodies / nested envelope fields directly without intermediate as casts; non-object bodies skip the field-name check.

body

type unknown

context

description for error messages

type string

returns

void

assert_no_fingerprint_headers
#

testing/cross_backend/conformance_table.ts view source

(headers: Record<string, string>, label: string): void import {assert_no_fingerprint_headers} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Assert a response carries none of the FINGERPRINT_HEADERS. Run on every case unconditionally — the always-on no-fingerprint floor.

headers

type Record<string, string>

label

type string

returns

void

assert_no_sensitive_fields_in_json
#

testing/integration_helpers.ts view source

(body: unknown, blocklist: readonly string[], context: string): void import {assert_no_sensitive_fields_in_json} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Assert that a parsed JSON body contains no fields from the given blocklist.

body

type unknown

blocklist

type readonly string[]

context

description for error messages

type string

returns

void

assert_no_testing_methods
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_no_testing_methods} from '@fuzdev/fuz_app/testing/surface_invariants.js';

No _testing_* backdoor action ever appears as a method on the declared surface (RPC or WS).

The test-backdoor actions (_testing_reset et al.) are daemon-token-gated privileged actions a consumer's test binary appends to its live RPC endpoint at assembly time — but they are deliberately excluded from surface generation (spine_rpc_endpoints and every consumer's create_*_app_surface_spec omit them) so the published attack surface, the committed *_attack_surface.json snapshot, and codegen never carry a backdoor. This invariant is the structural guard against a future wiring change that folds create_testing_actions(...) into the *declared* registry instead of the live-only append — the surface stays the authoritative "what the server exposes" map only if a backdoor can never hide in it.

Pairs with the wire-level negative-credential check (describe_testing_backdoor_cross_tests, cross-process) and the spec-level gate check: this one pins absence-from-surface, those pin the daemon-token gate and the 401/403 behavior.

surface

returns

void

throws

  • AssertionError - naming the offending endpoint + method.

assert_no_unexpected_public_mutations
#

testing/surface_invariants.ts view source

(surface: AppSurface, allowlist?: string[]): void import {assert_no_unexpected_public_mutations} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Public mutation routes (auth: none + is_mutation) must be in the allowlist.

Catches accidentally unprotected POST/PUT/DELETE routes. Routes like login and bootstrap are public mutations by design — they go in the allowlist.

surface

allowlist?

type string[]
optional default []

returns

void

assert_non_admin_schemas_no_admin_fields
#

testing/data_exposure.ts view source

(surface: AppSurface, admin_only_fields?: readonly string[]): void import {assert_non_admin_schemas_no_admin_fields} from '@fuzdev/fuz_app/testing/data_exposure.js';

Assert that non-admin route output schemas don't contain admin-only fields.

surface

admin_only_fields?

type readonly string[]
optional default admin_only_field_blocklist

returns

void

assert_only_expected_public_routes
#

testing/assertions.ts view source

(surface: AppSurface, expected_public: string[]): void import {assert_only_expected_public_routes} from '@fuzdev/fuz_app/testing/assertions.js';

Bidirectional check: no unexpected public routes, no missing expected ones.

surface

the app surface to check

expected_public

format: ['GET /health', 'POST /api/account/login']

type string[]

returns

void

throws

  • AssertionError - if the live surface has public routes not in `expected_public`, or if any entry in `expected_public` is missing from the live surface.

assert_origin_carve_outs_valid
#

auth/middleware.ts view source

(carve_outs: readonly MiddlewareCarveOut[], auth_path?: string): void import {assert_origin_carve_outs_valid} from '@fuzdev/fuz_app/auth/middleware.js';

Throw unless every origin carve-out names a path and a reason, and stays off the auth middleware's scope: a path that starts with / (a Hono pattern like /hooks/* or /health), a non-blank reason — a carve-out is an argument someone wrote down, never an empty placeholder — and a pattern that matches nothing under auth_path (middleware_paths_overlap).

The scope rule is what keeps a carve-out off every credentialed route. Assembly holds every route that reads a credential (auth.account not 'none', or reads_credential) or mints one (mints_credential: login, signup, bootstrap) inside AUTH_MIDDLEWARE_PATH (assert_credential_routes_in_auth_scope), and every RPC, WS, and bootstrap endpoint besides (assert_endpoint_in_auth_scope). The minting routes declare no auth but are what a forged cross-site request targets, so refusing by route auth would miss them; refusing by scope catches them. A route outside the scope reads no credential the auth middleware parses, and mints none, so a carve-out there removes no credentialed check.

carve_outs

the carve-outs to check

type readonly MiddlewareCarveOut[]

auth_path?

the auth middleware's path pattern (default AUTH_MIDDLEWARE_PATH)

type string
optional default AUTH_MIDDLEWARE_PATH

returns

void

throws

  • Error - naming the first carve-out with no path, no reason, or a pattern overlapping `auth_path`

assert_origin_conformance
#

testing/cross_backend/origin_conformance.ts view source

(checks: readonly OriginConformanceCheck[], answer: readonly unknown[], backend: string): void import {assert_origin_conformance} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

Compare each check's answer with its expectation and fail once, listing every row that disagrees.

checks

the table's checks

type readonly OriginConformanceCheck[]

answer

the answer each check got, in order

type readonly unknown[]

backend

the name the failure message gives the answerer

type string

returns

void

assert_output_schemas_no_sensitive_fields
#

testing/data_exposure.ts view source

(surface: AppSurface, sensitive_fields?: readonly string[]): void import {assert_output_schemas_no_sensitive_fields} from '@fuzdev/fuz_app/testing/data_exposure.js';

Assert that no output schema in the surface contains sensitive field names.

surface

sensitive_fields?

type readonly string[]
optional default sensitive_field_blocklist

returns

void

assert_params_routes_declare_400
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_params_routes_declare_400} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route with non-null params_schema has 400 in error_schemas.

surface

returns

void

assert_protected_routes_declare_401
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_protected_routes_declare_401} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every protected route has 401 in error_schemas.

surface

returns

void

assert_query_routes_declare_400
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_query_routes_declare_400} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route with non-null query_schema has 400 in error_schemas.

surface

returns

void

assert_rate_limit_retry_after_header
#

testing/integration_helpers.ts view source

(response: Response, body: { retry_after: number; }): void import {assert_rate_limit_retry_after_header} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Assert that a 429 response includes a valid Retry-After header matching the JSON body's retry_after field.

response

type Response

body

type { retry_after: number; }

returns

void

assert_response_matches_spec
#

testing/integration_helpers.ts view source

(route_specs: RouteSpec[], method: string, path: string, response: Response): Promise<void> import {assert_response_matches_spec} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Validate a response body against the route spec's declared schemas.

For 2xx responses, validates against spec.output. For error responses, validates against the merged error schema for that status code.

route_specs

type RouteSpec[]

method

type string

path

type string

response

type Response

returns

Promise<void>

throws

  • Error - if no route spec matches `method` + `path`, if the response body fails to parse against the declared output / error schema, or if the response is non-JSON despite a declared schema for that status.

assert_role_routes_declare_403
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_role_routes_declare_403} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route with an authority gate that can answer 403 declares one — a role gate, or a credential-channel gate of any kind (keeper included).

The credential half is not just the keeper routes: the session-only gates point the same axis the other way and refuse with the same status, so a route carrying one and no 403 schema would have its denial body go unvalidated by the DEV-only error-schema check.

surface

returns

void

assert_route_auth_acting_biconditional
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }, slots: ActingSlots, context: string): void import {assert_route_auth_acting_biconditional} from '@fuzdev/fuz_app/http/auth_shape.js';

Registry-time biconditional check: `auth.actor !== 'none' ⟺ some supplied slot declares acting?: ActingActor`. Throws on violation.

The slot set differs by surface: REST passes {input, query} (both locatable, query only set for GETs); action dispatchers pass {input} (no query shape on ActionSpec). The throw message lists the slots that were actually in play, so an actor-required action without acting doesn't point the operator at a query slot that doesn't exist on their spec.

Called by every dispatcher registration loop (apply_route_specs, compile_action_registry) on every spec it accepts.

auth

the route/action's auth shape

slots

the spec's acting-bearing schemas; query omitted on action call sites

context

identifier for the throwing message (route key, RPC method, etc.)

type string

returns

void

throws

  • Error - when the biconditional is violated

assert_route_authenticated
#

testing/round_trip.ts view source

(route_key: string, status: number, body: unknown, principal: AuthPrincipal): void import {assert_route_authenticated} from '@fuzdev/fuz_app/testing/round_trip.js';

Guard against a refusal that says the request carried no valid credential when it carried one (is_unauthenticated_refusal), the REST twin of assert_rpc_authenticated. Every non-public route is sent a live credential its auth admits, so a 401 reading authentication_required, or naming no reason, means the credential stopped working — most often because an earlier request ended it. A public route may answer 401 (a login with a wrong password), and so may a handler refusing an authenticated request with a reason of its own (invalid_credentials).

route_key

the route, as METHOD /path

type string

status

the HTTP status

type number

body

the parsed response body, or undefined when not JSON

type unknown

principal

the principal the request was sent as

returns

void

assert_row
#

db/assert_row.ts view source

<T>(row: T | undefined, context?: string | undefined): T import {assert_row} from '@fuzdev/fuz_app/db/assert_row.js';

Assert that a row is present, throwing a descriptive error if missing.

Use after INSERT ... RETURNING queries where the database guarantees a row is returned on success. Replaces bare row! non-null assertions with an explicit runtime check.

row

the row from query_one (T | undefined) or rows[0] (T | undefined)

type T | undefined

context?

optional context for the error message (e.g. table or operation name)

type string
optional

returns

T

the row, guaranteed non-undefined

generics

assert_row<T>
T

throws

  • Error - if `row` is `undefined`

assert_rpc_authenticated
#

testing/rpc_round_trip.ts view source

(method: string, status: number, body: unknown, principal: AuthPrincipal): void import {assert_rpc_authenticated} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

Guard against a refusal that says the call carried no valid credential when it carried one (is_unauthenticated_refusal). The round-trip sends every non-public method a live credential its auth admits, so such a 401 means the backend failed to authenticate it — most often because an earlier call destroyed it (a self-delete, a revoke-all), which would turn every later method's coverage into a vacuous refusal. A public method may still answer 401 (a login with a wrong password), and so may a handler refusing an authenticated call with a reason of its own (invalid_credentials).

method

the RPC method name

type string

status

the HTTP status

type number

body

the parsed response body (or raw text when not JSON)

type unknown

principal

the principal the call was sent as

returns

void

assert_rpc_envelope_refusal
#

testing/rpc_envelope_cases.ts view source

(status: number, body: unknown, c: RpcEnvelopeCase): void import {assert_rpc_envelope_refusal} from '@fuzdev/fuz_app/testing/rpc_envelope_cases.js';

Assert a response body is the refusal an envelope case owes: a well-formed JSON-RPC error envelope with the case's code, echoed id, and data.reason.

status

the response's HTTP status

type number

body

the parsed response body

type unknown

c

the case

returns

void

assert_rpc_method_coverage
#

testing/cross_backend/method_coverage.ts view source

(input: RpcMethodCoverageInput): void import {assert_rpc_method_coverage} from '@fuzdev/fuz_app/testing/cross_backend/method_coverage.js';

Assert the live RPC method set reconciles exactly with the coverage manifest, and that every manifest entry's tier is internally consistent.

Fails loud on: a live method missing from the manifest (mounted-but-unclaimed), a manifest row naming a method the live mount no longer exposes (stale row), a declared-surface method absent from the live mount (the full mount must be a superset), a duplicate manifest entry, or any tier/declared/backdoor inconsistency (e.g. an off_surface row that is actually on the declared surface, or one missing its suite).

input

returns

void

throws

  • AssertionError - naming the specific divergence.

assert_rpc_method_descriptions_present
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_rpc_method_descriptions_present} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every RPC method on every endpoint has a non-empty description.

Parallel of assert_descriptions_present over surface.rpc_endpoints. Empty descriptions on RPC methods leak through library.json codegen and consumer-facing docs without the route-level check catching them.

surface

returns

void

assert_rpc_ws_surface_invariants
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_rpc_ws_surface_invariants} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Run all RPC / WS structural invariants. Options-free — applies universally to the surface.rpc_endpoints and surface.ws_endpoints slots produced by generate_app_surface.

Parallel of assert_surface_invariants for the non-REST surfaces. Within-endpoint duplicate method names and the auth-shape biconditional are already enforced at startup by compile_action_registry (see actions/CLAUDE.md §Registry compile) — these assertions cover only the contract-surface concerns that a runtime registration check cannot: empty descriptions, missing protocol-action spread on WS endpoints, kind ⇔ auth drift on WS methods, and a _testing_* backdoor action leaking onto the declared surface.

surface

returns

void

throws

  • AssertionError - on the first invariant violation; the message names the offending endpoint, method, and field.

assert_schema_snapshots_equal
#

testing/schema_parity.ts view source

(a: { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }, b: { ...; }, labels?: SchemaDiffLabels): void import {assert_schema_snapshots_equal} from '@fuzdev/fuz_app/testing/schema_parity.js';

Throw if the two snapshots disagree. The error message names the impls (via labels) and lists every diff, so the failure is self-diagnosing.

Consumers wire this after bootstrapping each impl against an isolated DB:

await drop_recreate_db('zzz_test'); await spawn_backend(deno_config); const snapshot_deno = await query_schema_snapshot(db, {}); await drop_recreate_db('zzz_test'); await spawn_backend(rust_config); const snapshot_rust = await query_schema_snapshot(db, {}); assert_schema_snapshots_equal(snapshot_deno, snapshot_rust, {a: 'deno', b: 'rust'});

a

b

labels?

optional default {}

returns

void

assert_secure_mode
#

runtime/secure_file.ts view source

(path: string, mode: number): void import {assert_secure_mode} from '@fuzdev/fuz_app/runtime/secure_file.js';

Refuse any group/other-accessible mode (only 0600/0400 pass).

Callers own the platform gating: the real runtimes skip the check where modes aren't meaningful (Node on Windows, a null Deno mode); the mock checks its simulated modes unconditionally.

path

type string

mode

type number

returns

void

throws

  • Error - naming the path, the offending mode, and the `chmod` fix

assert_secure_regular_file
#

runtime/secure_file.ts view source

(path: string, is_file: boolean): void import {assert_secure_regular_file} from '@fuzdev/fuz_app/runtime/secure_file.js';

Refuse anything but a regular file — a FIFO, device node, socket, or directory at the secret's path.

path

type string

is_file

type boolean

returns

void

throws

  • Error - naming the path

assert_secure_size
#

runtime/secure_file.ts view source

(path: string, size: number): void import {assert_secure_size} from '@fuzdev/fuz_app/runtime/secure_file.js';

Refuse a byte count over MAX_SECURE_FILE_SIZE.

path

type string

size

type number

returns

void

throws

  • Error - naming the path, the size, and the cap

assert_sensitive_routes_rate_limited
#

testing/surface_invariants.ts view source

(surface: AppSurface, sensitive_patterns?: (string | RegExp)[]): void import {assert_sensitive_routes_rate_limited} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Sensitive routes must declare rate limiting (rate_limit_key is non-null) or have 429 in their error schemas.

Matches routes against sensitive patterns and flags any that lack rate limit declarations. Catches forgotten rate limiting on credential-handling routes.

surface

sensitive_patterns?

type (string | RegExp)[]
optional default DEFAULT_SENSITIVE_PATTERNS

returns

void

assert_surface_deterministic
#

testing/assertions.ts view source

(build_surface: () => AppSurface): void import {assert_surface_deterministic} from '@fuzdev/fuz_app/testing/assertions.js';

Verify surface generation is deterministic (build twice, compare).

build_surface

type () => AppSurface

returns

void

assert_surface_invariants
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_surface_invariants} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Run all structural invariants. Options-free — applies universally.

Catches schema/surface generation bugs: missing 401/403/400 declarations, empty descriptions, duplicate routes, middleware-injected error codes unpropagated to routes, structurally invalid error schemas, error codes appearing at multiple statuses, and generic 404 schemas on param routes.

surface

returns

void

throws

  • AssertionError - on the first invariant violation; the message names the offending route and the missing/inconsistent field.

assert_surface_matches_snapshot
#

testing/assertions.ts view source

(surface: AppSurface, snapshot_path: string): void import {assert_surface_matches_snapshot} from '@fuzdev/fuz_app/testing/assertions.js';

Compare live surface against a committed snapshot JSON file.

Failure message instructs the developer to run gro gen to update the snapshot — every fuz_app consumer wires the snapshot through a *.gen.ts file so regeneration goes through the same pipeline as the rest of the generated artifacts.

surface

the live surface to check

snapshot_path

absolute path to the committed JSON snapshot

type string

returns

void

throws

  • AssertionError - if the live surface does not deep-equal the snapshot, or `Error` if the snapshot file is unreadable / malformed JSON.

assert_surface_security_policy
#

testing/surface_invariants.ts view source

(surface: AppSurface, options?: SurfaceSecurityPolicyOptions): void import {assert_surface_security_policy} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Run security policy invariants. Configurable with sensible defaults.

Checks:

  • Sensitive routes are rate-limited
  • No unexpected public mutation routes
  • Input schemas use mutation methods (not GET)
  • Keeper routes under expected prefixes

surface

options?

optional default {}

returns

void

throws

  • AssertionError - on the first policy violation; the message names the offending route.

assert_valid_sql_identifier
#

db/sql_identifier.ts view source

(name: string): string import {assert_valid_sql_identifier} from '@fuzdev/fuz_app/db/sql_identifier.js';

Assert that a string is a valid SQL identifier.

Use this before interpolating table or column names into DDL queries where parameterized placeholders ($1) are not supported.

name

the identifier to validate

type string

returns

string

the validated identifier

throws

  • Error - if the identifier contains characters outside `[a-zA-Z0-9_]` or starts with a digit

assert_ws_endpoints_include_protocol_actions
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_ws_endpoints_include_protocol_actions} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every WS endpoint's methods includes every protocol action method (heartbeat, cancel).

Consumers register WS endpoints by spreading protocol_actions from actions/protocol.ts before their own actions:

ws_endpoints: [{path: '/api/ws', actions: [...protocol_actions, ...consumer_actions], ...}]

Forgetting the spread compiles cleanly but breaks at runtime: client-side heartbeats and cancel notifications get method_not_found from the dispatcher, so disconnect detection silently regresses and per-request cancel never aborts the matching handler. Catch the mistake at the surface layer rather than at runtime.

surface

returns

void

assert_ws_method_descriptions_present
#

assert_ws_notifications_have_null_auth
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_ws_notifications_have_null_auth} from '@fuzdev/fuz_app/testing/surface_invariants.js';

WS methods follow the kind ⇔ auth biconditional emitted by surface generation: kind === 'remote_notification' ⟺ auth === null.

generate_app_surface produces this shape directly from the action spec union (notifications carry auth: null per ActionSpecUnion; request_response carries a RouteAuth). The assertion guards against drift if a future surface emitter, transform, or test fixture violates it — and gives consumers a clear failure message when a hand-built surface mocks the shape incorrectly.

surface

returns

void

AssertAuditEventMetadataOptions
#

testing/audit_drift_guard.ts view source

AssertAuditEventMetadataOptions import type {AssertAuditEventMetadataOptions} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

reject_unknown_keys?

Refuse metadata keys the event type doesn't declare. Off by default: the builtin schemas admit consumer-added fields. For a suite whose backend adds none — a spine's own — so an undeclared key on any spine fails.

type boolean

readonly

AsyncSlot
#

ui/async_slot.svelte.ts view source

import {AsyncSlot} from '@fuzdev/fuz_app/ui/async_slot.svelte.js';

Reactive container for a single async operation.

generics

AsyncSlot<T = void, E = string>
T
default void
E
default string

status

type AsyncStatus

$state.raw

data

type T | undefined

$state.raw

error

type E | null

$state.raw

error_data

The raw caught value from the last failed run(), for programmatic inspection.

type unknown

$state.raw

initial

Convenience derived: status === 'initial'.

type boolean

readonly $derived

loading

Convenience derived: status === 'pending'.

type boolean

readonly $derived

succeeded

Convenience derived: status === 'success'.

type boolean

readonly $derived

failed

Convenience derived: status === 'failure'.

type boolean

readonly $derived

constructor

type new <T = void, E = string>(options?: AsyncSlotOptions<T, E>): AsyncSlot<T, E>

options?

type AsyncSlotOptions<T, E>
optional default {}

run

Run an async operation. The callback receives an AbortSignal it can forward to fetch / RPC clients that support cancellation; the slot also discards superseded results internally even if the callback ignores the signal.

Supersession rule: a second run() aborts the first's signal AND silently drops its commit if it resolves anyway. So back-to-back-to-back run() calls leave only the last call's result in data.

Abort rule: a run() that throws because of its own signal (manual abort(), external options.signal, OR supersession by another run()) does NOT promote to 'failure'. Manual / external aborts revert status to the previous resolved state ('initial' if no run() has ever succeeded, 'success' otherwise). Supersession is handled by the bail-on-mismatch check, leaving the second run's 'pending' standing.

type (fn: (signal: AbortSignal) => Promise<T>, options?: RunOptions): Promise<T | undefined>

fn

type (signal: AbortSignal) => Promise<T>

options?

optional default {}
returns Promise<T | undefined>

the resolved value on success; undefined on failure, abort, or supersession

abort

Manually abort the in-flight run, if any. Reverts status synchronously to the prior resolved state — 'initial' if no run() (or set()) has ever succeeded on this slot, 'success' otherwise. The aborted run's eventual resolution / rejection is dropped without writing to state (the run's Promise resolves to undefined).

type (reason?: unknown): void

reason?

type unknown
optional
returns void

set

Replace data directly and mark the slot 'success'. For post-mutation hydration where the calling RPC already returned the canonical row (parallels CellState.set_cell).

Aborts any in-flight run() first — without this, the in-flight callback could resolve after set() and overwrite the explicit value (the bail-on-mismatch check only fires when #controller was rotated).

type (data: T): void

data

type T
returns void

mutates

  • this

reset

Reset to 'initial', clear data / error / error_data, and abort any in-flight run. After reset() the slot looks like a fresh instance with no initial option.

type (): void

returns void

mutates

  • this

AsyncSlotOptions
#

ui/async_slot.svelte.ts view source

AsyncSlotOptions<T, E> import type {AsyncSlotOptions} from '@fuzdev/fuz_app/ui/async_slot.svelte.js';

generics

AsyncSlotOptions<T, E = string>
T
E
default string

initial?

Seed data and put the slot in 'success' before any run(). Useful when the page already has the resource in hand (SSR hydration, a mutation response, hand-off from a parent slot).

type T

map_error?

Convert a caught throw into the error value stored in AsyncSlot.error. Default extracts Error.message (falling back to 'Request failed' for non-Error throws). Pass to_rpc_error_message to unwrap JSON-RPC data.reason codes.

type (e: unknown): E

e

type unknown
returns E

preserve_error_on_retry?

When true, the previous error / error_data survive the start of a new run() until the next success (or another failure overwrites them). Useful for retry UX that wants to keep the failure message visible alongside an inline spinner. Default false — run() clears the error at the start so the pending state reads "no current error."

type boolean

audit_error_schema_tightness
#

testing/surface_invariants.ts view source

(surface: AppSurface): ErrorSchemaAuditEntry[] import {audit_error_schema_tightness} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Audit error schema tightness across all routes in a surface.

Reports which route x status code combinations use generic ApiError (z.string()) vs specific z.literal() or z.enum() error codes. Use the output to prioritize progressive tightening of error schemas.

surface

returns

ErrorSchemaAuditEntry[]

audit entries for every route x status combination

audit_event_revocation_scopes
#

auth/audit_log_schema.ts view source

Readonly<Record<"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", RevocationScope>>

type Readonly<Record<AuditEventType, RevocationScope>>

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

The RevocationScope of every builtin audit event — declared once, the one answer the revocation listener reads, and the twin of the Rust spine's table. Typed over AuditEventType, so a new event type does not compile until it declares its scope here.

A scope other than none marks an event whose success invalidates a live connection's credential or access: logout, password_change, session_revoke_all, token_revoke_all, account_delete, and account_purge are account-wide, session_revoke and token_revoke name the one credential they ended, and role_grant_revoke names the lost role. Granting access revokes nothing, and neither does an event about a subject other than a connection's identity.

Twin of the revocation_scope column of the Rust spine's AUDIT_EVENT_SPECS.

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_CHANNEL
#

realtime/audit_log_sse.ts view source

"audit_log" import {AUDIT_LOG_CHANNEL} from '@fuzdev/fuz_app/realtime/audit_log_sse.js';

SSE channel the audit-log stream route publishes on.

AUDIT_LOG_COLUMNS
#

auth/audit_log_queries.ts view source

readonly ["id", "seq", "event_type", "outcome", "actor_id", "account_id", "target_account_id", "target_actor_id", "ip", "created_at", "metadata"] import {AUDIT_LOG_COLUMNS} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

The full audit_log column set, named explicitly so a row read fails loud on schema drift — audit rows ride the audit_log_list / role-grant-history RPC responses and the SSE broadcast raw, so a SELECT * would silently carry a dropped or leftover column into the strict-validated wire shapes (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class; the Rust twin names columns at every audit site). Keep in sync with AuditLogEvent and the migration chain's end state. Exported for the per-cell timeline read in db/cell_audit_queries.ts.

AUDIT_LOG_DEFAULT_LIMIT
#

audit_log_event_specs
#

audit_log_expr
#

auth/audit_log_queries.ts view source

(alias: string) => ColumnExpr import {audit_log_expr} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

The audit_log timestamp override, by row qualifier ('' bare, al for the username JOINs). Exported for the per-cell timeline read in db/cell_audit_queries.ts, which projects the same table.

audit_log_filter_matches
#

ui/audit_log_state.svelte.ts view source

(event: { id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 4 more ...; metadata: Record<...> | null; }, filter: AuditLogFilter | undefined): boolean import {audit_log_filter_matches} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

Whether an event passes an AuditLogFilter — the client-side twin of the list query's filter conditions, applied to live stream events.

event

the event to test

filter

the filter; absent or empty fields match everything

type AuditLogFilter | undefined

returns

boolean

AUDIT_LOG_INDEXES
#

audit_log_list_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ event_type: ZodOptional<ZodNullable<ZodString>>; ... 5 more ...; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; as... import {audit_log_list_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds admin-side enumeration of the entire audit log via (limit, offset) walking — same shape as admin_account_list_action_spec. The listing carries cross-account forensic detail (target ids, IPs, metadata), so the read-rate cap is the only check that distinguishes a human reviewer from a scraping script.

AUDIT_LOG_LIST_LIMIT_MAX
#

audit_log_role_grant_history_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ limit: ZodOptional<ZodNullable<ZodNumber>>; offset: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...... import {audit_log_role_grant_history_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds admin-side enumeration of the role_grant history via (limit, offset) walking — same shape as audit_log_list, narrower projection but identical scraping vector.

audit_log_rpc_context
#

ui/audit_log_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AuditLogRpc; get_maybe: () => (() => AuditLogRpc) | undefined; set: (value: () => AuditLogRpc) => () => AuditLogRpc; } import {audit_log_rpc_context} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

Svelte context carrying the reactive AuditLogRpc accessor. Mirrors admin_accounts_rpc_context. get() throws when no provisioner ran above the component — the adapter is required.

AUDIT_LOG_SCHEMA
#

auth/audit_log_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS audit_log (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n seq BIGSERIAL NOT NULL,\n event_type TEXT NOT NULL,\n outcome TEXT NOT NULL DEFAULT 'success',\n actor_id UUID,\n account_id UUID,\n target_account_id UUID,\n target_actor_id UUID,\n ip TEXT,\n created_at TIMESTAMP... import {AUDIT_LOG_SCHEMA} from '@fuzdev/fuz_app/auth/audit_log_ddl.js';

AUDIT_LOG_SSE_MAX_PER_SCOPE
#

realtime/audit_log_sse.ts view source

10 import {AUDIT_LOG_SSE_MAX_PER_SCOPE} from '@fuzdev/fuz_app/realtime/audit_log_sse.js';

Default max concurrent SSE subscribers per session scope for the audit log.

The audit log SSE subscribes with scope = session_hash and groups = [account_id]. Only scope is capped — so this limits tabs per session. An account's total streams across all sessions is bounded transitively by max_sessions × AUDIT_LOG_SSE_MAX_PER_SCOPE. 10 tabs per session is a comfortable ceiling for normal use; consumers raising it above ~50 should consider server-side connection limits.

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<...>; }>

type 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.

AUDIT_SSE_KEEP_ALIVE_MS_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_AUDIT_SSE_KEEP_ALIVE_MS" import {AUDIT_SSE_KEEP_ALIVE_MS_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var replacing the audit stream's keep-alive interval on both spine binaries (a positive integer of ms up to 2³¹ − 1; unset keeps SSE_KEEP_ALIVE_INTERVAL_MS). Both refuse to boot on anything else. The cross-language contract for the timing cross project, which shortens it so a suite sees keep-alives within a second.

audit_unmatched_peer_response
#

actions/peer_request.ts view source

(log: Logger, connection_id: string & $brand<"Uuid">, id: string | number | null): void import {audit_unmatched_peer_response} from '@fuzdev/fuz_app/actions/peer_request.js';

Sampled, bounded audit for an inbound response that matched no pending request on its connection — an unsolicited {id, result}, a cross-connection id echo, or a late/duplicate reply for an already-settled id. Auditing every rejected frame would let a junk flood turn the log into the DoS target, so this warns on the first few then samples 1-in-256. Twin of the Rust fuz_realtime::peer::audit_unmatched_response.

log

the WS dispatcher's logger

type Logger

connection_id

the socket the unmatched reply arrived on

type Uuid

id

the reply's echoed id (null when absent)

type JsonrpcRequestId | null

returns

void

AuditCompletenessTestOptions
#

testing/audit_completeness.ts view source

AuditCompletenessTestOptions import type {AuditCompletenessTestOptions} from '@fuzdev/fuz_app/testing/audit_completeness.js';

setup_test

Per-test fixture-producing function. The audit suite calls this in every test() body — auth_integration_truncate_tables clears the audit log between tests, so each test re-bootstraps the keeper and the observer admin against a fresh table.

type (): Promise<TestFixture>

returns Promise<TestFixture>

surface_source

App surface (with route specs). Constructed in TS by the consumer; same shape for in-process and cross-process tests. The audit suite is Tier 2 today (stays in-process per the cross-backend-integration design), but takes the same surface shape as Tier 1 suites for options uniformity.

type AppSurfaceSpec

session_options

Session config — needed for factory-form rpc_endpoints resolution.

type SessionOptions<string>

rpc_endpoints

RPC endpoint specs — required. The admin role_grant flow is RPC-only and the suite hard-fails without it.

type RpcEndpointsSuiteOption

AuditEmitFn
#

auth/audit_emitter.ts view source

AuditEmitFn import type {AuditEmitFn} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Signature of AuditEmitter.emit — captured by the inner closure so emit_role_grant_target reaches the decorated function rather than a this.emit lookup. Exposed as a type so EmitDecorator can name the inner / outer slot.

(call)

type <T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>): Promise<void>

ctx

input

returns Promise<void>

AuditEmitRoleGrantContext
#

auth/audit_emitter.ts view source

AuditEmitRoleGrantContext import type {AuditEmitRoleGrantContext} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Context required by AuditEmitter.emit_role_grant_target — adds client_ip so the helper can lift the ip: ctx.client_ip boilerplate every role-grant-shape emit site repeated.

inheritance

client_ip

Resolved client IP from the trusted-proxy middleware — 'unknown' if not resolved.

type string

AuditEmitRoleGrantInput
#

auth/audit_emitter.ts view source

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

Input to AuditEmitter.emit_role_grant_target — the event fields that vary per call site; actor_id, account_id, and ip are lifted from the auth and request contexts.

generics

AuditEmitRoleGrantInput<T extends string>
T
constraint string

event_type

type T

target_account_id

type Uuid | null

target_actor_id

type Uuid | null

metadata

type (T extends "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete" ? (AuditMetadataMap[T] & Record<string, unknown>) | null : Record<string, unknown> | null) | undefined

outcome?

Default 'success', as for emit.

type "success" | "failure"

AuditEmitter
#

auth/audit_emitter.ts view source

AuditEmitter import type {AuditEmitter} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Bound audit-emit capability. Built once at backend assembly via create_audit_emitter; lives on AppDeps.audit so factories never see the pool.

emit

Write an audit row for a request, routed by outcome and ctx.db (module doc, "Where a row is written").

A success row inside a request transaction is written in it: the promise settles once the INSERT has run, and rejects when it fails, so the handler throws and the change rolls back with its row. Any other row is a fire-and-forget pool write: its in-flight promise goes on ctx.pending_effects (so tests with await_pending_effects: true can assert it inline), its errors are logged and counted, never thrown, and the returned promise resolves at once.

A written row fans out to every listener on the chain (notify): a failure row as soon as it is written, a success row from ctx.post_commit_effects — after the request's transaction commits, and never when the handler throws.

Always await it — the twin of the Rust ctx.audit_emit(db, input).await?.

type <T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>): Promise<void>

ctx

input

returns Promise<void>

throws

  • Error - when an in-transaction INSERT fails

mutates

  • audit_log table — inserts the row in `ctx.db`'s transaction or via the captured pool
  • ctx.pending_effects — appends a pool write's in-flight settled promise
  • ctx.post_commit_effects — appends a success row's listener fan-out

emit_role_grant_target

Emit a role-grant-shape audit event with actor_id / account_id / ip lifted from auth + ctx. Delegates to emit.

Use for any event populating one of the target_*_id columns. Reach for the lower-level emit only when the event is non-role-grant shape (e.g. app_settings_update, bootstrap, signup).

type <T extends string>(ctx: AuditEmitRoleGrantContext, auth: RequestActorContext, input: AuditEmitRoleGrantInput<T>): Promise<...>

ctx

auth

input

returns Promise<void>

emit_pool

Awaitable pool write for code paths without a request transaction — a maintenance script, or a post-commit thunk whose transaction has ended (db_admin_row_delete, http/db_routes.ts).

Writes, then notifies the listeners, whatever the outcome — there is no request transaction to order the fan-out against. Tracked by drain_inflight like emit's pool writes. Errors are logged and swallowed (resolved void), so callers can sequence writes with await audit.emit_pool(...) without try/catch boilerplate.

Not for a success audit paired with a state mutation: the pool write can't roll back with the state, and a swallowed error leaves the mutation unaudited. Write those in the mutation's transaction (query_audit_log against the tx) and notify after the commit — the shape auth/cleanup.ts's offer sweep and query_accept_offer use.

type <T extends string>(input: AuditLogInput<T>): Promise<void>

input

returns Promise<void>

mutates

  • audit_log table — inserts the row via the captured pool

notify

Fan out an already-written audit row to the registered listeners.

Use only when the row was inserted in-transaction by a query helper that returned the AuditLogEvent (e.g. query_accept_offer.audit_events). Per-listener exceptions are caught and logged; one failing listener does not starve siblings.

type (event: AuditLogEvent): void

event

returns void

add_listener

Register an audit-event listener. Listeners fire in registration order for every row emit / emit_pool wrote and on every notify.

Returns a remover that unregisters this registration. Each call is its own registration: the same function registered twice fires twice, and each remover takes out only the registration it was returned for, leaving the others in place and in order. Calling a remover again is a no-op. A notify already in progress still runs a listener removed during its fan-out — it iterates a snapshot — and the next one does not.

create_app_server registers the factory-managed audit-log SSE listener and the one revocation guard (create_revocation_guard) here so SSE + WS fan-out compose on top of the consumer's on_audit_event callback without shallow-copying AppDeps, and removes them when its assembly fails or the server closes. Consumers can also register listeners directly for setups that don't run through create_app_server. The on_audit_event passed at construction has no remover — it lives as long as the emitter.

Twin of the Rust fuz_auth AuditEmitter::add_listener in what registration means — firing order, snapshot-at-notify.

type (listener: (event: AuditLogEvent) => void): () => void

listener

called with each announced audit row

type (event: AuditLogEvent) => void
returns () => void

a remover for this registration, idempotent

listener_count

Count of registered listeners, the construction-time on_audit_event included — introspection for tests and diagnostics.

type (): number

returns number

drain_inflight

Await every fire-and-forget emit write in flight — the deterministic barrier behind the _testing_drain_effects test action.

Waits until no tracked write is outstanding, so a write that starts while the drain is waiting (an emit from a post-commit thunk) is awaited too. Once it resolves, a following audit_log_list read sees every row whose emit ran before the drain was called. A write that starts after the drain resolves is not covered. A success row's listener fan-out runs as a post-commit effect and is not tracked, so an SSE broadcast or an auth-guard close driven by one isn't guaranteed done when the drain resolves — the row itself is.

Resolves at once unless the emitter was built with track_inflight: true — production never tracks, so there is nothing to wait for. Twin of the Rust fuz_auth AuditEmitter::drain_inflight.

type (): Promise<void>

returns Promise<void>

is_tracking_inflight

Whether this emitter tracks its in-flight emit writes — true only when built with track_inflight: true. When false, drain_inflight resolves at once and waits on nothing.

The _testing_drain_effects action reports it as tracked, and the list_audit_events reads refuse false: a test binary whose emitter was built without tracking would otherwise answer the drain at once and its audit reads would race the writes. Twin of the Rust fuz_auth AuditEmitter::is_tracking_inflight.

type (): boolean

returns boolean

AuditEmitterContext
#

auth/audit_emitter.ts view source

AuditEmitterContext import type {AuditEmitterContext} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Per-request context required by AuditEmitter.emit — the request's db and its two side-effect queues. The bound emitter carries its own log reference inside the closure, so per-call contexts don't need one.

db decides where a success row is written: inside the request's transaction when db.in_transaction, else on the pool (module doc, "Where a row is written"). A pool write is eager — the emitter fires it immediately and pushes the in-flight Promise<void> onto pending_effects. A success row's listener fan-out goes on post_commit_effects (module doc, "When listeners hear about a row").

Both RouteContext and ActionContext structurally satisfy this shape, so handlers pass route / ctx directly. Never pass one from a post-commit thunk: its transaction has ended — use emit_pool there.

inheritance

pending_effects

type Promise<void>[]

AuditEventHandler
#

actions/connection_closer.ts view source

AuditEventHandler import type {AuditEventHandler} from '@fuzdev/fuz_app/actions/connection_closer.js';

Audit-event callback shape — the function CreateAuditEmitterOptions.on_audit_event accepts, AuditEmitter.add_listener registers, and create_revocation_guard returns.

(call)

type (event: AuditLogEvent): void

event

returns void

AuditEventType
#

auth/audit_log_schema.ts view source

value + type

"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"

type "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete"

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

Zod schema for audit event types.

AuditEventTypeName
#

auth/audit_log_schema.ts view source

value + type

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

Zod schema for valid audit event-type name strings.

AuditFactory
#

server/app_backend.ts view source

AuditFactory import type {AuditFactory} from '@fuzdev/fuz_app/server/app_backend.js';

Callback that builds the bound AuditEmitter after the backend's pool Db and Logger exist. The db it is handed is the backend's pool with the audit checkout bound (db_audit_checkout_timeout_ms, Db.with_checkout_timeout), so the emitter's pool writes wait longer than a request's checkout. Required on CreateAppBackendOptions so the consumer owns subscriber-chain composition and AuditLogConfig selection without the factory holding a default.

The factory is invoked exactly once during create_app_backend, after create_db resolves and migrations run. The emitter it returns lands on AppDeps.audit and is captured by every query/handler that reaches deps.audit.emit(...).

The canonical body is a one-liner over create_audit_emitter:

audit_factory: ({db, log}) => create_audit_emitter({ db, log, on_audit_event, audit_log_config, })

Returning an emitter built against a different db than the one passed in would route audit writes to a different pool than handlers query — the callback shape exists specifically to make that mistake structurally impossible.

(call)

type (params: { db: Db; log: Logger; }): AuditEmitter

params

type { db: Db; log: Logger; }
returns AuditEmitter

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 readonly 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, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

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 string

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

value + type

{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 4 more ...; metadata: Record<...> | null; } import {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 narrows metadata per event type.

id

type string & $brand<"Uuid">

seq

type number

event_type

type string

outcome

type "success" | "failure"

actor_id

type (string & $brand<"Uuid">) | null

account_id

type (string & $brand<"Uuid">) | null

target_account_id

type (string & $brand<"Uuid">) | null

target_actor_id

type (string & $brand<"Uuid">) | null

ip

type string | null

created_at

type string

metadata

type Record<string, unknown> | null

AuditLogEventWithUsernamesJson
#

auth/audit_log_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 6 more ...; target_username: string | null; } import {AuditLogEventWithUsernamesJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for audit log events with resolved usernames.

id

type string & $brand<"Uuid">

seq

type number

event_type

type string

outcome

type "success" | "failure"

actor_id

type (string & $brand<"Uuid">) | null

account_id

type (string & $brand<"Uuid">) | null

target_account_id

type (string & $brand<"Uuid">) | null

target_actor_id

type (string & $brand<"Uuid">) | null

ip

type string | null

created_at

type string

metadata

type Record<string, unknown> | null

username

type string | null

target_username

type string | null

AuditLogFilter
#

ui/audit_log_state.svelte.ts view source

AuditLogFilter import type {AuditLogFilter} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

The filter fields of AuditLogListInput — what AuditLogState.subscribe applies to live events and to its gap fill, matching what a fetch with the same fields returns. account_id matches either side of an event (account_id or target_account_id), as the list query does.

account_id?

type (string & $brand<"Uuid">) | null

event_type?

type string | null

outcome?

type "success" | "failure" | null

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 "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | ... 17 more ... | "db_admin_row_delete" ? (AuditMetadataMap[T] & Record<...>) | null : Record<...> | null

AuditLogListInput
#

auth/admin_action_specs.ts view source

value + type

{ event_type?: string | null | undefined; outcome?: "success" | "failure" | null | undefined; account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | ... 1 more ... | undefined; since_seq?: number | ... 1 more ... | undefined; acting?: (string & $brand<...>) | ... import {AuditLogListInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for audit_log_list. All filter fields are optional — omit for the default newest-first page. since_seq exists for SSE reconnection gap fill (caller supplies the highest seq seen; server returns everything after).

event_type?

type string | null

outcome?

type "success" | "failure" | null

account_id?

type (string & $brand<"Uuid">) | null

limit?

type number | null

offset?

type number | null

since_seq?

type number | null

acting?

type string & $brand<"Uuid">

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 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. seq is assigned at INSERT; an in-transaction row commits later, so a row can become visible after a higher seq — a gap fill can miss one that commits while the stream is down.

type number

AuditLogListOutput
#

auth/admin_action_specs.ts view source

value + type

{ events: { id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 6 more ...; target_username: string | null; }[]; } import {AuditLogListOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for audit_log_list.

events

type AuditLogEventWithUsernamesJson[]

AuditLogRoleGrantHistoryInput
#

auth/admin_action_specs.ts view source

value + type

{ limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {AuditLogRoleGrantHistoryInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for audit_log_role_grant_history.

limit?

type number | null

offset?

type number | null

acting?

type string & $brand<"Uuid">

AuditLogRoleGrantHistoryOutput
#

auth/admin_action_specs.ts view source

value + type

{ events: { id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 6 more ...; target_username: string | null; }[]; } import {AuditLogRoleGrantHistoryOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for audit_log_role_grant_history.

events

type RoleGrantHistoryEventJson[]

AuditLogRouteOptions
#

auth/audit_log_routes.ts view source

AuditLogRouteOptions import type {AuditLogRouteOptions} from '@fuzdev/fuz_app/auth/audit_log_routes.js';

Options for audit log route specs.

required_role?

Role required to access audit routes. Default 'admin'.

type string

stream?

When provided, includes an SSE route at /audit/stream for realtime audit events. The route registers each stream on registry under the subscriber's session hash (scope) and account id (groups, with the API token id beside it for a bearer) — the keys close_by_identity() closes on for auth revocation. An AuditLogSse (create_audit_log_sse, or AppServerContext.audit_sse) satisfies it.

type { registry: SseRegistry<SseNotification>; log: Logger; keep_alive_ms?: number | null | undefined; }

AuditLogRpc
#

ui/audit_log_state.svelte.ts view source

AuditLogRpc import type {AuditLogRpc} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

Narrow RPC surface consumed by AuditLogState. Consumers adapt their typed RPC client to this shape. Mirrors AdminAccountsRpc / AdminInvitesRpc. Method signatures track the wire spec inputs/outputs directly so the adapter needs no casts.

list

type (params?: { event_type?: string | null | undefined; outcome?: "success" | "failure" | null | undefined; account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | ... 1 more ... | undefined; since_seq?: number | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } | undefined): Promise<...>

params?

optional
returns Promise<AuditLogListOutput>

role_grant_history

type (params?: { limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ events: { id: string & $brand<...>; ... 11 more ...; target_username: string | null; }[]; }>

params?

optional

AuditLogSse
#

realtime/audit_log_sse.ts view source

AuditLogSse import type {AuditLogSse} from '@fuzdev/fuz_app/realtime/audit_log_sse.js';

Convenience factory result for audit log SSE.

Satisfies AuditLogRouteOptions['stream'] and provides the on_audit_event callback that broadcasts each row to the stream.

registry

The SSE registry the audit stream route registers each stream on (two-phase: subscribe_pending then admit) — pass as part of the stream option to create_audit_log_route_specs. Also exposed for subscriber count monitoring.

type SseRegistry<SseNotification>

log

Logger — pass as part of stream option to create_audit_log_route_specs.

type Logger

on_audit_event

Broadcasts each audit row to the stream's subscribers. Wired by create_app_server's audit_log_sse option, or compose inside the consumer's audit_factory body. Closes nothing: revocations reach the streams through the registry's membership in the backend's closer.

type (event: AuditLogEvent): void

event

returns void

required_role

The role the stream requires — what a caller adapting registry passes create_sse_connection_closer, so a role revocation closes its streams.

type string

keep_alive_ms?

Each stream's keep-alive interval, read by the audit stream route (AuditLogRouteOptions['stream']); undefined keeps the default.

type number | null

AuditLogState
#

ui/audit_log_state.svelte.ts view source

import {AuditLogState} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

role_grant_history

type AsyncSlot<void, string>

readonly

gap_fill

The catch-up fetch run each time the stream opens; its error surfaces a failed fill.

type AsyncSlot<void, string>

readonly

events

type AuditLogEventWithUsernamesJson[]

$state.raw

role_grant_history_events

type RoleGrantHistoryEventJson[]

$state.raw

count

type number

readonly $derived

stream_status

Lifecycle of the SSE stream — see AuditLogStreamStatus.

type AuditLogStreamStatus

$state.raw

gap_truncated

Whether a gap fill hit its row limit, so events between the last one seen and the stream's reopening may be missing. Cleared by fetch, which replaces the list (as is a failed gap_fill).

type boolean

$state.raw

constructor

type new (options: AuditLogStateOptions): AuditLogState

options

fetch

Load a page of events, replacing events.

While the stream is live, events it delivered after the page's newest one that match the fetch's filter are kept, so a fetch answering mid-stream doesn't drop them. A newest-page fetch aborts an in-flight gap fill, which its own query covers, and when the stream opened before the list matched its filter or while the fetch was in flight, runs that open's gap fill from the fetched page.

type (options?: { event_type?: string | null | undefined; outcome?: "success" | "failure" | null | undefined; account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | ... 1 more ... | undefined; since_seq?: number | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } | undefined): Promise<...>

options?

the list input; its filter fields become the list's filter

optional
returns Promise<void>

mutates

  • this

fetch_role_grant_history

type (limit?: number | undefined, offset?: number | undefined): Promise<void>

limit?

type number
optional

offset?

type number
optional
returns Promise<void>

subscribe

Connect to the SSE stream for realtime audit events.

Live events matching filter merge into events by seq. EventSource reconnects on transient errors (stream_status reads 'connecting'); each open runs a gap fill from the last seq seen, with the same filter. A refused reconnect ends the stream ('closed') — call subscribe again to retry. Gap fills run only while events was fetched with the same filter: an open the list doesn't match yet defers its fill to the fetch with the stream's filter, and live events under another filter than the list's merge only when they match the list's filter too, without advancing the anchor.

type (filter?: AuditLogFilter | undefined): () => void

filter?

which events to keep; omit for all

optional
returns () => void

cleanup function that closes the connection

mutates

  • this

disconnect

Close the SSE connection and abort an in-flight gap fill.

type (): void

returns void

mutates

  • this

AuditLogStateOptions
#

ui/audit_log_state.svelte.ts view source

AuditLogStateOptions import type {AuditLogStateOptions} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

get_rpc

Reactive accessor for the RPC adapter. Matches the get_rpc pattern on AdminAccountsState. The SSE stream uses EventSource directly and is independent of this adapter.

type (): AuditLogRpc

returns AuditLogRpc

stream_url?

SSE stream URL. Defaults to the shipped admin audit-log stream route.

type string

AuditLogStreamStatus
#

ui/audit_log_state.svelte.ts view source

AuditLogStreamStatus

type "off" | "connecting" | "open" | "closed"

import type {AuditLogStreamStatus} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

Lifecycle of the audit SSE stream.

  • 'off' — not subscribed (initial, or after disconnect)
  • 'connecting' — opening, or EventSource is retrying after a transient error
  • 'open' — connected and receiving events
  • 'closed' — the stream gave up: the server refused the reconnect (a revoked credential or a lost role answers it with 401/403), so EventSource stopped retrying; call subscribe again to retry

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.

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_kind?: string | null | 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: string; role_grant_id?: (string & $brand<"Uuid">) | undefined; scope_id?: (string & $brand<"Uuid">) | null | undefined; reason?: string | 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" | "account_deleted"; cause_id: string & $brand<...>; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; }

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; }

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; }

app_settings_update

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

db_admin_row_delete

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

AuditOutcome
#

auth/audit_log_schema.ts view source

value + type

"success" | "failure"

type "success" | "failure"

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

Zod schema for audit event outcomes.

AuditReadFixture
#

testing/cross_backend/audit_cross_helpers.ts view source

AuditReadFixture import type {AuditReadFixture} from '@fuzdev/fuz_app/testing/cross_backend/audit_cross_helpers.js';

The fixture members an audit read uses — every TestFixture has them.

fresh_transport

type (options?: { readonly origin?: string | null | undefined; } | undefined): FetchTransport

readonly

options?

type { readonly origin?: string | null | undefined; }
optional

create_session_headers

The keeper's session headers — the keeper holds the admin role the audit read needs.

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_daemon_token_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

reject_unknown_audit_metadata_keys?

Refuse undeclared metadata keys — see TestFixture.reject_unknown_audit_metadata_keys.

type boolean

readonly

AuditStreamQuery
#

auth/audit_log_route_schema.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; } import {AuditStreamQuery} from '@fuzdev/fuz_app/auth/audit_log_route_schema.js';

Query schema for the audit-log SSE route — multi-actor admins pass ?acting=<uuid>.

acting?

type string & $brand<"Uuid">

auth_integration_truncate_tables
#

testing/db.ts view source

string[] import {auth_integration_truncate_tables} from '@fuzdev/fuz_app/testing/db.js';

Auth tables including audit_log — for integration tests that exercise the full middleware stack (login, admin, rate limiting).

Separate from auth_truncate_tables because unit-level DB tests that don't touch audit logging don't need to truncate it.

AUTH_MIDDLEWARE_PATH
#

auth/middleware.ts view source

"/api/*" import {AUTH_MIDDLEWARE_PATH} from '@fuzdev/fuz_app/auth/middleware.js';

The Hono path pattern the auth middleware stack mounts on by default — session parsing, request context, bearer and daemon token auth all run for the paths it matches, and no others (the origin check runs on every path, ORIGIN_MIDDLEWARE_PATH).

create_app_server mounts the stack here, and refuses at assembly (via assert_endpoint_in_auth_scope, which create_test_app_surface_spec also runs) an rpc_endpoints, ws_endpoints, or bootstrap path the pattern does not match (tested with middleware_applies, which follows Hono: '/api/*' matches /api and everything under /api/): an endpoint outside it would get no session or bearer parsing, so every call or upgrade would be unauthenticated.

AUTH_MIGRATION_NS
#

AUTH_SESSION_COLUMNS
#

auth/session_queries.ts view source

readonly ["id", "account_id", "created_at", "expires_at"] import {AUTH_SESSION_COLUMNS} from '@fuzdev/fuz_app/auth/session_queries.js';

The full auth_session column set, named explicitly so a row read fails loud on schema drift — SELECT * would silently carry a dropped or leftover column into the strict-validated wire shapes (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class this discipline exists to prevent; the Rust twin already names columns at every session site). Keep in sync with AuthSession and the migration chain's end state — not the frozen v0 DDL in auth/auth_ddl.ts, which still creates last_seen_at for the appended drop migration to remove.

AUTH_SESSION_INDEXES
#

AUTH_SESSION_LIFETIME_MS
#

auth/session_queries.ts view source

number import {AUTH_SESSION_LIFETIME_MS} from '@fuzdev/fuz_app/auth/session_queries.js';

Session lifetime in milliseconds (30 days).

An absolute cap: expires_at is set once at mint and never extended — there is deliberately no touch/renewal query on either spine (a sliding window renews a leaked cookie forever; see docs/security.md §Session Security). The cookie's SESSION_AGE_MAX mirrors this value.

AUTH_SESSION_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS auth_session (\n id TEXT PRIMARY KEY,\n account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ NOT NULL,\n last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n)" import {AUTH_SESSION_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

auth_state_context
#

ui/auth_state.svelte.ts view source

{ get: (error_message?: string | undefined) => AuthState; get_maybe: () => AuthState | undefined; set: (value: AuthState) => AuthState; } import {auth_state_context} from '@fuzdev/fuz_app/ui/auth_state.svelte.js';

Svelte context for AuthState. Use auth_state_context.set(state) in the provider and auth_state_context.get() to access.

auth_truncate_tables
#

testing/db.ts view source

string[] import {auth_truncate_tables} from '@fuzdev/fuz_app/testing/db.js';

Auth table names in truncation order (children first for FK safety).

Consumer projects can spread this into their own list and append app-specific tables.

AuthActionHandler
#

actions/action_types.ts view source

AuthActionHandler<TInput, TOutput> import type {AuthActionHandler} from '@fuzdev/fuz_app/actions/action_types.js';

Handler signature for an account-grain RPC action — auth.account === 'required' and auth.actor === 'none'. Mirrors ActionHandler but tightens the ctx.auth slot to the non-null RequestContext (with actor: null).

generics

AuthActionHandler<TInput = any, TOutput = any>
TInput
default any
TOutput
default any

(call)

type (input: TInput, ctx: ActionAuthContext): TOutput | Promise<TOutput>

input

type TInput

ctx

returns TOutput | Promise<TOutput>

AuthAxisState
#

http/auth_shape.ts view source

value + type

"optional" | "none" | "required"

type "optional" | "none" | "required"

import {AuthAxisState} from '@fuzdev/fuz_app/http/auth_shape.js';

Per-axis auth state — names the dispatcher's behavior on account and actor independently:

  • 'none' — explicitly skipped, even when the credential provides it. Public actions (no auth surface) and notifications declare this.
  • 'optional' — surfaced if the credential provides it, null otherwise. Identity-aware reads with anonymous fallback (cell_get-style) declare this on account / actor.
  • 'required' — must be resolved; the dispatcher rejects requests that fail to provide it (401 for account === 'required' without a credential; the authorization phase 4xx for actor === 'required' without an actor binding).

AuthCleanupDeps
#

auth/cleanup.ts view source

AuthCleanupDeps import type {AuthCleanupDeps} from '@fuzdev/fuz_app/auth/cleanup.js';

Dependencies for the cleanup helpers. AppDeps satisfies it structurally, so a server passes backend.deps.

inheritance

extends: QueryDeps

log

type Logger

audit

Bound audit emitter. cleanup_expired_role_grant_offers writes its audit rows in its own transaction and fans each one out through audit.notify after the commit, so listeners never see an event that rolled back. Required — production wiring always has a bound emitter on AppDeps.audit, and tests that need a no-op pass create_test_audit_emitter().

type AuditEmitter

connection_closer

Closes the connections of each session the sweep deletes. Pass the closer the app's revocation handlers hold — AppDeps.connection_closer, the RealtimeCloser over every registry it serves. Required: a sweep with nothing to close through would strand the swept sessions' connections. noop_connection_closer is for a database no other process holds connections on — a sweep run beside a live server from another process deletes the rows that server's own pass would have closed for.

type ConnectionCloser

AuthCleanupResult
#

auth/cleanup.ts view source

AuthCleanupResult import type {AuthCleanupResult} from '@fuzdev/fuz_app/auth/cleanup.js';

Counts from one run_auth_cleanup pass. The Rust AuthCleanupResult is the same shape.

expired_sessions

Number of expired session rows deleted — each one's connections closed.

type number

expired_offers

Number of expired role_grant offer rows audit-stamped.

type number

AuthCleanupScheduleOptions
#

AuthGuardResolver
#

http/route_spec.ts view source

AuthGuardResolver import type {AuthGuardResolver} from '@fuzdev/fuz_app/http/route_spec.js';

Resolves a RouteAuth to middleware guard handlers.

Injected into apply_route_specs to decouple route registration from auth-specific middleware. See fuz_auth_guard_resolver in auth/auth_guard_resolver.ts for the standard implementation.

(call)

type (auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): AuthGuards

auth

returns AuthGuards

AuthGuards
#

http/route_spec.ts view source

AuthGuards import type {AuthGuards} from '@fuzdev/fuz_app/http/route_spec.js';

Two-phase auth guard set returned by AuthGuardResolver.

pre_authorization runs before the authorization phase — the 401 check and the rule-3 token-scope refusal live here, so a caller either one turns away never reaches the actor resolution or the route's body. post_authorization runs after the authorization phase has populated RequestContext — credential / role checks live here because they read c.var.request_context.role_grants.

Both phases run ahead of body validation, which is why neither is named for it: route-shape information stays behind every authority gate. The names say which side of the authorization phase a guard sits on, the only axis that still distinguishes them.

pre_authorization

type MiddlewareHandler[]

post_authorization

type MiddlewareHandler[]

AuthMiddlewareOptions
#

auth/middleware.ts view source

AuthMiddlewareOptions import type {AuthMiddlewareOptions} from '@fuzdev/fuz_app/auth/middleware.js';

Per-factory configuration for the standard auth middleware stack.

allowed_origins

type RegExp[]

session_options

type SessionOptions<string>

path?

Path pattern for the session, request-context, bearer, and daemon-token layers (default: AUTH_MIDDLEWARE_PATH, '/api/*'). The origin check always mounts on every path (ORIGIN_MIDDLEWARE_PATH).

type string

origin_carve_outs?

Paths the origin check skips, each with its reason (default none). Copied at assembly (freeze_origin_carve_outs), then checked by assert_origin_carve_outs_valid — none may overlap path; recorded on the origin spec, so the generated surface leaves the check's 403 off the carved-out routes.

type readonly MiddlewareCarveOut[]

daemon_token_state?

Daemon token state for keeper auth. Omit to disable daemon token middleware.

type DaemonTokenState

AuthorizationFailureBody
#

auth/request_context.ts view source

AuthorizationFailureBody

type { error: "actor_required"; available: { id: string; name: string; }[]; } | { error: "actor_not_on_account"; } | { error: "no_actors_on_account"; } | { error: "account_vanished"; }

import type {AuthorizationFailureBody} from '@fuzdev/fuz_app/auth/request_context.js';

Resolution-failure shape returned by apply_authorization_phase. Each transport binds this to the appropriate wire shape — REST emits the body directly via c.json(body, status); the RPC dispatcher folds it into a JSON-RPC error envelope {jsonrpc, id, error: {code, message, data}}.

The auth phase deliberately stops short of constructing a Response so the same failure flows through every transport without the auth-domain code knowing about JSON-RPC. See ../../../CLAUDE.md §Cleanest architecture takes priority for the rationale.

AuthorizationHandler
#

http/route_spec.ts view source

AuthorizationHandler import type {AuthorizationHandler} from '@fuzdev/fuz_app/http/route_spec.js';

Per-route authorization phase. Runs after the pre-authorization auth guards and before input validation; resolves the acting actor (when `auth.actor !== 'none') from the acting selector — c.var.validated_query.acting` on GETs, read off the raw body on mutations — and sets the request context on the Hono context. Per-route order in apply_route_specs: content type (415) → params → query → pre-authorization auth (401 + rule-3 scope) → authorization phase → post-authorization auth (403) → input validation (400) → handler.

Returns a Response to short-circuit (resolution failure → 400 / 500), or void to continue. The http framework stays auth-agnostic — fuz_app provides the implementation via create_fuz_authorization_handler in auth/request_context.ts.

(call)

type (c: Context<any, any, {}>, spec: RouteSpec): Promise<void | Response>

c

type Context<any, any, {}>

spec

returns Promise<Response | void>

AuthorizationResult
#

auth/request_context.ts view source

AuthorizationResult

type { ok: true; request_context: RequestContext | null; } | { ok: false; status: 500 | 400; body: AuthorizationFailureBody; }

import type {AuthorizationResult} from '@fuzdev/fuz_app/auth/request_context.js';

Result of the authorization phase. Pure data — the auth domain stops short of touching the Hono context or producing a Response so HTTP RPC, WS, and REST each bind the same shape to their wire surface.

  • {ok: true, request_context} — request_context is non-null on resolved (actor-bound or account-only) outcomes; null for public actions ({account: 'none', actor: 'none'}) and for genuine anonymous access on an 'optional' axis. Public and unauthenticated collapse to the same null request_context; every transport already treated them identically.
  • {ok: false, status, body} — 400/500 failure. status is narrowed to the two values the auth phase emits, so Hono's c.json status overload accepts the literals directly. The 500 reasons stay distinct in body: no_actors_on_account (signup invariant violation); account_vanished (torn read after resolve).

AuthPrincipal
#

testing/integration_helpers.ts view source

AuthPrincipal

type "public" | "daemon_token" | "admin" | "keeper" | "authed"

import type {AuthPrincipal} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Who a generated request calls as, picked from the declared auth: no credential for a public route or method, else the keeper's daemon token (credential_types names it), the admin account's session (an admin-gated one), the keeper's session (another role), or the authed account's session.

AuthSession
#

auth/account_schema.ts view source

AuthSession import type {AuthSession} from '@fuzdev/fuz_app/auth/account_schema.js';

Server-side auth session row, keyed by blake3 hash of the session token. The wire shape AuthSessionJson less is_current, which no column holds — the session list computes it per request.

id

type string & $brand<"SessionId">

account_id

type string & $brand<"Uuid">

created_at

type string

expires_at

type string

AuthSessionJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; is_current: boolean; } import {AuthSessionJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for AuthSession — id is the blake3 hash, safe for client.

id

type string & $brand<"SessionId">

account_id

type string & $brand<"Uuid">

created_at

type string

expires_at

type string

is_current

type boolean

AuthSessionRouteOptions
#

auth/account_routes.ts view source

AuthSessionRouteOptions import type {AuthSessionRouteOptions} from '@fuzdev/fuz_app/auth/account_routes.js';

Shared options for route factories that create sessions.

Extended by AccountRouteOptions and SignupRouteOptions. Consumers can destructure these from AppServerContext once and spread into multiple factories.

The per-IP limiter is deliberately *not* here. Each auth surface names its own (login_ip_rate_limiter, signup_ip_rate_limiter, bootstrap_ip_rate_limiter) because the buckets are monotone within their window — see RateLimiter.reset. A shared base field made one instance the path of least resistance, which let a failure on any surface spend the budget that bounds guessing on every other one.

session_options

type SessionOptions<string>

AuthState
#

ui/auth_state.svelte.ts view source

import {AuthState} from '@fuzdev/fuz_app/ui/auth_state.svelte.js';

verifying

type boolean

$state.raw

verified

type boolean

$state.raw

verify_error

type string | null

$state.raw

account

type SessionAccount | null

$state.raw

actor

type ActorSummaryJson | null

$state.raw

role_grants

type RoleGrantSummaryJson[]

$state.raw

active_role_grants

The live subset of role_grants, at every scope. Liveness is judged when role_grants is assigned, so a grant that expires while the page stays open reads as active until the next check_session.

type RoleGrantSummaryJson[]

readonly $derived

global_roles

The roles held globally — the role of each active grant whose scope_id is null. A grant scoped to one resource is not in here: it confers its role on that resource only, and a scoped grant of a builtin role confers nothing, so a role gate reads the global grant. Scoped grants are read from active_role_grants, by scope_id.

Shapes the UI only — the server re-checks every request.

type ReadonlySet<string>

readonly $derived

is_admin

True for an active global admin grant, the rule the server's admin gates apply.

type boolean

readonly $derived

needs_bootstrap

True when bootstrap is available (no accounts exist yet).

type boolean

$state.raw

has_global_role

Whether the account holds an active global grant of role — reactive when read in a $derived or the template. A scoped grant doesn't count; see global_roles.

type (role: string): boolean

role

the role to check

type string
returns boolean

check_session

Check auth state and bootstrap availability.

Fetches GET /api/account/status — returns account info (200) or 401 with optional bootstrap_available flag. Called on init, and after login/bootstrap to refresh state.

type (): Promise<void>

returns Promise<void>

mutates

  • this

login

Log in with username and password. Translates 401 / 429 to friendly messages on verify_error; refreshes the session via check_session on success.

type (username: string, password: string): Promise<boolean>

username

type string

password

type string
returns Promise<boolean>

true if login succeeded, false otherwise

mutates

  • this

bootstrap

Bootstrap the first keeper account using a single-use token.

type (token: string, username: string, password: string): Promise<boolean>

token

type string

username

type string

password

type string
returns Promise<boolean>

true if bootstrap succeeded, false otherwise

mutates

  • this

signup

Sign up via invite (or open signup, when app_settings.open_signup is true server-side). Translates 403 / 409 / 429 to friendly messages on verify_error.

type (username: string, password: string, email?: string | undefined): Promise<boolean>

username

type string

password

type string

email?

type string
optional
returns Promise<boolean>

true if signup succeeded, false otherwise

mutates

  • this

logout

Log out — best-effort POST /api/account/logout to clear the session cookie, then clears local state regardless of the network outcome.

type (): Promise<void>

returns Promise<void>

mutates

  • this

AuthTestApps
#

testing/auth_apps.ts view source

AuthTestApps import type {AuthTestApps} from '@fuzdev/fuz_app/testing/auth_apps.js';

Pre-built Hono apps for each auth level, shared across adversarial test suites.

public

type Hono<BlankEnv, BlankSchema, "/">

authed

type Hono<BlankEnv, BlankSchema, "/">

keeper

type Hono<BlankEnv, BlankSchema, "/">

by_credential_type

One authenticated app per builtin credential type, each holding the keeper role — the channel axis, where by_role is the authority axis.

A credential gate is pre-authorization, so probing it needs an app on a channel the route refuses, and proving a gated route still works needs one on a channel it admits. keeper is the 'daemon_token' entry of this map, not a second app.

type Map<BuiltinCredentialType, Hono<BlankEnv, BlankSchema, "/">>

by_role

type Map<string, Hono<BlankEnv, BlankSchema, "/">>

BACKEND_PROBE_ABORTED
#

ui/backend_probe.svelte.ts view source

"aborted" import {BACKEND_PROBE_ABORTED} from '@fuzdev/fuz_app/ui/backend_probe.svelte.js';

The refusal for a probe its caller's signal aborted — a cancel, not a timeout.

BACKEND_PROBE_NOT_HEALTH
#

ui/backend_probe.svelte.ts view source

"not a health response" import {BACKEND_PROBE_NOT_HEALTH} from '@fuzdev/fuz_app/ui/backend_probe.svelte.js';

The refusal for a 2xx whose body isn't the health reply.

BACKEND_PROBE_TIMEOUT_MS
#

ui/backend_probe.svelte.ts view source

10000 import {BACKEND_PROBE_TIMEOUT_MS} from '@fuzdev/fuz_app/ui/backend_probe.svelte.js';

How long the default fetch waits for the health reply before the probe reads as down.

BackendBootstrapConfig
#

testing/cross_backend/backend_config.ts view source

BackendBootstrapConfig import type {BackendBootstrapConfig} from '@fuzdev/fuz_app/testing/cross_backend/backend_config.js';

Auth-bootstrap configuration for a spawnable test binary. The runner writes token to token_path before launching the child, then POSTs bootstrap_path (default /api/account/bootstrap) with the token plus the username / password to mint the keeper account and capture the session cookie. After health-probe, the runner reads daemon_token_path to load the binary's deterministic daemon token, which default_cross_process_setup threads onto the per-test TestFixture for _testing_reset calls and other keeper-credential operations.

token_path

Path the binary reads for the bootstrap token (env: *_BOOTSTRAP_TOKEN_PATH).

type string

readonly

token

Token text written to token_path before spawn.

type string

readonly

username

Username for the bootstrapped keeper.

type string

readonly

password

Password for the bootstrapped keeper.

type string

readonly

daemon_token_path

Path the test binary writes its daemon-token JSON to on boot (env: *_DAEMON_TOKEN_PATH). spawn_backend reads this file once after the health probe succeeds and threads the token onto BackendHandle.daemon_token for _testing_reset calls plus any other admin/keeper-gated cross-process tests. spawn_backend deletes the file before each spawn, so a token a previous run left is never read.

type string

readonly

BackendCapabilities
#

testing/cross_backend/capabilities.ts view source

BackendCapabilities import type {BackendCapabilities} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

Optional behaviors a backend may support. Each flag's TSDoc names the tests that gate on it; add a new flag here before referencing it from a suite body, and document the gating tests inline. A wiring fact no suite reads doesn't belong here.

ws

WebSocket transport is reachable end-to-end. Gates the cross-process WS round-trip suite, the per-account connection-cap suite (describe_ws_connection_cap_cross_tests), and the front door's WS-path cases (describe_transport_composition_cross_tests) — the upgrade rows, and the rows that ask for no upgrade, which run in-process too; the in-process describe_ws_round_trip_tests runs against register_action_ws directly and ignores this flag.

type boolean

readonly

sse

SSE transport is reachable end-to-end. Gates the cross-process SSE suite (describe_cross_process_sse_tests — connect, audit data frame, close-on-revoke, the per-session stream cap); in-process SSE uses the on_audit_event hook and ignores this flag.

type boolean

readonly

cell_crud

Cell CRUD verbs (cell_create / cell_get / cell_update / cell_delete / cell_list) are live-mounted on the backend's RPC path and its DB carries the fuz_cell migration namespace. Gates the dedicated describe_cell_crud_cross_tests suite. Like ws / sse, cells stay off the standard declared surface — only this flag opts a backend into the cell parity coverage.

type boolean

readonly

cell_relations

The relation / ACL / audit cell verbs beyond plain CRUD (cell_grant_* / cell_field_* / cell_item_* / cell_clone / cell_audit_list) are live-mounted on the backend's RPC path. Gates the dedicated describe_cell_relations_cross_tests suite — grant lifecycle, field / item bidirectional relations, clone shallow + deep, manage-tier audit gating, and the now-reachable cell_visibility_manage_only 403 (editor-grant principal). Like cell_crud, these stay off the standard declared surface; a backend mounting only plain CRUD declares cell_crud: true, cell_relations: false.

type boolean

readonly

account_lifecycle

The account-lifecycle admin verbs (account_delete soft-delete, account_undelete reactivation, account_purge keeper hard-delete) are live-mounted on the backend's RPC path. Gates the dedicated describe_account_lifecycle_cross_tests suite.

Unlike cells / fact-serving / ws / sse, these verbs are on the standard declared surface — they live in create_admin_actions, so create_spine_surface_spec carries them and the spec-derived round-trip + attack-surface suites already auto-enumerate their wire shape + auth. This flag gates the *behavioral* parity the generic round-trip can't provide: it can't drive verbs that delete their own subject (soft-delete → undelete round-trip, keeper-confirmed purge, the keeper-guard refusal), so the dedicated cross suite adds them.

type boolean

readonly

fact_serving

The cell-gated fact-serving route (GET /api/cells/:cell_id/facts/:hash) is live-mounted on the backend, its DB carries the fuz_facts migration namespace, and it mounts the _testing_put_fact seeder — declared in testing_backdoors, which describe_spine_cross_suites requires. Gates describe_fact_serving_cross_tests' per-reference read model (cell-scoped admit via a viewable cell, cross-owner-dedup-no-leak, 404-mask), describe_cell_ref_admission_cross_tests (its grant, item, and clone cases with cell_relations), and the fact cases of the moderation suite. Like cells, the serve route stays off the standard declared surface. The admin bare-hash route is its own flag, fact_serving_admin_hash. The suite's missing-disk-file case assumes the backend serves external facts from disk: one with X-Accel delivery on hands off with a 200 (nginx would answer the missing file) and fails it.

type boolean

readonly

fact_serving_admin_hash

The admin-only bare-hash fact route (GET /api/facts/:hash) is live-mounted. Gates the bare-hash cases of describe_fact_serving_cross_tests (with fact_serving, whose seeder they use), describe_query_shape_cross_tests and describe_token_scope_surface_cross_tests. A backend serving only the cell-gated route states `fact_serving: true, fact_serving_admin_hash: false`.

type boolean

readonly

ready

The /ready readiness deploy gate (GET /ready) is live-mounted on the backend — the public column-presence schema-drift probe over the committed expected_schema.json. Gates describe_ready_cross_tests (anonymous GET /ready → 200 {ready: true} on a clean spine bootstrap). Like ws/sse/cells, the route stays off the standard declared surface (create_spine_surface_spec); this flag opts a backend into the dedicated readiness parity coverage. The drift → 503 path stays per-impl unit tests.

type boolean

readonly

account_status

The account surface serves GET /api/account/status (account info + bootstrap_available flag). Bundled into create_account_route_specs, so any backend mounting the account routes serves it — true for every spine. Gates the account status response body case in describe_standard_integration_tests: when true the case asserts the route is present (fail-loud on 404, no silent skip); when false it skips explicitly (a backend that deliberately omits the route).

type boolean

readonly

peer_request

The backend can initiate a JSON-RPC request to a connected client and await its typed reply (the server→client request/response direction ActionPeer adds). Gates describe_peer_ping_ws_tests — the on-demand peer/ping round-trip plus its security negatives (unsolicited-response rejection, per-connection id isolation, never-replying Timeout, wrong-shape reply rejection).

true on fuz_app's own spine presets — the Rust testing_spine_stub and the TS spine (ConnectionRegistry.request_connection drives the round-trip, correlated by register_action_ws). A MountFlag: a backend states true once it mounts peer/ping on a WS endpoint and the HTTP RPC endpoint (the no-transport refusal). Like ws/sse/cells, peer/ping stays off the standard declared surface (it's a protocol action, manifest-excluded), so this flag is the only opt-in into the peer parity coverage.

type boolean

readonly

ws_handshake_pipelining

The backend's HTTP server accepts WebSocket frames written in the same TCP write as the upgrade request, handing them to the socket once the handshake completes. Gates the frames sent with the handshake case in describe_cross_process_ws_tests — the deterministic form of "frames that arrive before the connection is admitted wait for admission", since those frames are in the backend's hands before it can have admitted anything.

true for Node (ws), Deno, and the Rust spine (hyper hands the leftover bytes to the upgraded stream); false for a Bun backend that upgrades after an asynchronous check, as one behind auth does — Bun.serve answers such a request 400 (observed on Bun 1.4.3). A backend without it still runs the suite's client-library case (requests sent the moment the socket opens), which pins the same contract without forcing the ordering.

type boolean

readonly

ws_account_actions

The backend's WebSocket endpoint mounts the self-service account actions (account_session_revoke_all among them), so a socket can revoke the session it is running on. Gates the self-revocation case in describe_cross_process_ws_tests: the socket reads the reply to its request, then is closed with WS_CLOSE_SESSION_REVOKED. Also gates that suite's WS-mutation audit case (with rpc_path; it calls _testing_drain_effects).

true on fuz_app's own spine presets — the TS spine binary mounts its full action set on /api/ws, and the Rust testing_spine_stub serves one action registry on RPC and WS. A MountFlag: a consumer's WS endpoint carries whatever its domain mounts, so it states true once the account actions are there.

type boolean

readonly

cell_gated_create

A test CellCreateAuthorize policy is live-mounted on the backend's cell layer — creating a kind: 'gated' cell requires the participant role or admin; every other kind (and a typeless cell) is open. Gates describe_cell_gated_create_cross_tests, the TS↔Rust cell-creation- authorizer parity proof. The authorizer adds no method / column / wire shape, so schema-snapshot + action-manifest parity are blind to an authorizer divergence — this behavioral cross case is the only gate that catches one. true only on the reference spine binaries that mount the test policy (the TS spine full_spine_mount + the Rust testing_spine_stub); consumers and the in-process default app don't mount it. The same binaries mount the steward-revoke test policy (test_cell_grant_steward_revoke_authorize) beside it, so the flag also gates describe_cell_grant_steward_cross_tests (with cell_relations).

type boolean

readonly

ws_role_gated

The backend mounts a second, role-gated WebSocket endpoint at SPINE_ROLE_GATED_WS_PATH, gated on SPINE_ROLE_GATED_WS_ROLES, beside its ungated ws_path. Gates describe_ws_role_gated_cross_tests: a revoked global grant of a listed role closes the account's gated socket with WS_CLOSE_PERMISSION_REVOKED, its ungated socket stays open and still receives the account's pushes, a re-upgrade is refused at the gate, and a scoped revoke closes nothing. true only on the reference spine binaries that mount that endpoint (the TS spine binary + the Rust testing_spine_stub).

type boolean

readonly

ws_refusal_body

A refused WebSocket upgrade carries its refusal body — the flat {error, ...} the same refusal answers over plain HTTP. Not a gate: it forks how describe_transport_composition_cross_tests reads its WS rows. true, an empty refusal body fails the case; false, an empty body is allowed (the case pins the status alone) and a non-empty one is still asserted.

false on Node: @hono/node-ws writes a refused upgrade to the raw socket with Content-Length: 0, dropping the response body. true on Deno, Bun, and the Rust spine, which send the response as built.

type boolean

readonly

testing_backdoors

The _testing_* backdoors the backend mounts on its RPC endpoint, from TESTING_BACKDOOR_METHODS (a consumer's own seams stay off it). Not a flag, but declared like a MountFlag: no family default, so every config states it. describe_testing_backdoor_cross_tests checks the declaration against the backend — a declared method must be mounted, an undeclared one must answer method_not_found — and runs its credential-gate rows over the declared ones; describe_spine_cross_suites requires _testing_reset and _testing_drain_effects (the per-test reset and the audit barrier), skips a suite whose backdoor is undeclared, naming it — credential_deadline (_testing_shorten_credential_expiry), origin_conformance (_testing_origin_check), ws_outbound_overflow (_testing_flood_notifications) — and requires _testing_put_fact of a backend declaring fact_serving.

fuz_app's spine presets declare every one; the in-process transport none (the backdoor suites are cross-process only).

type readonly TestingBackdoorMethod[]

readonly

BackendConfig
#

testing/cross_backend/backend_config.ts view source

BackendConfig import type {BackendConfig} from '@fuzdev/fuz_app/testing/cross_backend/backend_config.js';

Configuration for one spawnable test backend. Consumer factories (deno_backend_config(), rust_backend_config()) produce these and the runner consumes them through spawn_backend.

Path defaults match the standard fuz_app surface — Deno + Rust spine (zzz_server, fuz_forge_server, testing_spine_stub) all converge on /api/account/{bootstrap,login,logout,password}, /api/rpc, /api/ws, /health. Override only when a backend deliberately diverges (which it shouldn't, per the contract).

name

Diagnostic label ("deno", "rust", "spine_stub"). Surfaces in test output.

type string

readonly

start_command

argv passed to the spawn. The first entry is the binary path.

type readonly string[]

readonly

base_url

Base URL for HTTP requests, including port (e.g. http://localhost:8788).

type string

readonly

rpc_path

JSON-RPC endpoint mount point. Default /api/rpc.

type string

readonly

ws_path

WebSocket endpoint mount point. Default /api/ws.

type string

readonly

sse_path?

SSE stream mount point — drives the cross-process SSE suite's stream path. Optional: only backends advertising capabilities.sse serve a stream, and the suite defaults to /api/admin/audit/stream (the standard fuz_app audit-log stream) when omitted. Set it only when a backend mounts its stream elsewhere.

type string

readonly

health_path

Readiness probe path. Default /health.

type string

readonly

bootstrap_path

Bootstrap POST path. Default /api/account/bootstrap.

type string

readonly

cookie_name

Session cookie name the backend issues. Default fuz_session per the ecosystem convergence; consumers using a custom session name (legacy zzz_session, etc.) override. default_cross_process_setup extracts the per-account session value from the transport jar by this name so the cross-process TestAccount.session_cookie matches the in-process shape.

type string

readonly

startup_timeout_ms

How long to wait for the health probe (ms) before giving up.

type number

readonly

env

Env vars merged into the child process. Must include the binary's *_BOOTSTRAP_TOKEN_PATH + *_DAEMON_TOKEN_PATH env var names so the binary reads/writes the right files. Also must include the binary's *_ALLOWED_ORIGINS (typically 'http://localhost:*' for cross-process tests).

type Readonly<Record<string, string>>

readonly

bootstrap

Auth bootstrap details — see BackendBootstrapConfig.

type BackendBootstrapConfig

readonly

capabilities

Capabilities this backend supports — drives test_if(capabilities.X, ...) gating in suite bodies. See testing/cross_backend/capabilities.ts for the vocabulary and existing flags.

type BackendCapabilities

readonly

BackendHandle
#

testing/cross_backend/spawn_backend.ts view source

BackendHandle import type {BackendHandle} from '@fuzdev/fuz_app/testing/cross_backend/spawn_backend.js';

Handle returned by spawn_backend — passed to per-test setup helpers.

config

The config used to spawn this backend. Carried for diagnostic + downstream access.

type BackendConfig

readonly

child

Child process reference — exposed for diagnostic logging only.

type ChildProcess

readonly

daemon_token

Deterministic daemon token captured from config.bootstrap.daemon_token_path after the binary booted. default_cross_process_setup builds keeper-daemon-token headers from this for _testing_reset calls.

type string

readonly

teardown

SIGTERM the child's process group — even when the child itself has exited, so no descendant outlives it — and await the group's exit, escalating to SIGKILL after a grace window. Idempotent — later calls await the same exit. A descendant that left the group — spawned detached, or one that called setsid — is out of reach and survives teardown.

type (): Promise<void>

readonly
returns Promise<void>

BackendProbe
#

ui/backend_probe.svelte.ts view source

import {BackendProbe} from '@fuzdev/fuz_app/ui/backend_probe.svelte.js';

Reactive probe_backend state for a login gate. reachable stays null until a probe settles — through SSR and prerender too, so a gate that waits on it renders its pending notice there.

examples

const backend = new BackendProbe(); if (BROWSER) void backend.probe(); // in markup: `backend.reachable === null` → checking; `!backend.reachable` → down

probing

Whether a probe is in flight.

type boolean

$state.raw

reachable

Whether the backend answered the last settled probe — null before one settles.

type boolean | null

$state.raw

error

Why the last settled probe failed — null when it succeeded or none has settled.

type string | null

$state.raw

constructor

type new (options?: OmitStrict<BackendProbeOptions, "signal"> | undefined): BackendProbe

options?

the health fetch and the default fetch's timeout; no signal, since one that aborted would fail every later probe as BACKEND_PROBE_ABORTED

type OmitStrict<BackendProbeOptions, "signal">
optional

probe

Probe the backend and record the answer. A probe started while another is in flight supersedes it: only the latest one's answer is recorded.

type (): Promise<boolean>

returns Promise<boolean>

whether the backend answered this probe

BackendProbeOptions
#

ui/backend_probe.svelte.ts view source

BackendProbeOptions import type {BackendProbeOptions} from '@fuzdev/fuz_app/ui/backend_probe.svelte.js';

Options for probe_backend; BackendProbe takes all but signal.

fetch_health?

Fetches the health route — defaults to fetch(HEALTH_PATH) with a timeout_ms signal, combined with signal when given. An injected fetch owns its own signal.

type (): Promise<Response>

returns Promise<Response>

timeout_ms?

How long the default fetch waits for the reply and its body — defaults to BACKEND_PROBE_TIMEOUT_MS; unused when fetch_health is injected.

type number

signal?

Cancels the default fetch, alongside its timeout; unused when fetch_health is injected. A probe that fails once it is aborted reports BACKEND_PROBE_ABORTED — whatever the signal's reason, so a deadline belongs in timeout_ms.

type AbortSignal

BackendProbeResult
#

ui/backend_probe.svelte.ts view source

BackendProbeResult

type ({ ok: true; } & object) | ({ ok: false; } & { error: string; })

import type {BackendProbeResult} from '@fuzdev/fuz_app/ui/backend_probe.svelte.js';

probe_backend's answer: live, or why not.

baseline
#

db/migrate.ts view source

(db: Db, ns: MigrationNamespace, names: readonly string[]): Promise<void> import {baseline} from '@fuzdev/fuz_app/db/migrate.js';

Insert tracker rows for the named migrations of a namespace without executing them.

Used to promote an existing schema (e.g. produced by an out-of-band bootstrap, or a hand-applied DDL set) into the identity tracker. baseline() trusts the operator-supplied list — it does not verify that the schema actually matches what the named migrations would have produced. Pair with a schema-assertion script post-baseline before re-enabling traffic.

Contract:

  • Creates the schema_version table if missing — a promotion script can call baseline() against a fresh tracker with no separate DDL step.
  • Acquires the same per-namespace advisory lock as run_migrations (with the same try/catch fallback for environments lacking pg_advisory_lock).
  • Refuses if any tracker rows already exist *for this namespace* — lets multi-call baseline scripts resume after partial failure (completed namespaces guard themselves while remaining ones still run).
  • Verifies the supplied names are a strict prefix of the namespace's current migrations array — a name not in the array, or out of order, errors before any INSERT.
  • Writes sequences 0..N-1 in one transaction.

db

the database instance

type Db

ns

the namespace whose migrations are being baselined

names

prefix of ns.migrations[].name to record as already-applied

type readonly string[]

returns

Promise<void>

throws

  • MigrationError - with `kind` of `namespace-reserved` (before touching the database), `baseline-name-not-in-code`, `baseline-name-out-of-order`, or `baseline-namespace-already-populated`

mutates

  • schema_version — inserts tracker rows for `names` without running the corresponding migration bodies

BaseServerEnv
#

server/env.ts view source

value + type

{ NODE_ENV: "development" | "production"; PORT: number; HOST: string; DATABASE_URL: string; SECRET_FUZ_COOKIE_KEYS: string; FUZ_ALLOWED_ORIGINS: string; FUZ_ALLOW_ORIGIN_WILDCARDS?: "true" | ... 1 more ... | undefined; FUZ_BOOTSTRAP_TOKEN_PATH?: string | undefined; FUZ_DB_CHECKOUT_TIMEOUT_MS?: number | undefined; FU... import {BaseServerEnv} from '@fuzdev/fuz_app/server/env.js';

Base Zod schema for server environment variables.

Declares only what the spine reads — server config, database and its pool checkout bounds, cookie keys, allowed origins and the wildcard opt-in, and the bootstrap token path — matching the Rust spine's BaseServerEnv. The numeric vars parse as loosely as PORT (z.coerce, so 5e3 reads as 5000 where the Rust twin refuses it); a blank or out-of-range value is refused on both. Anything else (fact storage, X-Accel delivery, public URLs) is the app's own var: extend with app-specific fields via .extend().

NODE_ENV

type "development" | "production"

PORT

type number

HOST

type string

DATABASE_URL

type string

SECRET_FUZ_COOKIE_KEYS

type string

FUZ_ALLOWED_ORIGINS

type string

FUZ_ALLOW_ORIGIN_WILDCARDS?

type "true" | "false"

FUZ_BOOTSTRAP_TOKEN_PATH?

type string

FUZ_DB_CHECKOUT_TIMEOUT_MS?

type number

FUZ_DB_AUDIT_CHECKOUT_TIMEOUT_MS?

type number

BearerAuthMocks
#

testing/middleware.ts view source

BearerAuthMocks import type {BearerAuthMocks} from '@fuzdev/fuz_app/testing/middleware.js';

Mocks bundle returned by create_bearer_auth_mocks.

mock_validate

type Mock

mock_find_by_id

type Mock

mock_find_actor_by_id

type Mock

mock_find_actors_by_account

type Mock

mock_find_active_for_actor

type Mock

BearerAuthTestCase
#

testing/middleware.ts view source

BearerAuthTestCase import type {BearerAuthTestCase} from '@fuzdev/fuz_app/testing/middleware.js';

A full test case for the table-driven bearer auth runner.

inheritance

validate_expectation

Whether the request should reach token validation or be short-circuited.

type "called" | "not_called"

assert_account_set?

If true, assert the request resolved to exactly the api_token arm of ResolvedAuth — its fields and no others.

type boolean

expected_account_id?

Expected ResolvedAuth.account_id when assert_account_set is true.

type string

expected_api_token_id?

If set, assert the resolved api_token_id after a successful bearer auth.

type string

expected_token_scope?

If set, assert the resolved scope after a successful bearer auth.

type TokenScope

expected_credential_deadline_ms?

If set, assert the resolved credential_deadline after a successful bearer auth: null for none (an eternal token), or the milliseconds it should lie ahead, within a second.

type number | null

assert_context_preserved?

If true, assert the pre-existing session credential is preserved.

type boolean

assert_mocks?

Optional callback for custom spy assertions on the mocks bundle.

type (mocks: BearerAuthMocks): void

mocks

returns void

BearerAuthTestOptions
#

testing/middleware.ts view source

BearerAuthTestOptions import type {BearerAuthTestOptions} from '@fuzdev/fuz_app/testing/middleware.js';

Mock configuration for bearer auth middleware test setup.

name

Test description.

type string

headers?

Request headers.

type Record<string, string>

pre_context?

Pre-set request context (simulates session already resolved).

type RequestContext

mock_validate_result?

What query_validate_api_token() returns.

type unknown

mock_find_by_id_result?

What query_account_by_id() returns.

type unknown

mock_find_actor_by_id_result?

What query_actor_by_id() returns.

type unknown

mock_role_grants_result?

What query_role_grant_find_active_for_actor() returns.

type unknown

expected_status

Expected HTTP status, or 'next' if the middleware should call next().

type number | "next"

expected_error?

Expected error field in JSON response body.

type string

expected_error_schema?

Zod schema to validate error response body against. Defaults to ApiError when expected_error is set.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

BenchScenario
#

testing/cross_backend/bench/scenario.ts view source

BenchScenario import type {BenchScenario} from '@fuzdev/fuz_app/testing/cross_backend/bench/scenario.js';

One benchmarkable wire scenario. The run body is the Benchmark task fn: it must throw on a non-success response so the benchmark records a failed iteration rather than timing an error path as if it succeeded.

Scenarios should be idempotent — they run thousands of times against a single bootstrapped backend with no reset between iterations. Prefer reads; a mutating scenario must not accumulate unbounded state.

name

Scenario name (groups the per-backend results in the report).

type string

readonly

requires?

Optional capability gate — return false to skip this scenario on a backend that can't serve it (e.g. a WS scenario needs capabilities.ws).

type (capabilities: BackendCapabilities): boolean

readonly

capabilities

returns boolean

run

The timed body. Throws on a non-success response.

type (ctx: BenchScenarioContext): Promise<void>

readonly

ctx

returns Promise<void>

BenchScenarioContext
#

testing/cross_backend/bench/scenario.ts view source

BenchScenarioContext import type {BenchScenarioContext} from '@fuzdev/fuz_app/testing/cross_backend/bench/scenario.js';

transport

Pre-authed transport — the bootstrapped keeper's session cookie jar.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

rpc_path

RPC endpoint path, e.g. '/api/rpc'.

type string

readonly

capabilities

Declared capabilities of the backend this context targets.

type BackendCapabilities

readonly

Blake3Hash
#

primitive_schemas.ts view source

value + type

string import {Blake3Hash} from '@fuzdev/fuz_app/primitive_schemas.js';

Zod schema for a BLAKE3 hex hash — 64 lowercase hex characters (256-bit output).

BodySizeCrossTestOptions
#

testing/cross_backend/body_size.ts view source

BodySizeCrossTestOptions import type {BodySizeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/body_size.js';

Options for the body-size parity suite — the standard RPC-dispatched cross-suite shape (setup_test / rpc_path) plus where the backend's socket is. The limit is on every spine, so no case is capability-gated and the flag bundle is not on the shape.

inheritance

base_url

Base URL the spawned backend is reachable at (e.g. http://localhost:1178), or null for an in-process run, which has no socket. Given a URL, the over-cap cases write to the socket directly and send only the bytes the refusal needs; with null they send whole bodies through the fixture's transport.

Pass the URL whenever the fixture's transport reaches a real socket. null there uploads each whole over-cap body through fetch, where the write races the close that follows the 413 and the case fails some of the time.

type string | null

readonly

rpc_max_body_bytes?

The backend's request-body cap on rpc_path, in bytes — the boundary the cases probe. Default SPINE_BODY_LIMIT_DEFAULT_BYTES; pass the backend's value when it caps its RPC path elsewhere.

type number

readonly

BodySizeSmugglingCrossTestOptions
#

testing/cross_backend/body_size_smuggling.ts view source

BodySizeSmugglingCrossTestOptions import type {BodySizeSmugglingCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/body_size_smuggling.js';

Options for the smuggling probe — needs the raw URL, not a transport.

base_url

Base URL the spawned backend is reachable at (e.g. http://localhost:1178).

type string

readonly

rpc_path?

RPC endpoint path to target. Default /api/rpc.

type string

readonly

rpc_max_body_bytes?

The backend's request-body cap on rpc_path, in bytes; the oversized POST declares a length past it. Default SPINE_BODY_LIMIT_DEFAULT_BYTES.

type number

readonly

bootstrap
#

testing/transports/bootstrap.ts view source

(options: BootstrapOptions): Promise<BootstrapResult> import {bootstrap} from '@fuzdev/fuz_app/testing/transports/bootstrap.js';

Fire POST {config.bootstrap_path} and capture the keeper session.

options

returns

Promise<BootstrapResult>

throws

  • Error - when the binary refuses bootstrap (non-2xx response) or the body fails to parse as the expected `{account, actor}` envelope. The error carries the status + raw body so a mistyped token / username collision / boot-time DB drift surfaces with enough context to debug.

bootstrap_account
#

auth/bootstrap_account.ts view source

(deps: BootstrapAccountDeps, provided_token: string, input: BootstrapAccountInput): Promise<BootstrapAccountResult> import {bootstrap_account} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Bootstrap the first account with keeper and admin privileges.

Uses an atomic bootstrap_lock UPDATE to prevent concurrent bootstrap attempts (TOCTOU). The full flow runs in a single transaction:

  1. Read and verify the bootstrap token (before transaction) — the file's raw bytes less one optional trailing LF, at least BOOTSTRAP_TOKEN_MIN_BYTES long, compared in constant time
  2. Hash the password (CPU-intensive, before transaction)
  3. Acquire the bootstrap lock atomically (inside transaction)
  4. Create account + actor
  5. Grant keeper and admin role_grants (no expiry, granted_by = null)
  6. Delete the token file (after commit, reported via token_file_deleted)

deps

database, token path, filesystem callbacks, and password hashing

provided_token

the bootstrap token from the user

type string

input

username and password

returns

Promise<BootstrapAccountResult>

the created account, actor, and role_grants — or a bootstrap failure

mutates

  • bootstrap_lock row — flips `bootstrapped` to `true` atomically
  • account / actor / role_grant tables — inserts the bootstrap account, actor, and the keeper + admin role_grants
  • filesystem — deletes the bootstrap token file after commit (reported via `token_file_deleted`)

bootstrap_backend
#

testing/cross_backend/bootstrap_backend.ts view source

(config: BackendConfig, options?: BootstrapBackendOptions | undefined): Promise<BootstrappedBackendHandle> import {bootstrap_backend} from '@fuzdev/fuz_app/testing/cross_backend/bootstrap_backend.js';

Spawn the test binary described by config, bootstrap a keeper, and return the enriched handle.

The keeper transport is constructed against config.base_url with no initial cookies; bootstrap() populates its jar with the session cookie returned by POST {config.bootstrap_path}. Subsequent calls against bootstrapped.keeper_transport are authenticated as keeper.

Mirrors the composition default_cross_process_setup's caller would otherwise hand-roll in every consumer's globalSetup.

config

options?

optional

returns

Promise<BootstrappedBackendHandle>

BOOTSTRAP_LOCK_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS bootstrap_lock (\n id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),\n bootstrapped BOOLEAN NOT NULL DEFAULT false\n)" import {BOOTSTRAP_LOCK_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

BOOTSTRAP_LOCK_SEED
#

auth/auth_ddl.ts view source

"\nINSERT INTO bootstrap_lock (id, bootstrapped)\n SELECT 1, EXISTS(SELECT 1 FROM account)\n ON CONFLICT DO NOTHING" import {BOOTSTRAP_LOCK_SEED} from '@fuzdev/fuz_app/auth/auth_ddl.js';

Seed the bootstrap_lock table, setting bootstrapped based on whether accounts exist.

bootstrap_route_shape
#

auth/bootstrap_route_schema.ts view source

{ method: "POST"; path: string; auth: { account: "none"; actor: "none"; }; mints_credential: true; description: string; transaction: false; input: ZodObject<{ token: ZodString; username: ZodPipe<ZodString, ZodTransform<...>>; password: ZodString; }, $strict>; output: ZodObject<...>; rate_limit: "ip"; errors: { ...; ... import {bootstrap_route_shape} from '@fuzdev/fuz_app/auth/bootstrap_route_schema.js';

The POST /bootstrap route shape minus its handler — pure hono-free data. create_bootstrap_route_specs spreads this and attaches the live handler; surface generation spreads it with a stub handler (handlers are never run during surface assembly, only the shape is read).

bootstrap_test_keeper
#

testing/app_server.ts view source

(options: CreateTestAccountWithCredentialsOptions): Promise<{ account: { id: string & $brand<"Uuid">; username: string; }; actor: { ...; }; api_token: string; session_cookie: string; }> import {bootstrap_test_keeper} from '@fuzdev/fuz_app/testing/app_server.js';

Bootstrap the test-DB keeper. Direct-query shortcut for the default create_test_app path — bootstrap is not what most tests exercise, so we skip the real bootstrap_account flow (no audit row, no on_bootstrap callback); the cross-backend harness drives the real POST /bootstrap against every backend it spawns.

Flips bootstrap_lock.bootstrapped = true so the post-insert DB state matches a real bootstrap completion — production code can trust the lock as the single signal without a belt-and-suspenders query_account_has_any defense.

options

returns

Promise<{ account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; api_token: string; session_cookie: string; }>

mutates

  • the underlying options.db — inserts the account/actor/roles/ — API token/session_cookie rows AND flips `bootstrap_lock.bootstrapped`.

BOOTSTRAP_TOKEN_MIN_BYTES
#

auth/bootstrap_account.ts view source

32 import {BOOTSTRAP_TOKEN_MIN_BYTES} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

The fewest bytes a bootstrap token holds, counted after its one optional trailing LF. A shorter file is read as unreadable: an empty or "\n" file would otherwise leave bootstrap advertised as open while nothing can ever match it, and a short token is guessable. Every generator writes more (32 random bytes as 64 lowercase hex characters); the readers accept any bytes that clear this floor. Twin of the Rust BOOTSTRAP_TOKEN_MIN_BYTES (fuz_auth).

BootstrapAccountDeps
#

auth/bootstrap_account.ts view source

BootstrapAccountDeps import type {BootstrapAccountDeps} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Dependencies for bootstrap_account.

db

type Db

token_path

Path to the bootstrap token file on disk.

type string

read_secure_file

Hardened secret-file read (see FsSecureReadDeps.read_secure_file) — the token mints the keeper account, so a symlinked, non-regular, group/other-readable, or oversized file must refuse rather than be honored. Throws on refusal; every throw reads as TOKEN_FILE_MISSING upstream.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

delete_file

Delete a file.

type (path: string): Promise<void>

path

type string
returns Promise<void>

password

Only hashing is needed — verification happens separately during login.

type Pick<PasswordHashDeps, "hash_password">

log

Structured logger instance.

type Logger

BootstrapAccountFailure
#

auth/bootstrap_account.ts view source

BootstrapAccountFailure

type { ok: false; error: "already_bootstrapped"; status: 403; } | { ok: false; error: "token_file_missing"; status: 404; } | { ok: false; error: "invalid_token"; status: 401; }

import type {BootstrapAccountFailure} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Bootstrap failure result.

BootstrapAccountInput
#

auth/bootstrap_account.ts view source

BootstrapAccountInput import type {BootstrapAccountInput} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Input for the bootstrap account creation.

username

type string

password

type string

BootstrapAccountResult
#

BootstrapAccountSuccess
#

auth/bootstrap_account.ts view source

BootstrapAccountSuccess import type {BootstrapAccountSuccess} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Successful bootstrap result with the created entities.

ok

type true

account

type Account

actor

type Actor

role_grants

type { keeper: RoleGrant; admin: RoleGrant; }

token_file_deleted

Whether the bootstrap token file was successfully deleted after account creation.

type boolean

BootstrapBackendOptions
#

testing/cross_backend/bootstrap_backend.ts view source

BootstrapBackendOptions import type {BootstrapBackendOptions} from '@fuzdev/fuz_app/testing/cross_backend/bootstrap_backend.js';

Options for bootstrap_backend.

before_bootstrap?

Runs after the binary is up and before the keeper bootstrap — the one window in a backend's life where POST {bootstrap_path} still answers on the token, so probes of the bootstrap route itself go here. A throw tears the backend down like a failed bootstrap.

type (config: BackendConfig): Promise<void>

readonly

config

returns Promise<void>

BootstrapDisabledOptions
#

server/app_server.ts view source

BootstrapDisabledOptions import type {BootstrapDisabledOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "disabled"

BootstrapForm
#

ui/BootstrapForm.svelte view source

import BootstrapForm from '@fuzdev/fuz_app/ui/BootstrapForm.svelte';

redirect_on_bootstrap?

Path to navigate to after the first-keeper account is created.

type string
optional default resolve('/')

BootstrapInput
#

auth/bootstrap_route_schema.ts view source

value + type

{ token: string; username: string; password: string; } import {BootstrapInput} from '@fuzdev/fuz_app/auth/bootstrap_route_schema.js';

Input for POST /bootstrap. token is the one-shot token file contents.

token

type string

username

type string

password

type string

BootstrapLiveOptions
#

server/app_server.ts view source

BootstrapLiveOptions import type {BootstrapLiveOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "live"

token_path

type string

route_prefix?

Route prefix for bootstrap routes. Default '/api/account'; must sit under /api/.

type string

on_bootstrap?

Called after successful bootstrap (account + session created). Use for app-specific post-bootstrap work like generating API tokens.

type (result: BootstrapAccountSuccess, c: Context<any, any, {}>): Promise<void>

result

c

type Context<any, any, {}>
returns Promise<void>

BootstrapOptions
#

testing/transports/bootstrap.ts view source

BootstrapOptions import type {BootstrapOptions} from '@fuzdev/fuz_app/testing/transports/bootstrap.js';

Input for bootstrap().

transport

The cookie-threading HTTP transport pointed at the binary. After bootstrap() resolves, the transport carries the keeper session cookie — every later call against it is authenticated as keeper.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

config

Backend config — used for bootstrap_path plus the bootstrap.username / bootstrap.password / bootstrap.token credentials. The runner already wrote bootstrap.token to bootstrap.token_path before spawning, so the binary picks the token up at startup.

type BackendConfig

readonly

BootstrapOutput
#

auth/bootstrap_route_schema.ts view source

value + type

{ ok: true; account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; } import {BootstrapOutput} from '@fuzdev/fuz_app/auth/bootstrap_route_schema.js';

Output for POST /bootstrap. Session cookie is the operative side effect.

ok

type true

account

type { id: string & $brand<"Uuid">; username: string; }

actor

type { id: string & $brand<"Uuid">; }

BootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

BootstrappedBackendHandle import type {BootstrappedBackendHandle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Cross-process backend handle enriched with the bootstrapped keeper's captured credentials. Consumers compose this in vitest's globalSetup:

const handle = await spawn_backend(config); const keeper_transport = create_fetch_transport({base_url: config.base_url}); const keeper = await bootstrap({transport: keeper_transport, config}); const bootstrapped: BootstrappedBackendHandle = { ...handle, keeper_transport, keeper_daemon_transport: create_fetch_transport({ base_url: config.base_url, initial_cookies: keeper.cookies, origin: null, }), keeper_account: keeper.account, keeper_actor: keeper.actor, keeper_cookies: keeper.cookies, };

default_cross_process_setup(bootstrapped, options) reads from this shape — the per-test fixture closes over the keeper credentials so cross-process tests can drive admin-RPC / audit-observer flows against the long-lived bootstrapped admin alongside the per-test signup+login account.

inheritance

extends: BackendHandle

keeper_transport

Transport carrying the keeper session cookie + cookie jar.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

keeper_daemon_transport

Daemon-token transport for the keeper — same cookie jar as keeper_transport but with origin: null so it sends no Origin header. The daemon-token middleware discards the credential in a browser context (any Origin / Referer), so the _testing_* daemon-token calls (_testing_reset / _testing_schema_snapshot / _testing_action_manifest / _testing_mint_session) must route through this Origin-free transport. CSRF/cookie-session calls (invite_create, role_grant_offer_create) stay on keeper_transport, which keeps its default Origin.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

keeper_account

Keeper account JSON captured from POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

keeper_actor

Keeper actor JSON captured from POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; }

readonly

keeper_cookies

Raw keeper Set-Cookie values — thread into ws_transport for keeper-authenticated WS upgrades.

type readonly string[]

readonly

BootstrapResult
#

testing/transports/bootstrap.ts view source

BootstrapResult import type {BootstrapResult} from '@fuzdev/fuz_app/testing/transports/bootstrap.js';

The keeper credentials captured from POST /api/account/bootstrap.

transport

Same transport that came in, now carrying the keeper session cookie in its jar. Returned for call-site clarity (callers don't have to remember the mutation happens in place).

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

account

Account JSON returned by POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

actor

Actor JSON returned by POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; }

readonly

cookies

Raw Set-Cookie values for threading into a WS transport.

type readonly string[]

readonly

BootstrapRouteOptions
#

auth/bootstrap_routes.ts view source

BootstrapRouteOptions import type {BootstrapRouteOptions} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Per-factory configuration for bootstrap route specs.

bootstrap_status is runtime state (a mutable ref), not a dep or options value — it is passed through so the route handler can flip it on success.

session_options

type SessionOptions<string>

bootstrap_status

Shared mutable reference — flipped to false after successful bootstrap.

type BootstrapStatus

on_bootstrap?

Called after successful bootstrap (account + session created). Use for app-specific post-bootstrap work like generating API tokens.

type (result: BootstrapAccountSuccess, c: Context<any, any, {}>): Promise<void>

result

c

type Context<any, any, {}>
returns Promise<void>

bootstrap_ip_rate_limiter

Rate limiter for bootstrap attempts, keyed by client IP. Pass null to disable. Its own instance, not login's: bootstrap is one-shot and its bucket is never refunded on success (see RateLimiter.reset), so a fumbled token would otherwise leave the operator's *login* budget nearly spent on a deployment where their new account is the only one that exists. The Rust spine rate-limits bootstrap not at all (the token is 32 bytes of CSPRNG compared in constant time); this is the tighter side of that divergence, kept because the token also sits in a file whose read path an operator can misconfigure.

type RateLimiter | null

BootstrapServerOptions
#

server/app_server.ts view source

BootstrapServerOptions

type BootstrapDisabledOptions | BootstrapSurfaceOnlyOptions | BootstrapLiveOptions

import type {BootstrapServerOptions} from '@fuzdev/fuz_app/server/app_server.js';

Bootstrap configuration for AppServerOptions.bootstrap.

Discriminated union over three deployment intents. Distinct from BootstrapRouteOptions in auth/bootstrap_routes.ts — that one is per-factory runtime state (mutable bootstrap_status ref, rate limiter); this one is the consumer-facing server option that create_app_server reads at startup to decide whether to mount the routes and where.

Three modes:

  • disabled — no route mounted, nothing in /api/surface. Equivalent to omitting bootstrap entirely; the explicit mode is for documentation and reviewable intent at the wiring layer.
  • surface_only — route present, permanent 403 via check_bootstrap_status. For tests asserting on the disabled-but-present wire shape.
  • live — route mounted, real token verification. Success path reachable. token_path is required (non-nullable).

BootstrapStatus
#

auth/bootstrap_routes.ts view source

BootstrapStatus import type {BootstrapStatus} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Bootstrap status — runtime state computed once at startup.

available

type boolean

token_path

type string | null

BootstrapSurfaceOnlyOptions
#

server/app_server.ts view source

BootstrapSurfaceOnlyOptions import type {BootstrapSurfaceOnlyOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "surface_only"

route_prefix?

Route prefix for surface generation. Default '/api/account'; must sit under /api/.

type string

BootstrapTestKeeperOptions
#

testing/app_server.ts view source

CreateTestAccountWithCredentialsOptions import type {BootstrapTestKeeperOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Alias for the keeper-flavored call site. Same shape.

db

type Db

keyring

type Keyring

session_options

type SessionOptions<string>

password

type PasswordHashDeps

username?

type string

password_value?

type string

roles?

type string[]

email?

Optional email stored on the account row — exercises the username-or-email login lookup.

type string

bounded_string
#

primitive_schemas.ts view source

(options: BoundedStringOptions): ZodString import {bounded_string} from '@fuzdev/fuz_app/primitive_schemas.js';

A string schema whose length is bounded in UTF-16 code units — what String.length measures, and what the Rust spine counts (encode_utf16().count()), so an astral-plane character costs two on both. Every .min() / .max() length bound on a wire string in the package is built here, bar a bare .min(1), which reads the same in either count.

Zod's .min() / .max() alone don't pin the unit: depending on the installed Zod they count code units or Unicode code points, and the peer range admits both. The schema still carries them — they are what puts minLength / maxLength in the generated JSON Schema — and a check beside them settles the verdict on String.length: it adds the over-max or under-min issue when Zod raised none, and withdraws one Zod raised for a string that is within the bound in units. The issue it adds has the fields of Zod's own and, like Zod's, lets the checks chained after it run, so a refusal reads the same whichever count Zod used.

Returns a plain ZodString, so format checks, .brand(), .nullish() and .meta() chain as usual. Don't chain a further .min() / .max() — pass the bound here.

options

the bounds, in UTF-16 code units

returns

ZodString

the bounded string schema

BoundedStringOptions
#

primitive_schemas.ts view source

BoundedStringOptions import type {BoundedStringOptions} from '@fuzdev/fuz_app/primitive_schemas.js';

Length bounds for bounded_string, in UTF-16 code units.

min?

Shortest string admitted. Omit for no lower bound.

type number

max

Longest string admitted.

type number

max_message?

Message for the over-max issue, in place of Zod's default.

type string

BroadcastTarget
#

actions/broadcast_api.ts view source

BroadcastTarget import type {BroadcastTarget} from '@fuzdev/fuz_app/actions/broadcast_api.js';

What a broadcast is sent through — the fan-out half of ConnectionRegistry. A structural Pick, so a test or another registry can stand in.

broadcast

Broadcast a notification to every admitted connection — a registration pending admission is not a connection yet. A server→client *request* has no single target here; that is request_connection.

A connection the message overflows is closed and not counted (module doc, "Outbound bound").

type (message: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }): number

message

returns number

the number of connections that accepted the message

broadcast_filtered

Broadcast to admitted connections whose identity satisfies a predicate. A registration pending admission is skipped before the predicate runs.

Used by the broadcast API when a consumer supplies a subscription ACL hook (e.g. an event that only reaches the account that owns the resource it reports). When no ACL is needed, callers should prefer broadcast to skip the per-connection predicate overhead.

A connection the message overflows is closed and not counted (module doc, "Outbound bound").

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; } | { ...; }, predicate: (identity: Readonly<...>) => boolean): number

message

predicate

type (identity: Readonly<ConnectionIdentity>) => boolean
returns number

the number of connections that accepted the message

build_account_context
#

auth/request_context.ts view source

(deps: QueryDeps, account_id: string): Promise<RequestContext | null> import {build_account_context} from '@fuzdev/fuz_app/auth/request_context.js';

Build an account-only RequestContext (no actor, no role_grants) from an account id.

Used by the dispatcher's authorization phase for authenticated routes that don't need an acting actor — account-grain operations (logout, password change, account self-service). Lets handlers read auth.account.id / auth.account.username uniformly with role_grant-bound routes; the cost is one extra query_account_by_id per request.

Returns null when the account row is missing (e.g. deleted between the auth middleware's session lookup and the dispatcher) — caller surfaces that as a 500 since it represents a torn read.

deps

query dependencies

account_id

the account to build context for

type string

returns

Promise<RequestContext | null>

an account-only request context, or null if the account is missing

build_action_manifest
#

testing/cross_backend/action_manifest.ts view source

(specs: readonly Pick<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }, "method" | ... 3 more ... | "rate_limit">[]): { ...; } import {build_action_manifest} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest.js';

Build the normalized ActionManifest from a list of request-response specs. Entries are sorted by method so two impls producing the same set serialize identically regardless of mount order. The caller owns the scope it passes (the TS spine passes its full mount; the Rust stub filters PROTOCOL_ACTION_SPECS first) — see the module doc.

specs

type readonly Pick<RequestResponseActionSpec, "method" | "input" | "auth" | "side_effects" | "rate_limit">[]

returns

ActionManifest

build_full_spine_mount
#

testing/cross_backend/full_spine_mount.ts view source

(deps: AppDeps, options: FullSpineMountOptions): FullSpineMount import {build_full_spine_mount} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

Build the full live mount for both of the spine binary's endpoints: the RPC list, and the WS list that serves the same actions behind the protocol actions.

The Rust testing_spine_stub serves one action registry on RPC and WS, so mounting the full list on WS keeps the two spines' WS method sets equal — the cell verbs among them, whose dispatcher-charged rate limit describe_ws_action_rate_limit_cross_tests drives over a socket. The one difference is cancel: a registered protocol action on TS, read-loop-owned (never dispatched) on Rust.

deps

the backend AppDeps

type AppDeps

options

daemon-token state + optional WS notification sender

returns

FullSpineMount

the RPC and WS action lists, built once and sharing handler instances

build_full_spine_rpc_actions
#

testing/cross_backend/full_spine_mount.ts view source

(deps: AppDeps, options: FullSpineMountOptions): RpcAction[] import {build_full_spine_rpc_actions} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

Build the complete live RPC action list the spine test binary mounts on its single endpoint: the declared create_standard_rpc_actions bundle plus the off-surface families (_testing_* backdoors, cells, actor resolvers).

Mirrors the previous inline assembly in testing_spine_server.ts exactly — session_options is pinned to spine_session_options (the binary's cookie config) and roles to spine_roles (carrying cell_editor), so the only runtime-varying inputs are the daemon-token state + notification sender.

deps

the backend AppDeps (stub deps suffice for method enumeration)

type AppDeps

options

daemon-token state + optional WS notification sender

returns

RpcAction[]

every RpcAction the binary exposes, in mount order

build_request_context
#

auth/request_context.ts view source

(deps: QueryDeps, account_id: string, actor_id: string): Promise<RequestActorContext | null> import {build_request_context} from '@fuzdev/fuz_app/auth/request_context.js';

Build a full RequestContext from an account id and an explicit actor id (already resolved via resolve_acting_actor).

Loads account + the named actor + the actor's active role_grants. Verifies the actor.account_id === account.id binding so downstream handlers can trust ctx.actor.account_id === ctx.account.id. Returns null when the account is missing, the actor is missing, or the actor doesn't belong to the supplied account.

Called by the route-spec / RPC dispatcher's authorization phase for routes that need an acting actor; account-grain routes use build_account_context instead.

deps

query dependencies

account_id

the account to build context for

type string

actor_id

the actor this request acts as

type string

returns

Promise<RequestActorContext | null>

a request context, or null if account/actor not found or mismatched

build_role_grant_offer_accepted_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }): { ...; } import {build_role_grant_offer_accepted_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_offer_declined_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }): { ...; } import {build_role_grant_offer_declined_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_offer_received_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }): { ...; } import {build_role_grant_offer_received_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_offer_retracted_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }): { ...; } import {build_role_grant_offer_retracted_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_offer_supersede_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; reason: "sibling_accepted" | ... 1 more ... | "scope_destroyed"; cause_id: string & $brand<...>; }): { ...; } import {build_role_grant_offer_supersede_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_revoke_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { role_grant_id: string & $brand<"Uuid">; role: string; scope_id: (string & $brand<"Uuid">) | null; reason: string | null; }): { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { ...; } | undefined; } import {build_role_grant_revoke_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_test_backend_paths
#

testing/cross_backend/build_test_backend_paths.ts view source

(prefix: string): TestBackendPaths import {build_test_backend_paths} from '@fuzdev/fuz_app/testing/cross_backend/build_test_backend_paths.js';

Build the generic path layout for a cross-process test backend under os.tmpdir()/<prefix>. prefix must be unique across every repo that runs cross tests on the machine (e.g. 'my_app_cross_rust'); for a default derived from the backend name, use default_test_backend_paths.

prefix

type string

returns

TestBackendPaths

BuildTestingAppContext
#

testing/cross_backend/testing_server_core.ts view source

BuildTestingAppContext import type {BuildTestingAppContext} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

What the core hands a StartTestingServerOptions.build_app seam.

prepare_websocket

Prepare the runtime adapter's WS upgrade helper and return the factory to pass to create_app_server as create_upgrade_websocket. The core runs the adapter's attach step once the server is bound, so a build that mounts ws_endpoints needs nothing else. An HTTP-only build doesn't call it. Call it at most once per build: the Node adapter attaches one upgrade listener per preparation, and two would race on the shared socket.

type (options?: PrepareWebsocketOptions | undefined): (app: Hono<BlankEnv, BlankSchema, "/">) => UpgradeWebSocket<unknown, any, WSEvents<unknown>>

options?

optional
returns (app: Hono<BlankEnv, BlankSchema, "/">) => UpgradeWebSocket<unknown, any, WSEvents<unknown>>

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.

builtin_credential_type_meta
#

auth/credential_type_schema.ts view source

ReadonlyMap<string, CredentialTypeMeta>

type ReadonlyMap<string, CredentialTypeMeta>

import {builtin_credential_type_meta} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Builtin credential-type metadata. Not overridable by consumers.

Typed ReadonlyMap for the contract — but JS Maps don't honor Object.freeze for .set / .delete / .clear (they mutate internal slots, not own properties), so freeze adds no runtime guard here. Read once at startup by create_credential_type_schema; runtime mutation has no effect on already-built schemas.

BUILTIN_CREDENTIAL_TYPES
#

auth/credential_type_schema.ts view source

readonly ["session", "api_token", "daemon_token"] import {BUILTIN_CREDENTIAL_TYPES} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

The builtin credential-type names as a const tuple.

builtin_grant_path_meta
#

auth/grant_path_schema.ts view source

ReadonlyMap<string, GrantPathMeta>

type ReadonlyMap<string, GrantPathMeta>

import {builtin_grant_path_meta} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Builtin grant-path metadata. Not overridable by consumers.

Typed ReadonlyMap for the contract — but JS Maps don't honor Object.freeze for .set / .delete / .clear (they mutate internal slots, not own properties), so freeze adds no runtime guard here. Read once at startup by create_grant_path_schema; runtime mutation has no effect on already-built schemas.

BUILTIN_GRANT_PATHS
#

auth/grant_path_schema.ts view source

readonly ["admin", "self_service", "system", "bootstrap"] import {BUILTIN_GRANT_PATHS} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

The builtin grant-path names as a const tuple.

builtin_role_specs_by_name
#

auth/role_schema.ts view source

ReadonlyMap<string, RoleSpec>

type ReadonlyMap<string, RoleSpec>

import {builtin_role_specs_by_name} from '@fuzdev/fuz_app/auth/role_schema.js';

Builtin role specs, keyed by role name. Not overridable by consumers — read once at startup by create_role_schema and the action factories that fall back to builtins when no consumer roles is supplied. ReadonlyMap encodes the contract; runtime mutation has no effect on already-built role schemas (the factory copies entries into a fresh Map).

BUILTIN_ROLES
#

auth/role_schema.ts view source

readonly ["keeper", "admin"] import {BUILTIN_ROLES} from '@fuzdev/fuz_app/auth/role_schema.js';

The builtin role names as a const tuple.

BuiltinCredentialType
#

auth/credential_type_schema.ts view source

value + type

"session" | "api_token" | "daemon_token"

type "session" | "api_token" | "daemon_token"

import {BuiltinCredentialType} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Zod enum for builtin credential types only.

BuiltinGrantPath
#

auth/grant_path_schema.ts view source

value + type

"bootstrap" | "admin" | "self_service" | "system"

type "bootstrap" | "admin" | "self_service" | "system"

import {BuiltinGrantPath} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Zod enum for builtin grant paths only.

BuiltinOfferRefusal
#

auth/role_grant_offer_queries.ts view source

BuiltinOfferRefusal

type "bootstrap_only" | "scoped" | "grantor_not_authorized"

import type {BuiltinOfferRefusal} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Why query_accept_offer refused to mint a builtin-role grant.

Each is a row no current grant path creates but a database can still hold (a direct query_role_grant_offer_create call, or an offer made under a since-closed path), so accept — the chokepoint where an offer becomes a role_grant — checks them itself.

  • bootstrap_only — the offer is of keeper, which is bootstrap-only: never conferred through an offer, whoever made it.
  • scoped — the offer binds a builtin role to a scope; builtin roles are global-only.
  • grantor_not_authorized — the grantor no longer holds an active global admin on an active actor of an active account. Conferring a builtin role is global-admin authority, so it is re-checked when the grant is minted, not only when the offer was made.

BuiltinRole
#

auth/role_schema.ts view source

value + type

"admin" | "keeper"

type "admin" | "keeper"

import {BuiltinRole} from '@fuzdev/fuz_app/auth/role_schema.js';

Zod schema for builtin roles only.

BuiltTestingApp
#

testing/cross_backend/testing_server_core.ts view source

BuiltTestingApp import type {BuiltTestingApp} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

The assembled app a StartTestingServerOptions.build_app seam returns.

app

The assembled Hono app (HTTP routes, RPC, and WS endpoints already mounted).

type Hono<BlankEnv, BlankSchema, "/">

close

Tear down backend(s) + DB + any rotation on graceful shutdown. Called after the listener stops accepting connections and before its drain is awaited, so it must end live WebSockets (AppServer.close does).

type (): Promise<void>

returns Promise<void>

caller_role_grants_cte
#

db/cell_view_sql.ts view source

(roles: string, scope_ids: string): string import {caller_role_grants_cte} from '@fuzdev/fuz_app/db/cell_view_sql.js';

The caller_role_grants CTE body (no WITH): the caller's (role, scope_id) pairs, unnested once per query from the parallel arrays at placeholders roles and scope_ids. PG 12+ inlines a non-recursive single-use CTE, so this is planner-equivalent to a subquery — the form is for clarity.

roles

the placeholder of the roles array (text[])

type string

scope_ids

the placeholder of the scope-id array (uuid[])

type string

returns

string

the CTE body

can_edit_cell
#

auth/cell_authorize.ts view source

(auth: RequestContext | null, cell: CellRow, grants: readonly CellGrantRow[] | null): boolean import {can_edit_cell} from '@fuzdev/fuz_app/auth/cell_authorize.js';

Edit authorization for a cell.

Unauthenticated callers can never edit. Admin always allowed.

IMPORTANT: the cell.created_by === null branch is explicit defense-in-depth. NULL created_by means system origin (well-known cells seeded by migration, future daemon/agent cells). Non-admin edits MUST be denied — editor-level grants do NOT bypass this guard, because system cells are policy-controlled at admin level. Do NOT collapse into a single equality check that would silently return false for NULL via JS equality semantics — the explicit branch survives refactors and reads as a load-bearing security property.

The editor grant arm requires the cell's moderation to admit an audience (cell_moderation_admits_audience), as in can_view_cell: a grant made while a contribution was approved stops admitting once it is rejected, for writes as for reads (every write verb echoes or reveals the row it touched).

auth

request context, or null for unauthenticated callers

type RequestContext | null

cell

the cell row

type CellRow

grants

the cell's grant list, or null to skip the grant branch

type readonly CellGrantRow[] | null

returns

boolean

whether the caller may edit the cell

can_manage_cell
#

auth/cell_authorize.ts view source

(auth: RequestContext | null, cell: CellRow): boolean import {can_manage_cell} from '@fuzdev/fuz_app/auth/cell_authorize.js';

Manage authorization for a cell — admin || owner.

The implicit tier above editor: gates visibility writes and all grant management (cell_grant_create / _list / _revoke). NOT delegable and NOT a grant level — an editor-grant holder is never a manager. Grants are not consulted.

NULL created_by (system origin) has no owner, so manage falls to admin only — the explicit NULL guard lives in is_owner.

auth

request context, or null for unauthenticated callers

type RequestContext | null

cell

the cell row

type CellRow

returns

boolean

whether the caller is in the manage tier for the cell

can_offer_global_role
#

ui/admin_accounts_state.svelte.ts view source

(entry: Pick<{ account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<...>) | null; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }, "role_grants" | "pending_offers">, role: string, now?: Date): boolean import {can_offer_global_role} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

Whether a listing row can be offered a global grant of role — the test behind AdminAccounts' + role button, which submit_grant backs with an unscoped offer. Only what the offer would duplicate rules it out: a global grant of the role, or a pending global offer of it. A grant or offer scoped to one resource is a different grant, so it doesn't.

entry

the account's listing row

type Pick<AdminAccountEntryJson, "role_grants" | "pending_offers">

role

the role the button would offer

type string

now?

current time for the grant-expiry check (defaults to new Date(), pass for testability); pending offers are taken as listed — the listing returns only unexpired ones

type Date
optional default new Date()

returns

boolean

can_steward_cell
#

auth/cell_authorize.ts view source

(auth: RequestContext | null, cell: CellRow, root: CellRow): boolean import {can_steward_cell} from '@fuzdev/fuz_app/auth/cell_authorize.js';

Steward authorization: whether auth manages root — the governing root of cell — and cell is a pending or rejected contribution under it.

The moderator's read of a contribution its audience can't see: true when cell.root_id is root.id, cell.moderation is exactly 'pending' or 'rejected' (cell_moderation_is_stewardable), root is not soft-deleted, and can_manage_cell admits auth on root (global admin or the root's owner — no grant tier confers manage). Unauthenticated callers are never stewards. An approved contribution later narrowed to private is not stewarded — it reads like any cell its author narrowed.

Not part of can_view_cell: the steward arm admits only where a moderator works (auth/cell_steward.ts names the sites), so the relation gates, clone, contribution parents, and the grant paths keep the narrower predicate.

auth

request context, or null for unauthenticated callers

type RequestContext | null

cell

the contribution

type CellRow

root

the cell's governing root

type CellRow

returns

boolean

whether the caller stewards the contribution

can_view_cell
#

auth/cell_authorize.ts view source

(auth: RequestContext | null, cell: Omit<CellViewRow, "id">, grants: readonly CellGrantRow[] | null): boolean import {can_view_cell} from '@fuzdev/fuz_app/auth/cell_authorize.js';

View authorization for a cell.

  • Admin: always allowed.
  • Owner (cell.created_by === auth.actor.id): allowed.
  • cell.visibility === 'public': allowed for everyone, including unauthenticated callers (e.g. a public landing cell) — while the cell's moderation admits an audience (cell_moderation_admits_audience).
  • Any active grant on the cell admits the caller (actor-shaped: match on actor_id; role-shaped: match on (role, scope_id?) against an active role_grant) — under the same moderation admission.
  • Otherwise: false.

A pending or rejected contribution stays with its owner and admins even when public or granted.

Reads only the view facts (visibility, moderation, created_by), so a full CellRow and the narrow CellViewRow both answer it.

auth

request context, or null for unauthenticated callers

type RequestContext | null

cell

the cell row, or its view facts

type Omit<CellViewRow, "id">

grants

the cell's grant list, or null to skip the grant branch

type readonly CellGrantRow[] | null

returns

boolean

whether the caller may view the cell

can_view_cell_sql
#

db/cell_view_sql.ts view source

(c_alias: string, params: CellViewerSqlParams): string import {can_view_cell_sql} from '@fuzdev/fuz_app/db/cell_view_sql.js';

can_view_cell over the cell alias c_alias: admin, the owner, or — while the cell's moderation admits an audience — public or a cell_grant admitting the caller.

Reads the caller at params; the grant arm needs the caller_role_grants_cte in scope. Liveness (deleted_at IS NULL) is the query's own clause, as can_view_cell reads a loaded live row.

c_alias

the cell alias in the enclosing query

type string

params

where the query binds the caller

returns

string

the SQL predicate (parenthesized)

cancel_action
#

actions/cancel.ts view source

Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | {...

type Action<ActionSpecUnion>

import {cancel_action} from '@fuzdev/fuz_app/actions/cancel.js';

Protocol-action tuple — spread into the server's actions array (or via protocol_actions from actions/protocol.ts) so the dispatcher registers the spec for input validation and so create_rpc_client codegen sees the method. The client doesn't need to call it directly; FrontendWebsocketClient.request({signal}) sends the cancel notification automatically when the signal fires.

cancel_action_spec
#

actions/cancel.ts view source

{ method: string; kind: "remote_notification"; initiator: "frontend"; auth: null; side_effects: true; input: ZodObject<{ request_id: ZodUnion<readonly [ZodString, ZodNumber]>; }, $strict>; output: ZodVoid; async: true; description: string; } import {cancel_action_spec} from '@fuzdev/fuz_app/actions/cancel.js';

ActionSpec for the shared cancel. auth: null matches every other remote-notification spec — upgrade-time auth has already admitted the socket, so per-action auth on a fire-and-forget notification is moot. The per-connection {request_id → AbortController} map enforces socket-scoped ownership naturally: a different socket's cancel for the same id misses in its own map.

CancelNotificationParams
#

actions/cancel.ts view source

value + type

{ request_id: string | number; } import {CancelNotificationParams} from '@fuzdev/fuz_app/actions/cancel.js';

Params for the cancel notification. request_id is the id of the pending request to abort. Must match the id of a request sent on the same socket; cancels from other sockets (or for unknown ids) are ignored.

request_id

type string | number

canonicalize_ip
#

http/ip_canonical.ts view source

(ip: string): string import {canonicalize_ip} from '@fuzdev/fuz_app/http/ip_canonical.js';

Canonicalize an IP address string.

Returns the RFC 5952 canonical form for parseable IPv4 or IPv6 input. Returns the input unchanged (only lowercased) when the input is non-IP ('unknown'), malformed ('attacker:controlled', '::1\n'), or any string the strict char-set filter rejects.

Idempotent. canonicalize_ip(canonicalize_ip(x)) === canonicalize_ip(x) for every input.

Order-safe for IPv4-mapped IPv6. The ::ffff: prefix strip runs AFTER the canonical emit because the canonical form of an IPv4-mapped IPv6 address is the dotted form (::ffff:127.0.0.1, not ::ffff:7f00:1). Stripping before canonicalize would miss the full-hex form. Closes the normalize_ipv4_mapped_collapse_is_order_safe test from the Rust port.

ip

type string

returns

string

examples

canonicalize_ip('::0001') // → '::1' canonicalize_ip('0:0:0:0:0:0:0:1') // → '::1' canonicalize_ip('2001:0DB8::0001') // → '2001:db8::1' canonicalize_ip('::ffff:127.0.0.1') // → '127.0.0.1' canonicalize_ip('0:0:0:0:0:ffff:7f00:1') // → '127.0.0.1' canonicalize_ip('::ffff:1') // → '::ffff:1' (NOT IPv4-mapped — group[5] is 0, not ffff) canonicalize_ip('127.0.0.1') // → '127.0.0.1' canonicalize_ip('not-an-ip') // → 'not-an-ip' (passes through) canonicalize_ip('::1\n') // → '::1\n' (fails char-set; passes through) canonicalize_ip('203.0.113.1:8080') // → '203.0.113.1:8080' (passes through; validate_ip_strict rejects)

CapabilityGatedCrossSuiteOptions
#

testing/cross_backend/setup.ts view source

CapabilityGatedCrossSuiteOptions import type {CapabilityGatedCrossSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

CrossSuiteOptions for a suite that reads capability flags — the ones whose cases are test_if-gated on a per-backend declaration. The field is on this variant rather than the base so a suite's option type states whether it consults capabilities: a caller cannot pass a flag bundle to a suite that ignores it, and a suite cannot quietly stop reading one while still demanding it.

inheritance

capabilities

Backend capability declarations — each suite gates on its own flag.

type BackendCapabilities

readonly

capture_action_manifest
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle): Promise<{ methods: { method: string; input: "object" | "void"; side_effects: boolean; account: "optional" | ... 1 more ... | "required"; actor: "optional" | ... 1 more ... | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; }[]; }> import {capture_action_manifest} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Capture a backend's live RPC action manifest over the _testing_action_manifest RPC action (keeper daemon-token channel). The action-surface twin of capture_schema_snapshot — pair two calls with assert_action_manifests_equal (testing/cross_backend/action_manifest_parity.ts) to gate that the TS spine and the Rust testing_spine_stub mount the same method set with the same per-method auth shape. Each impl answers from its own live registry (TS via build_action_manifest, Rust via the fuz_testing mirror); the normalized shapes match by design.

handle

returns

Promise<ActionManifest>

capture_migration_tracker
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle): Promise<{ entries: { namespace: string; name: string; sequence: number; }[]; }> import {capture_migration_tracker} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Capture a backend's schema_version migration tracker over the _testing_migration_tracker RPC action (keeper daemon-token channel). The provenance twin of capture_schema_snapshot — where that reads the resulting schema (and excludes the tracker), this reads the tracker rows themselves. Pair two calls with assert_migration_trackers_equal (testing/schema_parity.ts) to gate that the TS spine and the Rust testing_spine_stub record byte-identical migration identity (namespace + name + sequence) — the invariant the schema-snapshot gate is blind to.

handle

returns

Promise<MigrationTracker>

capture_schema_snapshot
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle, options?: { exclude_tables?: readonly string[] | undefined; }): Promise<{ tables: Record<string, { columns: Record<...>; indexes: { ...; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }> import {capture_schema_snapshot} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Capture a backend's schema snapshot over the _testing_schema_snapshot RPC action (keeper daemon-token channel). The canonical way for a cross-impl parity gate to read each backend's live schema — pair two calls with assert_schema_snapshots_equal (testing/schema_parity.ts).

exclude_tables drops documented divergences from both sides before comparison (e.g. a cell-primary Rust backend lacks tables the TS schema has). Each impl answers from its own introspection — TS via query_schema_snapshot, Rust via fuz_db::query_schema_snapshot — and the snapshot shapes match by design.

handle

options?

type { exclude_tables?: readonly string[] | undefined; }
optional default {}

returns

Promise<SchemaSnapshot>

CardinalPosition
#

ui/position_helpers.ts view source

CardinalPosition

type "left" | "right" | "top" | "bottom"

import type {CardinalPosition} from '@fuzdev/fuz_app/ui/position_helpers.js';

Basic position options for UI elements (cardinal directions).

CargoBinaryOptions
#

testing/cross_backend/cargo_binary.ts view source

CargoBinaryOptions import type {CargoBinaryOptions} from '@fuzdev/fuz_app/testing/cross_backend/cargo_binary.js';

Options naming one Cargo-built binary.

workspace_dir

The Cargo workspace (or package) directory the build runs in.

type string

readonly

package

The Cargo package holding the binary (cargo build -p).

type string

readonly

bin?

The binary target (cargo build --bin). Default the package name.

type string

readonly

profile?

The Cargo profile. Default 'dev', whose output is debug/.

type string

readonly

cell_audit_events
#

auth/cell_audit_events.ts view source

Readonly<Record<string, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

type Readonly<Record<string, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

import {cell_audit_events} from '@fuzdev/fuz_app/auth/cell_audit_events.js';

Cell-layer event_type → metadata schema map for extra_events.

Covers the generic cell verbs' mutation events plus the grant / field / item relation events. Read-only verbs (cell_get, cell_list, cell_*_list, cell_audit_list) emit nothing and are absent here.

cell_audit_list_action_spec
#

auth/cell_audit_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {cell_audit_list_action_spec} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

CELL_AUDIT_LIST_DEFAULT_LIMIT
#

cell_clone_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ source_id: $ZodBranded<ZodUUID, "Uuid", "out">; deep: ZodOptional<...>; with_data_patch: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObj... import {cell_clone_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_COLUMNS
#

db/cell_queries.ts view source

readonly ["id", "data", "kind", "visibility", "path", "refs", "parent_id", "root_id", "moderation", "policy", "created_at", "updated_at", "deleted_at", "created_by", "updated_by"] import {CELL_COLUMNS} from '@fuzdev/fuz_app/db/cell_queries.js';

The full cell column set, named explicitly so a row read fails loud on schema drift — a SELECT * silently omits a dropped column and the deleted_at IS NULL-shaped predicates on the hydrated row then misread undefined (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class). Column order mirrors the Rust twin's cell_columns(alias) (fuz_cell), which decodes the same projection positionally. The derived grant_count is not a column — cell_row_projection appends it. Keep in sync with CellRow and the cell DDL in db/cell_ddl.ts.

cell_create_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ data: ZodObject<{ label: ZodOptional<ZodString>; summary: ZodOptional<...>; }, $loose>; ... 5 more ...; acting: ZodOptional<...>; }, $strict>; output: ZodObject... import {cell_create_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell_delete_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {cell_delete_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_DROP_TABLES
#

db/cell_ddl.ts view source

readonly ["cell_field", "cell_item", "cell_grant", "cell"] import {CELL_DROP_TABLES} from '@fuzdev/fuz_app/db/cell_ddl.js';

Tables created by CELL_MIGRATION_NS, in drop order (children first).

CELL_EDITOR_ROLE
#

CELL_FIELD_COLUMNS
#

cell_field_delete_action_spec
#

auth/cell_field_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ source_id: $ZodBranded<ZodUUID, "Uuid", "out">; name: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description:... import {cell_field_delete_action_spec} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

cell_field_list_action_spec
#

auth/cell_field_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "optional"; actor: "optional"; }; side_effects: false; input: ZodObject<{ source_id: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; target_id: ZodOptional<...>; name_after: ZodOptional<...>; limit: ZodOptional<...>; acting: ZodOpt... import {cell_field_list_action_spec} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

CELL_FIELD_NAME_REGEX
#

auth/cell_field_action_specs.ts view source

RegExp import {CELL_FIELD_NAME_REGEX} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Field name grammar — fuz snake_case identifier convention. Anchored ^[a-z][a-z0-9_]{0,63}$: leading letter, alphanumeric + underscore trailing, 64-char cap. No reserved names yet.

cell_field_set_action_spec
#

auth/cell_field_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ source_id: $ZodBranded<ZodUUID, "Uuid", "out">; name: $ZodBranded<...>; target_id: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<..... import {cell_field_set_action_spec} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

cell_get_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "optional"; actor: "optional"; }; side_effects: false; input: ZodObject<{ id: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; path: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; rate_... import {cell_get_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell_grant_admits_caller_sql
#

db/cell_view_sql.ts view source

(g_alias: string, actor_id: string): string import {cell_grant_admits_caller_sql} from '@fuzdev/fuz_app/db/cell_view_sql.js';

The cell_grant admits-caller predicate over grant alias g_alias: an actor-shaped grant matches the actor at placeholder actor_id; a role-shaped grant matches a (role, scope_id) pair in the caller_role_grants CTE (a NULL grant scope_id matches any scope). Mirrors grant_admits in auth/cell_authorize.ts.

g_alias

the cell_grant alias in the enclosing query

type string

actor_id

the placeholder of the caller's actor (uuid)

type string

returns

string

the SQL predicate

CELL_GRANT_COLUMNS
#

db/cell_grant_queries.ts view source

readonly ["id", "cell_id", "level", "actor_id", "role", "scope_id", "granted_by", "created_at"] import {CELL_GRANT_COLUMNS} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

The full cell_grant column set, named explicitly so a row read fails loud on schema drift (see CELL_COLUMNS in db/cell_queries.ts). Column order mirrors the Rust twin's grant_columns(alias) (fuz_cell). Keep in sync with CellGrantRow and the cell_grant DDL in db/cell_ddl.ts.

cell_grant_create_action_spec
#

auth/cell_grant_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; level: ZodEnum<...>; principal: ZodDiscriminatedUnion<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObje... import {cell_grant_create_action_spec} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

cell_grant_list_action_spec
#

auth/cell_grant_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {cell_grant_list_action_spec} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

cell_grant_revoke_action_spec
#

auth/cell_grant_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ grant_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {cell_grant_revoke_action_spec} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

CELL_ITEM_COLUMNS
#

cell_item_delete_action_spec
#

auth/cell_item_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ parent_id: $ZodBranded<ZodUUID, "Uuid", "out">; position: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; descript... import {cell_item_delete_action_spec} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

cell_item_insert_action_spec
#

auth/cell_item_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ parent_id: $ZodBranded<ZodUUID, "Uuid", "out">; child_id: $ZodBranded<...>; position: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject... import {cell_item_insert_action_spec} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

cell_item_list_action_spec
#

auth/cell_item_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "optional"; actor: "optional"; }; side_effects: false; input: ZodObject<{ parent_id: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; child_id: ZodOptional<...>; position_after: ZodOptional<...>; limit: ZodOptional<...>; acting: Zod... import {cell_item_list_action_spec} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

cell_item_move_action_spec
#

auth/cell_item_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ parent_id: $ZodBranded<ZodUUID, "Uuid", "out">; position: $ZodBranded<...>; new_position: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodOb... import {cell_item_move_action_spec} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

CELL_KIND_LENGTH_MAX
#

auth/cell_action_specs.ts view source

64 import {CELL_KIND_LENGTH_MAX} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Maximum length of a cell kind on the wire, in UTF-16 code units (what String.length measures; enforced by bounded_string) — on cell_create, and on the cell_list filter. The cell layer imposes no grammar on kind; the bound is there because the tag is copied into every audit row the cell's mutations write. The same bound a cell field name carries (CELL_FIELD_NAME_REGEX), the cell layer's other machine-readable tag, and the same value as the Rust spine's CELL_KIND_LENGTH_MAX.

cell_list_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "optional"; actor: "optional"; }; side_effects: false; input: ZodDefault<ZodObject<{ ids: ZodOptional<ZodArray<$ZodBranded<ZodUUID, "Uuid", "out">>>; ... 12 more ...; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; as... import {cell_list_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_LIST_LIMIT_DEFAULT
#

CELL_LIST_LIMIT_MAX
#

auth/cell_action_specs.ts view source

200 import {CELL_LIST_LIMIT_MAX} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Soft cap on the size of a cell.list request page. Larger pages chew memory both server- and client-side; combined with the visibility predicate's filter cost, 200 is a safe ceiling.

CELL_MIGRATION_NS
#

db/cell_ddl.ts view source

MigrationNamespace import {CELL_MIGRATION_NS} from '@fuzdev/fuz_app/db/cell_ddl.js';

Migration namespace consumed by run_migrations — the spine's own, so splice this object itself (a copy is refused under the reserved name).

cell_moderate_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; moderation: ZodEnum<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; rate_limit: ... import {cell_moderate_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell_moderation_admits_audience
#

db/cell_moderation.ts view source

(moderation: string | null): boolean import {cell_moderation_admits_audience} from '@fuzdev/fuz_app/db/cell_moderation.js';

Whether a cell's moderation marker admits it to an audience beyond its owner — unmoderated (null) or 'approved'; 'pending', 'rejected', and any unknown marker do not.

moderation

the cell's moderation column

type string | null

returns

boolean

whether the public and grant read arms may admit the cell

cell_moderation_admits_audience_sql
#

db/cell_moderation.ts view source

(alias: string): string import {cell_moderation_admits_audience_sql} from '@fuzdev/fuz_app/db/cell_moderation.js';

SQL form of cell_moderation_admits_audience over the cell alias alias (a parenthesized boolean expression).

alias

the cell table alias in the enclosing query

type string

returns

string

the SQL predicate

CELL_MODERATION_APPROVED
#

db/cell_moderation.ts view source

"approved" import {CELL_MODERATION_APPROVED} from '@fuzdev/fuz_app/db/cell_moderation.js';

cell.moderation for an approved contribution.

cell_moderation_is_stewardable
#

db/cell_moderation.ts view source

(moderation: string | null): boolean import {cell_moderation_is_stewardable} from '@fuzdev/fuz_app/db/cell_moderation.js';

Whether a cell's moderation marker is one its governing root's manager stewards — exactly 'pending' or 'rejected'.

moderation

the cell's moderation column

type string | null

returns

boolean

whether the steward read arm may admit the cell

cell_moderation_is_stewardable_sql
#

db/cell_moderation.ts view source

(alias: string): string import {cell_moderation_is_stewardable_sql} from '@fuzdev/fuz_app/db/cell_moderation.js';

SQL form of cell_moderation_is_stewardable over the cell alias alias.

alias

the cell table alias in the enclosing query

type string

returns

string

the SQL predicate

CELL_MODERATION_PENDING
#

db/cell_moderation.ts view source

"pending" import {CELL_MODERATION_PENDING} from '@fuzdev/fuz_app/db/cell_moderation.js';

cell.moderation for a gated contribution awaiting a decision.

CELL_MODERATION_REJECTED
#

db/cell_moderation.ts view source

"rejected" import {CELL_MODERATION_REJECTED} from '@fuzdev/fuz_app/db/cell_moderation.js';

cell.moderation for a rejected contribution.

cell_not_found_error
#

auth/cell_action_helpers.ts view source

(): ThrownJsonrpcError import {cell_not_found_error} from '@fuzdev/fuz_app/auth/cell_action_helpers.js';

The 404 every cell gate answers for a miss and a denial alike.

returns

ThrownJsonrpcError

the cell_not_found JSON-RPC error

CELL_PATH_LENGTH_MAX
#

auth/cell_action_specs.ts view source

256 import {CELL_PATH_LENGTH_MAX} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Wire form for cell.path.

At the spec level we only enforce that the value is a non-empty string with a sane upper bound — the cell layer is generic and doesn't impose a path grammar. App-side curation (well-known names like /map/main, /site/events) is admin-driven. The bound is in UTF-16 code units.

cell_policy_set_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; policy: ZodNullable<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; rate_limit: ... import {cell_policy_set_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_REF_NOT_HELD_MESSAGE
#

auth/cell_ref_admission.ts view source

"cell data references a fact the caller does not hold" import {CELL_REF_NOT_HELD_MESSAGE} from '@fuzdev/fuz_app/auth/cell_ref_admission.js';

The refusal message — byte-identical with the Rust spine's.

CELL_RELATION_LIST_LIMIT_DEFAULT
#

auth/cell_relation_list_limit.ts view source

50 import {CELL_RELATION_LIST_LIMIT_DEFAULT} from '@fuzdev/fuz_app/auth/cell_relation_list_limit.js';

The page size a relation list that omits limit gets, forward and reverse alike.

CELL_RELATION_LIST_LIMIT_MAX
#

CELL_RELATIONS_BUNDLE_LIMIT
#

auth/cell_action_specs.ts view source

500 import {CELL_RELATIONS_BUNDLE_LIMIT} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Hard cap on bundled relation arrays in cell_get (per-relation LIMIT). Beyond this the response sets *_truncated: true and the client paginates via cell_item_list({parent_id, position_after}) / cell_field_list({source_id, name_after}).

CELL_ROLE_HOLDER_USERNAME
#

cell_row_projection
#

db/cell_queries.ts view source

(alias: string): string import {cell_row_projection} from '@fuzdev/fuz_app/db/cell_queries.js';

The full CellRow projection — CELL_COLUMNS qualified by alias plus the derived grant_count (a correlated subquery against cell_grant, served by idx_cell_grant_cell) — used by every cell-row SELECT / RETURNING. alias is the row qualifier: the table name ('cell') for single-table reads + RETURNING, the query alias ('c') for query_cell_list. Twin of fuz_cell's cell_columns(alias).

::int narrows the count from bigint so the JS row hydrates a number rather than a bigint primitive — counts on a single cell are trivially within int32.

Exported for consumers writing their own cell-row reads: CELL_COLUMNS alone hydrates a row without grant_count, which typechecks as a CellRow but fails at to_cell_json — this is the projection that actually produces one.

alias

type string

returns

string

cell_steward_admits
#

auth/cell_steward.ts view source

(deps: QueryDeps, auth: RequestContext | null, cell: CellRow): Promise<boolean> import {cell_steward_admits} from '@fuzdev/fuz_app/auth/cell_steward.js';

Whether auth stewards cell — loads the governing root and applies can_steward_cell.

Resolves false without a query for an unauthenticated caller, a root, or a cell whose moderation isn't 'pending' / 'rejected'; a missing or soft-deleted root is false too.

deps

query deps

auth

request context, or null for unauthenticated callers

type RequestContext | null

cell

the cell the caller couldn't otherwise view

type CellRow

returns

Promise<boolean>

whether the steward arm admits the caller

cell_update_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; data: ZodOptional<...>; visibility: ZodOptional<...>; path: ZodOptional<...>; acting: ZodOptional<...>; }, $strict... import {cell_update_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_VIEW_COLUMNS
#

CELL_VIEWER_ANONYMOUS
#

db/cell_view_sql.ts view source

CellViewer import {CELL_VIEWER_ANONYMOUS} from '@fuzdev/fuz_app/db/cell_view_sql.js';

An anonymous caller — admitted by the public arm alone.

cell_viewer_from_auth
#

auth/cell_authorize.ts view source

(auth: RequestContext | null, now?: Date): CellViewer import {cell_viewer_from_auth} from '@fuzdev/fuz_app/auth/cell_authorize.js';

The CellViewer a SQL view filter (db/cell_view_sql.ts) binds for auth — the facts can_view_cell reads, so the two admit the same cells: the acting actor, the global admin grant, and the active role grants grant_admits matches against.

auth

request context, or null for unauthenticated callers

type RequestContext | null

now?

the instant role-grant expiry is judged at

type Date
optional default new Date()

returns

CellViewer

the viewer

cell_viewer_params
#

db/cell_view_sql.ts view source

(viewer: CellViewer): [boolean, (string & $brand<"Uuid">) | null, readonly string[], readonly ((string & $brand<"Uuid">) | null)[]] import {cell_viewer_params} from '@fuzdev/fuz_app/db/cell_view_sql.js';

The viewer's bind values, in the order `[is_admin, actor_id, roles, scope_ids]`.

viewer

the caller

returns

[boolean, Uuid | null, readonly string[], readonly (Uuid | null)[]]

the four bind values

throws

  • Error - when the role-grant arrays aren't parallel

CELL_VIEWER_UNRESTRICTED
#

db/cell_view_sql.ts view source

CellViewer import {CELL_VIEWER_UNRESTRICTED} from '@fuzdev/fuz_app/db/cell_view_sql.js';

A server-side read that filters nothing — the admin arm admits every cell. For a caller that applies its own view check afterwards (the cell_get bundle) or acts for the server itself.

CellActionDeps
#

auth/cell_actions.ts view source

CellActionDeps

type ActionFactoryDeps & { validate_data?: ((kind: string | null, data: Readonly<{ [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }>) => void) | undefined; authorize_create?: CellCreateAuthorize | undefined; }

import type {CellActionDeps} from '@fuzdev/fuz_app/auth/cell_actions.js';

Dependencies for create_cell_actions.

validate_data is the optional sub-API hook for per-kind shape validation (e.g., a collection/entry registry), the TS twin of the Rust CellDataValidator. It is called as (kind, data) — kind is the cell's top-level kind column, so it dispatches on it: the input's on create, the existing cell's on an update carrying data (kind is write-once), the source's for every row a clone writes (on the merged data). It accepts or throws, never rewrites: the handler stores the caller's data as sent, and it must not mutate it. A thrown ZodError becomes the standard invalid_params JSON-RPC error so per-kind validation failures surface to clients with code -32602 (not -32603 / internal); throw a JsonrpcError (jsonrpc_errors.invalid_params with a reason) to choose the reason. When omitted, payloads pass through as-is.

authorize_create is the optional creation-gate hook (see CellCreateAuthorize). When omitted, create is open, and every non-null root policy write is refused.

log

Structured logger instance.

type Logger

audit

Bound audit emitter for audit writes.

type AuditEmitter

validate_data?

type (kind: string | null, data: Readonly<{ [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }>): void

kind

type string | null

data

type Readonly<CellData>
returns void

authorize_create?

type CellCreateAuthorize

CellAuditEventJson
#

auth/cell_audit_action_specs.ts view source

value + type

{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; created_at: string; } import {CellAuditEventJson} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

Wire shape for a single cell-audit row. Narrower than AuditLogEventJson — account_id and target_account_id are deliberately omitted so this verb does NOT surface the actor↔account join. target_actor_id and metadata are dropped too: target_actor_id is NULL for every cell-domain event (the grant recipient lives inside metadata.principal on grant rows, not on the audit-log top-level field); metadata is unread by the timeline UI.

ip is also omitted: it is PII about the actors who touched the cell, and even at the manage tier this per-cell timeline has no need for it (admins reach the full audit_log surface, which carries ip, through the admin audit verbs). Keeping it off this wire avoids leaking collaborators' IPs to a cell's owner.

All omitted fields can be re-added under a richer admin-only event-detail view later — keep the wire surface honest about what consumers use.

id

type string & $brand<"Uuid">

seq

type number

event_type

type string

outcome

type "success" | "failure"

actor_id

type (string & $brand<"Uuid">) | null

created_at

type string

CellAuditListInput
#

auth/cell_audit_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellAuditListInput} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

cell_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellAuditListOptions
#

db/cell_audit_queries.ts view source

CellAuditListOptions import type {CellAuditListOptions} from '@fuzdev/fuz_app/db/cell_audit_queries.js';

limit

type number

before?

Cursor — return rows with seq < before.

type number

CellAuditListOutput
#

auth/cell_audit_action_specs.ts view source

value + type

{ events: { id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; created_at: string; }[]; } import {CellAuditListOutput} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

events

type CellAuditEventJson[]

CellAuditMetadata
#

auth/cell_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; kind?: string | undefined; path?: string | undefined; } import {CellAuditMetadata} from '@fuzdev/fuz_app/auth/cell_audit_metadata.js';

Shared metadata envelope for cell mutations. kind and path are captured at emit-time so the audit-log viewer can show useful context for soft-deleted rows even after the cell snapshot is gone. Relation membership is tracked independently via the cell_item_* / cell_field_* per-row audit events.

Loose object: per-kind handlers may extend the metadata without spec churn.

cell_id

type string & $brand<"Uuid">

kind?

type string

path?

type string

[key: string]

type unknown

CellCloneAuditMetadata
#

auth/cell_audit_metadata.ts view source

value + type

{ [x: string]: unknown; source_id: string & $brand<"Uuid">; new_id: string & $brand<"Uuid">; deep: boolean; item_count: number; kind?: string | undefined; } import {CellCloneAuditMetadata} from '@fuzdev/fuz_app/auth/cell_audit_metadata.js';

Metadata envelope for cell_clone.

source_id and new_id capture the parent → clone edge. deep flags whether children were walked. item_count reports the number of children actually cloned (post-skip). kind is captured at emit-time so an operator can filter the audit log by source shape (e.g., "every collection clone").

No skipped-child count is recorded: surfacing how many children the caller couldn't view would leak the source's hidden-child count to the cloner (who owns — and can audit — the clone). Non-viewable children are dropped silently (D8).

source_id

type string & $brand<"Uuid">

new_id

type string & $brand<"Uuid">

deep

type boolean

item_count

type number

kind?

type string

[key: string]

type unknown

CellCloneInput
#

auth/cell_action_specs.ts view source

value + type

{ source_id: string & $brand<"Uuid">; deep?: boolean | undefined; with_data_patch?: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; } | undefined; acting?: (string & $brand<...>) | undefined; } import {CellCloneInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_clone. Source must be view-admitted by can_view_cell (404 otherwise — IDOR mask). The clone is owned by the caller; path is always nulled (admin-only paths can't auto-clone). Provenance lives only in the cell_clone audit row's source_id — no provenance fields are stamped into data.

source_id

type string & $brand<"Uuid">

deep?

type boolean

with_data_patch?

type CellData

acting?

type string & $brand<"Uuid">

CellCloneOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 11 more ...; grant_count: number; }; } import {CellCloneOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellCreateAuthorize
#

auth/cell_actions.ts view source

CellCreateAuthorize import type {CellCreateAuthorize} from '@fuzdev/fuz_app/auth/cell_actions.js';

Opt-in, parent-aware creation authorizer — the TS twin of the Rust CellCreateAuthorize trait, its two members the trait's two methods. Omitted = open create, and every non-null policy write refused ERROR_CELL_POLICY_UNSUPPORTED.

  • authorize gates both roots (parent_id = null) and contributions; answers "may *this actor* create *this kind* here?" and returns a CellCreateVerdict. Runs in cell_create after validate_data and after the handler resolves root_id and root_policy from the parent (an unviewable parent or a missing or soft-deleted root already 404-masks before this runs). ctx is the request's: cell_create and cell_clone have side effects, so ctx.db is the request transaction, and an impl needing more than root_policy (a content pre-screen, say) reads through it. Read-only — the authorizer decides, it never writes; a write would commit or roll back with the create, and nothing enforces this but the impl.
  • validate_policy accepts or refuses a root policy before it is written (a root's cell_create, cell_policy_set) — the shape authorize reads. It runs after the manage and root-only gates, so it decides validity, never authority: throw jsonrpc_errors.invalid_params with ERROR_CELL_POLICY_INVALID (or a reason of the consumer's), or a ZodError, which the handler turns into invalid_params. Omitted refuses every policy (ERROR_CELL_POLICY_UNSUPPORTED, the Rust trait's default): an authorizer that reads no policy stores none.

authorize

type (auth: RequestActorContext, input: CellCreateAuthorizeInput, ctx: ActionContext): CellCreateVerdict | Promise<...>

auth

input

ctx

validate_policy?

type (policy: { [x: string]: unknown; }): void

policy

returns void

CellCreateAuthorizeInput
#

auth/cell_actions.ts view source

CellCreateAuthorizeInput import type {CellCreateAuthorizeInput} from '@fuzdev/fuz_app/auth/cell_actions.js';

Input to CellCreateAuthorize.authorize — the TS twin of the Rust CellCreateAuthorizeInput. Parent-aware: it carries the directory context (parent_id / the handler-resolved root_id) and the governing root's policy, so the authorizer resolves the rule that applies.

kind

The cell's kind (the top-level cell.kind value); null for a typeless cell.

type string | null

data

The cell data (kind-free — a kind key is rejected upstream). For richer M3-era policies (e.g. content pre-screen).

type CellData

parent_id

The immediate container the create targets. null = a root creation; otherwise a contribution under that parent.

type Uuid | null

parent_kind

The immediate container's kind column, read by the handler with the parent — so an authorizer checks the parent → child edge (which kinds may sit under which) without a read of its own. null for a root creation and for a typeless parent.

type string | null

root_id

The governing root of the directory subtree, resolved by the handler from the parent (parent.root_id ?? parent.id). null for a root creation.

type Uuid | null

root_policy

The governing root's contribution policy column — the handler reads the root in-tx and hands its policy over, so a directory-aware authorizer resolves root_policy[kind] without a DB read of its own. null for a root creation and for a root carrying no policy (the authorizer's floor governs); a missing or soft-deleted root answers cell_not_found before the authorizer runs. Never the root's data: an editor grant can rewrite data, so a policy read from it would be the editor's to loosen.

type CellPolicy | null

CellCreateInput
#

auth/cell_action_specs.ts view source

value + type

{ data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind?: string | null | undefined; visibility?: "private" | "public" | undefined; path?: (string & $brand<"CellPath">) | null | undefined; parent_id?: (string & $brand<...>) | ... 1 more ... | undefined; policy?: { ...; } | .... import {CellCreateInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_create. created_by is NOT on the wire — the handler stamps it from auth.actor.id. path is admin-only; non-admin callers supplying path get ERROR_CELL_PATH_ADMIN_ONLY (forbidden).

data

type CellData

kind?

type string | null

visibility?

type "private" | "public"

path?

type (string & $brand<"CellPath">) | null

parent_id?

type (string & $brand<"Uuid">) | null

policy?

type CellPolicy | null

acting?

type string & $brand<"Uuid">

CellCreateOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 11 more ...; grant_count: number; }; } import {CellCreateOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellCreateQueryInput
#

db/cell_queries.ts view source

CellCreateQueryInput import type {CellCreateQueryInput} from '@fuzdev/fuz_app/db/cell_queries.js';

Input for query_cell_create. refs is derived from data. kind is the write-once capability axis (cell.kind column) — set here at INSERT and absent from CellUpdatePatch, so it can never change post-create. parent_id / root_id / moderation are the directory-tree + lifecycle columns the handler derives (parent containment, governing root, the create-authorizer verdict) — likewise write-once / control-gated, never on CellUpdatePatch. policy is a root's contribution policy (the table CHECK refuses one beside a parent_id), changed later only by query_cell_set_policy.

data

type Json

kind?

type string | null

visibility?

type CellVisibility

path?

type string | null

parent_id?

type Uuid | null

root_id?

type Uuid | null

moderation?

type string | null

policy?

type CellPolicy | null

created_by?

type Uuid | null

CellCreateVerdict
#

auth/cell_actions.ts view source

CellCreateVerdict

type { allow: false; } | { allow: true; moderation_required: boolean; }

import type {CellCreateVerdict} from '@fuzdev/fuz_app/auth/cell_actions.js';

An authorizer's decision for a cell_create — the TS twin of the Rust Verdict. Folds the moderation outcome into the authority decision (one policy resolution, not two): {allow: false} denies (the handler surfaces a 403 forbidden for a viewable parent / a root creation), `{allow: true, moderation_required} admits — true → born pending + private, false` → born approved at the author's visibility.

CellData
#

auth/cell_data_schema.ts view source

value + type

{ [x: string]: unknown; label?: string | undefined; summary?: string | undefined; } import {CellData} from '@fuzdev/fuz_app/auth/cell_data_schema.js';

Base cell-data shape. All fields optional; loose mode admits arbitrary additional keys so apps can attach metadata or stage new kinds without touching the wire schema. kind lives on the top-level cell.kind column, not here (see the module doc).

label?

type string

summary?

type string

[key: string]

type unknown

CellDeleteInput
#

auth/cell_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellDeleteInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellDeleteOutput
#

auth/cell_action_specs.ts view source

value + type

{ ok: true; deleted: boolean; } import {CellDeleteOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

ok

type true

deleted

type boolean

CellFieldActionDeps
#

auth/cell_field_actions.ts view source

ActionFactoryDeps import type {CellFieldActionDeps} from '@fuzdev/fuz_app/auth/cell_field_actions.js';

log

Structured logger instance.

type Logger

audit

Bound audit emitter for audit writes.

type AuditEmitter

CellFieldDeleteAuditMetadata
#

auth/cell_field_audit_metadata.ts view source

value + type

{ [x: string]: unknown; source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; } import {CellFieldDeleteAuditMetadata} from '@fuzdev/fuz_app/auth/cell_field_audit_metadata.js';

Metadata envelope for cell_field_delete.

source_id

type string & $brand<"Uuid">

name

type string

target_id

type string & $brand<"Uuid">

[key: string]

type unknown

CellFieldDeleteInput
#

auth/cell_field_action_specs.ts view source

value + type

{ source_id: string & $brand<"Uuid">; name: string & $brand<"CellFieldName">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellFieldDeleteInput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Input for cell_field_delete. Idempotent: a successful response is returned even when no row matched.

source_id

type string & $brand<"Uuid">

name

type string & $brand<"CellFieldName">

acting?

type string & $brand<"Uuid">

CellFieldDeleteOutput
#

auth/cell_field_action_specs.ts view source

value + type

{ ok: true; deleted: boolean; } import {CellFieldDeleteOutput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

ok

type true

deleted

type boolean

CellFieldListInput
#

auth/cell_field_action_specs.ts view source

value + type

{ source_id?: (string & $brand<"Uuid">) | undefined; target_id?: (string & $brand<"Uuid">) | undefined; name_after?: (string & $brand<"CellFieldName">) | undefined; limit?: number | undefined; acting?: (string & $brand<...>) | undefined; } import {CellFieldListInput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Input for cell_field_list. Pass source_id for forward fields or target_id for reverse upfields — exactly one; both or neither is invalid_params with ERROR_CELL_FIELD_LIST_REQUIRES_SOURCE_OR_TARGET, from the handler (the schema leaves both optional). Reverse listing has 2-layer authz (target view-check gates the call; per-source view-check filters the rows).

Either direction returns a page of up to limit fields (default CELL_RELATION_LIST_LIMIT_DEFAULT) whose other end the caller can view — the view check runs before the limit. Forward listing pages on via name_after (return rows whose name > name_after lex); the reverse listing has no cursor, oldest field first.

source_id?

type string & $brand<"Uuid">

target_id?

type string & $brand<"Uuid">

name_after?

type string & $brand<"CellFieldName">

limit?

type number

acting?

type string & $brand<"Uuid">

CellFieldListOutput
#

auth/cell_field_action_specs.ts view source

value + type

{ fields: { source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; created_at: string; }[]; } import {CellFieldListOutput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

fields

type FieldJson[]

CellFieldName
#

auth/cell_field_action_specs.ts view source

value + type

string & $brand<"CellFieldName">

type string & $brand<"CellFieldName">

import {CellFieldName} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

external types

z.infer<typeof CellFieldName>

CellFieldRow
#

db/cell_field_queries.ts view source

CellFieldRow import type {CellFieldRow} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Row shape returned by cell_field SELECTs.

source_id

type Uuid

name

type string

target_id

type Uuid

created_at

type string

CellFieldSetAuditMetadata
#

auth/cell_field_audit_metadata.ts view source

value + type

{ [x: string]: unknown; source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; } import {CellFieldSetAuditMetadata} from '@fuzdev/fuz_app/auth/cell_field_audit_metadata.js';

Metadata envelope for cell_field_set. Emitted on every successful create OR update path (UPSERT on (source_id, name)); the audit reader correlates create-vs-update via repeated (source_id, name) if needed.

source_id

type string & $brand<"Uuid">

name

type string

target_id

type string & $brand<"Uuid">

[key: string]

type unknown

CellFieldSetInput
#

auth/cell_field_action_specs.ts view source

value + type

{ source_id: string & $brand<"Uuid">; name: string & $brand<"CellFieldName">; target_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellFieldSetInput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Input for cell_field_set. UPSERT on (source_id, name) — re-issuing the same input updates target_id and bumps created_at.

source_id

type string & $brand<"Uuid">

name

type string & $brand<"CellFieldName">

target_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellFieldSetOutput
#

auth/cell_field_action_specs.ts view source

value + type

{ field: { source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; created_at: string; }; } import {CellFieldSetOutput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

field

type FieldJson

CellFieldSetQueryInput
#

CellGetInput
#

auth/cell_action_specs.ts view source

value + type

{ id?: (string & $brand<"Uuid">) | undefined; path?: (string & $brand<"CellPath">) | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {CellGetInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_get. Pass id OR path (exactly one expected; both accepted, id takes precedence). Neither is invalid_params with ERROR_CELL_GET_REQUIRES_ID_OR_PATH, from the handler (the schema leaves both optional). The handler responds with 404 when no row matches OR when can_view_cell rejects the caller — same code so private-cell existence doesn't leak.

id?

type string & $brand<"Uuid">

path?

type string & $brand<"CellPath">

acting?

type string & $brand<"Uuid">

CellGetOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 11 more ...; grant_count: number; }; ... 5 more ...; can_grant: boolean; } import {CellGetOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Output for cell_get. Bundles relation arrays (fields + items) server-side via JOINs so the common "show this cell with its children" flow needs one round-trip. Targets are filtered to those the caller may view (strict target-visibility); *_truncated reports the relation's size before that filter, so it can be true while fewer than CELL_RELATIONS_BUNDLE_LIMIT edges are returned. Per-relation `LIMIT CELL_RELATIONS_BUNDLE_LIMIT`; clients paginate via cell_item_list({parent_id, position_after}) / cell_field_list({source_id}) when truncated.

cell

type CellJson

fields

type FieldJson[]

fields_truncated

type boolean

items

type ItemJson[]

items_truncated

type boolean

can_edit

type boolean

can_grant

type boolean

CellGrantActionDeps
#

auth/cell_grant_actions.ts view source

CellGrantActionDeps

type ActionFactoryDeps & { roles: RoleSchemaResult; authorize_grant_create?: CellGrantCreateAuthorize | undefined; authorize_grant_revoke?: CellGrantRevokeAuthorize | undefined; }

import type {CellGrantActionDeps} from '@fuzdev/fuz_app/auth/cell_grant_actions.js';

Dependencies for create_cell_grant_actions.

roles is the role schema — read for the role-validity gate on cell_grant_create; authorize_grant_create is the optional consumer gate (CellGrantCreateAuthorize) and authorize_grant_revoke the optional steward widening of cell_grant_revoke (CellGrantRevokeAuthorize). The other slots match CellActionDeps so audit-log emit goes through the same fire-and-forget plumbing.

log

Structured logger instance.

type Logger

audit

Bound audit emitter for audit writes.

type AuditEmitter

roles

type RoleSchemaResult

authorize_grant_create?

type (auth: RequestActorContext, input: CellGrantCreateAuthorizeInput, ctx: ActionContext): boolean | Promise<...>

auth

input

ctx

returns boolean | Promise<boolean>

authorize_grant_revoke?

type (auth: RequestActorContext, input: CellGrantRevokeAuthorizeInput, ctx: ActionContext): boolean | Promise<...>

auth

input

ctx

returns boolean | Promise<boolean>

CellGrantCreateAuditMetadata
#

auth/cell_grant_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; grant_id: string & $brand<"Uuid">; level: "viewer" | "editor"; principal: { [x: string]: unknown; actor_id: string & $brand<"Uuid">; } | { ...; }; } import {CellGrantCreateAuditMetadata} from '@fuzdev/fuz_app/auth/cell_grant_audit_metadata.js';

Metadata envelope for cell_grant_create.

Emitted on every successful create OR re-share update path (UPSERT-on-unique-index). The audit reader correlates create-vs-update via grant_id if needed; the design doesn't require distinguishing the two at the metadata level.

cell_id

type string & $brand<"Uuid">

grant_id

type string & $brand<"Uuid">

level

type "viewer" | "editor"

principal

type CellGrantPrincipalAuditMetadata

[key: string]

type unknown

CellGrantCreateAuthorize
#

auth/cell_grant_actions.ts view source

CellGrantCreateAuthorize import type {CellGrantCreateAuthorize} from '@fuzdev/fuz_app/auth/cell_grant_actions.js';

Opt-in gate on cell_grant_create — the TS twin of the Rust CellGrantCreateAuthorize trait. Asked after the spine's own gates — the manage tier (a non-manager gets the 404 mask and never reaches it), the moderation admission, and the role registry — and before the owner check and the write, for a new grant and a level change alike. false refuses with 403 ERROR_CELL_GRANT_NOT_AUTHORIZED, writing neither the grant nor an audit row. A consumer narrows sharing with it (say, role-shaped grants only to roles of the cell's own space); it can't widen it, as every caller it sees already manages the cell. Omitted = no gate past the spine's.

ctx is the request's: cell_grant_create has side effects, so ctx.db is the request transaction, and a callback needing the database (a scope-existence check, say) reads through it. Read-only — the callback decides, it never writes; a write would commit or roll back with the grant, and nothing enforces this but the callback.

(call)

type (auth: RequestActorContext, input: CellGrantCreateAuthorizeInput, ctx: ActionContext): boolean | Promise<...>

auth

input

ctx

returns boolean | Promise<boolean>

CellGrantCreateAuthorizeInput
#

auth/cell_grant_actions.ts view source

CellGrantCreateAuthorizeInput import type {CellGrantCreateAuthorizeInput} from '@fuzdev/fuz_app/auth/cell_grant_actions.js';

Input to a CellGrantCreateAuthorize callback — the twin of the Rust CellGrantCreateAuthorizeInput.

cell_id

The cell the grant names.

type Uuid

cell_created_by

The cell's owner (null = a system cell).

type Uuid | null

level

The level the grant confers — on an upsert, the new level.

type "viewer" | "editor"

principal

Who the grant admits: an actor, or a role at an optional scope.

type CellGrantPrincipalQueryInput

CellGrantCreateInput
#

auth/cell_grant_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; principal: { kind: "actor"; actor_id: string & $brand<"Uuid">; } | { kind: "role"; role: string; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; }; acting?: (string & $brand<...>) | undefined; } import {CellGrantCreateInput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Input for cell_grant_create. Idempotent on the unique index — re-granting the same (cell_id, principal) pair updates level + granted_by rather than producing a duplicate row.

cell_id

type string & $brand<"Uuid">

level

type "viewer" | "editor"

principal

type CellGrantPrincipalInput

acting?

type string & $brand<"Uuid">

CellGrantCreateOutput
#

auth/cell_grant_action_specs.ts view source

value + type

{ grant: { id: string & $brand<"Uuid">; cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; actor_id: (string & $brand<"Uuid">) | null; role: string | null; scope_id: (string & $brand<...>) | null; granted_by: (string & $brand<...>) | null; created_at: string; }; } import {CellGrantCreateOutput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

grant

type GrantJson

CellGrantCreateQueryInput
#

CellGrantLevel
#

auth/cell_grant_action_specs.ts view source

value + type

"viewer" | "editor"

type "viewer" | "editor"

import {CellGrantLevel} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Grant level — view-only or view-plus-edit.

CellGrantListInput
#

auth/cell_grant_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellGrantListInput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

cell_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellGrantListOutput
#

auth/cell_grant_action_specs.ts view source

value + type

{ grants: { id: string & $brand<"Uuid">; cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; actor_id: (string & $brand<"Uuid">) | null; role: string | null; scope_id: (string & $brand<...>) | null; granted_by: (string & $brand<...>) | null; created_at: string; }[]; } import {CellGrantListOutput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

grants

type GrantJson[]

CellGrantPrincipalAuditMetadata
#

auth/cell_grant_audit_metadata.ts view source

value + type

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

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

import {CellGrantPrincipalAuditMetadata} from '@fuzdev/fuz_app/auth/cell_grant_audit_metadata.js';

Principal columns as stored on cell_grant. Discriminated by which keys are present: {actor_id} for an actor-shaped grant, {role, scope_id} for a role-shaped grant. Actor-shaped grants carry only the id; names are never persisted in the audit envelope.

CellGrantPrincipalInput
#

auth/cell_grant_action_specs.ts view source

value + type

{ kind: "actor"; actor_id: string & $brand<"Uuid">; } | { kind: "role"; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

type { kind: "actor"; actor_id: string & $brand<"Uuid">; } | { kind: "role"; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

import {CellGrantPrincipalInput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Wire-input principal. Discriminated by kind. Actor-shaped principals carry a resolved actor_id — the picker UI runs actor_search to convert a typed name to an id before this verb is called.

CellGrantPrincipalQueryInput
#

db/cell_grant_queries.ts view source

CellGrantPrincipalQueryInput

type { kind: "actor"; actor_id: string & $brand<"Uuid">; } | { kind: "role"; role: string; scope_id: (string & $brand<"Uuid">) | null; }

import type {CellGrantPrincipalQueryInput} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Discriminated principal input for query_cell_grant_create. Wire and query shapes are aligned — actor-shaped principals carry a pre-resolved actor_id; pickers run actor_search to convert a typed name to an id upstream of the handler.

CellGrantRevokeAuditMetadata
#

auth/cell_grant_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; grant_id: string & $brand<"Uuid">; level: "viewer" | "editor"; principal: { [x: string]: unknown; actor_id: string & $brand<"Uuid">; } | { ...; }; self?: true | undefined; steward?: true | undefined; } import {CellGrantRevokeAuditMetadata} from '@fuzdev/fuz_app/auth/cell_grant_audit_metadata.js';

Metadata envelope for cell_grant_revoke.

self: true distinguishes the recipient-side "leave shared cell" path (actor-shaped grant where the principal actor === caller actor) from a delegator-side revoke, and steward: true a scope steward's revoke of a scoped role-shaped grant, admitted by the consumer's CellGrantRevokeAuthorize. Single event type for all three — the boolean is enough for forensic review and avoids surface-doubling with parallel events.

cell_id

type string & $brand<"Uuid">

grant_id

type string & $brand<"Uuid">

level

type "viewer" | "editor"

principal

type CellGrantPrincipalAuditMetadata

self?

type true

steward?

type true

[key: string]

type unknown

CellGrantRevokeAuthorize
#

auth/cell_grant_actions.ts view source

CellGrantRevokeAuthorize import type {CellGrantRevokeAuthorize} from '@fuzdev/fuz_app/auth/cell_grant_actions.js';

Opt-in widening of cell_grant_revoke to a scope's steward — the TS twin of the Rust CellGrantRevokeAuthorize trait.

The role-shaped twin of self-revoke: an actor-shaped grant's grantee may leave a cell, and for a role-shaped grant the grantee is the scope, so the scope's steward speaks for it (say, a teacher removing a cell shared into their classroom). The consumer decides what stewards a scope. The input carries the grant's role so a consumer whose scopes are stewarded only for certain roles can refuse the rest.

Deliberately fenced. The spine asks it only when the caller neither manages the cell nor holds the grant (self-revoke), and only for a role-shaped grant with a non-null scope_id — an actor-shaped grant or a scopeless role grant never reaches it, and a soft-deleted cell 404s first. true revokes as a manager would — same delete, still_admitted, and cell_grant_revoke audit row, its metadata stamped steward: true; false answers the same 404 ERROR_CELL_GRANT_NOT_FOUND mask as every other non-qualifying caller (not a 403 — the caller mustn't learn the grant exists); a throw is a hard error — it is not masked, so refuse with false, never a throw. Omitted = revoke is the manager's and the self-revoker's alone.

The grant it decides on is the grant revoked: the delete matches the grant's level as the callback saw it, so a level change landing between the read and the delete answers the 404 mask rather than revoking a grant the callback never judged. ctx.db is the request transaction (cell_grant_revoke has side effects), read through like CellGrantCreateAuthorize's, and read-only like it.

(call)

type (auth: RequestActorContext, input: CellGrantRevokeAuthorizeInput, ctx: ActionContext): boolean | Promise<...>

auth

input

ctx

returns boolean | Promise<boolean>

CellGrantRevokeAuthorizeInput
#

auth/cell_grant_actions.ts view source

CellGrantRevokeAuthorizeInput import type {CellGrantRevokeAuthorizeInput} from '@fuzdev/fuz_app/auth/cell_grant_actions.js';

Input to a CellGrantRevokeAuthorize callback — the twin of the Rust CellGrantRevokeAuthorizeInput. Only a role-shaped grant with a scope reaches the callback, so role and scope_id are always present.

grant_id

The grant being revoked.

type Uuid

cell_id

The cell the grant names.

type Uuid

cell_created_by

The cell's owner (null = a system cell).

type Uuid | null

role

The role the grant admits.

type string

scope_id

The scope the grant is bound to — the scope whose steward the callback decides on.

type Uuid

level

The level the grant confers.

type CellGrantLevel

CellGrantRevokeInput
#

auth/cell_grant_action_specs.ts view source

value + type

{ grant_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellGrantRevokeInput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

grant_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellGrantRevokeOutput
#

auth/cell_grant_action_specs.ts view source

value + type

{ ok: true; still_admitted: boolean; } import {CellGrantRevokeOutput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Output for cell_grant_revoke. still_admitted is true when the caller retains some admit path on the cell after the revoke (other grant, ownership, admin). Always true for a manager's revoke; a self-revoker or a scope steward may hold no other path.

ok

type true

still_admitted

type boolean

CellGrantRoleCrossTestOptions
#

testing/cross_backend/cell_grant_role.ts view source

CellGrantRoleCrossTestOptions import type {CellGrantRoleCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/cell_grant_role.js';

inheritance

role?

The role the grants name, which the CELL_ROLE_HOLDER_USERNAME account the setup seeds must hold and the backend's cell role registry must know. Default CELL_EDITOR_ROLE. It must not be ROLE_ADMIN: a global admin views and edits every cell, so the holder arms would pass without the grant — the call throws. ROLE_KEEPER serves a backend with only the builtin roles, since keeper reach is not admin reach.

type string

readonly

CellGrantRow
#

db/cell_grant_queries.ts view source

CellGrantRow import type {CellGrantRow} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Row shape returned by cell_grant SELECTs.

id

type Uuid

cell_id

type Uuid

level

type CellGrantLevel

actor_id

type Uuid | null

role

type string | null

scope_id

type Uuid | null

granted_by

type Uuid | null

created_at

type string

CellItemActionDeps
#

auth/cell_item_actions.ts view source

ActionFactoryDeps import type {CellItemActionDeps} from '@fuzdev/fuz_app/auth/cell_item_actions.js';

log

Structured logger instance.

type Logger

audit

Bound audit emitter for audit writes.

type AuditEmitter

CellItemDeleteAuditMetadata
#

auth/cell_item_audit_metadata.ts view source

value + type

{ [x: string]: unknown; parent_id: string & $brand<"Uuid">; position: string; child_id: string & $brand<"Uuid">; } import {CellItemDeleteAuditMetadata} from '@fuzdev/fuz_app/auth/cell_item_audit_metadata.js';

Metadata envelope for cell_item_delete.

parent_id

type string & $brand<"Uuid">

position

type string

child_id

type string & $brand<"Uuid">

[key: string]

type unknown

CellItemDeleteInput
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellItemDeleteInput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Input for cell_item_delete. Idempotent on the slot key.

parent_id

type string & $brand<"Uuid">

position

type string & $brand<"CellItemPosition">

acting?

type string & $brand<"Uuid">

CellItemDeleteOutput
#

auth/cell_item_action_specs.ts view source

value + type

{ ok: true; deleted: boolean; } import {CellItemDeleteOutput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

ok

type true

deleted

type boolean

CellItemInsertAuditMetadata
#

auth/cell_item_audit_metadata.ts view source

value + type

{ [x: string]: unknown; parent_id: string & $brand<"Uuid">; position: string; child_id: string & $brand<"Uuid">; } import {CellItemInsertAuditMetadata} from '@fuzdev/fuz_app/auth/cell_item_audit_metadata.js';

Metadata envelope for cell_item_insert.

parent_id

type string & $brand<"Uuid">

position

type string

child_id

type string & $brand<"Uuid">

[key: string]

type unknown

CellItemInsertInput
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id: string & $brand<"Uuid">; child_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellItemInsertInput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Input for cell_item_insert. Caller computes position via fractional_index_between(prev, next) (@fuzdev/fuz_util/fractional_index.ts) client-side. Returns cell_item_position_taken on `(parent_id, position)` unique violation; client refreshes bracket and retries.

parent_id

type string & $brand<"Uuid">

child_id

type string & $brand<"Uuid">

position

type string & $brand<"CellItemPosition">

acting?

type string & $brand<"Uuid">

CellItemInsertOutput
#

auth/cell_item_action_specs.ts view source

value + type

{ item: { parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; }; } import {CellItemInsertOutput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

item

type ItemJson

CellItemInsertQueryInput
#

CellItemListInput
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id?: (string & $brand<"Uuid">) | undefined; child_id?: (string & $brand<"Uuid">) | undefined; position_after?: (string & $brand<"CellItemPosition">) | undefined; limit?: number | undefined; acting?: (string & $brand<...>) | undefined; } import {CellItemListInput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Input for cell_item_list. Pass parent_id for forward items or child_id for reverse lists — exactly one; both or neither is invalid_params with ERROR_CELL_ITEM_LIST_REQUIRES_PARENT_OR_CHILD, from the handler (the schema leaves both optional). Reverse listing has 2-layer authz (child view-check gates the call; per-parent view-check filters the rows).

Either direction returns a page of up to limit items (default CELL_RELATION_LIST_LIMIT_DEFAULT) whose other end the caller can view — the view check runs before the limit. Forward listing pages on via position_after (return rows with position > position_after); the reverse listing has no cursor, oldest item first.

parent_id?

type string & $brand<"Uuid">

child_id?

type string & $brand<"Uuid">

position_after?

type string & $brand<"CellItemPosition">

limit?

type number

acting?

type string & $brand<"Uuid">

CellItemListOutput
#

auth/cell_item_action_specs.ts view source

value + type

{ items: { parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; }[]; } import {CellItemListOutput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

items

type ItemJson[]

CellItemMoveAuditMetadata
#

auth/cell_item_audit_metadata.ts view source

value + type

{ [x: string]: unknown; parent_id: string & $brand<"Uuid">; position_old: string; position_new: string; } import {CellItemMoveAuditMetadata} from '@fuzdev/fuz_app/auth/cell_item_audit_metadata.js';

Metadata envelope for cell_item_move. Carries both old and new position so the audit trail shows the reorder without a join back to the live row.

parent_id

type string & $brand<"Uuid">

position_old

type string

position_new

type string

[key: string]

type unknown

CellItemMoveInput
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; new_position: string & $brand<"CellItemPosition">; acting?: (string & $brand<...>) | undefined; } import {CellItemMoveInput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Input for cell_item_move. Move within the same parent (cross-parent moves are a future extension).

parent_id

type string & $brand<"Uuid">

position

type string & $brand<"CellItemPosition">

new_position

type string & $brand<"CellItemPosition">

acting?

type string & $brand<"Uuid">

CellItemMoveOutput
#

auth/cell_item_action_specs.ts view source

value + type

{ item: { parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; }; } import {CellItemMoveOutput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

item

type ItemJson

CellItemPosition
#

auth/cell_item_action_specs.ts view source

value + type

string & $brand<"CellItemPosition">

type string & $brand<"CellItemPosition">

import {CellItemPosition} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Position grammar — base62 fractional-indexing key. Wire enforces non-empty, alphabet only, and the helper's FRACTIONAL_INDEX_LENGTH_MAX cap (well above realistic lengths even for hundreds of consecutive front-inserts; set high to avoid arbitrary cliffs). Lex ordering is the contract; the no-trailing-'0' invariant lives in the helper, not the wire.

external types

z.infer<typeof CellItemPosition>

CellItemRow
#

db/cell_item_queries.ts view source

CellItemRow import type {CellItemRow} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Row shape returned by cell_item SELECTs.

parent_id

type Uuid

position

type string

child_id

type Uuid

created_at

type string

CellJson
#

auth/cell_action_specs.ts view source

value + type

{ id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 11 more ...; grant_count: number; } import {CellJson} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Wire form for a cell row. data is the typed-but-permissive CellData shape (kind / label / summary typed-and-optional, additional fields pass through). Per-kind shape validation is sub-API and handled by the app's validate_data deps callback (see auth/cell_actions.ts).

visibility is the access-control axis — a top-level column on the row, not a field inside data. cell_grant and visibility are the two ACL surfaces; both live as peers, not embedded in content.

path is the global namespace axis (no tenant/hub scoping).

Relations (items, fields) are NOT carried on the cell row — they live in the cell_item / cell_field sibling tables. Bundled arrays appear on CellGetOutput; other read verbs (cell_list) do not bundle.

id

type string & $brand<"Uuid">

path

type (string & $brand<"CellPath">) | null

data

type CellData

kind

type string | null

visibility

type "private" | "public"

refs

type (string & Brand<"FactHash">)[] | null

parent_id

type (string & $brand<"Uuid">) | null

root_id

type (string & $brand<"Uuid">) | null

moderation

type string | null

policy

type CellPolicy | null

created_by

type (string & $brand<"Uuid">) | null

updated_by

type (string & $brand<"Uuid">) | null

created_at

type string

updated_at

type string | null

deleted_at

type string | null

grant_count

type number

CellListInput
#

auth/cell_action_specs.ts view source

value + type

{ ids?: (string & $brand<"Uuid">)[] | undefined; kind?: string | undefined; visibility?: "private" | "public" | undefined; ref?: (string & Brand<"FactHash">) | undefined; ... 9 more ...; acting?: (string & $brand<...>) | undefined; } import {CellListInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_list. Filters are optional and combine with AND. The handler applies the SQL-side visibility predicate from query_cell_list so the page-window stays correct under pagination — post-filtering in JS would silently truncate pages.

ids is the batch-read filter — pass a list of cell ids to fetch them in one round-trip (avoids N+1 when rendering a collection's items[]). The visibility predicate still runs, so callers passing ids they can't view simply get fewer rows back. Capped at CELL_LIST_LIMIT_MAX.

shared_with: 'me' narrows to cells that admit the caller via a cell_grant row (actor-shaped or role-shaped principal) AND that the caller does not own. Authenticated only; combine with kind / path_prefix etc. to scope further. Combining with created_by: <my-actor-id> produces an empty result by definition (owner is implicit, never appears as a grant principal); we don't reject the combination at the schema layer because SQL emptiness is correct.

ids?

type (string & $brand<"Uuid">)[]

kind?

type string

visibility?

type "private" | "public"

ref?

type string & Brand<"FactHash">

created_by?

type string & $brand<"Uuid">

path_prefix?

type string & $brand<"CellPath">

root_id?

type string & $brand<"Uuid">

moderation?

type string

shared_with?

type "me"

order_by?

type "created_at" | "updated_at"

order_direction?

type "asc" | "desc"

limit?

type number

offset?

type number

acting?

type string & $brand<"Uuid">

CellListOptions
#

db/cell_queries.ts view source

CellListOptions import type {CellListOptions} from '@fuzdev/fuz_app/db/cell_queries.js';

Common pagination + tombstone-visibility options for list queries.

limit?

type number

offset?

type number

include_deleted?

type boolean

CellListOutput
#

auth/cell_action_specs.ts view source

value + type

{ cells: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 11 more ...; grant_count: number; }[]; cell_grants?: Record<...> | undefined; } import {CellListOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cells

type CellJson[]

cell_grants?

type Record<string, GrantJson[]>

CellListParams
#

db/cell_queries.ts view source

CellListParams import type {CellListParams} from '@fuzdev/fuz_app/db/cell_queries.js';

Parameters for query_cell_list. All filter dimensions are optional.

kind?

Match cell.kind = ? (uses idx_cell_kind).

type string

visibility?

Match cell.visibility = ? directly on the top-level column. Additional narrowing on top of the SQL-side auth visibility predicate — useful for the public discovery feed where authed callers must NOT see their own private entries mixed in.

type CellVisibility

ref?

Match cells whose refs[] contains this hash (uses idx_cell_refs).

type FactHash

created_by?

Filter to cells created by this actor (uses idx_cell_created_by).

type Uuid

path_prefix?

Filter to cells whose path starts with this prefix. Wildcard metachars in the prefix are NOT special — starts_with() does literal matching.

type string

root_id?

Scope to a directory subtree by governing root (cell.root_id = ?, uses idx_cell_root).

type Uuid

moderation?

Filter by moderation lifecycle marker ('pending' drives the mod queue via idx_cell_moderation_pending).

The visibility predicate still applies, so `{root_id, moderation: 'pending'}` is the queue for the callers who moderate it — a global admin, and the governing root's owner through the steward arm — while each author sees only their own entries.

type string

ids?

Batch-fetch by id. The visibility predicate still runs, so callers passing ids they can't view simply get fewer rows back. Order of the returned rows follows order_by / order_direction, not the input list — callers that need positional output (e.g. preserving a collection's items[] order) should re-index client-side.

type Uuid[]

viewer

The caller the visibility predicate binds — one value for the owner, admin, grant and steward arms, so visibility and grant admission can't be read for two different callers. cell_viewer_from_auth builds it from a request context; CELL_VIEWER_ANONYMOUS admits public rows alone, CELL_VIEWER_UNRESTRICTED every row.

type CellViewer

shared_with_caller_only?

When true, narrow to cells admitting the caller via a cell_grant row AND that the caller does not own. Authenticated only (viewer.actor_id must be set). Combine with kind / path_prefix etc. to scope further.

type boolean

order_by?

Sort column. Default created_at.

type "created_at" | "updated_at"

order_direction?

Sort direction. Default desc.

type "asc" | "desc"

limit?

Page size.

type number

offset?

Page offset.

type number

include_deleted?

Include soft-deleted rows. Default false.

type boolean

CellModerateAuditMetadata
#

auth/cell_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; root_id: string & $brand<"Uuid">; moderation: "approved" | "rejected"; } import {CellModerateAuditMetadata} from '@fuzdev/fuz_app/auth/cell_audit_metadata.js';

Metadata envelope for cell_moderate. cell_id is the moderated contribution; root_id is its governing root (the container whose moderation authority gated the call); moderation is the terminal decision applied ('approved' | 'rejected').

cell_id

type string & $brand<"Uuid">

root_id

type string & $brand<"Uuid">

moderation

type "approved" | "rejected"

[key: string]

type unknown

CellModerateInput
#

auth/cell_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; moderation: "approved" | "rejected"; acting?: (string & $brand<"Uuid">) | undefined; } import {CellModerateInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_moderate — the pending → approved | rejected decision, plus the approved → rejected takedown, gated on moderation authority over the governing root (admin / root owner in v1), not the contribution (which the author manages, and could otherwise self-approve). 404 when the caller neither views the target nor stewards it (a root manager reads a pending or rejected contribution through the steward arm); 403 when the caller reaches it but isn't a root manager (the author lands here); 400 cell_not_pending when a root manager names a transition the contribution's state doesn't allow — approving one that is not pending, or rejecting one already rejected or never moderated.

cell_id

type string & $brand<"Uuid">

moderation

type "approved" | "rejected"

acting?

type string & $brand<"Uuid">

CellModerateOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 11 more ...; grant_count: number; }; } import {CellModerateOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellModerationDecision
#

auth/cell_action_specs.ts view source

value + type

"approved" | "rejected"

type "approved" | "rejected"

import {CellModerationDecision} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

The terminal transition a cell_moderate call applies to a gated contribution's moderation lifecycle. The born state ('pending') is set by the create authorizer, never on the wire; the verb only moves it to a terminal state. 'approved' publishes a pending contribution (also flips visibility → 'public'); 'rejected' holds a pending one back or takes an approved one down, leaving visibility as it was.

CellPath
#

auth/cell_action_specs.ts view source

value + type

string & $brand<"CellPath">

type string & $brand<"CellPath">

import {CellPath} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Branded so the type system distinguishes a validated path from any other string. Construct via CellPath.parse(s) at external boundaries; the RPC dispatcher does this automatically when the wire schema (CellCreateInput, CellGetInput, CellUpdateInput, CellListInput) is parsed at the entry point. Frontend callers handing a raw string to api.cell_* cast at the callsite (as CellPath) — the runtime check still runs server-side.

external types

z.infer<typeof CellPath>

CellPolicy
#

auth/cell_action_specs.ts view source

value + type

{ [x: string]: unknown; } import {CellPolicy} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

A root's contribution policy — any JSON object. Opaque to the spine: the consumer's create authorizer validates it (CellCreateAuthorize.validate_policy) and interprets it (CellCreateAuthorizeInput.root_policy). Loose for that reason — the keys are the consumer's. Twin of the Rust CellPolicy.

[key: string]

type unknown

CellPolicySetAuditMetadata
#

auth/cell_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; policy: Record<string, unknown> | null; previous_policy: Record<string, unknown> | null; } import {CellPolicySetAuditMetadata} from '@fuzdev/fuz_app/auth/cell_audit_metadata.js';

Metadata envelope for cell_policy_set. cell_id is the root; policy is the policy written (null when cleared) and previous_policy the one it replaced, so the audit trail carries every policy a root has had.

cell_id

type string & $brand<"Uuid">

policy

type Record<string, unknown> | null

previous_policy

type Record<string, unknown> | null

[key: string]

type unknown

CellPolicySetInput
#

auth/cell_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; policy: { [x: string]: unknown; } | null; acting?: (string & $brand<"Uuid">) | undefined; } import {CellPolicySetInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_policy_set — replace a root's contribution policy, or clear it with null. Manage-tier on the root: 404 when the caller can't view it, 403 ERROR_CELL_POLICY_MANAGE_ONLY when they can but don't manage it (an editor grant included), 400 ERROR_CELL_POLICY_ROOT_ONLY on a cell with a parent, then the mounted authorizer's validation of a non-null policy (ERROR_CELL_POLICY_UNSUPPORTED when none can).

cell_id

type string & $brand<"Uuid">

policy

type CellPolicy | null

acting?

type string & $brand<"Uuid">

CellPolicySetOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 11 more ...; grant_count: number; }; } import {CellPolicySetOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellRelationListLimit
#

auth/cell_relation_list_limit.ts view source

ZodOptional<ZodNumber>

type ZodOptional<ZodNumber>

import {CellRelationListLimit} from '@fuzdev/fuz_app/auth/cell_relation_list_limit.js';

The limit input of cell_field_list and cell_item_list.

CellRow
#

db/cell_queries.ts view source

CellRow import type {CellRow} from '@fuzdev/fuz_app/db/cell_queries.js';

Row shape returned by cell SELECTs. data is typed as CellData — the storage layer trusts the wire validation; the row is what was written, and the wire validates CellData on every write.

Parent↔child membership and named relations live in the cell_item / cell_field sibling tables (see db/cell_item_queries.ts / db/cell_field_queries.ts). The cell row carries identity + content only.

grant_count is a derived projection (correlated subquery against cell_grant keyed by cell_id, served by idx_cell_grant_cell) — not a table column. New cells naturally land at 0.

id

type Uuid

data

type CellData

kind

type string | null

visibility

type CellVisibility

path

type string | null

refs

type FactHash[] | null

parent_id

type Uuid | null

root_id

type Uuid | null

moderation

type string | null

policy

type CellPolicy | null

created_at

type string

updated_at

type string | null

deleted_at

type string | null

created_by

type Uuid | null

updated_by

type Uuid | null

grant_count

type number

CellSetPolicyResult
#

db/cell_queries.ts view source

CellSetPolicyResult import type {CellSetPolicyResult} from '@fuzdev/fuz_app/db/cell_queries.js';

What query_cell_set_policy answers: the updated root and the policy it replaced.

cell

type CellRow

previous_policy

The root's policy as the UPDATE found it, under the row lock it writes with.

type CellPolicy | null

CellUpdateInput
#

auth/cell_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; data?: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; } | undefined; visibility?: "private" | "public" | undefined; path?: (string & $brand<...>) | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } import {CellUpdateInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_update. Fields left undefined keep their existing value. path writes are admin-only (handler-enforced); non-admin callers supplying path get ERROR_CELL_PATH_ADMIN_ONLY even if no other field is changing. visibility writes require the manage tier (can_manage_cell = admin / owner) — editor-grant holders editing data cannot flip visibility (ERROR_CELL_VISIBILITY_MANAGE_ONLY).

cell_id

type string & $brand<"Uuid">

data?

type CellData

visibility?

type "private" | "public"

path?

type (string & $brand<"CellPath">) | null

acting?

type string & $brand<"Uuid">

CellUpdateOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 11 more ...; grant_count: number; }; } import {CellUpdateOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellUpdatePatch
#

db/cell_queries.ts view source

CellUpdatePatch import type {CellUpdatePatch} from '@fuzdev/fuz_app/db/cell_queries.js';

Patch for query_cell_update. Fields left undefined are unchanged; path may be explicitly set to null to clear. refs is re-derived from data whenever data is updated.

data?

type Json

visibility?

type CellVisibility

path?

type string | null

updated_by?

type Uuid | null

CellViewer
#

db/cell_view_sql.ts view source

CellViewer import type {CellViewer} from '@fuzdev/fuz_app/db/cell_view_sql.js';

The caller a view predicate binds — what can_view_cell reads off the request context.

role_grant_roles and role_grant_scope_ids are parallel, one entry per active role grant (null = a global grant): unnest pads the shorter with NULLs, and a NULL scope reads as a global grant, so cell_viewer_params refuses a mismatch.

actor_id

The acting actor; null for an anonymous or account-grain caller.

type Uuid | null

readonly

is_admin

Whether the caller holds the global admin role — the arm that admits every cell.

type boolean

readonly

role_grant_roles

The roles of the caller's active role grants, parallel to role_grant_scope_ids.

type readonly string[]

readonly

role_grant_scope_ids

The scopes of the caller's active role grants, parallel to role_grant_roles.

type readonly (Uuid | null)[]

readonly

CellViewerSqlParams
#

db/cell_view_sql.ts view source

CellViewerSqlParams import type {CellViewerSqlParams} from '@fuzdev/fuz_app/db/cell_view_sql.js';

Where a query binds the CellViewer facts can_view_cell_sql reads — each a $N placeholder.

is_admin

The global-admin flag (bool).

type string

readonly

actor_id

The acting actor (uuid, NULL when anonymous) — the owner arm and the actor-shaped grant arm.

type string

readonly

CellViewRow
#

CellVisibility
#

auth/cell_action_specs.ts view source

value + type

"private" | "public"

type "private" | "public"

import {CellVisibility} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Cell visibility — the coarse-grained access-control axis for a cell. Sibling to cell_grant (the fine-grained allowlist of actor- / role-shaped principals at viewer / editor levels). Together they form the cell-layer access-control surface:

  • cell.visibility = 'public' admits everyone, including unauthenticated visitors. cell_grant rows still apply for edit- level admit; read is universal.
  • cell.visibility = 'private' (default) restricts read to admin / owner (created_by) / cell_grant-admitted callers.

Stored as a top-level PG enum column (cell.visibility) — NOT inside cell.data, which is content metadata only. can_view_cell reads the column directly.

check_bootstrap_status
#

auth/bootstrap_routes.ts view source

(deps: CheckBootstrapStatusDeps, options: { token_path: string | null; }): Promise<BootstrapStatus> import {check_bootstrap_status} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Check bootstrap availability at startup.

Bootstrap is available when:

  1. A token path is configured
  2. The token file passes the secure read (exists, not a symlink, mode 0600/0400, within the size cap) and holds at least BOOTSTRAP_TOKEN_MIN_BYTES (a shorter one logs an error)
  3. The bootstrap_lock table shows bootstrapped = false

The probe reads through the same read_bootstrap_token the request-time read uses, so "bootstrap is available" and "the token file can actually be read" can't drift apart — an availability check laxer than the read it gates is the misconfiguration that reports green at boot and fails at request time. Twin of the Rust spine's is_bootstrap_available.

deps

filesystem and database access for the check

options

static configuration including token_path

type { token_path: string | null; }

returns

Promise<BootstrapStatus>

an object with available (boolean) and token_path (string | null)

check_known_divergence_ledger
#

testing/cross_backend/differential.ts view source

(cases: readonly { name: string; request: { method: string; as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"; params?: unknown; params_json?: string | undefined; acting?: "principal" | ... 2 more ... | undefined; verb?: "GET" | ... 1 more ... | undefined; }; expect: { ...; }; note?: string | undefined; xfail?: { ...; } | undefined; }[], ledger: readonly KnownDivergence[]): string[] import {check_known_divergence_ledger} from '@fuzdev/fuz_app/testing/cross_backend/differential.js';

The ledger's static problems against a case list: an entry that matches no case, and every case_name it lists that no case carries, or every method no case sends — a dead name would otherwise ride along behind a live one in the same entry.

Needs no backend; the differential's closing test reports these, and a consumer can check its ledger with it directly.

cases

the cases the ledger is run against

type readonly ConformanceCase[]

ledger

the known divergences

type readonly KnownDivergence[]

returns

string[]

one message per problem, empty when the ledger names only real cases

check_schema_drift
#

db/schema_ready.ts view source

(db: Db, expected: ExpectedSchema): Promise<SchemaDriftResult> import {check_schema_drift} from '@fuzdev/fuz_app/db/schema_ready.js';

Compare the live DB's columns against expected. Reports tables and columns the running code expects that the live DB lacks — the drift that breaks queries. Extra live tables / columns are ignored: forward-compatible, and a newer-than-fixture DB shouldn't fail readiness.

db

live database to introspect

type Db

expected

the committed column map a fresh bootstrap produces

returns

Promise<SchemaDriftResult>

check_spine_case_exclusions
#

testing/cross_backend/spine_suites.ts view source

internal

(exclude_cases: Readonly<Partial<Record<"cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | ... 33 more ... | "ws_round_trip", Readonly<...>>>>, collected: ReadonlyMap<...>): void import {check_spine_case_exclusions} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

Check every exclude_cases entry against the test paths each suite collected — the static half of the case exclusions, which the closing test register_spine_case_exclusions registers runs.

exclude_cases

the entries, by suite

type Readonly<Partial<Record<"cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | "body_size" | "body_size_smuggling" | "cell_grant_role" | "cell_grant_steward" | "cell_moderate" | "cell_ref_admission" | "cookie_attributes" | "credential_deadline" | "credential_header_robustness" | "identity_parity" | "origin_conformance" | "origin_scope" | "peer_ping_ws" | "query_shape" | "role_grant_offer_enumeration" | "role_grant_offer_lifecycle" | "role_grant_offer_notification_ws" | "role_grant_participation" | "role_grant_role_grammar" | "rpc_adversarial" | "session_cap" | "session_cookie_codec" | "simple_request" | "sse_round_trip" | "standard" | "testing_backdoor" | "token_lifetime" | "token_scope_surface" | "transport_composition" | "ws_connection_cap" | "ws_envelope" | "ws_inbound_dispatch" | "ws_outbound_overflow" | "ws_round_trip", Readonly<Record<string, string>>>>>

collected

each run suite's collected test paths

type ReadonlyMap<SuiteName, ReadonlySet<string>>

returns

void

throws

  • Error - naming every entry whose suite collected no test at its path

CheckBootstrapStatusDeps
#

auth/bootstrap_routes.ts view source

CheckBootstrapStatusDeps import type {CheckBootstrapStatusDeps} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Dependencies for checking bootstrap status at startup.

read_secure_file

Hardened secret-file read — the same capability the request-time read uses.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

db

Only the single-row bootstrap_lock read — narrower than the full Db.

type Pick<Db, "query_one">

log

type Logger

ClassifiedJsonrpcMessage
#

http/jsonrpc_helpers.ts view source

ClassifiedJsonrpcMessage

type { kind: "request"; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "notification"; method: string; params: unknown; } | { kind: "response"; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; } | { kind: "invalid"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }

import type {ClassifiedJsonrpcMessage} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

A parsed JSON value classified as a JSON-RPC message by classify_jsonrpc_message — transport-agnostic, each transport applying its own semantics to the arms:

  • request — a valid request envelope, ready for dispatch.
  • notification — a string method and no id key. params is handed back unvalidated (a notification has no id to answer an error on). The WebSocket path routes cancel and drops every other one; HTTP answers invalid_request with id: null.
  • response — no string method, a string or number id, and exactly one of result / error: a reply to a server-initiated request. The WebSocket path routes it to the pending peer request; HTTP issues no server-initiated requests, so it answers invalid_request with id: null.
  • invalid — the error to answer and the id to answer it on.

Twin of the Rust spine's fuz_http::Classified.

classify_jsonrpc_message
#

http/jsonrpc_helpers.ts view source

(json: unknown): ClassifiedJsonrpcMessage import {classify_jsonrpc_message} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Classify a parsed JSON value as a JSON-RPC message, in the Rust twin's order: version, then the method axis (none, or a non-string one: a response or invalid), then id (none: a notification), then the request envelope (JsonrpcRequest — string or number id, params absent or an object).

The method axis is string-typed like the twin, whose obj.get("method").and_then(Value::as_str) reads a present non-string method as absent — so {id, method: 123, result} is a response on both sides. The result / error exclusive-or refuses a frame carrying both.

A request carries the frame as given — validated, never re-built — so params reaches the action's input schema with every own key the client sent, __proto__ included, as on the GET path and the Rust twin.

Every invalid is invalid_request. Its id is the frame's own when the frame is an object carrying a string or number one, else null — a non-object frame (an array included) has none. A refused request envelope carries the schema's issues in DEV, as data.issues; nothing else carries data.

json

the parsed frame or body, of any shape

type unknown

returns

ClassifiedJsonrpcMessage

the classification

cleanup_expired_role_grant_offers
#

auth/cleanup.ts view source

(deps: AuthCleanupDeps): Promise<number> import {cleanup_expired_role_grant_offers} from '@fuzdev/fuz_app/auth/cleanup.js';

Sweep expired role_grant offers and emit one role_grant_offer_expire audit event per newly expired row.

One transaction: query_role_grant_offer_sweep_expired claims the expired, not-yet-audited offers (stamping expire_audited_at) and each claimed row's audit row is inserted beside it. After the commit, each event fans out through deps.audit.notify — per-listener throws are isolated there, so one bad listener never starves the rest. Twin of the Rust cleanup_expired_role_grant_offers.

Returns the count of offers audited. The offer rows themselves are preserved — offers carry audit value for the history view even after expiry, and accepted rows are the provenance for the resulting role_grant (deleting expired rows would not threaten that, but keeping them uniform with the retention policy for terminal rows is simpler).

deps

returns

Promise<number>

throws

  • Error - if the claim or an audit insert fails — the transaction rolls back, so no offer is stamped and the next run retries

mutates

  • role_grant_offer rows — stamps `expire_audited_at` on each swept offer
  • audit_log table — inserts one `role_grant_offer_expire` row per swept offer

cleanup_expired_sessions
#

auth/cleanup.ts view source

(deps: AuthCleanupDeps): Promise<number> import {cleanup_expired_sessions} from '@fuzdev/fuz_app/auth/cleanup.js';

Delete every expired session and close the connections each one opened, returning the count of sessions deleted.

The delete runs in its own transaction and the closes follow its commit, so on a successful pass every close is for a row already gone (see the module doc's § Expired sessions). Each swept session is closed by its id — the session-token hash the transports key on — so another session of the same account keeps its connections. A pass that closed something logs the counts at info. No audit row is written.

A closer that throws for one session does not spare the rest: every swept session is attempted and each failure is logged, as the post-commit queue does for a revocation's close.

Twin of the Rust cleanup_expired_sessions.

deps

returns

Promise<number>

how many expired sessions were deleted

throws

  • Error - from opening the transaction, the delete, or the commit. The first two close nothing and delete nothing. A failed commit is thrown **after** the closes: the sessions the delete returned are expired whether or not the commit landed, so their connections are closed either way, and the next pass deletes any row the commit left.

mutates

  • auth_session table — deletes every row past `expires_at`

clear_session_cookie
#

ClientApiTokenJson
#

auth/account_schema.ts view source

value + type

{ id: string; account_id: string & $brand<"Uuid">; name: string; expires_at: string | null; last_used_at: string | null; last_used_ip: string | null; created_at: string; scope: string; } import {ClientApiTokenJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for client-safe API token listing (excludes token_hash).

id

type string

account_id

type string & $brand<"Uuid">

name

type string

expires_at

type string | null

last_used_at

type string | null

last_used_ip

type string | null

created_at

type string

scope

Human-readable scope label (full / full (grandfathered) / N methods). A label rather than the document so the list stays a display surface — nothing authorizes off this, and a grandfathered token reads differently from a deliberately-full one.

type string

close_connections_after_commit
#

actions/connection_closer.ts view source

(closer: ConnectionCloser, target: ConnectionCloseTarget, log: Logger): void import {close_connections_after_commit} from '@fuzdev/fuz_app/actions/connection_closer.js';

Close the connections target names, now, for a revocation that has already committed — logging a close that throws instead of throwing it.

For a handler with no route transaction, closing straight after its own commit: a throw there would fail a request whose revocation stands, and skip the work after the close (the response, the audit row). A closer over several transports closes the rest before it rethrows, so catching loses no close. The twin of the post-commit flush, which catches and logs the same way.

closer

the closer to run, deps.connection_closer in a handler

target

the connections to close

log

where a failing close is reported

type Logger

returns

void

collect_config_diagnostics
#

server/app_server.ts view source

internal

(input: ConfigDiagnosticsInput): AppSurfaceDiagnostic[] import {collect_config_diagnostics} from '@fuzdev/fuz_app/server/app_server.js';

The config-level diagnostics create_app_server appends to the surface's spec-level ones: a weakened session cookie, and an open rate-limited surface — one warning for 'disabled_for_testing', otherwise one per explicitly-null limiter, so a deployment that disables login's limiter but keeps signup's reads differently from one that disables both — and a disabled request body size limit (max_body_size: null).

input

returns

AppSurfaceDiagnostic[]

collect_json_keys_recursive
#

testing/integration_helpers.ts view source

(value: unknown): Set<string> import {collect_json_keys_recursive} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Recursively collect all key names from a parsed JSON value.

Walks objects and arrays to find every property name at any nesting depth.

value

type unknown

returns

Set<string>

collect_json_schema_property_names
#

testing/data_exposure.ts view source

(schema: unknown): Set<string> import {collect_json_schema_property_names} from '@fuzdev/fuz_app/testing/data_exposure.js';

Recursively collect all property names from a JSON Schema.

Walks properties, items, allOf/anyOf/oneOf, and additionalProperties to find every declared field name at any depth.

schema

type unknown

returns

Set<string>

collect_middleware_errors
#

http/surface.ts view source

(middleware: MiddlewareSpec[], route_path: string): Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> | null import {collect_middleware_errors} from '@fuzdev/fuz_app/http/surface.js';

Collect error schemas from all middleware that applies to a route path.

middleware

route_path

type string

returns

Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> | null

merged middleware error schemas, or null if none

ColumnExpr
#

db/sql_columns.ts view source

ColumnExpr import type {ColumnExpr} from '@fuzdev/fuz_app/db/sql_columns.js';

Per-column expression override for a projection — return the SQL to project for column, or undefined to keep the bare column reference.

Twin of the expr closure fuz_db::qualify_columns takes. The Rust side decodes rows positionally, so its overrides need no output name; TS reads rows by name, so columns_sql / qualify_columns alias every override back to its column (… AS created_at) — otherwise a to_char(…) projection would arrive under Postgres' derived name and read as undefined.

undefined is the *only* decline sentinel. An empty string is a returned expression, not a decline: it renders AS created_at and the query fails to parse. This is documented rather than guarded — every override in the repo is a literal lookup returning a fragment or nothing, and a malformed projection failing loud at the first query beats silently swallowing an expression the caller meant to supply.

(call)

type (column: string): string | undefined

column

type string
returns string | undefined

ColumnInfo
#

http/db_routes.ts view source

ColumnInfo import type {ColumnInfo} from '@fuzdev/fuz_app/http/db_routes.js';

Column metadata from information_schema.

column_name

type string

data_type

type string

is_nullable

type string

ColumnLayout
#

ui/ColumnLayout.svelte view source

accepts children

import ColumnLayout from '@fuzdev/fuz_app/ui/ColumnLayout.svelte';

aside

type Snippet<[]>

children

type Snippet<[]>

column_width?

CSS width of the fixed aside column.

type string
optional default '280px'

toggle_label?

The stacked toggle's text.

type string
optional default 'menu'

class?

type ClassValue | null
optional default ''

CSS custom properties

--column_toggle_bg
fallback var(--shade_00)

external types

SvelteHTMLElements['div']

columns_sql
#

db/sql_columns.ts view source

(columns: readonly string[], expr?: ColumnExpr | undefined): string import {columns_sql} from '@fuzdev/fuz_app/db/sql_columns.js';

Render a *_COLUMNS const as a SQL select list, in projection order.

columns

the column names

type readonly string[]

expr?

optional per-column expression override; an overridden column is aliased back to its own name

optional

returns

string

a, b, c

ColumnSnapshot
#

testing/schema_introspect.ts view source

value + type

{ data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; } import {ColumnSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Per-column structural metadata. The Zod schema is the canonical source for the column shape — SchemaSnapshot reuses it as the cross-impl _testing_schema_snapshot RPC action's wire validator, so the introspection type and the wire contract can't drift apart.

data_type

SQL standard type name from information_schema.columns.data_type.

type string

udt_name

Postgres-native type name from information_schema.columns.udt_name.

type string

is_nullable

true when the column accepts NULL.

type boolean

column_default

Default-value expression as Postgres reports it, or null if none.

type string | null

is_identity

true when the column was declared GENERATED ... AS IDENTITY.

type boolean

CommandDeps
#

runtime/deps.ts view source

CommandDeps import type {CommandDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Command execution.

run_command

Run a command and return the result. Never throws — failures surface as success: false.

options.cwd sets the child's working directory. options.signal aborts the child when the signal fires. options.timeout_ms kills the child after the given duration and returns timed_out: true on the result.

type (cmd: string, args: string[], options?: RunCommandOptions | undefined): Promise<CommandResult>

cmd

type string

args

type string[]

options?

optional
returns Promise<CommandResult>

CommandResult
#

runtime/deps.ts view source

CommandResult import type {CommandResult} from '@fuzdev/fuz_app/runtime/deps.js';

Result of executing a command.

timed_out is present only when timeout_ms was passed in RunCommandOptions and the process was killed after exceeding the timeout. Callers that pass timeout_ms should check this flag to distinguish timeout from exit-code failure.

success

type boolean

code

type number

stdout

type string

stderr

type string

timed_out?

type boolean

compare_cross_impl
#

testing/cross_backend/bench/bench_report.ts view source

(result: CrossImplBenchResult, options?: CompareCrossImplOptions | undefined): CrossImplComparisonEntry[] import {compare_cross_impl} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

Welch-test verdict for every non-reference backend vs the reference, per scenario. With deno + node + rust and reference: 'deno' you get node vs deno and rust vs deno for each scenario.

result

options?

optional

returns

CrossImplComparisonEntry[]

CompareCrossImplOptions
#

testing/cross_backend/bench/bench_report.ts view source

CompareCrossImplOptions import type {CompareCrossImplOptions} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

reference?

Backend to compare every other backend against. Defaults to result.backends[0].

type string

readonly

compile_action_registry
#

actions/compile_action_registry.ts view source

(actions: readonly Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<...>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }>[], ctx_label: string): ActionRegistryCompileResult import {compile_action_registry} from '@fuzdev/fuz_app/actions/compile_action_registry.js';

Validate registry-time invariants and build the dispatcher's method → action lookup.

actions

polymorphic action array; HTTP RPC passes RpcAction[] (narrower), WebSocket passes Action[] (kind-polymorphic — handler-less notification specs are accepted)

type readonly Action<ActionSpecUnion>[]

ctx_label

per-spec error-message prefix, e.g. 'RPC action' or 'WS action'. Combined with the spec method as ${ctx_label} "${method}".

type string

returns

ActionRegistryCompileResult

throws

  • Error - on biconditional violation, rate-limit/account-axis mismatch, JSON-RPC null-input or void-output, or duplicate method.

compose_gen_file
#

actions/action_codegen.ts view source

(input: { origin_path: string; imports: ImportBuilder; blocks: readonly string[]; }): string import {compose_gen_file} from '@fuzdev/fuz_app/actions/action_codegen.js';

Wrap the per-*.gen.ts boilerplate (banner + imports.build() + blocks join + template literal) into one call. Returns the full file body as a string ready to return from a Gen function.

Each consumer producer collapses to one compose_gen_file call wrapping the helper invocations.

input

type { origin_path: string; imports: ImportBuilder; blocks: readonly string[]; }

returns

string

examples

export const gen: Gen = ({origin_path}) => { const imports = new ImportBuilder(); return compose_gen_file({ origin_path, imports, blocks: [ generate_action_specs_record(all_action_specs, imports), generate_action_inputs_outputs(all_action_specs, imports), generate_action_event_datas(all_action_specs, imports), ], }); };

Empty blocks ('') are filtered out so helpers that short-circuit on empty spec sets don't introduce stray double blank lines.

ConfigDiagnosticsInput
#

server/app_server.ts view source

internal

ConfigDiagnosticsInput import type {ConfigDiagnosticsInput} from '@fuzdev/fuz_app/server/app_server.js';

cookie_options

The session cookie overrides, from AppServerOptions.session_options.

type Partial<SessionCookieOptions> | undefined

rate_limiter_mode

The rate_limiters mode, defaulted.

type RateLimiterMode

rate_limiters

The resolved limiters, from resolve_rate_limiters.

type Record<AppServerRateLimiterKey, RateLimiter | null>

max_body_size

AppServerOptions.max_body_size as passed; null disables the body size limit.

type number | null | undefined

ConfirmButton
#

ui/ConfirmButton.svelte view source

accepts children

import ConfirmButton from '@fuzdev/fuz_app/ui/ConfirmButton.svelte';

onconfirm

type (popover: Popover) => void
snippet parameters
popover Popover

popover_button_attrs?

type HTMLButtonAttributes
optional

hide_on_confirm?

type boolean
optional default true

popover_content?

Unlike on PopoverButton this is optional and has a confirm arg

type Snippet<[popover: Popover, confirm: () => void]>
optional
snippet parameters
popover Popover
confirm () => void

popover_button_content?

Content for the popover button

type Snippet<[popover: Popover, confirm: () => void]>
optional
snippet parameters
popover Popover
confirm () => void

children?

Unlike on PopoverButton this has a confirm arg

type Snippet<[popover: Popover, confirm: () => void]>
optional
snippet parameters
popover Popover
confirm () => void

label?

Simple string content for the trigger. Mutually exclusive with children.

type string
optional

pending?

When true, the trigger is disabled and a spinner overlays the content (mirrors PendingButton). The label / children stay rendered underneath so the button keeps its size.

type boolean
optional default false

position?

optional default 'left'

align?

optional

disable_outside_click?

type boolean
optional

popover_class?

type string
optional

popover_attrs?

type HTMLAttributes<HTMLDivElement>
optional

popover_container_attrs?

type HTMLAttributes<HTMLDivElement>
optional

button?

type Snippet<[popover: Popover]>
optional
snippet parameters
popover Popover

disabled?

type boolean | null
optional

external types

OmitStrict<SvelteHTMLElements['button'], 'children'>

ConformanceActingBinding
#

testing/cross_backend/conformance_case.ts view source

value + type

"principal" | "extra_actor" | "keeper_actor"

type "principal" | "extra_actor" | "keeper_actor"

import {ConformanceActingBinding} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

Which actor the runner writes into a case's params.acting at run time.

An actor id exists only once a fixture has seeded it, so a case names the actor by its relation to the fixture and the runner fills in the id:

  • principal — the actor of the account the principal's credential names: the keeper's bootstrap actor for the keeper-credential principals (keeper, daemon, token, scoped_token, …), the account's one actor for fresh_non_admin / role_holder / wrong_role. Binding it makes a row hold whether the account has one actor or several: a single-actor account resolves the same actor with or without the selector, a multi-actor one needs it.
  • extra_actor — the keeper's first seeded extra actor (fixture.extra_actors[0]); the fixture must declare extra_actors. With a keeper-credential principal, the request acts as a sibling actor on the keeper's own account that holds none of the keeper's roles.
  • keeper_actor — the keeper's bootstrap actor whatever the principal, so a principal on another account names a real actor that is not its own.

Unavailable for principals that present no account (anonymous, bearer_browser, malformed_bearer) — the runner throws.

ConformanceCase
#

testing/cross_backend/conformance_case.ts view source

value + type

{ name: string; request: { method: string; as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"; params?: unknown; params_json?: string | undefined; acting?... import {ConformanceCase} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

A single conformance case. name is the assertion; the optional free-text note is printed in the test label / failure output. A security case's note should reference a public fuz_app doc property (security.md / architecture.md / module TSDoc), since the table ships in a public package — not an internal planning doc. The note is documentation, not a gate: it stays free-text by design because a non-empty-string check never catches a *wrong* citation — the citation is verified in review.

name

type string

request

type ConformanceCaseRequest

expect

type ConformanceCaseExpectation

note?

type string

xfail?

type ConformanceCaseXfail

ConformanceCaseExpectation
#

testing/cross_backend/conformance_case.ts view source

value + type

{ status: number; error_reason?: string | undefined; fields?: Record<string, unknown> | undefined; absent_fields?: string[] | undefined; headers?: Record<string, string | null> | undefined; equivalence_group?: string | undefined; } import {ConformanceCaseExpectation} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

The expected response shape a conformance case asserts.

status

type number

error_reason?

type string

fields?

type Record<string, unknown>

absent_fields?

type string[]

headers?

type Record<string, string | null>

equivalence_group?

type string

ConformanceCaseRequest
#

testing/cross_backend/conformance_case.ts view source

value + type

{ method: string; as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"; params?: unknown; params_json?: string | undefined; acting?: "principal" | ... 2 mor... import {ConformanceCaseRequest} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

The request a conformance case issues.

method

type string

as

type "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"

params?

type unknown

params_json?

type string

acting?

type "principal" | "extra_actor" | "keeper_actor"

verb?

type "GET" | "POST"

ConformanceCaseResponse
#

testing/cross_backend/conformance_table.ts view source

ConformanceCaseResponse import type {ConformanceCaseResponse} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

One case's request as sent and its response unparsed: the HTTP status, the response-headers snapshot (lowercased keys), and the JSON body exactly as the server sent it — the whole JSON-RPC envelope for an RPC method, undefined for a REST body that is not JSON.

Over a WebSocket the body is the response frame, the headers are empty (a frame carries none), and the status is the one the HTTP binding answers for the frame's outcome: 200 for a result, else jsonrpc_error_code_to_http_status of its error code — so a row's expect.status reads as the error code on both transports.

target

type ConformanceCaseTarget

readonly

status

type number

readonly

headers

type Record<string, string>

readonly

body

type unknown

readonly

ConformanceCaseTarget
#

testing/cross_backend/conformance_table.ts view source

ConformanceCaseTarget

type { readonly kind: "rpc"; readonly path: string; readonly action: RpcAction; } | { readonly kind: "rest"; readonly route: RouteSpec; }

import type {ConformanceCaseTarget} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

The spec a sent case resolved to — the RPC action, or the REST route.

ConformanceCaseXfail
#

testing/cross_backend/conformance_case.ts view source

value + type

{ tracking_id: string; reason: string; } import {ConformanceCaseXfail} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

Marks a case as a deferred-by-design gap. The runner routes it through xfail_until instead of a normal test — visible (distinct from pass) and self-cleaning (flips red when the impl starts passing, forcing the marker's removal). Use for declared gaps (e.g. facts), never for in-scope gaps (those fail loud as a red test).

tracking_id

type string

reason

type string

ConformancePrincipal
#

testing/cross_backend/conformance_case.ts view source

value + type

"token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"

type "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"

import {ConformancePrincipal} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

Closed enum of fixture-provisioned principals a case runs as. Each value maps to a TestFixture accessor (or a seeded extra_accounts entry) in the runner's resolve_conformance_principal — there is no inline credential minting in a case (that would be the setup-DSL trap).

  • keeper — the per-test bootstrapped keeper (holds ROLE_KEEPER + ROLE_ADMIN), session credential.
  • daemon — the keeper authenticated via the daemon-token header.
  • invalid_daemon — a *malformed/invalid* X-Daemon-Token carried alongside the keeper's session cookie, over a non-browser (no-Origin) transport. The middleware soft-fail-discards the invalid daemon token (matching the Rust spine's None), so auth falls through to the session leg: the request authenticates as the keeper-via-session and a daemon-gated action then refuses the session credential with credential_type_required — not a hard invalid_daemon_token 401. The no-Origin transport keeps the daemon token on the invalid-token path rather than the browser-context discard; the session base credential is what makes the credential-type gate (not the auth gate) the refusing layer (without it the discard would 401 anonymous).
  • daemon_browser — a *valid* X-Daemon-Token carried in a browser context (default Origin present) alongside the keeper's session cookie. Browsers attach Origin automatically; the daemon-token middleware discards a header-bearing daemon token as browser context (mirroring the bearer guard and the Rust spine's is_browser_context), so the *valid* token is dropped and auth falls through to the session leg → a daemon-gated action then refuses the session credential with credential_type_required. Distinct from invalid_daemon: here the token is well-formed and current, so a 403 (not a 400 confirm-guard hit like daemon) proves the browser-context discard fired — a valid daemon token does NOT authenticate when an Origin is present. Origin is deliberately NOT suppressed; its presence is the signal under test.
  • token — the keeper authenticated via a bearer api-token (non-browser context; the runner suppresses Origin so the token isn't discarded).
  • bearer_browser — a *valid* bearer api-token carried in a browser context (default Origin present), fresh jar so NO session rides alongside. The bearer middleware discards the token as browser context (mirroring the daemon guard + the Rust spine's is_browser_context), so the request arrives anonymous and an authed action 401s. Proves a stolen bearer cannot be replayed from a browser — wire-indistinguishable from sending no credential (the token principal is the honored counterpart: it suppresses Origin, so the same token authenticates).
  • malformed_bearer — the keeper's bearer api-token with its last character dropped, non-browser context, fresh jar. A value that cannot be a token fails each spine's shape precheck and soft-fails before any database work, so the request arrives anonymous — wire-indistinguishable from sending no credential.
  • scoped_token — the keeper authenticated via a bearer api-token minted with a narrowed TokenScope (`{kind: 'methods', methods: [SCOPED_TOKEN_ADMITTED_METHOD]}`), non-browser context. Minted through the production account_token_create path over the keeper's session, not seeded — _testing_reset deliberately seeds a full token, and a harness that narrowed the seed would silently re-scope every other bearer case. The scope gate sits between the credential gate and the role gate, so this principal is what distinguishes token_scope_required from the credential-type and role denials on either side of it.
  • anonymous — no credential, fresh cookie jar.
  • fresh_non_admin — a freshly minted account with no roles, session credential (via the production invite → signup → login flow).
  • role_holder — a seeded extra_accounts principal holding a specific role; the runner reads it by the username named in ConformanceTableOptions.principals.role_holder.
  • wrong_role — a seeded extra_accounts principal holding a role other than the one a route requires; named via ConformanceTableOptions.principals.wrong_role.
  • expired_session — the keeper account presented via an *expired server-side session* cookie (minted by fixture.mint_expired_session(): a backdated auth_session row behind a still-valid signed cookie payload, so the authoritative DB-row expiry gate is what refuses it).

ConformancePrincipalConfig
#

testing/cross_backend/conformance_table.ts view source

ConformancePrincipalConfig import type {ConformancePrincipalConfig} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Names a seeded extra_accounts username for the role_holder / wrong_role principals — the only two that aren't backed by an always-available fixture accessor. Suites exercising those principals declare the matching extra_accounts at setup and name them here.

role_holder?

extra_accounts username for the role_holder principal.

type string

readonly

wrong_role?

extra_accounts username for the wrong_role principal.

type string

readonly

ConformanceRpcEndpointsOptions
#

testing/cross_backend/conformance_table.ts view source

ConformanceRpcEndpointsOptions import type {ConformanceRpcEndpointsOptions} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

rpc_endpoints

The declared RPC endpoints.

type RpcEndpointsSuiteOption

readonly

live_rpc_endpoints?

The live-mounted RPC endpoints, for methods off the declared surface.

type RpcEndpointsSuiteOption

readonly

session_options

Session options — needed to resolve the factory forms.

type SessionOptions<string>

readonly

ConformanceTableOptions
#

testing/cross_backend/conformance_table.ts view source

ConformanceTableOptions import type {ConformanceTableOptions} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

cases

The conformance cases to run, in order.

type readonly ConformanceCase[]

readonly

setup_test

Per-test fixture producer (in-process or cross-process).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

surface_source

Surface spec — supplies the RouteSpecs for the REST branch.

type AppSurfaceSpec

readonly

capabilities

Declared backend capabilities (reserved for capability-gated rows).

type BackendCapabilities

readonly

rpc_endpoints

RPC endpoints — resolved to find each method's action spec.

type RpcEndpointsSuiteOption

readonly

live_rpc_endpoints?

The RPC endpoints the backend live-mounts, for methods kept off the declared rpc_endpoints (cell verbs, opt-in resolvers, _testing_* backdoors). A method resolves from rpc_endpoints first and from here only when the declared surface lacks it, so passing a backend's whole live mount is fine. Omitted, only declared methods resolve.

type RpcEndpointsSuiteOption

readonly

session_options

Session options — needed to resolve the rpc_endpoints factory form.

type SessionOptions<string>

readonly

principals?

Maps the role_holder / wrong_role principals to seeded usernames.

type ConformancePrincipalConfig

readonly

ws?

Run the cases over a WebSocket at this endpoint instead of HTTP: each RPC case's principal opens an admitted socket and sends its request as a frame. Only the cases is_ws_conformance_case admits run; the rest are left out, and so is an equivalence group left with fewer than two members.

type WsEndpointTarget

readonly

suite_name?

describe block label. Defaults to 'conformance table'.

type string

readonly

connect_raw_ws
#

testing/transports/ws_raw_client.ts view source

(options: RawWsConnectOptions): Promise<RawWsClient> import {connect_raw_ws} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

Upgrade a raw TCP socket to a WebSocket.

Resolves once the server answers 101; rejects on any other status or a connection error.

options

returns

Promise<RawWsClient>

throws

  • Error - when the upgrade is refused or the connection fails

connection_identity_matches
#

actions/connection_registry.ts view source

(identity: ConnectionIdentity, target: ConnectionCloseTarget): boolean import {connection_identity_matches} from '@fuzdev/fuz_app/actions/connection_registry.js';

Whether the connection identity belongs to is one target names — the predicate ConnectionRegistry.close_connections runs per connection. Twin of the Rust spine's ConnectionIdentity::matches. The SSE closer (create_sse_connection_closer) does not run it: it closes by the ids its registry holds, with one gating role for the whole registry.

  • session — identity.session_hash equals target.session_hash
  • token — identity.api_token_id equals target.api_token_id
  • account — identity.account_id equals target.account_id
  • role — the account matches and role is one of the endpoint's required_roles (an ungated connection never matches)

identity

the connection's identity

target

the connections to close

returns

boolean

whether target names this connection

ConnectionCloser
#

actions/connection_closer.ts view source

ConnectionCloser import type {ConnectionCloser} from '@fuzdev/fuz_app/actions/connection_closer.js';

Close live connections by identity, or all of them.

Each method returns how many connections it closed. Closing is idempotent: a connection already gone isn't counted, so a handler's close and an audit listener's close on the same revocation compose. Implementations are synchronous.

One connection that fails to close must not spare the rest: an implementation attempts every matching connection, then throws the first error it met.

ConnectionRegistry satisfies this structurally, and create_sse_connection_closer adapts an SseRegistry. Twin of the Rust spine's ConnectionCloser (with its ShutdownCloser supertrait's close_all_connections).

close_connections

Close every connection target names — pending admission or admitted.

A revocation handler calls queue_connection_close instead, which runs this after the commit.

type (target: ConnectionCloseTarget): number

target

returns number

close_all_connections

Close every connection, whoever opened it — the shutdown close AppServer.close runs. Not a revocation: a WebSocket gets WS_CLOSE_GOING_AWAY, so its client reconnects rather than treating its credential as revoked. The registry is left closing: a registration that arrives afterward is born closed, refused at once (ConnectionRegistry and SseRegistry both do).

type (): number

returns number

ConnectionCloseTarget
#

actions/connection_closer.ts view source

ConnectionCloseTarget

type { kind: "session"; session_hash: string; } | { kind: "token"; api_token_id: string; } | { kind: "account"; account_id: string & $brand<"Uuid">; } | { kind: "role"; account_id: string & $brand<"Uuid">; role: string; }

import type {ConnectionCloseTarget} from '@fuzdev/fuz_app/actions/connection_closer.js';

Which connections a revocation closes — the argument of ConnectionCloser.close_connections and queue_connection_close. Twin of the Rust spine's ConnectionCloseTarget, the same four variants.

  • session — every connection authenticated by the session whose blake3 hash is session_hash (ConnectionIdentity.session_hash)
  • token — every connection authenticated by the API token api_token_id
  • account — every connection of account_id, whatever credential opened it: the close for a revocation that invalidates all of an account's credentials
  • role — the connections of account_id whose endpoint gated them on role: the close for a revoked global grant of that role (a scoped grant never admits at a role gate, so revoking one closes nothing; to_connection_close_target filters). A WebSocket gets WS_CLOSE_PERMISSION_REVOKED, not a revocation's code: the credential is still good. It over-closes an any-of gate: a connection whose endpoint listed several roles closes on the loss of any of them, whether or not the account still holds another; the client reconnects into the gate if it still qualifies.

ConnectionIdentity
#

actions/connection_registry.ts view source

ConnectionIdentity import type {ConnectionIdentity} from '@fuzdev/fuz_app/actions/connection_registry.js';

Auth identity attached to a single WebSocket connection.

One record per connection. session_hash is set for cookie-session connections, api_token_id for bearer (api_token) connections, and both are null for daemon-token connections (reachable only via an account close target). required_roles is what the connection's endpoint gated the upgrade on, kept per registration because endpoints with different gates can share one registry.

session_hash

Blake3 session token hash, or null for non-session credentials.

type string | null

account_id

Authenticated account id. Always set.

type Uuid

api_token_id

api_token.id for bearer-authenticated connections, else null.

type string | null

required_roles

The roles the endpoint required at upgrade (any-of), or empty for an ungated endpoint — what a role close target matches on.

type readonly string[]

ConnectionRegistry
#

actions/connection_registry.ts view source

import {ConnectionRegistry} from '@fuzdev/fuz_app/actions/connection_registry.js';

constructor

type new (options?: ConnectionRegistryOptions | undefined): ConnectionRegistry

options?

optional

throws

  • Error - when `max_connections_per_account` is neither `null` nor a positive integer, or `outbound_queue_bytes` is not a positive integer

register_pending

Register a connection un-admitted — phase one of the two-phase registration an upgrade runs (module doc).

The entry is closeable at once: a close_connections whose target matches session_hash / account_id / api_token_id removes it, aborts abort_controller, and closes ws with WS_CLOSE_SESSION_REVOKED (a role target, matching required_roles, with WS_CLOSE_PERMISSION_REVOKED); the later admit then refuses. Until admitted it evicts nothing, is not counted toward the per-account cap, and receives no message.

The caller owns the entry until it is admitted: a refusal closes it with close_connection, and remove_connection drops it once its socket has closed.

Once the registry is closing (close_all_connections) the registration is born closed: nothing is inserted, abort_controller is aborted, and ws is closed with WS_CLOSE_GOING_AWAY before this returns. The id names nothing, so admit refuses it (module doc, "Shutdown").

Twin of the Rust ConnectionRegistry::register_pending, which takes the same ConnectionIdentity.

type (ws: WSContext<unknown>, identity: ConnectionIdentity, abort_controller?: AbortController | null): string & $brand<"Uuid">

ws

the socket to close on revocation

type WSContext<unknown>

identity

who the connection is — what every close target and the per-account cap match on; the registry keeps a copy

abort_controller?

the socket-scoped controller to abort when the registry closes this connection (revocation or cap eviction)

type AbortController | null
optional default null
returns Uuid

the freshly assigned connection_id (branded Uuid)

mutates

  • this — inserts an un-admitted entry into `#connections`, unless the registry is closing

take_pending_close_code

The close code the registry closed a still-pending registration with — WS_CLOSE_SESSION_REVOKED for a credential revocation, WS_CLOSE_PERMISSION_REVOKED for a role close, WS_CLOSE_GOING_AWAY for the shutdown — read once: the record is dropped by this call (and by remove_connection). null when the registry did not close it while pending. For a caller whose admit was refused, to say why; the twin of the reason the Rust ConnectionRegistry::admit refuses with.

type (connection_id: string & $brand<"Uuid">): number | null

connection_id

the id register_pending returned

type Uuid
returns number | null

the close code, or null

mutates

  • this — drops the recorded code

admit

Admit a pending connection — phase two (module doc).

If the entry is gone — a revocation closed it while it was pending, or it was removed — nothing is evicted and the connection is refused. Otherwise the per-account cap is enforced exactly as in add_connection, counting only admitted connections, and the entry becomes admitted: it now receives messages, is counted, and can itself be superseded. Admitting an already-admitted connection is a no-op that answers true.

type (connection_id: string & $brand<"Uuid">): boolean

connection_id

the id register_pending returned

type Uuid
returns boolean

false when the connection is no longer registered — the caller closes the socket as revoked

mutates

  • this — marks the entry admitted; past the cap, first removes the account's oldest admitted connections and closes their `WSContext`

add_connection

Register a connection, admitted at once — the one-step form of register_pending + admit.

Not for an upgrade that authenticated a revocable credential: a revocation landing between that upgrade's credential check and this call closes nothing, and the connection would be admitted on a dead credential (module doc, "Two-phase registration"). This is for a connection with nothing to revoke behind it.

A session connection's identity carries its session hash for targeted revocation. A bearer token connection (api_token) carries the api_token.id so the socket can be closed when that specific token is revoked without tearing down the account's other sockets. A daemon-token connection carries null for both — it's only reachable via an account close target.

Enforces the per-account cap (max_connections_per_account): when account_id already holds the maximum, its oldest admitted connections are removed and sent a WS_CLOSE_CONNECTION_LIMIT close before the new one is inserted. A removed connection gets no further broadcast or peer request, stops counting toward the cap at once, and has its abort_controller aborted; its socket is closed when the close handshake completes. The new connection is always admitted — unless the registry is closing: then it is born closed, as in register_pending, and evicts nothing.

type (ws: WSContext<unknown>, identity: ConnectionIdentity, abort_controller?: AbortController | null): string & $brand<"Uuid">

ws

the socket to close on revocation

type WSContext<unknown>

identity

who the connection is — what every close target and the per-account cap match on; the registry keeps a copy

abort_controller?

the socket-scoped controller to abort when the registry closes this connection (revocation or cap eviction)

type AbortController | null
optional default null
returns Uuid

the freshly assigned connection_id (branded Uuid)

mutates

  • this — inserts an admitted entry into `#connections`; past the cap, first removes the account's oldest admitted connections and closes their `WSContext`

remove_connection

Remove a connection and its auth tracking data, pending or admitted, by the id register_pending / add_connection returned. Idempotent — safe to call after revocation or a cap eviction has already cleaned up, and a no-op for an unknown id.

Keyed by id rather than by WSContext because a runtime adapter may hand each socket event its own context object (@hono/bun does), so the one a close event carries can't identify the connection.

Removal only: the socket is not closed and the registered abort_controller is not aborted — the caller that ends a socket does both. What was waiting to be written to it is dropped.

For a socket that has closed (an adapter's close event): on a connection the registry closed it also drops the close deadline, so calling it while that socket is still closing leaves a client that is not reading uncut.

type (connection_id: string & $brand<"Uuid">): void

connection_id

type Uuid
returns void

mutates

  • this — deletes the connection's entry from `#connections`, drops its waiting frames and write deadlines, and settles its pending peer requests as `connection_gone`

close_connection

Close one connection, pending or admitted, with code and reason — the close register_action_ws makes for a heartbeat timeout, an oversized message, a failed on_socket_open, and a refused admission. Not to be confused with close_connections, which closes every connection a ConnectionCloseTarget names.

The connection is removed and its controller aborted at once; frames still waiting for the socket are handed to it ahead of the close frame while its buffer has room and discarded otherwise, and the close frame is held to WS_CLOSE_WRITE_TIMEOUT (module doc, "Outbound bound"). A socket whose close throws is logged, not thrown.

type (connection_id: string & $brand<"Uuid">, code: number, reason: string): boolean

connection_id

the id register_pending / add_connection returned

type Uuid

code

the close code

type number

reason

the close reason

type string
returns boolean

false when the connection is not registered — nothing was closed

mutates

  • this — removes the connection, aborts its registered controller, and closes its `WSContext`

close_connections

Close every connection target names, including a registration still pending admission (connection_identity_matches). Not to be confused with close_connection, which closes one connection by id.

A credential target — session, token, account — closes with WS_CLOSE_SESSION_REVOKED. A token close takes just that token's sockets, so revoking one token — or evicting one past the token cap — leaves the account's session sockets and other tokens' sockets open.

A role target — a revoked global grant of a role the upgrade gate admitted on — closes with WS_CLOSE_PERMISSION_REVOKED, not a revocation's code: the credential is still good. It over-closes an any-of gate: an endpoint gated on several roles admits an account holding any one of them, but this closes on the loss of any listed role, without checking whether the account still holds another. The client can reconnect when it still qualifies. The account's sockets on endpoints that did not list role, and on ungated ones, stay open.

type (target: ConnectionCloseTarget): number

target

returns number

the number of sockets closed

throws

  • the - first error a socket's `close` threw, after every match was attempted

mutates

  • this — removes matching connections, aborts their registered controllers, and closes their underlying `WSContext`

close_all_connections

Close every connection, pending or admitted, with WS_CLOSE_GOING_AWAY — the shutdown close AppServer.close runs. Not a revocation: the client reconnects under its ordinary backoff.

Each closed connection is removed and its controller aborted, as a revocation does, so its heartbeat timer stops and nothing more dispatches on it. A socket whose close throws does not stop the loop.

Leaves the registry closing, for good: every later registration is born closed (module doc, "Shutdown"). A second call closes nothing.

type (): number

returns number

the number of sockets closed

throws

  • the - first error a socket's `close` threw, after every connection was attempted

mutates

  • this — sets the registry closing, removes every connection, aborts its registered controller, and closes its underlying `WSContext` with `WS_CLOSE_GOING_AWAY`

is_closing

Whether close_all_connections has run — the registry is shutting down and every registration from now on is born closed (module doc, "Shutdown"). For telemetry and logs: register_action_ws reads it to say why an upgrade was refused.

type (): boolean

returns boolean

broadcast

Broadcast a notification to every admitted connection — a registration pending admission is not a connection yet. A server→client *request* has no single target here; that is request_connection.

A connection the message overflows is closed and not counted (module doc, "Outbound bound").

type (message: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }): number

message

returns number

the number of connections that accepted the message

broadcast_filtered

Broadcast to admitted connections whose identity satisfies a predicate. A registration pending admission is skipped before the predicate runs.

Used by the broadcast API when a consumer supplies a subscription ACL hook (e.g. an event that only reaches the account that owns the resource it reports). When no ACL is needed, callers should prefer broadcast to skip the per-connection predicate overhead.

A connection the message overflows is closed and not counted (module doc, "Outbound bound").

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; } | { ...; }, predicate: (identity: Readonly<...>) => boolean): number

message

predicate

type (identity: Readonly<ConnectionIdentity>) => boolean
returns number

the number of connections that accepted the message

send_to_account

Send a message to every admitted socket bound to a specific account.

Targeted per-account fan-out for any flow where the delivery target is a single known account. Prefer this over broadcast_filtered when the filter is exactly "this account_id"; reach for broadcast_filtered when the ACL is an arbitrary predicate over ConnectionIdentity.

Mirrors an account close target on the send side: every connection for the account (session, bearer, and daemon-token) receives the message.

type (account_id: string & $brand<"Uuid">, message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { ...; }; } | { ...; }): number

account_id

type Uuid

message

returns number

the number of connections that accepted the message

send_to

Send one serialized frame to a single admitted connection — the path register_action_ws sends every response, error frame, and socket-scoped notification through.

Never waits and never throws. A frame the connection's outbound budget does not fit closes the connection with WS_CLOSE_OUTBOUND_OVERFLOW before this returns (module doc, "Outbound bound").

type (connection_id: string & $brand<"Uuid">, frame: string): boolean

connection_id

the id register_pending / add_connection returned

type Uuid

frame

the frame's text, already serialized

type string
returns boolean

false when the frame was not accepted: the connection is not registered, is still pending admission, or was just closed for the overflow this frame caused

mutates

  • this — queues the frame on the connection's writer; on an overflow removes the connection, aborts its controller, and closes its `WSContext`

request_connection

Initiate a JSON-RPC request to a single connected client and await its reply — the server→client request/response direction (ActionPeer).

Sends {jsonrpc, method, params, id} to exactly the connection_id socket (never a broadcast) and registers a pending entry scoped to that connection. Resolves when the client's matching reply arrives (routed in via resolve_peer_response), the deadline elapses (timeout), the per-connection cap is hit (too_many_in_flight), or the socket closes (connection_gone). A connection still pending admission answers connection_gone like an unknown one. Never throws — every failure is a PeerRequestError.

Delegates correlation to #pending (id allocation, deadline, cap, drain); this method owns only the socket lookup + the send. Server-issued ids are s-prefixed so a malicious client echoing a non-s id (or an id it chose for its own request) matches nothing.

type (connection_id: string & $brand<"Uuid">, method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<...>

connection_id

type Uuid

method

type string

params

type JsonrpcRequestParams | undefined

options?

optional
returns Promise<PeerRequestOutcome>

the client's success result, or a PeerRequestError

mutates

  • this — registers then clears an entry in `#pending`

resolve_peer_response

Route an inbound client reply to the matching pending server→client request on connection_id (delegates to #pending.resolve).

Returns false when no entry matches — an unsolicited, cross-connection, or already-settled reply — so the caller drops it. Per-connection scoping means a reply arriving on the wrong socket resolves nothing.

type (connection_id: string & $brand<"Uuid">, response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { ...; }; }): boolean

connection_id

type Uuid

response

returns boolean

whether a pending request was resolved

mutates

  • this — clears the matched entry from `#pending`

is_registered

Whether a connection is still registered — and admitted: a registration pending admission answers false, like every other reader of the registry.

register_action_ws checks it before dispatching each inbound message, so a socket the server has closed — revoked, superseded by the cap, or refused at admission — dispatches nothing more while its close handshake is still in flight. The twin of the Rust ConnectionRegistry::is_registered.

type (connection_id: string & $brand<"Uuid">): boolean

connection_id

type Uuid
returns boolean

connection_count

Number of currently admitted WebSocket connections.

Read-only counter intended for telemetry, logging, and tests. A registration still pending admission is not a connection yet and is counted by pending_connection_count instead. Includes connections that have been closed by the peer but not yet removed by the WS adapter's onClose callback.

type (): number

returns number

pending_connection_count

Number of registrations awaiting admission — between register_pending and admit (or their refusal). Each lives for one credential re-check, so a count that stays high means upgrades are stalling there. For telemetry and tests.

type (): number

returns number

ConnectionRegistryOptions
#

actions/connection_registry.ts view source

ConnectionRegistryOptions import type {ConnectionRegistryOptions} from '@fuzdev/fuz_app/actions/connection_registry.js';

Options for ConnectionRegistry.

max_connections_per_account?

Max concurrent connections per account_id, across every credential type (session, API token, daemon token — none exempt). The policy is evict-oldest: a connection past the cap is always admitted, and the account's oldest connections are closed with WS_CLOSE_CONNECTION_LIMIT to make room. Refusing the new one instead would let half-open sockets lock a user out with their own dead connections, and would let a stolen credential fill the slots ahead of the real user — revocation is the fix for a stolen credential, and evict-oldest never stands in its way.

The cap is per registry: endpoints sharing one registry share one count per account. null disables it.

type number | null

default DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT

outbound_queue_bytes?

Byte budget of the frames waiting to be written to one connection. Each waiting frame is charged its UTF-8 length plus OUTBOUND_FRAME_OVERHEAD_BYTES; a frame that does not fit closes the connection with WS_CLOSE_OUTBOUND_OVERFLOW, and a frame is always accepted when nothing is waiting, so one message larger than the whole budget is still deliverable (module doc, "Outbound bound"). Not derived from the inbound message cap.

type number

default DEFAULT_WS_OUTBOUND_QUEUE_BYTES

log?

Logger for cap evictions, outbound overflows and stalls, and failed sends or closes. An overflow or a stall logs one warn line — the connection, its account, and what was waiting. A cap eviction logs at info — the account, how many connections were closed, and the cap — so an operator can see a client that keeps meeting it. Unset falls back to a [ws] logger; null silences it. register_action_ws passes its own logger to the registry it creates.

type Logger | null

console_action_log
#

actions/action_event_types.ts view source

ActionLog import {console_action_log} from '@fuzdev/fuz_app/actions/action_event_types.js';

Fallback log for environments that leave log unset: warnings and errors go to console, so a notification that fails to parse or a handler that throws is visible rather than silently dropped; per-message debug chatter is discarded. A plain console adapter rather than a Logger so it's safe in any runtime, the browser included.

ContributionRule
#

CookieAttributesCrossTestOptions
#

testing/cross_backend/cookie_attributes.ts view source

CookieAttributesCrossTestOptions import type {CookieAttributesCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/cookie_attributes.js';

Options for the session-cookie-attribute parity suite.

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

cookie_name

The spine's session cookie name (handle.config.cookie_name, e.g. fuz_session). The Set-Cookie under test is matched by this name so a future cookie-name change surfaces here as "no session cookie set" rather than a silent miss.

type string

readonly

login_path?

REST login route path. Default /api/account/login.

type string

readonly

logout_path?

REST logout route path. Default /api/account/logout.

type string

readonly

count_in_window
#

ui/ui_format.ts view source

(rows: readonly { created_at: string; }[], window_start: number, cap: number): { count: number; label: string; } import {count_in_window} from '@fuzdev/fuz_app/ui/ui_format.js';

Count the rows of a newest-first list created at or after window_start, for a list fetched with a row cap. The label reads N+ when the cap may cut the window off — the list is full and its oldest row is still inside the window.

rows

newest-first rows, as a capped list query returns them

type readonly { created_at: string; }[]

window_start

the window's start, in epoch milliseconds

type number

cap

the row limit the list was fetched with

type number

returns

{ count: number; label: string; }

the count and its display label

CoverageFilterOptions
#

testing/error_coverage.ts view source

CoverageFilterOptions import type {CoverageFilterOptions} from '@fuzdev/fuz_app/testing/error_coverage.js';

Options controlling which routes/statuses are considered for coverage.

ignore_routes?

Routes to skip, in 'METHOD /path' format.

type string[]

ignore_statuses?

HTTP status codes to skip.

type number[]

create_account_actions
#

auth/account_actions.ts view source

(deps: RevokingActionFactoryDeps, options?: AccountActionOptions): RpcAction[] import {create_account_actions} from '@fuzdev/fuz_app/auth/account_actions.js';

Create the self-service account RPC actions.

deps

RevokingActionFactoryDeps (log, audit, connection_closer). audit.emit writes a success row in the request transaction and a failure row via the captured pool; the bound emitter encapsulates on_audit_event fan-out and the optional AuditLogConfig. connection_closer closes a revoked credential's live connections after the revocation commits.

options?

per-factory configuration

optional default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

create_account_revocations
#

testing/cross_backend/revocation_drivers.ts view source

(options: AccountRevocationsOptions): readonly AccountRevocation[] import {create_account_revocations} from '@fuzdev/fuz_app/testing/cross_backend/revocation_drivers.js';

The account-wide revocations that close every connection of the target account: admin_token_revoke_all, POST /logout, POST /password, and account_purge (gated on capabilities.account_lifecycle).

options

the backend's capabilities and route paths

returns

readonly AccountRevocation[]

the revocation rows, in a stable order

create_account_route_shapes
#

auth/account_route_schema.ts view source

(options: AccountRouteShapeOptions): [Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<...>] import {create_account_route_shapes} from '@fuzdev/fuz_app/auth/account_route_schema.js';

The four account route shapes (/verify, /login, /logout, /password) minus their handlers — pure hono-free data. create_account_route_specs spreads each and attaches the live handler; cross-process surface builders spread them with stub handlers. Single source of truth — the shapes can't drift between the live routes and the surface.

Returns a fixed 4-tuple [verify, login, logout, password] so destructuring yields non-optional shapes under noUncheckedIndexedAccess.

options

returns

[Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">]

create_account_route_specs
#

auth/account_routes.ts view source

(deps: RouteFactoryDeps, options: AccountRouteOptions): RouteSpec[] import {create_account_route_specs} from '@fuzdev/fuz_app/auth/account_routes.js';

Create account route specs for session-based auth.

The returned specs cover the REST flows that stay after the RPC migration: /status (account info + bootstrap availability), /verify (nginx auth_request shim), /login, /logout, /password. /status is bundled here (relative path, prefixed to /api/account/status by the caller) so every account surface serves it, matching the Rust account_router. Self-service session/token management is on auth/account_actions.ts.

/login closes the connections of the sessions its cap evicts, and /logout and /password close the account's, each once its revocation has committed: /logout through the route transaction's post-commit queue (queue_connection_close), /login and /password — which open their own transaction around just their writes — straight after it commits (close_connections_after_commit, which logs a failing close rather than failing the committed request), so a later throw can't drop a close a committed revocation owes. All on deps.connection_closer.

deps

stateless capabilities (keyring, password, log, audit, connection_closer)

options

per-factory configuration (session_options, login_ip_rate_limiter, login_account_rate_limiter, bootstrap_status)

returns

RouteSpec[]

route specs (not yet applied to Hono)

create_account_status_route_spec
#

auth/account_routes.ts view source

(options?: AccountStatusOptions | undefined): RouteSpec import {create_account_status_route_spec} from '@fuzdev/fuz_app/auth/account_routes.js';

Create the account status route spec.

Handles both authenticated and unauthenticated requests:

  • Authenticated: returns {account} with 200
  • Unauthenticated: returns 401 with optional bootstrap_available flag

A scope-narrowed API token is answered as the unauthenticated caller, byte for byte. A narrowed token reaches no non-RPC surface (token_scope_admits_non_rpc), and this one hands out the account's email, its verification state, and the acting actor's role grants; the route already answers anonymous callers, so that answer is the refusal — a 403 would tell the holder nothing that dropping the header doesn't. The handler reads the credential through get_public_account_id, which applies it.

This eliminates the need for a separate /health fetch on page load — the frontend gets both session state and bootstrap availability in one request.

options?

optional configuration (bootstrap_status for bootstrap detection)

optional

returns

RouteSpec

a single account status route spec

create_action_event
#

actions/action_event.ts view source

<TMethod extends string = string>(environment: ActionEventEnvironment, spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<...>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, input: unknown, initial_phase?: "send_request" | ... 8 more ... | undefined): ActionEvent<...> import {create_action_event} from '@fuzdev/fuz_app/actions/action_event.js';

Create an action event from a spec and initial input.

environment

spec

input

type unknown

initial_phase?

optional

returns

ActionEvent<TMethod, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">

generics

create_action_event<TMethod extends string = string>
TMethod
constraint string
default string

throws

  • Error - if `initial_phase` is omitted and the executor cannot initiate the action (per `spec.initiator`)

create_action_event_from_json
#

actions/action_event.ts view source

<TMethod extends string = string>(json: ActionEventDataUnion<TMethod>, environment: ActionEventEnvironment): ActionEvent<TMethod, "send_request" | ... 7 more ... | "execute", "initial" | ... 3 more ... | "failed"> import {create_action_event_from_json} from '@fuzdev/fuz_app/actions/action_event.js';

Reconstruct an action event from serialized JSON data.

json

environment

returns

ActionEvent<TMethod, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">

generics

create_action_event_from_json<TMethod extends string = string>
TMethod
constraint string
default string

throws

  • Error - if the JSON's `method` field has no spec registered in `environment`

create_action_event_spec
#

actions/action_bridge.ts view source

(spec: { method: string; kind: "request_response" | "remote_notification" | "local_call"; initiator: "frontend" | "backend" | "both"; auth: { account: "optional" | "none" | "required"; actor: "optional" | ... 1 more ... | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; } | null; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }, options?: ActionEventOptions | undefined): EventSpec import {create_action_event_spec} from '@fuzdev/fuz_app/actions/action_bridge.js';

Derive an EventSpec from an ActionSpec.

Only remote_notification actions can become push events.

spec

the action spec (must have kind: 'remote_notification')

options?

optional event-specific options (channel)

optional

returns

EventSpec

throws

  • Error - if `spec.kind` is not `'remote_notification'`

create_active_role_grant
#

testing/cross_backend/role_grant_cross_helpers.ts view source

(fixture: TestFixture, recipient: TestAccount, options?: RoleGrantFixtureOptions): Promise<string> import {create_active_role_grant} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_cross_helpers.js';

Materialize an active global grant of role on recipient through a fresh keeper offer and the recipient's accept, returning its role_grant_id — the handle role_grant_revoke keys on. Idempotent on a grant the account already holds: the accept returns that grant's id.

fixture

the per-test fixture

recipient

the account that accepts the offer

options?

the role and the RPC path

optional default {}

returns

Promise<string>

the active grant's id

create_actor_lookup_actions
#

create_actor_search_actions
#

create_admin_actions
#

auth/admin_actions.ts view source

(deps: RevokingActionFactoryDeps, options?: AdminActionOptions): RpcAction[] import {create_admin_actions} from '@fuzdev/fuz_app/auth/admin_actions.js';

Create the admin-only RPC actions.

deps

RevokingActionFactoryDeps (log, audit, connection_closer). log drives RPC-internal error logging; audit.emit writes a success row in the request transaction and a failure row via the captured pool. The bound emitter encapsulates listener fan-out and the optional AuditLogConfig. connection_closer closes the target account's live connections after a revoke-all, delete, or purge commits.

options?

role schema for grantable_roles derivation

optional default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

create_admin_rpc_adapters
#

ui/admin_rpc_adapters.ts view source

(api: AdminRpcApi): AdminRpcAdapters import {create_admin_rpc_adapters} from '@fuzdev/fuz_app/ui/admin_rpc_adapters.js';

Build the four admin RPC adapters from a typed throwing RPC client.

Method-name mapping:

Narrow RPC methodAction spec method
admin_accounts.list_accountsadmin_account_list
admin_accounts.delete_accountaccount_delete (soft)
admin_accounts.undelete_accountaccount_undelete
admin_accounts.list_sessionsadmin_session_list
admin_accounts.create_role_grantrole_grant_offer_create
admin_accounts.revoke_role_grantrole_grant_revoke
admin_accounts.retract_offerrole_grant_offer_retract
admin_accounts.session_revoke_alladmin_session_revoke_all
admin_accounts.token_revoke_alladmin_token_revoke_all
admin_invites.listinvite_list
admin_invites.createinvite_create
admin_invites.deleteinvite_delete
audit_log.listaudit_log_list
audit_log.role_grant_historyaudit_log_role_grant_history
app_settings.getapp_settings_get
app_settings.updateapp_settings_update

All four adapter factories call through the same api — consumers pass the typed throwing Proxy from create_frontend_rpc_client once, regardless of how many admin surfaces they mount.

api

returns

AdminRpcAdapters

create_admitted_ws_transport
#

testing/transports/ws_transport.ts view source

(options: WsTransportOptions): Promise<WsTransportClient> import {create_admitted_ws_transport} from '@fuzdev/fuz_app/testing/transports/ws_transport.js';

Open a real-upgrade WS client and round-trip heartbeat on it, so the backend has admitted the connection by the time this resolves.

Both spines answer the upgrade handshake before the connection is registered and admitted (the credential is re-read in between), and deliver no broadcast or targeted notification to a connection still pending admission. A request is answered only once the connection is admitted, so the round trip is the one signal a client has. Suites that open a socket and then wait for a server-initiated message, or that need connections registered in a known order, open through this.

heartbeat is a protocol action, present on every WS endpoint. Its response frame (id WS_ADMISSION_PROBE_ID) stays in the client's messages.

options

returns

Promise<WsTransportClient>

throws

  • Error - if the upgrade fails, or the heartbeat is refused or times out — the socket is closed first

create_all_cell_actions
#

auth/all_cell_actions.ts view source

(deps: AllCellActionDeps, options: AllCellActionsOptions): RpcAction[] import {create_all_cell_actions} from '@fuzdev/fuz_app/auth/all_cell_actions.js';

Build the full cell RPC action set — CRUD (create_cell_actions, which also carries cell_clone) + grant ACL + field + item relations + per-cell audit — as a single handler-bound bundle.

The handler-side twin of the all_cell_action_specs spec bundle and the sibling of create_standard_rpc_actions. Assembling the five cell factories here means an HTTP-RPC mount and a WS mount (or two different backends) can't silently diverge on which cell verbs they expose — the spine_method_coverage reconciliation gate enforces that the spine's live mount matches its coverage manifest, and this aggregator is the single list every mount draws from.

Distinct from create_cell_actions (the CRUD-only factory this bundles) — reach for this whenever a backend mounts the complete cell layer.

deps

AllCellActionDeps (log, audit, the optional validate_data, authorize_create, authorize_grant_create, and authorize_grant_revoke)

options

the role schema for grant validation

returns

RpcAction[]

every cell RpcAction, in mount order

create_app_backend
#

server/app_backend.ts view source

(options: CreateAppBackendOptions): Promise<AppBackend> import {create_app_backend} from '@fuzdev/fuz_app/server/app_backend.js';

Initialize the backend: database + auth migrations + deps.

Calls create_db → run_migrations (auth namespace, then any migration_namespaces from options in order) → audit_factory({db, log}) (db with the audit checkout bound) and bundles the result with the provided keyring and password deps and a connection_closer (empty until create_app_server adds its transports).

options

keyring, password deps, audit_factory, optional database URL, and optional migration_namespaces

returns

Promise<AppBackend>

app backend with deps, database metadata, and combined migration results

throws

  • MigrationError - `namespace-reserved` if a `migration_namespaces` entry bears a `reserved_migration_namespaces` name and is not the spine's own namespace

create_app_error_handler
#

server/app_error_handler.ts view source

(log: Logger): ErrorHandler import {create_app_error_handler} from '@fuzdev/fuz_app/server/app_error_handler.js';

Build the app's onError handler (module doc).

log

receives each failed checkout and each unhandled error

type Logger

returns

ErrorHandler

the handler create_app_server registers with app.onError

create_app_server
#

server/app_server.ts view source

(options: AppServerOptions): Promise<AppServer> import {create_app_server} from '@fuzdev/fuz_app/server/app_server.js';

Create a fully assembled Hono app with auth, middleware, and routes.

Handles the assembly lifecycle: rate limiters → audit SSE → proxy middleware → auth middleware → bootstrap status → route specs (consumer, bootstrap, RPC endpoints, surface) → surface generation → Hono app assembly (routes, then WS endpoints) → post-route middleware → static serving → the auth cleanup schedule. Database migrations belong to the backend lifecycle — pass migration_namespaces to create_app_backend.

When audit_log_sse is set, the SSE registry's listener is registered via backend.deps.audit.add_listener — no shallow-copy of AppDeps. That registry and every WebSocket connection registry mounted from ws_endpoints are added to backend.deps.connection_closer, so the revocation handlers built from ctx.deps close their connections with no further wiring, and — when the server mounts either — one create_revocation_guard listener on that closer repeats each close on the audit row. The audit_sse field on the returned AppServer (and the AppServerContext passed to create_route_specs) is non-null in that case; consumers can call require_audit_sse(ctx) / require_audit_sse(server) to assert the invariant.

Assembly acquires a few things that outlive it — the rate limiters it builds, the listeners it registers on deps.audit, the members it adds to deps.connection_closer — and records a release for each as it goes. When assembly throws, every release recorded so far runs, last acquired first (one that throws is logged and the rest still run), and the original error is rethrown: a failed assembly leaves the backend's emitter and closer as it found them and no limiter timer running. When auth_cleanup is set, the auth cleanup schedule starts as the last step of assembly — nothing after it can throw, so an assembly that throws leaves no schedule running.

The returned close owns shutdown: it stops that schedule, closes every live connection on the backend's closer, runs the same releases, and closes the database (AppServer.close).

options

returns

Promise<AppServer>

the assembled Hono app with its surface, bootstrap status, migration results, audit SSE, mounted WS registries, and close

throws

  • Error - from any assembly step, after releasing what assembly acquired

create_app_surface_spec
#

create_audit_emitter
#

auth/audit_emitter.ts view source

(options: CreateAuditEmitterOptions): AuditEmitter import {create_audit_emitter} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Build a bound AuditEmitter. Typical caller is the consumer's audit_factory callback on CreateAppBackendOptions — create_app_backend invokes that callback with its constructed {db, log} and lands the result on AppDeps.audit.

options

pool, logger, optional initial subscriber, optional config

returns

AuditEmitter

the bound emitter; closes over the pool + config + listener chain

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?

optional

returns

AuditLogConfig

throws

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

create_audit_log_route_shape
#

auth/audit_log_route_schema.ts view source

(required_role?: string): Omit<RouteSpec, "handler"> import {create_audit_log_route_shape} from '@fuzdev/fuz_app/auth/audit_log_route_schema.js';

The GET /audit/stream SSE route shape minus its handler — pure hono-free data. create_audit_log_route_specs spreads this and attaches the live SSE handler; cross-process surface builders spread it with a stub handler. The output is z.null() because SSE streams have no JSON response body.

required_role?

role gating the stream (default DEFAULT_AUDIT_STREAM_ROLE)

type string
optional default DEFAULT_AUDIT_STREAM_ROLE

returns

Omit<RouteSpec, "handler">

the SSE route shape minus its handler

create_audit_log_route_specs
#

auth/audit_log_routes.ts view source

(options?: AuditLogRouteOptions | undefined): RouteSpec[] import {create_audit_log_route_specs} from '@fuzdev/fuz_app/auth/audit_log_routes.js';

Create the optional audit-log SSE route spec.

Returns an empty array when options.stream is not set — no REST routes live here apart from the stream.

Admission

A stream is authorized once, so the handler must not open one on a credential or role revoked while the request was in flight. The route's gates (401, credential channel, token scope, acting actor, role) run in the route-spec pipeline, before the handler and so before any stream is registered — a revocation landing after them would close nothing. So the handler registers the stream pending (SseRegistry.subscribe_pending), then re-reads the credential, the acting actor, and its role grants (recheck_admission, which the WebSocket upgrade shares), and only then admits (SseRegistry.admit). A revocation that committed before those reads is seen by them — 401 authentication_required for a dead credential or an acting actor deleted since the gates ran, 403 insufficient_permissions for a lost role — and one whose close ran after the registration found the pending entry: admission then fails, and the response is a 200 whose body is the connect comment and nothing else — to an EventSource, a stream closed an instant after it opened (the client's reconnect is answered by the gates, with the precise status). A pending registration receives no audit row, counts toward no cap, and is removed when a re-read refuses the request. Once the server is shutting down (SseRegistry class doc, "Shutdown") the registration is born closed, the re-reads are skipped, and the answer is that same connect comment alone — as it is when the shutdown closes a registration whose re-read is in flight, even if the re-read then fails. The twin of the Rust spine's audit_stream_router, which runs the same order through its recheck_admission.

Credential deadline

Expiry writes nothing a revocation listener could hear, so an admitted stream watches its own credential's expiry (ResolvedAuth.credential_deadline — for the session-only stock route, the earlier of the cookie's and the row's) and is closed at it, as a revoked stream is: the body ends with no final frame, and the client's reconnect meets the gates. The wait is schedule_credential_deadline's chain of bounded timers, cancelled when the stream closes.

options?

optional stream wiring + role override

optional

returns

RouteSpec[]

the SSE route spec (when options.stream is provided) or an empty array

create_audit_log_sse
#

realtime/audit_log_sse.ts view source

(options: { role?: string | undefined; log: Logger; connection_closer: RealtimeCloser | null; max_per_scope?: number | null | undefined; keep_alive_ms?: number | null | undefined; }): AuditLogSse import {create_audit_log_sse} from '@fuzdev/fuz_app/realtime/audit_log_sse.js';

Create a complete audit log SSE setup: the registry, its broadcaster, and its connection closer.

Builds the SseRegistry and the broadcast callback, and adds the registry to connection_closer for good — the remover add returns is dropped, so a caller that must take the registry out again passes null and adds it itself. Revocations close the streams through that closer: the handlers' post-commit closes, and the create_revocation_guard listener on it (which create_app_server registers; a hand wiring registers its own). The result satisfies AuditLogRouteOptions['stream'] and provides the on_audit_event listener for the audit emitter.

Most consumers pass audit_log_sse: true to create_app_server and never touch this directly — the factory builds an AuditLogSse, registers audit_sse.on_audit_event via backend.deps.audit.add_listener, and exposes it via AppServerContext.audit_sse. Reach for the manual path (compose inside audit_factory body, or audit.add_listener(audit_sse.on_audit_event) post-assembly) only when wiring outside create_app_server.

options

factory options

type { role?: string | undefined; log: Logger; connection_closer: RealtimeCloser | null; max_per_scope?: number | null | undefined; keep_alive_ms?: number | null | undefined; }

returns

AuditLogSse

audit log SSE setup (stream options + on_audit_event + registry)

examples

const connection_closer = create_realtime_closer(); const audit_sse = create_audit_log_sse({log, connection_closer}); // On CreateAppBackendOptions — the same closer, and the listeners inside // the audit_factory body: connection_closer, audit_factory: ({db, log}) => { const guard = create_revocation_guard(connection_closer, log); return create_audit_emitter({ db, log, on_audit_event: (event) => { audit_sse.on_audit_event(event); guard(event); }, }); }, // In create_route_specs: create_audit_log_route_specs({stream: audit_sse}); // In create_app_server options: event_specs: audit_log_event_specs,

create_auth_middleware_specs
#

auth/middleware.ts view source

(deps: AppDeps, options: AuthMiddlewareOptions): Promise<MiddlewareSpec[]> import {create_auth_middleware_specs} from '@fuzdev/fuz_app/auth/middleware.js';

Create the auth middleware stack.

Returns [origin, daemon_token?, session, request_context, bearer_auth] middleware specs: the origin check on every path less its carve-outs, the rest for the given path pattern, with the daemon_token layer only when daemon_token_state is provided. The credential legs run daemon token → session cookie → bearer, the order of the Rust spine's resolve_auth_from_headers: the first that authenticates sets ResolvedAuth, and the later ones skip. The first leg composed (the daemon token's, else the session's) clears ResolvedAuth on entry, so the chain starts from a clean slate whatever a layer before it set. Apps can append extra entries for non-standard paths (e.g., tx's /tx binary endpoint).

deps

stateless capabilities (keyring, db)

type AppDeps

options

middleware configuration (allowed_origins, session_options, path, daemon_token_state, origin_carve_outs)

returns

Promise<MiddlewareSpec[]>

the middleware spec array

throws

  • Error - when an origin carve-out has no path or no reason, or overlaps `path`

create_auth_test_apps
#

testing/auth_apps.ts view source

(route_specs: RouteSpec[], roles: string[]): AuthTestApps import {create_auth_test_apps} from '@fuzdev/fuz_app/testing/auth_apps.js';

Create one Hono test app per auth level.

route_specs

the route specs to register

type RouteSpec[]

roles

all roles in the app

type string[]

returns

AuthTestApps

create_bearer_auth_middleware
#

auth/bearer_auth.ts view source

(deps: QueryDeps, log: Logger): MiddlewareHandler import {create_bearer_auth_middleware} from '@fuzdev/fuz_app/auth/bearer_auth.js';

Create middleware that authenticates via bearer token.

Soft-fails for malformed, invalid, expired, or empty tokens — calls next() without setting account identity, letting downstream auth enforcement (the RPC dispatcher's pre-authorization / post-authorization auth gates or require_auth) return a consistent JSON-RPC or route-level error. This avoids leaking token-specific diagnostics (invalid_token, account_not_found) that could aid enumeration attacks, and ensures public actions are not blocked by bad credentials.

Discards bearer tokens when an Origin or Referer header is present — next() with no identity, as for a bad token — so browsers must use cookie auth, reducing attack surface. A token that fails the is_api_token_shaped precheck soft-fails before the database is touched. Auth scheme matching is case-insensitive per RFC 7235. On success, sets the api_token arm of ResolvedAuth (RESOLVED_AUTH_KEY): the account, the token id, its scope, and its deadline. The last leg of the chain, it skips when an earlier one (the daemon token or a session cookie) already resolved the request — before reading the header, so a token sent beside a winning credential is never looked up and its last_used_at / last_used_ip never move. Acting-actor resolution + RequestContext construction are deferred to the dispatcher's authorization phase.

There is deliberately no rate limit on this path, and no 429 — every failure soft-fails to "no credential". An API token is 32 bytes of CSPRNG output resolved by a blake3 hash lookup, so guessing is bounded by entropy, not by throttling; a limiter here would buy nothing measurable while costing availability (the check/record has to precede the async lookup to close its own TOCTOU window, so concurrent requests bearing a *valid* token race each other into a 429). The Rust spine never had one here; this is the converged shape. See docs/security.md §Why bearer auth is not rate limited.

deps

query dependencies (pool-level db for middleware)

log

the logger instance

type Logger

returns

MiddlewareHandler

mutates

  • Hono context — sets `RESOLVED_AUTH_KEY` on success

create_bearer_auth_mocks
#

testing/middleware.ts view source

(tc: BearerAuthTestOptions): BearerAuthMocks import {create_bearer_auth_mocks} from '@fuzdev/fuz_app/testing/middleware.js';

Create mock dependencies for create_bearer_auth_middleware, configured per test case.

Configures the module-level mocks for query_validate_api_token, query_account_by_id, query_actor_by_id, and query_role_grant_find_active_for_actor so each test case controls return values independently.

tc

returns

BearerAuthMocks

mocks bundle with spy references

mutates

  • module-level vi.mock registrations for api_token_queries, — `account_queries`, and `role_grant_queries` — each call resets and re-binds every spy in the bundle, so cases run in sequence without bleeding state.

create_bearer_auth_test_app
#

testing/middleware.ts view source

(tc: BearerAuthTestOptions): { app: Hono<BlankEnv, BlankSchema, "/">; mocks: BearerAuthMocks; } import {create_bearer_auth_test_app} from '@fuzdev/fuz_app/testing/middleware.js';

Create a Hono app wired with create_bearer_auth_middleware using mocked deps.

The route handler at /api/test returns the resolved credential in the response body, enabling assertions on ResolvedAuth and REQUEST_CONTEXT_KEY.

tc

returns

{ app: Hono<BlankEnv, BlankSchema, "/">; mocks: BearerAuthMocks; }

create_body_limit_middleware
#

http/body_limit.ts view source

(max_size: number): MiddlewareHandler import {create_body_limit_middleware} from '@fuzdev/fuz_app/http/body_limit.js';

Create the body size limit middleware.

max_size

the largest accepted body, in bytes; a safe integer, 0 or more

type number

returns

MiddlewareHandler

middleware answering 413 {error: 'payload_too_large'} past the cap and 400 {error: 'invalid_content_length'} to a Content-Length that is not a run of digits

throws

  • Error - when `max_size` is not a safe integer `0` or more — a `NaN` cap compares false against every length and would admit any body

create_bootstrap_route_specs
#

auth/bootstrap_routes.ts view source

(deps: RouteFactoryDeps, options: BootstrapRouteOptions): RouteSpec[] import {create_bootstrap_route_specs} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Create bootstrap route specs for first-time account creation.

deps

stateless capabilities including filesystem access

options

per-factory configuration (session, token path, bootstrap status)

returns

RouteSpec[]

route specs (not yet applied to Hono)

create_broadcast_api
#

actions/broadcast_api.ts view source

<TApi extends object>(options: CreateBroadcastApiOptions): TApi import {create_broadcast_api} from '@fuzdev/fuz_app/actions/broadcast_api.js';

Builds a typed broadcast API from a set of action specs.

For each spec, adds a method keyed by spec.method that:

  • Validates input against the spec's Zod schema (logs and returns on failure)
  • Creates a JSON-RPC notification from the validated input
  • Broadcasts on the registry (filtered by should_deliver when supplied)

A broadcast with no connections reaches nobody and is not an error. Errors during send are logged but never thrown — broadcasts are fire-and-forget from the handler's perspective.

Typed consumer surface

Consumers declare an explicit interface and pin it via the type parameter:

export interface BackendActionsApi { filer_change: (input: ActionInputs['filer_change']) => Promise<void>; workspace_changed: (input: ActionInputs['workspace_changed']) => Promise<void>; } const api = create_broadcast_api<BackendActionsApi>({ registry: app_server.ws_endpoints['/api/ws']!, specs: [filer_change_action_spec, workspace_changed_action_spec], });

The cast is unchecked — callers must keep the interface and the specs array in sync. Codegen (action_collections.gen.ts) is a natural fit if the consumer already generates per-method type maps.

options

returns

TApi

generics

create_broadcast_api<TApi extends object>
TApi
constraint object

create_bun_testing_adapter
#

create_cell_actions
#

create_cell_audit_actions
#

create_cell_field_actions
#

create_cell_grant_actions
#

create_cell_item_actions
#

create_credential_type_schema
#

auth/credential_type_schema.ts view source

(consumer_types?: Record<string, CredentialTypeMeta>): CredentialTypeSchemaResult import {create_credential_type_schema} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Create a credential-type schema from the builtin set plus optional consumer-declared additions.

Builtins (session, api_token, daemon_token) are always present; consumer entries that collide with a builtin name throw at construction. Pass the result into create_role_schema's optional credential_types parameter so each role's required_credential_types entries are validated against this set at construction time.

consumer_types?

optional consumer-declared credential-type set with optional metadata

type Record<string, CredentialTypeMeta>
optional default {}

returns

CredentialTypeSchemaResult

{CredentialType, credential_types} — Zod schema and metadata map

throws

  • Error - if any `consumer_types` key fails the `CredentialTypeName` regex, collides with a builtin name, or appears more than once

examples

// simple — builtins only const {CredentialType, credential_types} = create_credential_type_schema(); // with consumer extensions const {CredentialType} = create_credential_type_schema({ sso_assertion: {description: 'OIDC SSO assertion bound to an IdP-asserted account.'}, });

create_cross_backend_global_setup
#

testing/cross_backend/create_cross_backend_global_setup.ts view source

({ configs, derive_name, provide_key, ensure_databases, before_spawn }: CrossBackendGlobalSetupOptions, deps?: CrossBackendGlobalSetupDeps): (project: TestProject) => Promise<...> import {create_cross_backend_global_setup} from '@fuzdev/fuz_app/testing/cross_backend/create_cross_backend_global_setup.js';

Build a vitest globalSetup default export. Returns the (project) => teardown function vitest 4 expects.

__0

deps?

the database creator and the spawn, the shipped ones by default

optional default default_cross_backend_global_setup_deps

returns

(project: TestProject) => Promise<() => Promise<void>>

throws

  • Error - when a `before_spawn` key names no config

create_daemon_token_middleware
#

auth/daemon_token_middleware.ts view source

(state: DaemonTokenState, deps: QueryDeps, log: Logger): MiddlewareHandler import {create_daemon_token_middleware} from '@fuzdev/fuz_app/auth/daemon_token_middleware.js';

Create middleware that authenticates via daemon token — the first leg of the auth middleware chain, as in the Rust spine's resolve_auth_from_headers.

Being first, it starts the chain from a clean slate: it clears RESOLVED_AUTH_KEY on entry, so a layer mounted before the credential legs cannot pre-set an identity that every leg would then skip past.

Checks the X-Daemon-Token header. Behavior:

  • No header: pass through.
  • Header present + Origin / Referer present: discard the credential (browser context) and pass through — daemon tokens are loopback-only and never carry an Origin in production, so a header-bearing request is not a legitimate daemon caller. Mirrors the bearer guard: next() rather than 401, so downstream auth enforcement returns credential_type_required (not a hard fail). Silent on the wire (anti-enumeration); in DEV only, sets X-Fuz-Auth-Debug: daemon_token_discarded_browser_context.
  • Header present + Zod-invalid (malformed): soft-fail discard (pass through, not 401) — mirrors the bearer guard and the Rust spine's resolve.rs (None). Downstream a daemon-gated action returns credential_type_required; a public action proceeds anonymous.
  • Header present + invalid value (not the current/previous token): soft-fail discard (pass through, not 401) — same downstream behavior.
  • Header present + valid + keeper_account_id null (still pre-bootstrap after the lazy refresh): soft-fail discard (pass through, not 503) — mirrors the Rust spine's resolve.rs (None), so the request falls through to anonymous and a daemon-gated action returns credential_type_required downstream.
  • Header present + valid + ok: set the daemon_token arm of ResolvedAuth (RESOLVED_AUTH_KEY) for the keeper account. The session and bearer legs after it see the request resolved and skip, so a cookie or bearer sent beside the daemon token is never looked up.

Acting-actor resolution + RequestContext construction are deferred to the dispatcher's authorization phase. Multi-actor keeper accounts surface actor_required from there if a daemon caller doesn't pass an explicit acting value.

state

the daemon token runtime state

deps

query dependencies (pool-level db for keeper-account resolution)

log

the logger instance

type Logger

returns

MiddlewareHandler

mutates

  • Hono context — clears `RESOLVED_AUTH_KEY` on entry, then sets it on a valid token

create_database
#

dev/setup.ts view source

(deps: CommandDeps, db_name: string, options?: CreateDatabaseOptions | undefined): Promise<CommandResult> import {create_database} from '@fuzdev/fuz_app/dev/setup.js';

Create a PostgreSQL database if createdb is available.

Does not throw — returns the underlying command result so callers can decide how to react to a missing createdb or an "already exists" failure.

deps

command execution capability

db_name

database name to create

type string

options?

logger

optional

returns

Promise<CommandResult>

the command result

mutates

  • external database — invokes `createdb` to create `db_name` when available

create_db
#

db/create_db.ts view source

(database_url: string, options?: CreateDbOptions | undefined): Promise<CreateDbResult> import {create_db} from '@fuzdev/fuz_app/db/create_db.js';

Create a database connection based on a URL.

The close callback is bound to the actual driver — callers never need to know which driver is in use.

For direct driver construction without URL routing, import create_pg_db from db/db_pg.ts, create_pglite_db from db/db_pglite.ts or create_pglet_db_driver from db/db_pglet.ts.

database_url

connection URL (postgres://, postgresql://, file://, or memory://)

type string

options?

the pool's checkout bound (Postgres only)

optional

returns

Promise<CreateDbResult>

database instance, close callback, type, and display name

throws

  • Error - if `database_url` uses an unsupported scheme. Driver construction (`pg.Pool`, `PGlite` or pglet) may also throw on bad connection parameters or missing peer-dependency packages.

create_db_route_specs
#

create_default_db_factory
#

testing/db.ts view source

(init_schema: (db: Db) => Promise<void>): DbFactory import {create_default_db_factory} from '@fuzdev/fuz_app/testing/db.js';

Create the default in-memory database factory for tests: pglet-wasm, or the builder set_default_db_factory installed.

The factory fuz_app's own test helpers fall back to when a suite passes none (create_test_app_server, describe_rate_limiting_tests, describe_sse_route_tests). Call create_pglite_factory or create_pglet_wasm_factory directly to pin a driver.

init_schema

callback to initialize the database schema

type (db: Db) => Promise<void>

returns

DbFactory

create_deno_runtime
#

runtime/deno.ts view source

(args: readonly string[]): RuntimeDeps import {create_deno_runtime} from '@fuzdev/fuz_app/runtime/deno.js';

Create a runtime backed by Deno APIs.

Returns an object satisfying all *Deps interfaces from runtime/deps.ts. Pass to shared functions that accept EnvDeps, FsReadDeps, etc.

args

CLI arguments (typically Deno.args)

type readonly string[]

returns

RuntimeDeps

runtime implementation using Deno APIs

create_deno_testing_adapter
#

create_describe_db
#

testing/db.ts view source

(factories: DbFactory | DbFactory[], truncate_tables: string[]): (name: string, fn: (get_db: () => Db) => void) => void import {create_describe_db} from '@fuzdev/fuz_app/testing/db.js';

Create a describe_db function bound to specific factories and truncate tables.

Returns a 2-arg (name, fn) function that runs the test suite against each factory. Each factory gets its own describe block with a shared database instance, automatic beforeEach truncation, and afterAll cleanup. Skipped factories use describe.skip.

factories

one or more database factories to run suites against

truncate_tables

tables to truncate between tests (children first for FK safety)

type string[]

returns

(name: string, fn: (get_db: () => Db) => void) => void

mutates

  • the underlying database between tests — beforeEach issues — `TRUNCATE <truncate_tables> CASCADE` against the shared instance.

create_dual_spawn_global_setup
#

testing/cross_backend/create_dual_spawn_global_setup.ts view source

({ configs, provide_keys }: DualSpawnGlobalSetupOptions): (project: TestProject) => Promise<() => Promise<void>> import {create_dual_spawn_global_setup} from '@fuzdev/fuz_app/testing/cross_backend/create_dual_spawn_global_setup.js';

Build a vitest globalSetup default export that spawns both backends.

__0

returns

(project: TestProject) => Promise<() => Promise<void>>

create_fake_hono_context
#

testing/ws_round_trip.ts view source

(opts: FakeHonoContextOptions): Context<any, any, {}> import {create_fake_hono_context} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Build a fake Hono Context exposing the auth values the dispatcher reads via c.get(...) — the ResolvedAuth the production auth middleware sets (its session hash the key a session connection is registered under) and a preset RequestContext. Only .get() is populated — no other Hono context surface is simulated.

opts

returns

Context<any, any, {}>

create_fake_ws
#

testing/ws_round_trip.ts view source

(options?: CreateFakeWsOptions | undefined): FakeWs import {create_fake_ws} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Build a real WSContext backed by in-memory send/close capture. Parsing of outgoing frames is left to the caller — sends holds the raw strings as the dispatcher wrote them.

readyState reads 1 (open) until the socket is closed (2) or terminated (3).

options?

optional

returns

FakeWs

create_fetch_transport
#

testing/transports/fetch_transport.ts view source

(options: FetchTransportOptions): FetchTransport import {create_fetch_transport} from '@fuzdev/fuz_app/testing/transports/fetch_transport.js';

Build a cookie-threading transport pinned to options.base_url. The returned function carries a private Map<name, cookie-head> jar that updates on every response's Set-Cookie and re-sends on every subsequent request.

Request rewriting:

  • Absolute URLs (http://other.example/...) pass through verbatim — handy for cross-origin negative tests that target a deliberately different host.
  • Relative URLs are resolved against base_url.
  • Origin is set to options.origin ?? base_url unless the caller already provided one.
  • Cookie is set from the jar unless the caller already provided one.

options

returns

FetchTransport

create_frontend_rpc_client
#

actions/frontend_rpc_client.ts view source

<TApi extends object>(options: CreateFrontendRpcClientOptions<TApi>): FrontendRpcClient<TApi> import {create_frontend_rpc_client} from '@fuzdev/fuz_app/actions/frontend_rpc_client.js';

Build a frontend-only typed RPC client. See module doc for the bundle's design.

options

returns

FrontendRpcClient<TApi>

generics

create_frontend_rpc_client<TApi extends object>
TApi
constraint object

create_fuz_authorization_handler
#

auth/request_context.ts view source

(deps: QueryDeps): (c: Context<any, any, {}>, spec: RouteSpec) => Promise<void | Response> import {create_fuz_authorization_handler} from '@fuzdev/fuz_app/auth/request_context.js';

Create the route-spec authorization handler used by apply_route_specs.

Reads the acting selector via read_route_acting — c.var.validated_query on GETs, the raw body on mutations, since input validation now runs after the authority gates. Public routes (`auth.account === 'none' && auth.actor === 'none'`) skip the phase entirely.

Per registry-time invariant 2, auth.actor !== 'none' ⟺ the input (or query) schema declares acting?: ActingActor — so the selector is present on exactly the specs that read it, and input validation is what rejects a malformed one.

Resolved contexts land on REQUEST_CONTEXT_KEY so the post-authorization REST middleware (require_role, require_credential_types) reads the actor-bound context off c.var. The HTTP RPC and WS dispatchers consume the apply_authorization_phase outcome directly without round-tripping through c.var.

deps

returns

(c: Context<any, any, {}>, spec: RouteSpec) => Promise<void | Response>

create_grant_path_schema
#

auth/grant_path_schema.ts view source

(consumer_paths?: Record<string, GrantPathMeta>): GrantPathSchemaResult import {create_grant_path_schema} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Create a grant-path schema from the builtin set plus optional consumer-declared additions.

Builtins (admin, self_service, system, bootstrap) are always present; consumer entries that collide with a builtin name throw at construction. Pass the result into create_role_schema's optional grant_paths parameter so each role's grant_paths entries are validated against this set at construction time.

consumer_paths?

optional consumer-declared grant-path set with optional metadata

type Record<string, GrantPathMeta>
optional default {}

returns

GrantPathSchemaResult

{GrantPath, grant_paths} — Zod schema and metadata map

throws

  • Error - if any `consumer_paths` key fails the `GrantPathName` regex, collides with a builtin name, or appears more than once

examples

// simple — builtins only const {GrantPath, grant_paths} = create_grant_path_schema(); // with consumer extensions const {GrantPath} = create_grant_path_schema({ invite_only: {description: 'Granted by claiming a consumer-issued invite.'}, });

create_health_route_spec
#

http/common_routes.ts view source

(): RouteSpec import {create_health_route_spec} from '@fuzdev/fuz_app/http/common_routes.js';

Create a public health check route spec.

Infrastructure endpoint for uptime monitors and load balancers. Bootstrap availability is exposed via /api/account/status instead.

returns

RouteSpec

create_holding_peer_ping_responder
#

testing/cross_backend/ws_inbound_dispatch.ts view source

(): { responder: WsRequestResponder; release: () => void; pinged: (count: number) => Promise<void>; } import {create_holding_peer_ping_responder} from '@fuzdev/fuz_app/testing/cross_backend/ws_inbound_dispatch.js';

A client responder that withholds every server-initiated peer/ping echo until release — so each client peer/ping stays in flight on the server — and answers at once after it. Shared with the WS envelope suite's cancel cases.

returns

{ responder: WsRequestResponder; release: () => void; pinged: (count: number) => Promise<void>; }

the responder, its release, and pinged(count), resolving once count pings have reached the client

create_initial_data
#

actions/action_event_helpers.ts view source

(kind: "request_response" | "remote_notification" | "local_call", phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", method: string, executor: "frontend" | "backend", input: unknown): { ...; } import {create_initial_data} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

kind

phase

method

type string

executor

input

type unknown

returns

ActionEventData

create_jsonrpc_error_response
#

http/jsonrpc_helpers.ts view source

(id: string | number | null, error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }): { ...; } import {create_jsonrpc_error_response} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC error response message.

id

type string | number | null

error

returns

JsonrpcErrorResponse

create_jsonrpc_error_response_from_thrown
#

http/jsonrpc_helpers.ts view source

(id: string | number | null, error: unknown): { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; } import {create_jsonrpc_error_response_from_thrown} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC error response from any error. Handles a thrown JSON-RPC error — a ThrownJsonrpcError from any copy of fuz_app, recognized by its brand (to_thrown_jsonrpc_error) — by preserving its code/message/data, and maps any other Error, a foreign class carrying a JSON-RPC code included, to internal_error, with its message and stack in DEV.

id

type JsonrpcRequestId | null

error

type unknown

returns

JsonrpcErrorResponse

create_jsonrpc_notification
#

http/jsonrpc_helpers.ts view source

(method: string, params: { [x: string]: unknown; } | undefined): { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } import {create_jsonrpc_notification} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC notification message (no id, no response expected).

method

type string

params

type JsonrpcNotificationParams | undefined

returns

JsonrpcNotification

create_jsonrpc_request
#

http/jsonrpc_helpers.ts view source

(method: string, params: { [x: string]: unknown; } | undefined, id: string | number): { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } import {create_jsonrpc_request} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC request message.

method

type string

params

type JsonrpcRequestParams | undefined

id

returns

JsonrpcRequest

create_jsonrpc_response
#

http/jsonrpc_helpers.ts view source

(id: string | number, result: JSONType): { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } import {create_jsonrpc_response} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC success response message.

id

result

type JSONType

returns

JsonrpcResponse

create_keyring
#

auth/keyring.ts view source

(env_value: string | undefined): CreateKeyringResult import {create_keyring} from '@fuzdev/fuz_app/auth/keyring.js';

Validate a keys value and create a keyring from it — the one way to build a Keyring.

Keys are separated by __ for rotation support; empty entries are skipped. The first key signs, every key verifies. A value with no key, or with any key shorter than MIN_KEY_LENGTH characters, is refused with every reason (validate_keyring), so a weak key fails loud at startup rather than signing cookies. Twin of the Rust Keyring::new.

Returns a discriminated union so callers handle exit/logging their own way (e.g. Deno.exit(1) vs runtime.exit(1)). CryptoKeys are cached on first use.

Key rotation is an operational concern. Put the new key first and keep the old one after it: new cookies are signed with the new key, and cookies the old key signed keep verifying. Nothing re-signs a cookie, so an old key stays live until the last cookie it signed expires — SESSION_AGE_MAX after the rotation — and dropping it sooner signs out every session it still carries. A key that leaked is dropped at once, since it can forge cookies for as long as it is listed. Treat SECRET_FUZ_COOKIE_KEYS changes as security-critical deploys.

env_value

the SECRET_FUZ_COOKIE_KEYS environment variable

type string | undefined

returns

CreateKeyringResult

{ok: true, keyring} or {ok: false, errors}

create_loopback_app_server_options
#

testing/app_server.ts view source

(): Pick<AppServerOptions, "allowed_origins" | "proxy" | "env_schema"> import {create_loopback_app_server_options} from '@fuzdev/fuz_app/testing/app_server.js';

The loopback create_app_server options an in-process test server takes: the TEST_ORIGIN origin allowed (an exact list, parsed by parse_allowed_origins as create_app_server requires), the loopback proxy trusted (so X-Forwarded-For sets the client IP), and an empty env schema. A fresh object per call — spread it ahead of the options a test varies.

returns

Pick<AppServerOptions, "allowed_origins" | "proxy" | "env_schema">

create_message_too_large_error
#

actions/transports_ws.ts view source

(size: number, max_message_bytes: number): { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; } import {create_message_too_large_error} from '@fuzdev/fuz_app/actions/transports_ws.js';

The error FrontendWebsocketTransport answers an oversized message with — invalid_request (the whole message is refused) carrying data.reason: 'payload_too_large', the REST 413's reason.

size

type number

max_message_bytes

type number

returns

JsonrpcErrorObject

create_mock_runtime
#

runtime/mock.ts view source

(args?: string[]): MockRuntime import {create_mock_runtime} from '@fuzdev/fuz_app/runtime/mock.js';

Create a mock RuntimeDeps for testing.

The mock exit records the code on exit_calls and throws MockExitError (so the never-returning contract holds in tests). fetch throws TypeError when no mock_fetch_responses pattern matches the request URL.

args?

type string[]
optional default []

returns

MockRuntime

MockRuntime with controllable state

examples

const runtime = create_mock_runtime(['apply', 'zap.ts']); runtime.mock_env.set('HOME', '/home/test'); runtime.mock_fs.set('/home/test/.app/config.json', '{}'); await some_function(runtime); assert.strictEqual(runtime.command_calls.length, 1); assert.deepStrictEqual(runtime.exit_calls, [0]);

create_namespace_qualifier
#

actions/action_codegen.ts view source

(sources: readonly SpecSource[], imports: ImportBuilder): { qualify_spec: (spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; ... 8 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string; all_specs: readonly ({ ...; } | ... 1 more ... | { ...; })[]; } import {create_namespace_qualifier} from '@fuzdev/fuz_app/actions/action_codegen.js';

Multi-source consumer helper. Takes a list of {ns, module, specs} rows, registers import * as ns from module for each on imports, builds the method_to_ns lookup with duplicate-method detection, and returns {qualify_spec, all_specs} ready to thread through the high-level helpers.

A per-call qualify_spec callback alone leaves each consumer the import registration and the duplicate-method check; this does both.

sources

type readonly SpecSource[]

imports

returns

{ qualify_spec: (spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<...>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string; all_specs: rea...

throws

  • Error - if two sources contain the same method name (same-method detection is the consumer's primary debugging signal). Also throws if the returned `qualify_spec` is later called with a method not registered in any source.

examples

const sources = [ {ns: 'tx_specs', module: './action_specs.ts', specs: all_zap_action_specs}, {ns: 'admin_specs', module: '@fuzdev/fuz_app/auth/admin_action_specs.ts', specs: all_admin_action_specs}, ]; export const gen: Gen = ({origin_path}) => { const imports = new ImportBuilder(); const {qualify_spec, all_specs} = create_namespace_qualifier(sources, imports); return compose_gen_file({ origin_path, imports, blocks: [ generate_action_specs_record(all_specs, imports, {qualify_spec}), generate_action_inputs_outputs(all_specs, imports, {qualify_spec}), ], }); };

create_node_runtime
#

runtime/node.ts view source

(args?: readonly string[]): RuntimeDeps import {create_node_runtime} from '@fuzdev/fuz_app/runtime/node.js';

Create a RuntimeDeps backed by Node.js APIs.

args?

CLI arguments (typically process.argv.slice(2))

type readonly string[]
optional default process.argv.slice(2)

returns

RuntimeDeps

RuntimeDeps implementation using Node.js runtime

create_node_testing_adapter
#

create_noop_stub
#

testing/stubs.ts view source

<T = any>(_label: string, overrides?: Record<string, unknown> | undefined): T import {create_noop_stub} from '@fuzdev/fuz_app/testing/stubs.js';

Create a Proxy where every method access returns a no-op async function.

Use for deps that may be reached during "correct auth passes guard" tests but whose return values don't matter. Unlike the explicit method listing, this auto-updates when interfaces change.

_label

type string

overrides?

explicit properties to set (e.g. {db: stub_db})

type Record<string, unknown>
optional

returns

T

generics

create_noop_stub<T = any>
T
default any

create_output_tail
#

testing/cross_backend/spawn_backend.ts view source

(max_bytes: number): OutputTail import {create_output_tail} from '@fuzdev/fuz_app/testing/cross_backend/spawn_backend.js';

Create an OutputTail keeping at most max_bytes.

max_bytes

how many trailing bytes to keep

type number

returns

OutputTail

create_pending_role_grant_offer
#

testing/cross_backend/role_grant_cross_helpers.ts view source

(fixture: TestFixture, to_account_id: string, grantor_headers?: Record<string, string>, options?: RoleGrantFixtureOptions): Promise<...> import {create_pending_role_grant_offer} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_cross_helpers.js';

Create a fresh pending offer of role from grantor_headers (defaults to the keeper) to to_account_id, returning the created offer.

fixture

the per-test fixture

to_account_id

the recipient account

type string

grantor_headers?

the grantor's session headers; the keeper's when omitted

type Record<string, string>
optional default fixture.create_session_headers()

options?

the role and the RPC path

optional default {}

returns

Promise<{ id: string; }>

the created offer

create_pg_db
#

db/db_pg.ts view source

(pool: DbCheckoutPool): DbDriverResult import {create_pg_db} from '@fuzdev/fuz_app/db/db_pg.js';

Create a Db backed by a create_pg_pool pool.

Owns the transaction implementation, acquiring a dedicated pool client per transaction. db.with_checkout_timeout(ms) returns a Db over the same pool whose statements check out under ms instead (its transactions keep the pool's own bound).

pool

a pool from create_pg_pool

returns

DbDriverResult

the Db instance and a close callback bound to pool.end()

create_pg_factory
#

testing/db.ts view source

(init_schema: (db: Db) => Promise<void>, test_url?: string | undefined): DbFactory import {create_pg_factory} from '@fuzdev/fuz_app/testing/db.js';

Create a pg (PostgreSQL) database factory for tests.

Skipped when test_url is not provided. Each create() opens a pool, resets the public schema (reset_public_schema) and runs init_schema — the lifecycle every factory here shares, so a persistent test database never carries stale DDL or tracker rows from an earlier run or another suite's schema. Point test_url at a database used only for tests.

init_schema

callback to initialize the database schema

type (db: Db) => Promise<void>

test_url?

PostgreSQL connection URL (e.g. from TEST_DATABASE_URL)

type string
optional

returns

DbFactory

a factory that creates pg databases. The returned create() throws when test_url is unset (despite the skip: true flag — defense against direct invocation), and rewrites Postgres "database does not exist" errors into a createdb hint message.

create_pg_pool
#

db/db_pg.ts view source

(pg: { Pool: typeof Pool; }, options: CreatePgPoolOptions): DbCheckoutPool import {create_pg_pool} from '@fuzdev/fuz_app/db/db_pg.js';

Build a pg.Pool whose every checkout is bounded by options.checkout_timeout_ms and throws DbCheckoutError when it fails (module doc, "Checkout timeout").

The pool wraps every error of its connect — the wait timing out, a new client's connect timing out or failing, a closed pool — and nothing a query on a checked-out client throws. pool.query checks out through the same connect (pg-pool's query calls this.connect), so per-statement queries keep pg-pool's own error-listener handling.

query_with_checkout_timeout lends one statement its own bound by setting options.connectionTimeoutMillis for the synchronous span of query → connect: pg-pool reads it there, to arm the queue wait's timer or a new client's connect timer, and nowhere later on that checkout's path but one — a waiter that a freed slot later hands a brand-new client connects under the pool's own bound. Pinned against the installed pg-pool by src/test/db/db_pg.db.test.ts.

pg

the pg module (dynamically imported by the caller)

type { Pool: typeof Pool; }

options

the URL and the checkout bound

returns

DbCheckoutPool

the pool

create_pglet_db_driver
#

db/db_pglet.ts view source

(pglet: PgletDb): DbDriverResult import {create_pglet_db_driver} from '@fuzdev/fuz_app/db/db_pglet.js';

Create a Db backed by a pglet instance.

Named _driver to stay clear of @fuzdev/pglet-wasm's own create_pglet_db, which creates the instance this wraps. Create it with create_pglet_instance (or with PGLET_COERCION) so it reads values as the other drivers do.

pglet

a @fuzdev/pglet-wasm instance

type PgletDb

returns

DbDriverResult

the Db instance and a close callback bound to pglet.close()

create_pglet_instance
#

db/db_pglet.ts view source

(): Promise<PgletDb> import {create_pglet_instance} from '@fuzdev/fuz_app/db/db_pglet.js';

Create an in-memory pglet instance with PGLET_COERCION.

Loads @fuzdev/pglet-wasm on demand and awaits its init() — a no-op on the package's Node entry, where the wasm loads at import; the browser entry loads it here.

returns

Promise<PgletDb>

a fresh, empty instance; wrap it with create_pglet_db_driver

create_pglet_wasm_factory
#

testing/db.ts view source

(init_schema: (db: Db) => Promise<void>): DbFactory import {create_pglet_wasm_factory} from '@fuzdev/fuz_app/testing/db.js';

Create a pglet-wasm (in-memory) database factory for tests.

Always enabled — needs the optional @fuzdev/pglet-wasm peer installed. Shares one pglet instance across the pglet-wasm factories this module builds, created with fuz_app's value coercion (PGLET_COERCION); each create() resets the schema via DROP SCHEMA public CASCADE.

init_schema

callback to initialize the database schema

type (db: Db) => Promise<void>

returns

DbFactory

create_pglite_db
#

db/db_pglite.ts view source

(pglite: PGlite): DbDriverResult import {create_pglite_db} from '@fuzdev/fuz_app/db/db_pglite.js';

Create a Db backed by a PGlite instance.

Delegates transactions to PGlite's native transaction() method.

pglite

an already-constructed PGlite instance

type PGlite

returns

DbDriverResult

the Db instance and a close callback bound to pglite.close()

create_pglite_factory
#

testing/db.ts view source

(init_schema: (db: Db) => Promise<void>): DbFactory import {create_pglite_factory} from '@fuzdev/fuz_app/testing/db.js';

Create a PGlite (in-memory) database factory for tests.

Always enabled — needs the optional @electric-sql/pglite peer installed. Shares one PGlite WASM instance across the PGlite factories this module builds; each create() resets the schema via DROP SCHEMA public CASCADE instead of paying the cold start again.

init_schema

callback to initialize the database schema

type (db: Db) => Promise<void>

returns

DbFactory

create_proxy_middleware
#

http/proxy.ts view source

(options: ProxyOptions): MiddlewareHandler import {create_proxy_middleware} from '@fuzdev/fuz_app/http/proxy.js';

Create a Hono middleware that resolves the client IP from trusted proxies.

Sets client_ip on the Hono context for downstream use by get_client_ip. All client IPs are normalized (lowercase, IPv4-mapped IPv6 stripped).

Resolution logic:

  1. No X-Forwarded-For → use connection IP directly.
  2. X-Forwarded-For present but connection is untrusted → ignore header (spoofed by a direct attacker), use connection IP.
  3. X-Forwarded-For present and connection is trusted → walk header right-to-left, strip trusted entries, use first untrusted entry; a malformed entry stops the walk and the connection IP is used.

options

trusted proxy configuration

returns

MiddlewareHandler

throws

  • Error - if any entry in `options.trusted_proxies` is invalid (parsed eagerly via `parse_proxy_entry`)

mutates

  • c.var.client_ip — set to the resolved (or `'unknown'`) client IP per request

create_proxy_middleware_spec
#

http/proxy.ts view source

(options: ProxyOptions): MiddlewareSpec import {create_proxy_middleware_spec} from '@fuzdev/fuz_app/http/proxy.js';

Create a middleware spec for trusted proxy resolution.

Apply before auth middleware so client_ip is available for rate limiting.

options

trusted proxy configuration

returns

MiddlewareSpec

create_rate_limiter
#

create_ready_route_spec
#

http/common_routes.ts view source

(options: ReadyRouteOptions): RouteSpec import {create_ready_route_spec} from '@fuzdev/fuz_app/http/common_routes.js';

Create the /ready readiness route spec — the deploy gate.

Returns 200 {ready: true} when the live DB's columns cover expected, else 503 {error} (schema_drift when columns are missing, db_unreachable when the introspection query throws). The detailed drift goes to the server log only — the public body stays a minimal code so the endpoint doesn't leak schema structure (mirrors why /api/surface is admin-only). A deploy poll treats 503 as a failed release and rolls back, turning a silent schema-drift auth outage into a loud blocked deploy. See db/schema_ready.ts for the column-presence rationale and auth/migrations.ts for the frozen-append discipline that prevents the drift in the first place.

options

returns

RouteSpec

create_realtime_closer
#

actions/connection_closer.ts view source

(): RealtimeCloser import {create_realtime_closer} from '@fuzdev/fuz_app/actions/connection_closer.js';

Create an empty RealtimeCloser.

create_app_backend creates the one on AppDeps.connection_closer, and create_app_server adds each WebSocket connection registry it mounts and its audit stream registry, removing them when its assembly fails or the server closes. A consumer that mounts a connection registry by hand adds it through the connection_closer option of register_action_ws / create_audit_log_sse, or with add.

returns

RealtimeCloser

create_recording_audit_emitter
#

testing/audit_drift_guard.ts view source

(calls_ref?: AuditLogInput<"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">[] | undefined): RecordingAuditEmitter import {create_recording_audit_emitter} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Build a no-op AuditEmitter that records every emit, emit_pool, and emit_role_grant_target call into calls as an AuditLogInput. Use to capture audit metadata shapes in unit tests (e.g. password change failure outcome, role-grant create denial) without standing up a database and the query_audit_log pipeline.

Capture scope — all four production fan-out shapes. emit_role_grant_target mirrors create_audit_emitter's lift logic in place — actor_id / account_id / ip are populated from auth + ctx and the event_type / outcome / target_*_id / metadata fields forward from the input envelope. Tests asserting on role-grant-shape emissions read out of the same homogeneous calls array. notify is a no-op; add_listener records into a local array that listener_count reports, and returns a remover with the production semantics (per registration, idempotent) — registered listeners never fire, since this emitter captures emit shapes, not fan-out. drain_inflight resolves at once and is_tracking_inflight answers false: a recorded call writes nothing, so nothing is ever in flight.

emit AND emit_pool both append to calls, so a test reads either entry point's writes off the same array.

Pass calls_ref to write into a caller-owned array (callers that declared const events: Array<AuditLogInput> = [] and want to keep the reference). Omit to let the helper allocate a fresh array and return it on the calls field of the result.

The returned emitter is deliberately NOT frozen — slots stay mutable so a test can override one when it needs bespoke shape (e.g. an emit_pool that throws on the first call). The production create_audit_emitter freeze invariant exists to catch the patch_audit_emit_capture hot-patch footgun against the closure-captured emit; the recording emitter has no inner closure, so the freeze isn't load-bearing here.

calls_ref?

type AuditLogInput<"login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete">[]
optional

returns

RecordingAuditEmitter

create_recording_closer
#

testing/connection_closer_helpers.ts view source

(): RecordingCloser import {create_recording_closer} from '@fuzdev/fuz_app/testing/connection_closer_helpers.js';

Build a ConnectionCloser that records every call into calls rather than touching real transports. Each method returns 1 ("one connection closed") regardless of whether a real connection exists — handlers ignore the return value.

To see a handler's closes, add the closer to the backend's deps.connection_closer (RealtimeCloser.add), or build the action factory's deps with it.

returns

RecordingCloser

create_request_context_middleware
#

auth/request_context.ts view source

(deps: QueryDeps, options?: RequestContextMiddlewareOptions): MiddlewareHandler import {create_request_context_middleware} from '@fuzdev/fuz_app/auth/request_context.js';

Create middleware that authenticates the account from a session cookie — the session leg of the auth middleware chain.

When it starts the chain (starts_chain, the default) it first clears RESOLVED_AUTH_KEY. It then skips when an earlier leg (the daemon token) already resolved the request. Otherwise reads the session token the session middleware set (SESSION_TOKEN_KEY), looks up the auth_session, and on a valid session sets the session arm of ResolvedAuth (RESOLVED_AUTH_KEY): the account, the session hash, and the credential deadline — the earlier of the cookie's signed expiry (SESSION_COOKIE_EXPIRES_AT_KEY) and the row's. Writes nothing to the session row. Does not load actor or role_grants; REQUEST_CONTEXT_KEY is left null — the route-spec / RPC dispatcher authorization phase resolves the acting actor and builds the full RequestContext when the route needs one.

An invalid or missing session leaves the request unresolved and calls next() — the bearer leg may still resolve it, and require_auth / require_role enforce.

deps

query dependencies (pool-level db for middleware)

options?

whether this leg starts the chain, and the logger for a start that clears a credential

optional default {}

returns

MiddlewareHandler

mutates

  • Hono context — resets `REQUEST_CONTEXT_KEY` (and `RESOLVED_AUTH_KEY` when it starts the chain), then on a valid session (and no credential already resolved) sets `RESOLVED_AUTH_KEY`

create_revocation_guard
#

actions/connection_closer.ts view source

(closer: ConnectionCloser, log: Logger): AuditEventHandler import {create_revocation_guard} from '@fuzdev/fuz_app/actions/connection_closer.js';

Create the audit listener that closes live connections on a successful revocation row: to_connection_close_target maps the row to the connections it names, and the listener closes them with closer.close_connections.

Why it exists beside the handlers' own closes. The revocation handlers close after their commit (queue_connection_close), whatever happens to the audit write; this listener repeats that close when the row is announced, which covers a revocation emitted by code that holds no closer. Closing is idempotent, so the overlap is a repeat. A success row is announced only after its request's transaction commits (auth/audit_emitter.ts), so this close is post-commit too, and it reaches connections still pending admission.

The role close. perform_action re-authorizes every request against its own action, but an endpoint's role gate is the only check on what its connections *receive* — WebSocket broadcasts and notifications, the audit stream's rows — with no per-recipient filter, so a connection left open past the revocation would keep receiving what the gate exists to withhold. The role revokes close for it directly, and this listener repeats the close: a WebSocket closes with WS_CLOSE_PERMISSION_REVOKED, a stream gated on the role ends.

Twin of the Rust spine's register_revocation_guard.

closer

what to close on — deps.connection_closer, the fan-out over every registry, which is what create_app_server registers it on

log

logger for the closes (info level, when any connection closed)

type Logger

returns

AuditEventHandler

an on_audit_event callback for create_audit_emitter, or for AuditEmitter.add_listener

create_role_grant_offer_actions
#

auth/role_grant_offer_actions.ts view source

(deps: RevokingActionFactoryDeps & { notification_sender?: NotificationSender | null | undefined; }, options?: RoleGrantOfferActionOptions): RpcAction[] import {create_role_grant_offer_actions} from '@fuzdev/fuz_app/auth/role_grant_offer_actions.js';

Create the role-grant-offer RPC actions: the offer-lifecycle methods plus role_grant_revoke and the immediate role_grant_assign.

deps

RevokingActionFactoryDeps (log, audit, and the connection_closer a revoked global grant closes the revokee's role-gated connections through) plus optional notification_sender for WS fan-out — when absent, WS fan-out is silently skipped (DB-only side effects still happen). Consumers wiring ConnectionRegistry assign its instance directly (the registry's send_to_account signature accepts the broader JsonrpcMessageFromServerToClient, which is contravariantly compatible)

type RevokingActionFactoryDeps & { notification_sender?: NotificationSender | null | undefined; }

options?

role schema, default TTL, authorization override

optional default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

create_role_schema
#

auth/role_schema.ts view source

(consumer_roles: readonly RoleSpec[], options?: CreateRoleSchemaOptions): RoleSchemaResult import {create_role_schema} from '@fuzdev/fuz_app/auth/role_schema.js';

Create a role schema and spec map that extends the builtins with app-defined roles.

Call once at server init. The returned Role schema validates role strings at I/O boundaries (grant endpoint, role_grant queries). The role_specs map is read by the admin / self-service factories to derive their default eligibility filters from RoleSpec.grant_paths, and by the cell grant verbs to refuse an unregistered role principal. RoleSpec.required_credential_types is validated here but enforced nowhere — credential gating lives on each route's and action's auth.credential_types.

Construction-time guards (all fire on misconfiguration):

  1. Every consumer_roles[i].name matches RoleName — its regex and its ROLE_NAME_LENGTH_MAX length cap.
  2. No two consumer roles share a name.
  3. No consumer role collides with a builtin (keeper / admin).
  4. When options.credential_types is supplied, every entry in required_credential_types is registered in that map.
  5. When options.scope_kinds is supplied, every entry in applicable_scope_kinds is registered in that map. (Builtins declare empty applicable_scope_kinds, so they pass any registry.)
  6. When options.grant_paths is supplied, every entry in grant_paths is registered in that map. (Builtins use only 'admin' and 'bootstrap', both of which are builtin grant paths, so they pass the default registry from create_grant_path_schema().)

consumer_roles

app-defined role specs

type readonly RoleSpec[]

options?

optional registries for cross-axis validation

optional default {}

returns

RoleSchemaResult

{Role, role_specs} — Zod schema and full spec map

throws

  • Error - if any `consumer_roles` entry fails any of the construction-time guards above

examples

// visiones — opt into all four registries for full construction-time validation const credential_types = create_credential_type_schema(); const scope_kinds = create_scope_kind_schema({ classroom: {description: 'A classroom — teacher and student role_grants scope here.'}, }); const grant_paths = create_grant_path_schema(); const {Role, role_specs} = create_role_schema( [ { name: 'teacher', description: 'Educator role. Web-grantable; applies at classroom scope.', grant_paths: ['admin'], applicable_scope_kinds: ['classroom'], }, ], {credential_types, scope_kinds, grant_paths}, );

create_route_skip_filter
#

testing/auth_apps.ts view source

(skip_routes: string[] | undefined): (route: { method: string; path: string; }) => boolean import {create_route_skip_filter} from '@fuzdev/fuz_app/testing/auth_apps.js';

Build the skip predicate an adversarial suite applies from its skip_routes option — routes named in 'METHOD /path' form, the surface key. undefined skips nothing.

skip_routes

type string[] | undefined

returns

(route: { method: string; path: string; }) => boolean

create_rpc_client
#

actions/rpc_client.ts view source

<TApi extends object>(options: CreateRpcClientOptions<TApi>): TApi import {create_rpc_client} from '@fuzdev/fuz_app/actions/rpc_client.js';

Creates a Proxy-based API from action specs.

Method calls are dynamically dispatched based on the action spec's kind:

  • request_response → send request, await response, return Result
  • remote_notification → send notification, return Result
  • local_call → execute locally (sync or async), return Result or throw

Generic TApi is the consumer's typed Proxy interface (typically a codegen-derived ActionsApi). Required — no default, so forgetting it is a type error rather than a silent slide into any. The `as unknown as TApi` coercion lives inside this function so call sites get a typed return without a cast at the seam. TApi is a type-layer promise about what the Proxy responds to; the runtime walks specs (kept in sync by the consumer, codegen recommended).

const api_result = create_rpc_client<MyActionsApi>({peer, environment});

options

returns

TApi

a Proxy typed as TApi that responds to any method name found in the environment's specs

generics

create_rpc_client<TApi extends object>
TApi
constraint object

create_rpc_endpoint
#

actions/action_rpc.ts view source

(options: CreateRpcEndpointOptions): RouteSpec[] import {create_rpc_endpoint} from '@fuzdev/fuz_app/actions/action_rpc.js';

Single JSON-RPC 2.0 endpoint — the canonical RPC transport binding.

Returns two RouteSpec entries (GET + POST on the same path) for apply_route_specs. The internal dispatcher handles:

  1. Parse envelope — POST: JSON body through classify_jsonrpc_message. GET: method and params from query string.
  2. Lookup method — find the RpcAction by method name.
  3. Pre-authorization auth — short-circuit unauthenticated when no account is on the request, before any later phase runs.
  4. Authorization phase — resolve the acting actor (when the action's auth requires role_grants or its input declares acting?: ActingActor) and build the request context. Runs before input validation so role-grant-grain auth checks return 403 before 400 invalid_params; acting is read from raw params by read_acting, which parses it as a UUID and treats anything else as omitted.
  5. Post-authorization auth — enforce role / keeper requirements against the request context.
  6. Rate limit — per-action IP / account throttling, ahead of params validation so a malformed call still charges.
  7. Validate params — parse input against the action's input schema.
  8. Dispatch — acquire DB handle (transaction for mutations, pool for reads), construct ActionContext, call handler, return JSON-RPC response.

GET is restricted to side_effects: false actions, and every response the GET handler writes, its refusals included, carries Cache-Control: no-store (RPC_GET_CACHE_CONTROL) — a refusal by middleware ahead of it does not. All errors use JSON-RPC format: {jsonrpc, id, error: {code, message, data?}}.

The RouteSpecs use auth: {type: 'none'} because auth is checked per-action inside the dispatcher, and transaction: false because transaction scope is per-action (mutations get a transaction, reads get pool). They are marked reads_credential, since the dispatcher reads the resolved credential, so assembly refuses the pair mounted outside the auth middleware's scope (assert_credential_routes_in_auth_scope) whether it arrives as rpc_endpoints or through create_route_specs.

options

endpoint path, actions, logger, and the two action rate limiters

returns

RouteSpec[]

route specs (GET + POST) ready for apply_route_specs

throws

  • Error - if two actions share the same `spec.method` (registration-time duplicate detection); also throws if any action's `spec.input` is `z.null()` (JSON-RPC 2.0 §4.2 forbids `params: null` on the wire — use `z.void()` for parameterless methods).

create_rpc_envelope_cases
#

testing/rpc_envelope_cases.ts view source

(endpoint: AppSurfaceRpcEndpoint, options?: RpcEnvelopeCaseOptions): RpcEnvelopeCase[] import {create_rpc_envelope_cases} from '@fuzdev/fuz_app/testing/rpc_envelope_cases.js';

Build the malformed-envelope cases for one RPC endpoint — the dispatcher's envelope parse, method lookup, and GET read restriction.

The POST cases and the GET read cases ride the endpoint's first read method (side_effects: false), the GET mutation cases its first mutation that needs an account. _testing_* and protocol methods are never chosen. A case whose method the endpoint lacks is left out.

Every read (side_effects: false) gets a GET with each of RPC_NON_OBJECT_PARAMS → invalid_params: the query string is no envelope, so the dispatcher's params validation is what refuses it.

Three cases pin an order against auth, sent with no credential: a GET of a mutating method is refused invalid_request (the read restriction runs after the method lookup and before authorization), a batch array is refused invalid_request with id: null (the envelope parse runs first, and batches are unsupported), and a GET with non-object params on a read requiring an identity is the 401 (params validation runs after authorization).

endpoint

the endpoint from the generated surface

options?

methods never chosen

optional default {}

returns

RpcEnvelopeCase[]

the cases, in a fixed order

create_rpc_get_url
#

testing/rpc_helpers.ts view source

(endpoint_path: string, method: string, params?: unknown, id?: string | number): string import {create_rpc_get_url} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Build a GET URL with JSON-RPC query parameters.

endpoint_path

the RPC endpoint path (e.g., /api/rpc)

type string

method

type string

params?

params (omit for parameterless methods)

type unknown
optional

id?

request id (default 'test')

type string | number
optional default 'test'

returns

string

create_rpc_post_init
#

testing/rpc_helpers.ts view source

(method: string, params?: unknown, id?: string | number): RequestInit import {create_rpc_post_init} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Create a RequestInit for a JSON-RPC POST request.

method

type string

params?

params (omit for parameterless methods; null is also stripped for ergonomic call sites — JSON-RPC 2.0 §4.2 forbids "params": null on the wire, and create_rpc_endpoint rejects z.null() action input schemas at registration). Tests that need to construct a literal "params": null envelope (e.g. asserting envelope-level rejection) should build the body inline rather than route through this helper.

type unknown
optional

id?

request id (default 'test')

type string | number
optional default 'test'

returns

RequestInit

create_rpc_request_text
#

testing/rpc_helpers.ts view source

(method: string, params_json: string | undefined, id?: string | number): string import {create_rpc_request_text} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Build a JSON-RPC request envelope as text with params_json spliced in verbatim as its params.

For params JSON.stringify cannot spell: it writes 5.0 as 5 and -0 as 0, so a value can't carry a float-spelled integer to the server. The text is not checked; a malformed one reaches the server as sent.

method

the JSON-RPC method

type string

params_json

the params as raw JSON text, or undefined to omit them

type string | undefined

id?

request id (default 'test')

type string | number
optional default 'test'

returns

string

the envelope's JSON text

create_rust_spine_consumer_rpc_endpoints
#

testing/cross_backend/rust_spine_consumer_surface.ts view source

(options: RustSpineConsumerRpcEndpointsOptions): (ctx: AppServerContext) => RpcEndpointSpec[] import {create_rust_spine_consumer_rpc_endpoints} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_consumer_surface.js';

The RPC endpoints a Rust spine consumer mounts: the standard admin / role-grant-offer / account actions, the extra families, then the domain specs — what describe_spine_cross_suites takes as rpc_endpoints.

options

the consumer's action specs, extras and roles

returns

(ctx: AppServerContext) => RpcEndpointSpec[]

the endpoints, as a factory of the stub context

throws

  • Error - when an `exclude_methods` entry names no domain spec

create_rust_spine_consumer_surface_spec
#

testing/cross_backend/rust_spine_consumer_surface.ts view source

(options: RustSpineConsumerSurfaceSpecOptions): AppSurfaceSpec import {create_rust_spine_consumer_surface_spec} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_consumer_surface.js';

Build the declared surface of a Rust spine consumer for its cross suites, on create_test_app_surface_spec: the spine routes (health, the account router's paths, signup, and bootstrap as a surface_only shape), the RPC endpoint from create_rust_spine_consumer_rpc_endpoints, the daemon-token layer every consumer test binary wires, and — with host_gate — the host_validation layer at the head of the middleware list.

options

the consumer's session config, action specs, extras and roles

returns

AppSurfaceSpec

the surface spec, for describe_spine_cross_suites's surface_source

throws

  • Error - when an account route shape the Rust router mounts is missing, or an `exclude_methods` entry names no domain spec

create_scope_kind_schema
#

auth/scope_kind_schema.ts view source

(consumer_kinds: Record<string, ScopeKindMeta>): ScopeKindSchemaResult import {create_scope_kind_schema} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

Create a scope-kind schema from a consumer-declared registry.

Open registry — no builtins. The 'GLOBAL' token used inside the partial unique indexes on role_grant and role_grant_offer is not a registry entry (it's an index expression only) and cannot collide with consumer-declared kinds because the regex rejects uppercase.

Call once at server init. Pass the result into create_role_schema's optional scope_kinds parameter so each role's applicable_scope_kinds entries are validated against this set at construction time. v1 keeps applicable_scope_kinds informative-only (registry-membership validation only); v2 may add INSERT-time (role, scope_kind) enforcement once the shape is clear from real consumer usage.

consumer_kinds

the consumer-declared scope-kind set with optional metadata

type Record<string, ScopeKindMeta>

returns

ScopeKindSchemaResult

{ScopeKind, scope_kinds} — Zod schema and metadata map

throws

  • Error - if any `consumer_kinds` key fails the `ScopeKindName` regex or appears more than once

examples

// visiones const {ScopeKind, scope_kinds} = create_scope_kind_schema({ classroom: {description: 'A classroom — teacher and student role_grants scope here.'}, });

create_self_service_role_actions
#

auth/self_service_role_actions.ts view source

(deps: RevokingActionFactoryDeps, options?: SelfServiceRoleActionsOptions): RpcAction[] import {create_self_service_role_actions} from '@fuzdev/fuz_app/auth/self_service_role_actions.js';

Build the unified self-service role toggle RPC action.

deps

RevokingActionFactoryDeps (log, audit, connection_closer); audit.emit writes a success row in the request transaction and a failure row via the captured pool, and a disable closes the caller's connections gated on the role through connection_closer.

options?

optional eligible-role override plus optional role schema for default-eligibility derivation

optional default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

throws

  • Error - at factory time if any `eligible_roles` entry is missing from `options.roles.role_specs`
  • Error - at factory time if the eligible set names a builtin role or an invalid role name

create_serve_cell_fact_route_spec
#

server/serve_fact_route.ts view source

(options: CreateServeFactRouteSpecOptions): RouteSpec import {create_serve_cell_fact_route_spec} from '@fuzdev/fuz_app/server/serve_fact_route.js';

Build the cell-scoped GET /api/cells/:cell_id/facts/:hash RouteSpec — the per-reference read.

Resolves the named cell (404 if missing / soft-deleted), requires can_view_cell(caller, cell) — or the steward arm, cell_steward_admits — and then cell.refs to include the hash (else 404, masked), then serves the bytes. The view check runs first, so a caller that can't view the cell learns nothing about its refs. Authz is scoped to this one (cell, hash) edge — never unioned across the fact's other referrers.

Pure-public auth — the handler builds the per-request RequestContext from the resolved account id (get_public_account_id) and enforces visibility per-reference.

options

returns

RouteSpec

create_serve_fact_route_spec
#

server/serve_fact_route.ts view source

(options: CreateServeFactRouteSpecOptions): RouteSpec import {create_serve_fact_route_spec} from '@fuzdev/fuz_app/server/serve_fact_route.js';

Build the admin-only bare-hash GET /api/facts/:hash RouteSpec.

An admin's reach already spans every cell, so serving by bare hash grants no escalation — the union concern that made this route a cross-owner leak for non-admins is vacuous for an admin. Non-admin callers are rejected at the auth phase (403) and never reach the handler. Confidential non-admin reads always go through the cell-scoped route above.

Auth is {account: 'required', actor: 'required', roles: ['admin']} — the dispatcher's authorization phase resolves the acting actor and the post-authorization guard enforces the admin role before the handler runs. The handler re-checks the global admin grant (has_scoped_role(_, admin, null), the auth phase's own reading) as defense-in-depth so a future mounting/auth-shape regression fails closed — a masked 404, logged at error — rather than serving by bare hash to a non-admin.

options

returns

RouteSpec

create_server_status_route_spec
#

http/common_routes.ts view source

(options: ServerStatusOptions): RouteSpec import {create_server_status_route_spec} from '@fuzdev/fuz_app/http/common_routes.js';

Create an authenticated server status route spec.

Returns version and uptime. Unlike the public health check, this requires authentication.

options

returns

RouteSpec

create_session_and_set_cookie
#

create_session_config
#

auth/session_cookie.ts view source

(cookie_name: string): SessionOptions<string> import {create_session_config} from '@fuzdev/fuz_app/auth/session_cookie.js';

Create a session config for raw session token identity.

The standard pattern: cookie stores the raw session token, server hashes it (blake3) to look up the auth_session row. Only the cookie_name varies per app.

cookie_name

cookie name (e.g. 'zap_session', 'visiones_session')

type string

returns

SessionOptions<string>

a SessionOptions<string> ready for use with session middleware

create_session_cookie_value
#

create_session_middleware
#

auth/session_middleware.ts view source

(keyring: Keyring, options: SessionOptions<string>): MiddlewareHandler import {create_session_middleware} from '@fuzdev/fuz_app/auth/session_middleware.js';

Create session middleware that parses cookies and sets the session token on context.

Always sets SESSION_TOKEN_KEY (null when invalid/missing) for type-safe reads — the key the session leg (create_request_context_middleware) reads. The identity is the session token, so the options are a SessionOptions<string>.

keyring

key ring for cookie verification

type Keyring

options

session configuration

type SessionOptions<string>

returns

MiddlewareHandler

mutates

  • Hono context — sets `SESSION_TOKEN_KEY` and `SESSION_COOKIE_EXPIRES_AT_KEY`, and may clear the session cookie

create_signup_route_shape
#

auth/signup_route_schema.ts view source

(options: SignupRouteShapeOptions): Omit<RouteSpec, "handler"> import {create_signup_route_shape} from '@fuzdev/fuz_app/auth/signup_route_schema.js';

The POST /signup route shape minus its handler — pure hono-free data. create_signup_route_specs spreads this and attaches the live handler; cross-process surface builders spread it with a stub handler. Single source of truth — the shape can't drift between the live route and the surface.

options

returns

Omit<RouteSpec, "handler">

create_signup_route_specs
#

auth/signup_routes.ts view source

(deps: RouteFactoryDeps, options: SignupRouteOptions): RouteSpec[] import {create_signup_route_specs} from '@fuzdev/fuz_app/auth/signup_routes.js';

Create signup route specs for account creation.

deps

stateless capabilities

options

per-factory configuration

returns

RouteSpec[]

route specs (not yet applied to Hono)

create_spine_live_rpc_endpoints
#

testing/cross_backend/full_spine_mount.ts view source

(): RpcEndpointSpec[] import {create_spine_live_rpc_endpoints} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

The spine binary's whole live RPC mount as an endpoint list, built on stub deps for method resolution only — no handler built here ever runs.

What a suite resolving off-surface methods takes as its live_rpc_endpoints (the conformance table, the differential, and the auth-row generator), so rows can name the cell verbs, the actor resolvers and the _testing_* backdoors the spines mount beside the declared surface. The requests go to the spawned backend, which runs its own handlers. Both spines mount this method set (the action-manifest parity gate holds them to it).

returns

RpcEndpointSpec[]

one endpoint at SPINE_RPC_PATH carrying build_full_spine_rpc_actions's list

create_spine_migration_namespace
#

db/migrate.ts view source

(namespace: string, migrations: Migration[]): MigrationNamespace import {create_spine_migration_namespace} from '@fuzdev/fuz_app/db/migrate.js';

Build a spine module's own migration namespace (auth, cells, facts) — the one form run_migrations and baseline admit under a reserved_migration_namespaces name. Admission is by object identity, so consumers splice the exported object itself (CELL_MIGRATION_NS), never a copy; consumer code builds a plain MigrationNamespace literal instead.

namespace

type string

migrations

the chain, in execution order

type Migration[]

returns

MigrationNamespace

the namespace, registered as the spine's

throws

  • Error - when `namespace` is not a reserved name

create_spine_ready_route_spec
#

testing/cross_backend/default_spine_surface.ts view source

(log?: Logger | undefined): RouteSpec import {create_spine_ready_route_spec} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

The spine's /ready route spec — the column-presence schema-drift deploy gate, reading SPINE_EXPECTED_SCHEMA_URL. Mounted live by the TS spine binary (in build_spine_app) and the in-process readiness parity leg, but kept off the declared surface (create_spine_surface_spec) like the fact-serving / ws / sse behaviors — describe_ready_cross_tests (gated on capabilities.ready) is its explicit coverage, not the generic round-trip.

log?

optional logger for server-side drift diagnostics

type Logger
optional

returns

RouteSpec

create_spine_route_specs
#

testing/cross_backend/default_spine_surface.ts view source

(ctx: AppServerContext): RouteSpec[] import {create_spine_route_specs} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

Account REST + signup route specs under /api/account (bootstrap auto-mounted by the surface builder / create_app_server), plus the audit-log SSE stream under /api/admin only when ctx.audit_sse is set (the TS spine binary passes audit_log_sse: true).

The shared create_spine_surface_spec() builds its ctx with audit_sse: null, so the declared surface snapshot stays SSE-free and the Rust spine_stub cross test is unaffected — only the live TS binary mounts the stream at SPINE_SSE_PATH.

ctx

returns

RouteSpec[]

create_spine_rpc_success_fixtures
#

testing/cross_backend/rpc_success_fixtures.ts view source

(options?: SpineRpcSuccessFixturesOptions): RpcSuccessFixtures import {create_spine_rpc_success_fixtures} from '@fuzdev/fuz_app/testing/cross_backend/rpc_success_fixtures.js';

Build the success-fixture map for the standard spine methods that need a real row id: account_token_revoke, account_session_revoke, invite_delete, role_grant_revoke, role_grant_offer_accept, role_grant_offer_decline, and role_grant_offer_retract.

Each factory mints fresh accounts — a fresh ROLE_ADMIN holder as grantor where the setup step is admin-gated — and builds its row as a real client would, then returns the headers of the account that calls: the owner for token and session revoke, accept, decline, and retract; the fresh admin for invite delete and role grant revoke.

options?

the RPC endpoint path

optional default {}

returns

RpcSuccessFixtures

the method → fixture-factory map

create_spine_surface_spec
#

testing/cross_backend/default_spine_surface.ts view source

(): AppSurfaceSpec import {create_spine_surface_spec} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

The AppSurfaceSpec for the standard spine surface — the wire-shape source the cross-process round-trip + RPC-round-trip suites validate against. bootstrap: {mode: 'surface_only'} mounts POST /api/account/bootstrap's shape to match the binary (which wires bootstrap for real); the harness's globalSetup already consumed the live bootstrap, so the cross-process round-trip validates the binary's 409 against the route's declared error schema.

returns

AppSurfaceSpec

create_sse_connection_closer
#

realtime/audit_log_sse.ts view source

<T>(registry: SseRegistry<T>, required_role: string | null): ConnectionCloser import {create_sse_connection_closer} from '@fuzdev/fuz_app/realtime/audit_log_sse.js';

Adapt an SseRegistry to ConnectionCloser, so a revocation handler's close reaches its streams.

close_connections is SseRegistry.close_by_identity on the id its target carries, so it closes the streams registered under that id as their scope or in their groups — pending registrations included. The audit stream route registers scope = session hash and groups = [account id]; a route that admits bearer tokens registers the API token id too, and a token target then reaches it. A role target closes the account's streams when its role is the one the registry's streams are gated on — the registry keeps one gating role for all its streams, not one per stream. close_all_connections is SseRegistry.close_all — every stream, for shutdown.

Twin of the Rust spine's ConnectionCloser impl on SseRegistry.

registry

the registry to close streams on

type SseRegistry<T>

required_role

the role every stream of registry was gated on, or null when they are not role-gated (a role target then closes nothing)

type string | null

returns

ConnectionCloser

generics

create_sse_connection_closer<T>
T

create_sse_frame_reader
#

testing/transports/sse_frame_reader.ts view source

(reader: ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>, default_timeout_ms?: number): SseFrameReader import {create_sse_frame_reader} from '@fuzdev/fuz_app/testing/transports/sse_frame_reader.js';

Wrap a byte-stream reader in \n\n-delimited SSE frame parsing.

Preserves bytes past a frame terminator in an internal buffer for the next read_frame. read_frame and wait_for_close both race each underlying read against timeout_ms so a misbehaving stream surfaces as a failure rather than a vitest hang.

reader

type ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>

default_timeout_ms?

type number
optional default SSE_FRAME_READ_TIMEOUT_MS

returns

SseFrameReader

create_sse_response
#

realtime/sse.ts view source

<T = unknown>(c: Context<any, any, {}>, log: Logger, options?: SseResponseOptions | undefined): { response: Response; stream: SseStream<T>; } import {create_sse_response} from '@fuzdev/fuz_app/realtime/sse.js';

Create an SSE response for a Hono context.

Wraps Hono's streamSSE to provide a {response, stream} API compatible with SseRegistry push-based broadcasting. The callback suspends via a promise that resolves on client disconnect or explicit close(), keeping the stream alive for external sends.

The stream closes when its client disconnects, whenever that happens — also before the handler returns the response, which no runtime reports by cancelling the body. A client already gone when this is called yields a stream that is already closed, so register cleanup with on_close (which runs a late listener at once) before handing the stream anywhere that would count it — SseRegistry.admit after on_close(pending.unsubscribe) refuses it.

Uses hono_stream.write() directly (not writeSSE) to avoid Hono's HTML callback resolution — keeps the same data: JSON\n\n format.

What the stream holds unread is bounded by SSE_OUTBOUND_QUEUE_BYTES; a frame past it ends the stream (module doc, "Outbound bound").

c

type Context<any, any, {}>

log

logger for serialization and on_close listener errors, and for the warning when the stream ends on its outbound budget

type Logger

options?

what the overflow warning names, and the keep-alive interval

optional

returns

{ response: Response; stream: SseStream<T>; }

object with the streaming Response and an SseStream controller

generics

create_sse_response<T = unknown>
T
default unknown

throws

  • Error - when `options.keep_alive_ms` is out of range (`validate_sse_keep_alive_ms`)

create_sse_transport
#

testing/transports/sse_transport.ts view source

(options: SseTransportOptions): Promise<SseTransport> import {create_sse_transport} from '@fuzdev/fuz_app/testing/transports/sse_transport.js';

Open a real-HTTP SSE stream pinned to options.base_url + sse_path.

Resolves once the response headers arrive and the body is a text/event-stream; rejects if the connect is refused (non-2xx status, wrong content type, missing body) so the test surfaces the real cause rather than hanging.

options

returns

Promise<SseTransport>

throws

  • Error - if the connect fails (status, content type, or no body).

create_standard_adversarial_cases
#

testing/adversarial_headers.ts view source

(allowed_origin: string): AdversarialHeaderCase[] import {create_standard_adversarial_cases} from '@fuzdev/fuz_app/testing/adversarial_headers.js';

7 standard adversarial header cases applicable to any middleware stack.

Origin verification is Origin-only — fuz_app's verify_request_source no longer falls back to Referer (matches zzz_server::auth::is_request_origin_allowed). Bearer auth still treats a Referer header as a browser-context indicator and silently discards the bearer token — so Referer-bearing requests reach the route as unauthenticated rather than 403.

allowed_origin

an origin that passes the origin check

type string

returns

AdversarialHeaderCase[]

create_standard_rpc_actions
#

auth/standard_rpc_actions.ts view source

(deps: StandardRpcActionsDeps, options?: StandardRpcActionsOptions): RpcAction[] import {create_standard_rpc_actions} from '@fuzdev/fuz_app/auth/standard_rpc_actions.js';

Build the combined admin + role-grant-offer + account RPC action set.

Spreads create_admin_actions(deps, {roles}), create_role_grant_offer_actions(deps, {roles, default_ttl_ms, authorize}), and create_account_actions(deps, {max_tokens}). The shared roles option flows to admin + role-grant-offer.

deps

StandardRpcActionsDeps (log, audit, connection_closer from RevokingActionFactoryDeps; optional notification_sender for WS fan-out)

options?

role schema, role-grant-offer config, account config

optional default {}

returns

RpcAction[]

RPC actions to pass as rpc_endpoints or spread into create_rpc_endpoint

create_static_middleware
#

server/static.ts view source

(serve_static: ServeStaticFactory, options?: { root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; } | undefined): MiddlewareHandler[] import {create_static_middleware} from '@fuzdev/fuz_app/server/static.js';

Create static file serving middleware for SvelteKit static builds.

Returns an array of middleware handlers to register on '/*'.

serve_static

runtime-specific serveStatic factory

options?

optional root directory and SPA fallback path

type { root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; }
optional

returns

MiddlewareHandler[]

create_stub_api_middleware
#

testing/stubs.ts view source

(options?: { include_daemon_token?: boolean | undefined; origin_carve_outs?: readonly MiddlewareCarveOut[] | undefined; } | undefined): MiddlewareSpec[] import {create_stub_api_middleware} from '@fuzdev/fuz_app/testing/stubs.js';

Create the API middleware stub array matching create_auth_middleware_specs output — the same names, paths, order, and declared errors (the surface merges middleware errors into every route under the path, so a stub that declared more than the real stack would widen the test surface). Like the real factory's, each spec gets its own pass-through handler, so assert_middleware_stack_mounted can tell the layers apart by identity.

options?

type { include_daemon_token?: boolean | undefined; origin_carve_outs?: readonly MiddlewareCarveOut[] | undefined; }
optional

returns

MiddlewareSpec[]

create_stub_app_backend
#

testing/stubs.ts view source

(options?: CreateStubAppBackendOptions | undefined): AppBackend import {create_stub_app_backend} from '@fuzdev/fuz_app/testing/stubs.js';

Create a stub AppBackend with no database behind it — no migration results, and a close that does nothing unless one is passed. For assembling a server (create_app_server) in tests that never reach the database; one whose close does nothing can host server after server.

options?

the deps and close to use in place of the defaults

optional

returns

AppBackend

create_stub_app_deps
#

testing/stubs.ts view source

(): AppDeps import {create_stub_app_deps} from '@fuzdev/fuz_app/testing/stubs.js';

Create no-op AppDeps for auth surface testing.

returns

AppDeps

create_stub_app_server_context
#

testing/stubs.ts view source

(session_options: SessionOptions<string>): AppServerContext import {create_stub_app_server_context} from '@fuzdev/fuz_app/testing/stubs.js';

Create a stub AppServerContext for attack surface testing.

Provides sensible defaults for all fields. Pass session_options since it varies per consumer; other fields use stubs/nulls.

session_options

consumer's session config (required — varies per app)

type SessionOptions<string>

returns

AppServerContext

create_stub_audit_sse
#

testing/stubs.ts view source

(): AuditLogSse import {create_stub_audit_sse} from '@fuzdev/fuz_app/testing/stubs.js';

Build a no-op AuditLogSse for tests that wire audit_sse into the surface helper but don't assert on SSE fan-out or subscriber state.

on_audit_event is a no-op, so nothing is broadcast; the registry is a fresh SseRegistry instance (call sites that inspect .size or call .close_* see a real registry, so writes are isolated per test). Tests that need real SSE plumbing build it via create_audit_log_sse against create_test_app.

returns

AuditLogSse

create_stub_db
#

testing/stubs.ts view source

(): Db import {create_stub_db} from '@fuzdev/fuz_app/testing/stubs.js';

Create a stub Db for handler tests that use apply_route_specs with declarative transactions.

Returns a real Db instance with:

  • query returns empty rows (safety net for unmocked query functions)
  • query_one returns undefined
  • transaction(fn) calls fn(db) synchronously (no real transaction)

returns

Db

create_stub_upgrade
#

testing/ws_round_trip.ts view source

(): StubUpgrade import {create_stub_upgrade} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Build a fake upgradeWebSocket that stands in for a runtime adapter's.

Like the real adapters, the middleware it returns passes a request without an Upgrade: websocket header to next(). An upgrade request is recorded in upgrades — its real Context and the endpoint's events factory — and answered 200 with no body (a Response cannot carry the 101 an adapter answers). No socket is opened: tests drive the recorded factory, so an upgrade sent through app.request exercises the app's whole middleware chain — origin, session or bearer, client IP — before the dispatcher sees it.

returns

StubUpgrade

create_surface_route_spec
#

server/surface_route.ts view source

(options: SurfaceRouteOptions): RouteSpec import {create_surface_route_spec} from '@fuzdev/fuz_app/server/surface_route.js';

Create the admin-only route spec that serves the AppSurface as JSON.

Surface data reveals API structure (routes, auth, schemas), so the route requires the global admin role. It is also a non-RPC surface: it declares required_scope: 'surface:app_surface', so a narrowed API token is refused it whatever its method list says (403 token_scope_required, ahead of the role gate), while a full-scope admin bearer passes. The admin role implies actor: 'required', so the route takes the ?acting= selector a multi-actor admin disambiguates with.

options

the surface to serve

returns

RouteSpec

the GET /api/surface route spec

create_test_account
#

create_test_account_with_actor
#

testing/db_entities.ts view source

(db: Db, options: { username: string; password_hash?: string | undefined; }): Promise<TestAccountWithActor> import {create_test_account_with_actor} from '@fuzdev/fuz_app/testing/db_entities.js';

Create an account + actor row pair in the database for tests.

Wraps query_create_account_with_actor with a default password_hash so suites that don't exercise password verification can stay terse. Replaces the per-file create_user / create_test_actor / create_test_account helpers that had accumulated across the auth test suite.

db

type Db

options

type { username: string; password_hash?: string | undefined; }

returns

Promise<TestAccountWithActor>

create_test_account_with_credentials
#

testing/app_server.ts view source

(options: CreateTestAccountWithCredentialsOptions): Promise<{ account: { id: string & $brand<"Uuid">; username: string; }; actor: { ...; }; api_token: string; session_cookie: string; }> import {create_test_account_with_credentials} from '@fuzdev/fuz_app/testing/app_server.js';

Create a test account with credentials. Use for additional accounts minted alongside the keeper (e.g. TestApp.create_account for cross-account / multi-user tests). Does NOT flip bootstrap_lock — non-keeper accounts should not appear to the system as bootstrap having happened.

Creates an account with actor, grants roles, creates an API token, creates a session, and signs a session cookie.

options

returns

Promise<{ account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; api_token: string; session_cookie: string; }>

mutates

  • the underlying options.db — inserts rows into account, actor, — `role_grant` (one per role), `api_token`, and `auth_session`.

create_test_actor
#

create_test_app
#

testing/app_server.ts view source

(options: CreateTestAppOptions): Promise<TestApp> import {create_test_app} from '@fuzdev/fuz_app/testing/app_server.js';

Create a fully assembled test app with a Hono server, middleware, and routes.

Combines create_test_app_server + create_app_server into a single call. Disables every rate limiter (rate_limiters: 'disabled_for_testing') and logging by default (test-friendly). A limiter passed through app_options wins over the mode, so a rate-limit test enables just the one it pins.

A fresh Hono app is created each call — middleware closures bind to the server's deps (db, keyring), so reuse across servers is unsafe. The expensive resource (the WASM database) is cached separately in testing/db.ts.

options

test app configuration

returns

Promise<TestApp>

a TestApp ready for HTTP testing

create_test_app_from_specs
#

testing/auth_apps.ts view source

(route_specs: RouteSpec[], auth_ctx?: RequestContext | undefined, credential_type?: "session" | "api_token" | "daemon_token" | undefined): Hono<BlankEnv, BlankSchema, "/"> import {create_test_app_from_specs} from '@fuzdev/fuz_app/testing/auth_apps.js';

Create a Hono test app from route specs with optional auth context.

route_specs

the route specs to register

type RouteSpec[]

auth_ctx?

optional request context to inject via middleware

optional

credential_type?

optional credential type (default: 'session' when auth_ctx provided)

optional

returns

Hono<BlankEnv, BlankSchema, "/">

create_test_app_server
#

testing/app_server.ts view source

(options: TestAppServerOptions): Promise<TestAppServer> import {create_test_app_server} from '@fuzdev/fuz_app/testing/app_server.js';

Create an app server with a bootstrapped account for testing.

Sets up:

  • Auth tables (via the cached default factory, or reuses existing db)
  • A keeper account with hashed password
  • Role role_grants for each role in options.roles
  • An API token for Bearer auth
  • A session with a signed cookie value

Uses stub_password_deps by default — deterministic hashing that works correctly for login/logout tests without Argon2 overhead.

options

session options and optional overrides

returns

Promise<TestAppServer>

a TestAppServer ready for HTTP testing

mutates

  • the underlying database — when db is supplied, first resets the — `app_settings` row (`open_signup`, `updated_at`, `updated_by`); in either branch inserts an account, actor, role role_grants, API token, and session row, and flips `bootstrap_lock.bootstrapped` (`bootstrap_test_keeper`).

create_test_app_surface_spec
#

testing/stubs.ts view source

(options: CreateTestAppSurfaceSpecOptions): AppSurfaceSpec import {create_test_app_surface_spec} from '@fuzdev/fuz_app/testing/stubs.js';

Create an AppSurfaceSpec for the standard testing suites.

Used by both in-process and cross-process tests as the schema source — the cross-process-ness lives in the transport + per-test fixture, not here. The on-disk *_attack_surface.json snapshot is observability (gen-time drift detection via assert_surface_matches_snapshot); the suites consume the spec object this function returns, not the JSON file.

Mirrors create_app_server's route assembly, in its order: consumer routes, then the factory-managed bootstrap routes, RPC endpoint routes, and GET /api/surface (with surface_route: true), then surface generation — including its refusal of an rpc_endpoints or ws_endpoints path outside the auth middleware's scope (assert_endpoint_in_auth_scope), of a route that reads or mints a credential outside it (assert_credential_routes_in_auth_scope), and of a transform_middleware that doesn't keep the proxy and auth stack mounted (assert_middleware_stack_mounted). The middleware list mirrors the real one: a stub trusted_proxy spec at '*', then the stub auth stack (with daemon_token when options.daemon_token is set). If create_app_server changes how it wires routes, update this helper to stay in sync (single source of truth for all consumers).

options

surface spec options

returns

AppSurfaceSpec

the surface spec for the standard suites

throws

  • Error - when an endpoint path, or a route that reads or mints a credential, is outside `AUTH_MIDDLEWARE_PATH`, a `ws_endpoints` spec declares no `allowed_origins` and no `allowed_origins` option is given, or `transform_middleware` moves, drops, duplicates, reorders, wraps, or replaces the stub proxy spec or a stub auth spec

create_test_audit_emitter
#

testing/stubs.ts view source

(): AuditEmitter import {create_test_audit_emitter} from '@fuzdev/fuz_app/testing/stubs.js';

Build a no-op AuditEmitter for tests that don't assert on audit fan-out.

emit / emit_role_grant_target are no-ops; emit_pool resolves immediately; notify is a no-op; add_listener throws, so a test that wires a listener fails loudly instead of silently never firing (create_recording_audit_emitter accepts listeners); listener_count returns 0; drain_inflight resolves immediately, since nothing is ever written, and is_tracking_inflight answers false. Tests asserting on real audit-row persistence (or on listener fan-out) build a real emitter via create_audit_emitter against a stub or real DB — create_test_app already does this on the test backend.

returns

AuditEmitter

create_test_audit_event
#

create_test_context
#

create_test_extra_actor
#

testing/db_entities.ts view source

(db: Db, account_id: string, name: string): Promise<Actor> import {create_test_extra_actor} from '@fuzdev/fuz_app/testing/db_entities.js';

Add a second actor to an existing test account — the multi-actor edge.

Its created_at is read from the wall clock (clock_timestamp()) rather than the transaction-fixed NOW() default the production query_create_actor relies on. Actors listed for an account sort by created_at ASC, id ASC, so an extra actor seeded in the transaction that created the account's first one would share its created_at and order by random id, and the multi-actor actor_required available list would come out in a random order. Seeded through here, the first actor sorts first and the extras in insert order. The bootstrap-cradle seeding of extra_actors uses it — the _testing_reset action and the in-process setup — as the Rust reset seeds them with query_create_actor_with_clock_timestamp.

db

the database, or the transaction the account was seeded in

type Db

account_id

the account the actor joins

type string

name

the actor's name

type string

returns

Promise<Actor>

the created actor

mutates

  • actor table — inserts the new row

create_test_middleware_stack_app
#

testing/middleware.ts view source

(options?: TestMiddlewareStackOptions | undefined): TestMiddlewareStackApp import {create_test_middleware_stack_app} from '@fuzdev/fuz_app/testing/middleware.js';

Create a Hono app with real proxy + origin + bearer middleware for integration testing.

All DB queries return undefined (no real database needed). The echo route at TEST_MIDDLEWARE_PATH returns {ok, client_ip, has_context}.

options?

optional

returns

TestMiddlewareStackApp

the app and mock spies (reconfigure via mockImplementation for valid-token paths)

throws

  • Error - when `allowed_origins` is not a valid allowlist

mutates

  • module-level vi.mock registrations for the bearer-auth query — modules — each call resets the spies before wiring the stack.

create_test_raw_api_token
#

testing/entities.ts view source

(label?: string): string import {create_test_raw_api_token} from '@fuzdev/fuz_app/testing/entities.js';

Create a well-formed raw API token for tests that need a bearer value to pass the is_api_token_shaped precheck and reach token validation. The body is label padded with A to the generated length, so distinct labels give distinct tokens and a token in a failure message reads back to its test. Not a real credential — it authenticates only where a test mocks the lookup.

label?

base64url characters (A-Z a-z 0-9 - _) that start the token body

type string
optional default 'test'

returns

string

throws

  • Error - when `label` is longer than the body or not base64url

create_test_request_context
#

testing/auth_apps.ts view source

(role?: string | undefined): RequestContext import {create_test_request_context} from '@fuzdev/fuz_app/testing/auth_apps.js';

Create a mock RequestContext with optional role role_grant.

role?

type string
optional

returns

RequestContext

create_test_request_headers
#

testing/test_origin.ts view source

(extra?: Record<string, string> | undefined): Record<string, string> import {create_test_request_headers} from '@fuzdev/fuz_app/testing/test_origin.js';

The headers every in-process test request starts from: host: localhost and TEST_ORIGIN, which the loopback allow list admits.

extra?

headers added after, replacing either of the defaults

type Record<string, string>
optional

returns

Record<string, string>

a fresh object

create_test_resolved_auth
#

create_test_role_grant
#

create_test_role_grant_direct
#

testing/db_entities.ts view source

(db: Db, input: CreateRoleGrantInput): Promise<RoleGrant> import {create_test_role_grant_direct} from '@fuzdev/fuz_app/testing/db_entities.js';

Materialize a role_grant directly via query_create_role_grant, bypassing the production offer/accept consent flow.

In-process only. This helper takes a raw Db handle and seeds rows without firing audit fan-out, WebSocket broadcasts, or the _supersede notification chain a real grant emits. Cross-process suites must instead drive role_grant_offer_create_action_spec + role_grant_offer_accept_action_spec via testing/role_grant_helpers.ts's role_grant_offer_and_accept so the fixture observes the full post-commit fan-out the way production does — otherwise tests would mask real divergence between the TS and Rust spines.

Use this helper for query-level (*.db.test.ts) tests that exercise revoke or isolation semantics — not the consent path itself. The schema's source_offer_id = null shape is an intentional admin-direct escape; this helper exposes it so suites don't reimplement the same direct-seed wrapper.

db

type Db

input

returns

Promise<RoleGrant>

create_test_validated_api_token
#

create_testing_action_manifest_action
#

testing/cross_backend/testing_reset_actions.ts view source

(mounted: readonly RpcAction[]): RpcAction import {create_testing_action_manifest_action} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the _testing_action_manifest action over a backend's full live RPC mount. The handler closes over a manifest computed once at assembly time from every mounted spec except the protocol methods (PROTOCOL_METHODS) plus this action's own spec — the manifest action is itself a live method, so it must appear in the dump it serves. mounted excludes it (it's appended to the mount *after* the rest is assembled, in build_full_spine_rpc_actions), so its static spec is folded in here; the Rust mirror folds in its own descriptor the same way.

mounted

every other action on the backend's live RPC mount

type readonly RpcAction[]

returns

RpcAction

create_testing_actions
#

testing/cross_backend/testing_reset_actions.ts view source

(deps: AppDeps, options: CreateTestingActionsOptions): RpcAction[] import {create_testing_actions} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the testing RPC actions for a test binary's registry.

Returns the bundled _testing_* actions (module doc). The test binary calls this at server-assembly time and registers the result on its dispatcher. _testing_drain_effects drains deps.audit, so the binary's audit_factory builds the emitter with track_inflight: true.

The reset action's table-wipe list mirrors auth_integration_truncate_tables from testing/db.ts — the canonical "auth tables a between-test reset must clear" set. testing_reset_actions.coverage.test.ts enforces the set-equality invariant so a future auth migration that adds a table to that list without updating this handler fails CI.

deps

type AppDeps

options

returns

RpcAction[]

create_testing_drain_effects_action
#

testing/cross_backend/testing_reset_actions.ts view source

(audit: AuditEmitter): RpcAction import {create_testing_drain_effects_action} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the standalone _testing_drain_effects action over audit — the emitter the backend's handlers emit through (deps.audit), built with track_inflight: true (see the spec doc). Mount it on any test endpoint whose suite asserts on audit rows (the spine binary bundles it via create_testing_actions; in-process suites mount it directly). Answers {ok: true, tracked}, tracked read from audit.is_tracking_inflight().

audit

the backend's emitter, whose in-flight writes the action awaits

returns

RpcAction

create_testing_flood_notifications_action
#

testing/cross_backend/testing_reset_actions.ts view source

(): RpcAction import {create_testing_flood_notifications_action} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the standalone _testing_flood_notifications action (see the spec doc). The spine binary bundles it via create_testing_actions; mount it directly on an in-process WS endpoint whose suite drives an outbound overflow.

returns

RpcAction

create_testing_migration_tracker_action
#

create_testing_origin_check_action
#

create_testing_schema_snapshot_action
#

create_testing_shorten_credential_expiry_action
#

testing/cross_backend/testing_reset_actions.ts view source

(): RpcAction import {create_testing_shorten_credential_expiry_action} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the standalone _testing_shorten_credential_expiry action (see the spec doc).

returns

RpcAction

create_throwing_api
#

actions/rpc_client.ts view source

<TApi extends object>(api_result: TApi): ThrowingApi<TApi> import {create_throwing_api} from '@fuzdev/fuz_app/actions/rpc_client.js';

Wrap a typed RPC client so every call resolves to its unwrapped value or throws an Error carrying the JSON-RPC {code, message, data?} shape.

Implementation is a Proxy because the underlying create_rpc_client return is itself a Proxy with no concrete keys — a key-by-key wrap would need to enumerate the typed surface, which only the consumer's generated ActionsApi interface knows.

Pass-through on non-Result returns is deliberate: sync local_call Proxy methods return values directly (see create_sync_local_call_method above). The Proxy can't distinguish those at get-time, so the wrapper inspects result shape at call-time and only unwraps when it sees a Result. Non-object returns pass through unchanged.

Only {code, data} cross onto the thrown Error — name / stack are left as the Error's own properties so attacker-shaped result.error payloads cannot overwrite them.

Recommended consumer convention: create_frontend_rpc_client ships both shapes by default — api (throwing) for hot-path call sites and api_result (Result) for sites that inspect error.data.reason without try/catch. Result is the protocol primitive; this wrapper is the ergonomic layer over it. Picking is per call site — both Proxies share the same underlying transport.

Catch blocks read err.data?.reason — optional chaining required because JSON-RPC data is spec-level optional.

On unknown string-keyed methods, the get trap returns a function that throws "rpc method not found: <prop>" on invocation — clearer than the JS default "api.foo is not a function". Symbol props and then stay undefined so the Proxy isn't accidentally treated as a thenable (await api would otherwise probe then and trip the thrower).

api_result

typed Result-returning RPC client from create_rpc_client<ActionsApi>(...). The "_result" suffix names what the underlying calls return (Result<{value}, {error}>).

type TApi

returns

ThrowingApi<TApi>

generics

create_throwing_api<TApi extends object>
TApi
constraint object

create_throwing_stub
#

testing/stubs.ts view source

<T = any>(label: string): T import {create_throwing_stub} from '@fuzdev/fuz_app/testing/stubs.js';

Create a Proxy that throws descriptive errors on any property access or method call.

Use for deps that should never be reached during a test. If a test accidentally calls through to a throwing stub, the error message identifies exactly which stub was hit, catching test bugs that would silently pass with {} as any.

JS-internal probes (Symbol, then, constructor, $$typeof) return undefined so the proxy doesn't crash framework-level identity checks; toJSON returns "[throwing_stub:label]" so accidental serialization surfaces the stub's identity in console output rather than silent "{}".

label

descriptive name for error messages (e.g. 'keyring', 'db')

type string

returns

T

generics

create_throwing_stub<T = any>
T
default any

throws

  • Error - on any non-internal property access, labeled with the stub name and the offending property.

create_uuid
#

create_validated_broadcaster
#

realtime/sse.ts view source

<T extends SseNotification>(broadcaster: { broadcast: (channel: string, data: T) => void; }, event_specs: EventSpec[], log: Logger): { broadcast: (channel: string, data: T) => void; } import {create_validated_broadcaster} from '@fuzdev/fuz_app/realtime/sse.js';

Create a broadcaster that validates events in DEV mode.

In DEV: warns on unknown methods and invalid params. In production: passes through with zero overhead.

broadcaster

duck-typed broadcaster (e.g. SseRegistry)

type { broadcast: (channel: string, data: T) => void; }

event_specs

event specs to validate against

type EventSpec[]

log

logger used to emit DEV warnings on unknown methods or param mismatches

type Logger

returns

{ broadcast: (channel: string, data: T) => void; }

validated broadcaster wrapper (passthrough in production)

generics

create_validated_broadcaster<T extends SseNotification>
T
constraint SseNotification

create_ws_socket_events
#

actions/ws_socket_session.ts view source

internal

(endpoint: WsSocketEndpoint, upgrade: WsSocketUpgrade): WSEvents<unknown> import {create_ws_socket_events} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

Build the Hono WSEvents for one upgraded socket: admission, the receive watchdog, the credential deadline, frame dispatch, and close (module doc).

endpoint

the endpoint-level values, shared by all of its sockets

upgrade

what the upgrade request captured for this socket

returns

WSEvents<unknown>

the adapter's onOpen / onMessage / onClose for the socket

create_ws_test_harness
#

testing/ws_round_trip.ts view source

(options: CreateWsTestHarnessOptions): WsTestHarness import {create_ws_test_harness} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Create a WebSocket test harness for the given specs + handlers.

Registers against a throwaway Hono app with a fake upgradeWebSocket; the captured events factory is invoked per connect() with a synthesized Hono context carrying the requested auth identity. Returned clients drive the real onOpen/onMessage/onClose path against a real WSContext.

options

returns

WsTestHarness

create_ws_transport
#

testing/transports/ws_transport.ts view source

(options: WsTransportOptions): Promise<WsTransportClient> import {create_ws_transport} from '@fuzdev/fuz_app/testing/transports/ws_transport.js';

Build a real-upgrade WS client pinned to options.base_url + ws_path.

Resolves once the upgrade succeeds and the socket is in OPEN state; rejects if the upgrade is refused (401, allowlist rejection, network failure). Incoming messages are JSON-parsed and pushed onto the messages array; wait_for checks already-received messages first before waiting for new arrivals.

An open socket is not yet an admitted connection: both spines answer the handshake first and admit the connection a credential re-check later, and deliver no server-initiated message to it until then. Requests sent meanwhile are queued and answered, so a suite that only round-trips can use this directly; one that waits for a push (a broadcast, a targeted notification) opens with create_admitted_ws_transport instead.

options

returns

Promise<WsTransportClient>

throws

  • Error - if the upgrade fails (status, network) — the rejection message carries the underlying error so the test surfaces the real cause rather than hanging.

create_x_accel_config
#

server/x_accel.ts view source

(redirect_prefix: string, nginx_config: string): { redirect_prefix: string; } & $brand<"XAccelConfig"> import {create_x_accel_config} from '@fuzdev/fuz_app/server/x_accel.js';

Build a validated XAccelConfig, asserting the nginx config serves redirect_prefix only from safe locations (the module-level rules).

The make-impossible-states gate: serving can only emit X-Accel-Redirect into a location proven internal; and header-carrying at boot.

redirect_prefix

the X-Accel redirect prefix, ending in / (e.g. /_facts/)

type string

nginx_config

the nginx config string to validate against

type string

returns

XAccelConfig

the validated XAccelConfig carrying redirect_prefix

throws

  • `XAccelConfigError` carrying every violation `validate_facts_internal_location` finds — a prefix not ending in `/`, a missing, public, header-less, re-headered, exact-match, or regex-shadowed facts location, or `ssi` / headers-more reaching it.

CreateAccountInput
#

auth/account_schema.ts view source

CreateAccountInput import type {CreateAccountInput} from '@fuzdev/fuz_app/auth/account_schema.js';

username

type string

password_hash

type string

email?

type string | null

CreateAppBackendOptions
#

server/app_backend.ts view source

CreateAppBackendOptions import type {CreateAppBackendOptions} from '@fuzdev/fuz_app/server/app_backend.js';

Input for create_app_backend().

keyring is passed pre-validated — callers handle their own error reporting (e.g., tx uses runtime.exit(1) on invalid keys).

read_secure_file

Hardened secret-file read for the bootstrap token — pass the runtime's read_secure_file (see FsSecureReadDeps). Rejects symlinks, non-regular files, group/other-accessible modes, and oversized files.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

delete_file

Delete a file.

type (path: string): Promise<void>

path

type string
returns Promise<void>

database_url

Database connection URL (postgres://, file://, or memory://).

type string

keyring

Validated cookie signing keyring.

type Keyring

password

Password hashing implementation. Use argon2_password_deps in production.

type PasswordHashDeps

log?

Structured logger instance. Omit for default (new Logger('server')).

type Logger

audit_factory

Build the bound AuditEmitter once the backend's pool Db + Logger exist. Required — the factory owns listener composition and AuditLogConfig selection without create_app_backend holding a default. Typical body:

audit_factory: ({db, log}) => create_audit_emitter({ db, log, on_audit_event, audit_log_config, })

Additional listeners (factory-managed audit SSE, the revocation guard) are registered at create_app_server time via audit.add_listener(...).

type (params: { db: Db; log: Logger; }): AuditEmitter

params

type { db: Db; log: Logger; }
returns AuditEmitter

connection_closer?

The closer to put on AppDeps.connection_closer. Omit for a fresh one — the usual case, since create_app_server adds the transports it mounts. Pass one built up front (create_realtime_closer()) when a transport is constructed before the backend and has to be added to the same closer the handlers will hold — e.g. an audit stream from create_audit_log_sse whose listener audit_factory registers.

type RealtimeCloser

migration_namespaces?

Additional migration namespaces to run after the builtin auth namespace. The shared schema_version table records one row per applied migration (namespace, name, sequence); order is append-only so forward-only guarantees hold per-namespace.

A name in reserved_migration_namespaces is rejected at startup (run_migrations refuses it before touching the database) unless the entry is the spine's own CELL_MIGRATION_NS / FACT_MIGRATION_NS object; auth always runs first, so it is never passed here. Omit for no extra namespaces. This is the only place to splice consumer migrations — DB init belongs to the backend lifecycle, not server assembly.

type readonly MigrationNamespace[]

db_checkout_timeout_ms?

Bound on the pool checkouts a request makes, in ms — the server env's FUZ_DB_CHECKOUT_TIMEOUT_MS. Default DEFAULT_DB_CHECKOUT_TIMEOUT_MS (5 s). A failed checkout answers 503 on every surface. Audit writes use db_audit_checkout_timeout_ms instead — the failed-login row a request waits on included. A checkout that waits and then connects can take about twice the bound (db/db_pg.ts, "Checkout timeout"). Postgres only: PGlite and pglet have no pool, and a value passed with one is warned of at boot. Twin of the Rust BaseServerEnv::db_checkout_timeout.

type number

db_audit_checkout_timeout_ms?

Bound on the checkout behind each audit pool write, in ms — the server env's FUZ_DB_AUDIT_CHECKOUT_TIMEOUT_MS. Default DEFAULT_DB_AUDIT_CHECKOUT_TIMEOUT_MS (30 s). A failed login awaits its row, so it can wait up to this bound. Postgres only, like db_checkout_timeout_ms. Twin of the Rust BaseServerEnv::db_audit_checkout_timeout.

type number

CreateAuditEmitterOptions
#

auth/audit_emitter.ts view source

CreateAuditEmitterOptions import type {CreateAuditEmitterOptions} from '@fuzdev/fuz_app/auth/audit_emitter.js';

db

Pool-level Db. Captured by every emit call.

type Db

log

Logger for write + listener-callback failures.

type Logger

on_audit_event?

Initial listener — registered as the first listener when set. Omit for backends that compose listeners post-assembly (e.g. via audit_log_sse).

type ((event: AuditLogEvent) => void) | null

audit_log_config?

Audit-log config. Defaults to builtin_audit_log_config. Consumer- extended configs from create_audit_log_config({extra_events}) get registered here once at backend assembly.

type AuditLogConfig

emit_decorator?

Test-only hook to wrap emit at construction time. The decorated function is captured by emit_role_grant_target's closure and is the function exposed on the returned AuditEmitter, so both call shapes route through it — see EmitDecorator for the rationale.

Leave unset in production — it is a test instrumentation seam.

type (inner: AuditEmitFn): AuditEmitFn

inner

returns AuditEmitFn

track_inflight?

Track every in-flight emit write so drain_inflight can await it. Defaults to false, which makes drain_inflight resolve at once.

Test binaries only. A transport can reply before a request's writes settle — a WebSocket message is answered before its effects flush — so a test reading the audit log after a reply needs this barrier. In production the tracking is pure overhead and nothing drains it. Twin of the Rust fuz_auth AuditEmitter::new_with_inflight_tracking.

type boolean

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, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | null>>

CreateBroadcastApiOptions
#

actions/broadcast_api.ts view source

CreateBroadcastApiOptions import type {CreateBroadcastApiOptions} from '@fuzdev/fuz_app/actions/broadcast_api.js';

registry

The connection registry broadcasts go out on — typically the WS endpoint's ConnectionRegistry (AppServer.ws_endpoints[path], or the one a WsEndpointSpec.registry supplies).

type BroadcastTarget

specs

Notification specs to expose as broadcast methods. Typically the remote_notification specs whose initiator is backend (or both). Other kinds are accepted — the helper only uses spec.method and spec.input — but the typical use is notifications.

type readonly ActionSpecUnion[]

log?

Logger for validation/send errors. Defaults to a [broadcast] namespace.

type Logger | null

should_deliver?

Optional per-connection ACL predicate. When set, the broadcast fans out via the registry's broadcast_filtered — each connection's identity is checked before the message is sent. When unset, the registry broadcasts unfiltered via registry.broadcast.

type (connection: Readonly<ConnectionIdentity>, method: string, input: unknown): boolean

connection

type Readonly<ConnectionIdentity>

method

type string

input

type unknown
returns boolean

CreateDatabaseOptions
#

CreateDbOptions
#

CreateDbResult
#

db/create_db.ts view source

CreateDbResult import type {CreateDbResult} from '@fuzdev/fuz_app/db/create_db.js';

Result of database initialization.

db

type Db

close

Close the database connection. Bound to the actual driver at construction.

type (): Promise<void>

returns Promise<void>

db_type

type DbType

db_name

type string

CreateFakeWsOptions
#

testing/ws_round_trip.ts view source

CreateFakeWsOptions import type {CreateFakeWsOptions} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Options for create_fake_ws.

stalled?

Start with the client not reading (FakeWsSocket.stalled).

type boolean

CreateFrontendRpcClientOptions
#

actions/frontend_rpc_client.ts view source

CreateFrontendRpcClientOptions<TApi> import type {CreateFrontendRpcClientOptions} from '@fuzdev/fuz_app/actions/frontend_rpc_client.js';

generics

CreateFrontendRpcClientOptions<TApi extends object = object>
TApi
constraint object
default object

specs

Action specs the typed Proxy can dispatch. Methods absent from this list silently return undefined from the Proxy — the generic TApi cannot constrain runtime membership, so consumers must keep this list in sync with the typed surface (codegen recommended).

Protocol actions (heartbeat, cancel) are not auto-spread — they're filtered out of generated action_specs by codegen's include_protocol_actions: false default and consumers spread them in explicitly so the contract stays visible at every registration site. For WS-using consumers, spread protocol_action_specs from actions/protocol.ts here: specs: [...protocol_action_specs, ...action_specs]. HTTP-only consumers can omit them.

type readonly ActionSpecUnion[]

path?

HTTP RPC endpoint path for the default FrontendHttpTransport. Defaults to /api/rpc. Ignored when transports is provided.

type string

http_headers?

Extra headers on every HTTP RPC call — see FrontendHttpTransportOptions.headers.

type Record<string, string>

max_get_url_length?

The longest path and query a read is sent as a GET with — see FrontendHttpTransportOptions.max_get_url_length.

type number

ws?

A WebSocket lane: a FrontendWebsocketTransport over client, registered after the HTTP transport, which every request_response method routes to unless transport_for_method says otherwise — the HTTP transport stays the fallback while the socket is down. Server-pushed requests and notifications on the socket dispatch through the peer (lookup_action_handler).

type FrontendRpcClientWsOptions

transports?

Optional explicit transport list. When provided, the default FrontendHttpTransport(path) is not registered — the caller is responsible for at least one ready transport. Exclusive with ws, http_headers, and max_get_url_length, which configure the built-in transports.

type readonly Transport[]

transport_for_method?

Optional per-method transport selector — pure pass-through to create_rpc_client. Return the transport name to use for a given method, or undefined to fall back to the peer's default selection. With ws set it defaults to routing every request_response method to the WebSocket.

Useful when methods are registered on different backend dispatchers (e.g. streaming actions on WS, REST RPC on HTTP) — a tx-style mixed setup. Per-call RpcClientCallOptions.transport_name overrides this for individual dispatches.

type (method: string): string | undefined

method

type string
returns string | undefined

on_action_event?

Optional callback fired once per dispatched action — pure pass-through to create_rpc_client. Used by zzz-style consumers that thread the ActionEvent into a reactive cell (add_from_json + listen_to_action_event) for pending / failed / value derivations.

event.spec.method and event.data.method narrow to keyof TApi & string — drop the as ActionMethod cast at the call site when TApi is a generated ActionsApi interface.

type (event: ActionEvent<keyof TApi & string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | ... 3 more ... | "failed">): void

event

type ActionEvent<(keyof TApi) & string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">
returns void

lookup_action_handler?

Optional handler resolver. Wired onto environment.lookup_action_handler — the registry the dispatcher uses to find handlers for inbound messages and lifecycle phases. Defaults to () => undefined, which is fine for HTTP-only frontends that never receive a server-pushed notification or register a receive_error recovery hook.

Common reasons to provide this:

  • Server-pushed notifications over WS — return a handler for (method, 'receive') so a remote_notification arriving on the socket dispatches to your subscriber bus (tx-style).
  • Per-method retry / telemetry on errors — return a handler for (method, 'receive_error'). Note that as of the extract_action_result fix, a missing handler already produces {ok: false, error} — the stub is no longer required just to surface server errors.

type (method: string, phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): ((event: any) => any) | undefined

method

type string

phase

returns ((event: any) => any) | undefined

log?

Where dispatch diagnostics go — pure pass-through to environment.log. Unset falls back to console_action_log; null silences them (see resolve_action_log).

type ActionLog | null

CreateInviteInput
#

auth/invite_schema.ts view source

CreateInviteInput import type {CreateInviteInput} from '@fuzdev/fuz_app/auth/invite_schema.js';

Input for creating an invite.

email?

type string | null

username?

type string | null

created_by

type Uuid | null

CreateKeyringResult
#

auth/keyring.ts view source

CreateKeyringResult

type { ok: true; keyring: Keyring; } | { ok: false; errors: string[]; }

import type {CreateKeyringResult} from '@fuzdev/fuz_app/auth/keyring.js';

Result of create_keyring. Discriminated union — callers handle the error case their own way.

CreatePgPoolOptions
#

CreateRoleGrantInput
#

auth/account_schema.ts view source

CreateRoleGrantInput import type {CreateRoleGrantInput} from '@fuzdev/fuz_app/auth/account_schema.js';

actor_id

type Uuid

role

type string

scope_kind?

Machine-readable kind for the scope_id. Required iff scope_id is set; must be null/omitted when scope_id is null. The DB-level role_grant_scope_kind_paired CHECK rejects mismatched pairs.

type string | null

scope_id?

Scope the grant applies to. null / omitted grants a global role_grant.

type Uuid | null

expires_at?

type Date | null

granted_by

type Uuid | null

source_offer_id?

Offer id that produced this role_grant. Set by query_accept_offer; leave unset for direct grants.

type Uuid | null

CreateRoleGrantOfferInput
#

auth/role_grant_offer_schema.ts view source

CreateRoleGrantOfferInput import type {CreateRoleGrantOfferInput} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Input for query_role_grant_offer_create.

expires_at must be supplied — the query layer does not apply a default, so callers can thread their own TTL (typically ROLE_GRANT_OFFER_DEFAULT_TTL_MS).

from_actor_id

type Uuid

to_account_id

type Uuid

to_actor_id?

Optional actor-grain target on the recipient account. When set, query_role_grant_offer_create validates that the actor belongs to to_account_id and stamps the column; accept then matches against this specific actor. Omit (or pass null) for the account-grain default — any actor on to_account_id may accept.

type Uuid | null

role

type string

scope_kind?

Machine-readable kind for the scope_id. Required iff scope_id is set; must be null when scope_id is null (DB-level CHECK rejects the mismatch). Consumer-declared via create_scope_kind_schema(...).

type string | null

scope_id?

type Uuid | null

message?

type string | null

expires_at

type Date

CreateRoleSchemaOptions
#

auth/role_schema.ts view source

CreateRoleSchemaOptions import type {CreateRoleSchemaOptions} from '@fuzdev/fuz_app/auth/role_schema.js';

Optional registries to validate RoleSpec cross-axis fields against at construction time.

credential_types?

Pass create_credential_type_schema() to validate RoleSpec.required_credential_types entries.

type CredentialTypeSchemaResult

scope_kinds?

Pass create_scope_kind_schema() to validate RoleSpec.applicable_scope_kinds entries.

type ScopeKindSchemaResult

grant_paths?

Pass create_grant_path_schema() to validate RoleSpec.grant_paths entries.

type GrantPathSchemaResult

CreateRpcClientOptions
#

actions/rpc_client.ts view source

CreateRpcClientOptions<TApi> import type {CreateRpcClientOptions} from '@fuzdev/fuz_app/actions/rpc_client.js';

Options for create_rpc_client.

generics

CreateRpcClientOptions<TApi extends object = object>
TApi
constraint object
default object

peer

type ActionDispatcher

environment

type ActionEventEnvironment

on_action_event?

Optional callback fired once per dispatched action with the live ActionEvent. Consumers wire reactive state here — e.g. zzz's Actions cell calls add_from_json + listen_to_action_event inside the callback so its history stays decoupled from the rpc_client surface.

event.spec.method and event.data.method narrow to keyof TApi & string — drop the as ActionMethod cast at the call site when TApi is a generated ActionsApi interface.

type (event: ActionEvent<keyof TApi & string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | ... 3 more ... | "failed">): void

event

type ActionEvent<(keyof TApi) & string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">
returns void

transport_for_method?

Optional per-method transport selector. When provided, the client calls peer.send(msg, {transport_name}) with the returned transport for each request_response / remote_notification dispatch. Returning undefined falls back to the peer's default selection.

type (method: string): string | undefined

method

type string
returns string | undefined

CreateRpcEndpointOptions
#

actions/action_rpc.ts view source

CreateRpcEndpointOptions import type {CreateRpcEndpointOptions} from '@fuzdev/fuz_app/actions/action_rpc.js';

Options for create_rpc_endpoint.

path

Mount path for the endpoint (e.g., /api/rpc).

type string

actions

RPC actions to serve.

type RpcAction[]

log

Logger instance for handler context.

type Logger

action_ip_rate_limiter

Per-IP rate limiter consulted for actions whose spec declares rate_limit: 'ip' or 'both'. Required, with no default: pass null to turn the IP check off, so an unthrottled endpoint is a choice the call site states rather than an option it forgot. create_app_server passes its own (a default limiter unless configured). Share one instance with the WebSocket action dispatcher — one budget per action, not per transport.

type RateLimiter | null

action_account_rate_limiter

Per-account rate limiter consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on request_context.account.id (account-grain — billed to the authenticated account regardless of which actor was resolved). Required, with no default: pass null to turn the account check off. Share one instance with the WebSocket action dispatcher.

type RateLimiter | null

CreateServeFactRouteSpecOptions
#

server/serve_fact_route.ts view source

CreateServeFactRouteSpecOptions import type {CreateServeFactRouteSpecOptions} from '@fuzdev/fuz_app/server/serve_fact_route.js';

facts_dir

Absolute path of the facts directory. Used for the dev/test streaming path.

type string

x_accel?

When set, external facts return an X-Accel-Redirect pointing at ${x_accel.redirect_prefix}<shard>/<rest> — nginx's internal facts location serves the bytes. When unset, external facts stream from <facts_dir>/<shard>/<rest> directly. Production sets a validated XAccelConfig (built via create_x_accel_config, which proves the facts location is internal;); tests + dev leave it unset.

type XAccelConfig

log

type Logger

CreateSessionAndSetCookieOptions
#

auth/session_middleware.ts view source

CreateSessionAndSetCookieOptions import type {CreateSessionAndSetCookieOptions} from '@fuzdev/fuz_app/auth/session_middleware.js';

keyring

Keyring for cookie signing.

type Keyring

deps

Query deps (needs db for session creation).

type QueryDeps

c

Hono context for setting the cookie.

type Context<any, any, {}>

account_id

The account to create a session for.

type string

session_options

Session cookie configuration.

type SessionOptions<string>

max_sessions?

Per-account session cap (null to skip enforcement).

type number | null

CreateStubAppBackendOptions
#

testing/stubs.ts view source

CreateStubAppBackendOptions import type {CreateStubAppBackendOptions} from '@fuzdev/fuz_app/testing/stubs.js';

deps?

The backend's deps. Default a fresh create_stub_app_deps().

type AppDeps

close?

The backend's close. Default one that does nothing.

type (): Promise<void>

returns Promise<void>

CreateTestAccountOptions
#

testing/cross_backend/setup.ts view source

CreateTestAccountOptions import type {CreateTestAccountOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Options for TestFixture.create_account — mints an additional bootstrapped account alongside the keeper. Matches the existing TestApp.create_account signature so the migration to fixture-style reads is a one-site call rewrite per use.

username?

type string

readonly

password_value?

type string

readonly

roles?

type string[]

readonly

email?

Optional email to store on the account, exercising the username-or-email login lookup. Cross-process this rides the production signup body (claimed against the username-scoped invite, which matches regardless of the email); in-process it lands directly on the inserted row. Must be a valid Email (the cross-process signup validates it) and unique per test (the LOWER(email) partial-unique index rejects collisions).

type string

readonly

CreateTestAccountWithCredentialsOptions
#

testing/app_server.ts view source

CreateTestAccountWithCredentialsOptions import type {CreateTestAccountWithCredentialsOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Options for bootstrap_test_keeper and create_test_account_with_credentials.

Same shape for both — the data inserted is identical; the only behavioral difference is the lock flip on the keeper path.

db

type Db

keyring

type Keyring

session_options

type SessionOptions<string>

password

type PasswordHashDeps

username?

type string

password_value?

type string

roles?

type string[]

email?

Optional email stored on the account row — exercises the username-or-email login lookup.

type string

CreateTestAppAccountArgs
#

testing/app_server.ts view source

CreateTestAppAccountArgs import type {CreateTestAppAccountArgs} from '@fuzdev/fuz_app/testing/app_server.js';

Args for TestApp.create_account — mint an additional account alongside the keeper. Mirrors the cross-process CreateTestAccountOptions (the two create_account paths share a shape so fixture call sites read identically), kept local to avoid a cross-module cycle with cross_backend/setup.ts.

username?

type string

password_value?

type string

roles?

type string[]

email?

Optional email stored on the account row — exercises the username-or-email login lookup.

type string

CreateTestAppOptions
#

testing/app_server.ts view source

CreateTestAppOptions import type {CreateTestAppOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Configuration for create_test_app.

inheritance

create_route_specs

Route spec factory — called with the assembled AppServerContext.

type (context: AppServerContext): RouteSpec[]

context

returns RouteSpec[]

rpc_endpoints?

RPC endpoints mounted by create_app_server — eager array or (ctx: AppServerContext) => Array<RpcEndpointSpec> factory. Single source of truth; the equivalent slot under app_options is Omit'd so setup-time path lookup and runtime dispatch read from one place. Symmetric with the suite-level rpc_endpoints option on describe_standard_admin_integration_tests etc.

type RpcEndpointsSuiteOption

bootstrap?

Bootstrap config — symmetric with AppServerOptions.bootstrap. Same single-source-of-truth precedent as rpc_endpoints: setup-time surface generation and runtime dispatch both read this slot, so the equivalent field under app_options is Omit'd. Discriminated union over {mode: 'disabled' | 'surface_only' | 'live'}. Omit (or pass {mode: 'disabled'}) for the default — no bootstrap route mounted.

type BootstrapServerOptions

app_options?

Optional overrides for AppServerOptions. Excludes fields create_test_app manages directly: backend, session_options, create_route_specs, rpc_endpoints, bootstrap (top-level slots above).

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

CreateTestAppSurfaceSpecOptions
#

testing/stubs.ts view source

CreateTestAppSurfaceSpecOptions import type {CreateTestAppSurfaceSpecOptions} from '@fuzdev/fuz_app/testing/stubs.js';

session_options

Consumer's session config (required — varies per app).

type SessionOptions<string>

create_route_specs

Consumer's route factory — receives the same AppServerContext as production.

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

env_schema?

Env schema for surface generation (default: BaseServerEnv).

type ZodObject<$ZodLooseShape, $strip>

event_specs?

SSE event specs for surface generation.

type EventSpec[]

rpc_endpoints?

RPC endpoint specs for surface generation.

Accepts either an array (eager) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> — symmetric with create_app_server's rpc_endpoints option, so consumers can pass the same factory to both entry points. The factory runs once against the stub AppServerContext this helper already builds.

type RpcEndpointSpec[] | ((ctx: AppServerContext) => RpcEndpointSpec[])

ws_endpoints?

WebSocket endpoint specs for surface generation. Symmetric with create_app_server's ws_endpoints option — pass the same value to both entry points so the attack surface tests see the same WS endpoints production auto-mounts. The factory runs once against the stub AppServerContext this helper already builds. No create_upgrade_websocket needed — this helper produces an AppSurfaceSpec only, never mounts.

type readonly WsEndpointSpec[] | ((ctx: AppServerContext) => readonly WsEndpointSpec[])

allowed_origins?

The server's origin allowlist — AppServerOptions.allowed_origins. Read only as the default for a ws_endpoints spec that declares no allowed_origins, as create_app_server does; required when any spec omits its own — without it, such a spec throws, since the surface records the exact patterns the upgrade gate matches.

type readonly RegExp[]

transform_middleware?

Transform middleware array — AppServerOptions.transform_middleware. Receives shallow copies of a stub trusted_proxy spec (at '*') followed by the stub auth stack, in a fresh array, as create_app_server passes the real ones. Throws, like create_app_server, if the result doesn't keep each of those specs mounted once at its original path in its original order (assert_middleware_stack_mounted).

type (specs: MiddlewareSpec[]): MiddlewareSpec[]

specs

returns MiddlewareSpec[]

origin_carve_outs?

The origin check's carve-outs — AppServerOptions.origin_carve_outs. Recorded on the stub origin spec, so the surface leaves the check's 403 off the carved-out routes as the live server's does; refused, like create_app_server, when one has no reason or overlaps the auth middleware's scope.

type MiddlewareCarveOut[]

bootstrap?

Bootstrap config — symmetric with AppServerOptions.bootstrap. Discriminated by mode: 'disabled' skips the route (same as omission), 'surface_only' mounts the route shape, 'live' accepts a token_path for production symmetry (surface assembly only uses it for shape symmetry; the value is a live-execution concern handled by create_test_app → create_app_server).

Surface assembly only reads route_prefix (default '/api/account').

type BootstrapServerOptions

surface_route?

Symmetric with AppServerOptions.surface_route — true lists the admin-only GET /api/surface, which create_app_server mounts only when opted in. Pass the same value to both entry points so the surface tests probe the route exactly when the live server serves it.

type boolean

default false

daemon_token?

List the daemon_token middleware layer, as create_app_server mounts it when given a daemon_token_state. Pass true exactly when the live server gets one — create_test_app always passes one.

type boolean

default false

CreateTestingActionsOptions
#

testing/cross_backend/testing_reset_actions.ts view source

CreateTestingActionsOptions import type {CreateTestingActionsOptions} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

session_options

Session cookie options — the reset action uses these when signing the fresh keeper's (and any extra accounts') session cookies. Pass the same SessionOptions the live create_app_server call was wired with.

type SessionOptions<string>

readonly

daemon_token_state

Daemon-token runtime state — the reset action points state.keeper_account_id at the freshly seeded keeper once its transaction commits (a post-commit effect, so a reset that rolls back leaves the previous keeper cached). Pass the same DaemonTokenState instance the daemon-token middleware reads.

type DaemonTokenState

readonly

reset_state?

Consumer-supplied callback invoked first, before the auth-table wipe and keeper re-seed, passed the same transactional Db the auth wipes then run on. It runs while the previous test's accounts and actors still exist, so a domain table with a restricting FK to actor / account empties before the auth wipe deletes what it references; it must not depend on the fresh keeper, which doesn't exist yet. DB-domain consumers (e.g. fuz_forge truncating its cell / fact / file tables) MUST use this db rather than a separately-pooled connection — under the in-memory drivers' single connection (pglet, PGlite) a second connection deadlocks against this still-open transaction. testing_zzz_server clears in-memory workspace registry + terminals + scoped-FS scratch (ignores db); the spine binaries (testing/cross_backend/full_spine_mount.ts and the Rust testing_spine_stub) empty the cell and fact tables. Runs inside the same RPC dispatch as the auth-table writes, so a throw rolls the whole reset back and surfaces to the caller as a JSON-RPC error and the per-test fixture short-circuits.

type (db: Db): void | Promise<void>

readonly

db

type Db
returns Promise<void> | void

CreateWsTestHarnessOptions
#

testing/ws_round_trip.ts view source

CreateWsTestHarnessOptions import type {CreateWsTestHarnessOptions} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

actions

The actions registered on this endpoint — matches the shape register_action_ws accepts. Each entry is a {spec, handler?} tuple; shared fuz_app primitives (like heartbeat_action) can be spread in alongside consumer-specific actions.

type readonly Action<ActionSpecUnion>[]

registry?

Pass a pre-created registry to share with a broadcast API.

type ConnectionRegistry

receive_timeout?

Threaded through to register_action_ws. Defaults to null (off) in tests — fake timers + receive-silence detection need explicit opt-in and per-test tuning to avoid spurious closes.

type number | null

log?

Optional logger. Defaults to a silent [ws-test] logger.

type Logger

on_socket_open?

Threaded straight through to register_action_ws.

type (ctx: SocketOpenContext): void | Promise<void>

ctx

returns void | Promise<void>

on_socket_close?

Threaded straight through to register_action_ws.

type (ctx: SocketCloseContext): void | Promise<void>

ctx

returns void | Promise<void>

on_request?

Optional responder for server-initiated requests, applied to every connect(). Purely additive (and unexercised until the TS server gains server-initiated requests): with it set, an inbound server→client request is answered rather than surfaced as a message; without it, behavior is unchanged. Mirrors the cross-process create_ws_transport seam so suite bodies share one shape.

type (request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }): WsResponderOutcome | Promise<WsResponderOutcome>

request

credential_deadline_after
#

auth/credential_deadline.ts view source

(remaining_ms: readonly (number | null)[], now?: number): number | null import {credential_deadline_after} from '@fuzdev/fuz_app/auth/credential_deadline.js';

The deadline now plus the shortest of remaining_ms, as a monotonic performance.now() timestamp, or null when no entry carries one.

A negative or NaN entry — a row its own expires_at > NOW() check already refused, read at the boundary — counts as zero.

remaining_ms

each source's remaining lifetime in ms, null for none

type readonly (number | null)[]

now?

the monotonic clock reading to anchor to

type number
optional default performance.now()

returns

number | null

the deadline, or null for a credential with no expiry

CREDENTIAL_DEADLINE_TIMER_CAP_MS
#

auth/credential_deadline.ts view source

3600000 import {CREDENTIAL_DEADLINE_TIMER_CAP_MS} from '@fuzdev/fuz_app/auth/credential_deadline.js';

The longest single timer the deadline wait sets — one hour, far inside setTimeout's ceiling (2^31 - 1 ms, about 24.8 days, past which a timer fires at once) and the Rust twin's tokio ceiling, and short enough that a resume from suspend is noticed within it. A longer deadline is reached through several.

CREDENTIAL_TYPE_API_TOKEN
#

auth/credential_type_schema.ts view source

"api_token" import {CREDENTIAL_TYPE_API_TOKEN} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

HTTP Authorization: Bearer API token credential. The wire literal 'api_token' aligns with the api_token storage table name; the constant is named _API_TOKEN (not _BEARER) to keep wire and storage nomenclature in lockstep.

CREDENTIAL_TYPE_DAEMON_TOKEN
#

auth/credential_type_schema.ts view source

"daemon_token" import {CREDENTIAL_TYPE_DAEMON_TOKEN} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Daemon-token credential — filesystem proof for the keeper account.

CREDENTIAL_TYPE_NAME_REGEX
#

credential_type_required_error
#

actions/perform_action.ts view source

(required_credential_types: readonly string[]): { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; } import {credential_type_required_error} from '@fuzdev/fuz_app/actions/perform_action.js';

The credential-type 403 — one builder, so the dispatcher's gate below and a handler that gates a credential channel on its input (account_delete's self path) emit a byte-identical error. Twin of the Rust fuz_auth::credential_type_required.

required_credential_types

the channels that would be admitted

type readonly string[]

returns

JsonrpcErrorObject

the forbidden error object with reason credential_type_required

CREDENTIAL_TYPE_SESSION
#

CredentialDeadlineCrossTestOptions
#

testing/cross_backend/credential_deadline.ts view source

CredentialDeadlineCrossTestOptions import type {CredentialDeadlineCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/credential_deadline.js';

inheritance

setup_test

Fixture producer (default_cross_process_setup(handle)), called once for the suite.

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags — the WebSocket cases gate on ws, the stream case on sse.

type BackendCapabilities

readonly

sse_path?

The audit-log stream path. Default /api/admin/audit/stream.

type string

readonly

rpc_path?

JSON-RPC endpoint path. Default SPINE_RPC_PATH.

type string

readonly

ws_account_roles?

Roles each account this suite opens a socket for is created with, for a backend whose WS upgrade requires a role (an admin-gated /api/ws refuses a roleless account's upgrade). Each must be admin-grantable — the fixture offers it. Default none.

type readonly string[]

readonly

CredentialHeaderRobustnessCrossTestOptions
#

testing/cross_backend/credential_header_robustness.ts view source

CredentialHeaderRobustnessCrossTestOptions import type {CredentialHeaderRobustnessCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/credential_header_robustness.js';

Options for the credential-header robustness probe — needs the raw URL + a valid daemon token.

base_url

Base URL the spawned backend is reachable at (e.g. http://localhost:1178).

type string

readonly

rpc_path?

RPC endpoint path to target. Default /api/rpc.

type string

readonly

daemon_token

A valid daemon token (keeper) — handle.daemon_token.

type string

readonly

CredentialTypeMeta
#

auth/credential_type_schema.ts view source

CredentialTypeMeta import type {CredentialTypeMeta} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Per-credential-type metadata. description is admin-UI-facing copy (mirrors RoleSpec.description and ScopeKindMeta.description). Open shape so v2 can extend without a breaking change.

description?

type string

CredentialTypeName
#

CredentialTypeRequiredError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "credential_type_required"; required_credential_types: readonly string[]; } import {CredentialTypeRequiredError} from '@fuzdev/fuz_app/http/error_schemas.js';

Credential-type error — returned by the dispatcher's post-authorization credential gate (and the require_credential_types REST middleware) when the request's credential type isn't in the route's auth.credential_types allowlist.

required_credential_types carries what the route declared (['daemon_token'] for keeper; future gates carry their own labels). Symmetric with PermissionError's required_roles: clients see what the route demanded, not what their credential is.

error

type "credential_type_required"

required_credential_types

type readonly string[]

[key: string]

type unknown

CredentialTypeSchemaResult
#

auth/credential_type_schema.ts view source

CredentialTypeSchemaResult import type {CredentialTypeSchemaResult} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

The result of create_credential_type_schema — a Zod schema and metadata map.

CredentialType

Zod schema that validates credential-type name strings against the registered set (builtins + consumer-declared). Use at I/O boundaries (admin UIs, codegen) and as the construction-time check inside create_role_schema for every RoleSpec.required_credential_types entry.

type ZodType<string, unknown, $ZodTypeInternals<string, unknown>>

credential_types

Map of every registered credential-type to its metadata. Keyed by name. Read at startup by admin / codegen surfaces.

type ReadonlyMap<string, CredentialTypeMeta>

cross_account_purge
#

testing/cross_backend/revocation_drivers.ts view source

(fixture: TestFixture, rpc_path: string, account_id: string): Promise<void> import {cross_account_purge} from '@fuzdev/fuz_app/testing/cross_backend/revocation_drivers.js';

Purge account_id over the keeper's daemon token (account_purge), asserting success. The request carries no Origin, as a daemon-token caller's wouldn't.

fixture

the per-test fixture

rpc_path

the RPC endpoint path

type string

account_id

the account to purge

type string

returns

Promise<void>

cross_admin_token_revoke_all
#

testing/cross_backend/revocation_drivers.ts view source

(fixture: TestFixture, rpc_path: string, account_id: string): Promise<void> import {cross_admin_token_revoke_all} from '@fuzdev/fuz_app/testing/cross_backend/revocation_drivers.js';

Revoke every API token of account_id over the keeper's admin session (admin_token_revoke_all), asserting success.

fixture

the per-test fixture; its keeper holds the admin role

rpc_path

the RPC endpoint path

type string

account_id

the account whose tokens to revoke

type string

returns

Promise<void>

cross_drain_effects
#

testing/cross_backend/audit_cross_helpers.ts view source

(fixture: AuditReadFixture, rpc_path?: string): Promise<void> import {cross_drain_effects} from '@fuzdev/fuz_app/testing/cross_backend/audit_cross_helpers.js';

Await the backend's in-flight audit writes via _testing_drain_effects, so a following audit_log_list sees every row emitted before the call. The backend must mount the action — every spine test binary does; in-process suites mount create_testing_drain_effects_action.

Refuses an answer without tracked: true. A binary whose audit emitter was built without in-flight tracking answers the drain at once, waiting on nothing, and its audit reads race the writes — an absent row then reads as a pass. Every audit read runs through here, so each one checks the barrier is live.

fixture

the per-test fixture, whose daemon token authorizes the drain

rpc_path?

the RPC endpoint the drain action is mounted on

type string
optional default SPINE_RPC_PATH

returns

Promise<void>

cross_post_as
#

testing/cross_backend/revocation_drivers.ts view source

(fixture: TestFixture, account: TestAccount, path: string, body: unknown): Promise<void> import {cross_post_as} from '@fuzdev/fuz_app/testing/cross_backend/revocation_drivers.js';

POST a JSON body to a REST route on account's session, asserting 200.

fixture

the per-test fixture whose fresh transport carries the request

account

the account whose session cookie authenticates it

path

the REST route path

type string

body

the JSON request body

type unknown

returns

Promise<void>

cross_put_fact
#

testing/cross_backend/fact_serving.ts view source

(fixture: Pick<TestFixture, "fresh_transport" | "create_daemon_token_headers">, rpc_path: string, input: CrossPutFactInput): Promise<...> import {cross_put_fact} from '@fuzdev/fuz_app/testing/cross_backend/fact_serving.js';

Seed an embedded fact over the daemon channel via _testing_put_fact; returns its blake3: hash. A holder is recorded in fact_holder (a deduplicated put included), as a real upload records its uploader.

fixture

the test fixture (its daemon token drives the backdoor)

type Pick<TestFixture, "fresh_transport" | "create_daemon_token_headers">

rpc_path

the backend's RPC path

type string

input

the content, optional content type, and optional holder

returns

Promise<string>

the fact hash

cross_rpc_call
#

testing/cross_backend/cross_rpc.ts view source

(transport: FetchTransport, path: string, method: string, params: unknown, headers: Record<string, string>): Promise<RpcResult> import {cross_rpc_call} from '@fuzdev/fuz_app/testing/cross_backend/cross_rpc.js';

POST a JSON-RPC call over a cross-process FetchTransport with the given auth headers. Distinct from testing/rpc_helpers.ts's app-based rpc_call: this variant drives the cookie-jar FetchTransport the cross-backend harness spawns against, and returns the slim RpcResult the suites read.

transport

the transport to send on

path

the RPC endpoint path

type string

method

the JSON-RPC method

type string

params

the request params

type unknown

headers

auth and other headers, merged over the JSON defaults

type Record<string, string>

returns

Promise<RpcResult>

the response's HTTP status, ok, result and error

throws

  • Error - when the reply is not a JSON object — naming the method, the HTTP status and the head of the body, so a proxy page or a crash reads as what it is

CrossBackendGlobalSetupDeps
#

testing/cross_backend/create_cross_backend_global_setup.ts view source

CrossBackendGlobalSetupDeps import type {CrossBackendGlobalSetupDeps} from '@fuzdev/fuz_app/testing/cross_backend/create_cross_backend_global_setup.js';

What create_cross_backend_global_setup creates databases and spawns backends with — ensure_database_for_url and bootstrap_backend by default.

ensure_database_for_url

type (database_url: string): unknown

readonly

database_url

type string
returns unknown

bootstrap_backend

type (config: BackendConfig, options?: BootstrapBackendOptions | undefined): Promise<BootstrappedBackendHandle>

readonly

config

options?

optional
returns Promise<BootstrappedBackendHandle>

CrossBackendGlobalSetupOptions
#

testing/cross_backend/create_cross_backend_global_setup.ts view source

CrossBackendGlobalSetupOptions import type {CrossBackendGlobalSetupOptions} from '@fuzdev/fuz_app/testing/cross_backend/create_cross_backend_global_setup.js';

configs

Map of derived backend name → BackendConfig factory. The derived name (see derive_name) selects the factory; unknown names throw with the full supported list so a misnamed project surfaces clearly.

type Readonly<Record<string, () => BackendConfig>>

readonly

derive_name?

Derive the backend name from the vitest project name. Default strips cross_backend_(ts_)?.

type (project_name: string): string

readonly

project_name

type string
returns string

provide_key?

Key passed to project.provide (and read by inject in test files). Default 'backend_handle', which provided_context.ts declares on vitest's ProvidedContext; augment it yourself for another key.

type string

readonly

ensure_databases?

Create the database the selected config's env.DATABASE_URL names, on the server it names, when it is missing and the URL is postgres: (ensure_database_for_url — idempotent createdb; a URL like memory:// is left alone). Off by default, so a test role without CREATEDB is never required unless the setup asks.

type boolean

readonly
default false

before_spawn?

Hooks run before a backend's config is built and spawned, by backend name — the Cargo build of a Rust binary (prepare_cargo_binary), so it runs with its output shown and outside the spawn's startup window, and the config can spawn the built binary. Runs only for the selected backend; a key naming no config throws.

type Readonly<Record<string, () => unknown>>

readonly

CrossBackendProjectOptions
#

testing/cross_backend/make_cross_backend_project.ts view source

CrossBackendProjectOptions import type {CrossBackendProjectOptions} from '@fuzdev/fuz_app/testing/cross_backend/make_cross_backend_project.js';

name

vitest project name. create_cross_backend_global_setup derives the backend name from it (by default stripping a cross_backend_(ts_)? prefix), so name projects cross_backend_<backend> (e.g. cross_backend_rust, cross_backend_ts_deno).

type string

readonly

global_setup

Path to the consumer's vitest globalSetup module, relative to the consumer repo root (e.g. './src/test/cross_backend/global_setup.ts'). That module is expected to export a create_cross_backend_global_setup result as its default.

type string

readonly

include?

Test-file globs. Default: ['src/test/cross_backend/*.cross.test.ts'].

type readonly string[]

readonly

exclude?

Globs to exclude from include (e.g. a backend-specific variant file). Default: [].

type readonly string[]

readonly

group_order?

vitest sequence.groupOrder. Default: 3 (runs after unit + db).

type number

readonly

test_timeout?

Per-test timeout in ms. Default: 30_000 — see DEFAULT_TEST_TIMEOUT for why the 5 s vitest default does not fit a spawned-backend suite. Always emitted, so a consumer's root testTimeout never leaks in.

type number

readonly

hook_timeout?

Per-hook timeout in ms for beforeAll / afterAll etc. Default: 30_000. Always emitted, so a consumer's root hookTimeout never leaks in.

type number

readonly

CrossImplBenchEntry
#

testing/cross_backend/bench/run_cross_impl_bench.ts view source

CrossImplBenchEntry import type {CrossImplBenchEntry} from '@fuzdev/fuz_app/testing/cross_backend/bench/run_cross_impl_bench.js';

One backend × one scenario, with its timing result.

backend

type string

readonly

scenario

type string

readonly

result

type BenchmarkResult

readonly

CrossImplBenchResult
#

testing/cross_backend/bench/run_cross_impl_bench.ts view source

CrossImplBenchResult import type {CrossImplBenchResult} from '@fuzdev/fuz_app/testing/cross_backend/bench/run_cross_impl_bench.js';

Full sweep across the supplied backends and scenarios.

backends

Backend names, in the order they were run.

type readonly string[]

readonly

scenarios

Scenario names that ran on at least one backend.

type readonly string[]

readonly

entries

type readonly CrossImplBenchEntry[]

readonly

CrossImplComparisonEntry
#

testing/cross_backend/bench/bench_report.ts view source

CrossImplComparisonEntry import type {CrossImplComparisonEntry} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

One backend's result compared against the reference backend, per scenario.

scenario

type string

readonly

reference

The reference backend (a side of the comparison).

type string

readonly

backend

The compared backend (b side).

type string

readonly

comparison

type BenchmarkComparison

readonly

CrossProcessSetupOptions
#

testing/cross_backend/setup.ts view source

CrossProcessSetupOptions import type {CrossProcessSetupOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

extra_keeper_roles?

Additional roles to grant the fresh keeper on every per-test reset, *in addition to* the [ROLE_KEEPER, ROLE_ADMIN] defaults the _testing_reset action seeds. Cross-process mirror of in-process extra_keeper_roles on default_in_process_suite_options.

Costs nothing extra per test — the _testing_reset action seeds the keeper in a single transaction regardless of how many roles are in the list.

ROLE_ADMIN is already in the default set, so admin-suite consumers usually pass an empty / omitted array. Consumer-defined roles (e.g. teacher) are passed here when the keeper-acting test needs them.

Keeper ≠ admin. Tests that need a *non-admin* secondary account with ROLE_KEEPER declare it via extra_accounts — ROLE_KEEPER's RoleSpec.grant_paths is bootstrap-only, so it can only be granted at the test-binary bootstrap-equivalent step.

type readonly string[]

readonly

extra_accounts?

Bootstrap-time secondary accounts seeded alongside the keeper on every per-test reset. See ExtraAccountSpec for why this is a cradle-only bypass. The reset action seeds them in the same transaction as the keeper.

type readonly ExtraAccountSpec[]

readonly

extra_actors?

Additional actor names to seed on the keeper account, beyond its single bootstrap actor — exposed on fixture.extra_actors. Declare to put the keeper into a multi-actor state so the actor_required / explicit-acting branches are reachable cross-process. See TestFixture.extra_actors.

type readonly string[]

readonly

reject_unknown_audit_metadata_keys?

Hold every audit row the fixture's readers parse to a closed world: metadata keys its event type doesn't declare are refused, not just mistyped or missing ones (assert_audit_event_metadata's reject_unknown_keys). Off by default, since the builtin schemas are open for consumer-added fields; a backend's own spine suite turns it on so neither spine can add an undeclared key. Exposed on fixture.reject_unknown_audit_metadata_keys.

type boolean

readonly

signup_path?

REST signup route fixture.create_account mints through. Default SPINE_ACCOUNT_SIGNUP_PATH; set it for a backend mounting the account routes under another prefix.

type string

readonly

login_path?

REST login route fixture.create_account logs in through. Default SPINE_ACCOUNT_LOGIN_PATH.

type string

readonly

CrossProcessSseTestOptions
#

testing/cross_backend/sse_round_trip.ts view source

CrossProcessSseTestOptions import type {CrossProcessSseTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/sse_round_trip.js';

setup_test

Per-test fixture producer (default_cross_process_setup(handle)). Each case reads the fresh-per-test keeper's session cookies from fixture.transport.cookies() to thread onto the stream. The keeper holds ROLE_ADMIN by default, so it can subscribe to the admin-gated audit stream and drive admin_session_revoke_all.

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags; every case gates on capabilities.sse.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at (e.g. http://localhost:1178).

type string

readonly

sse_path?

SSE stream path on the backend. Defaults to /api/admin/audit/stream.

type string

readonly

rpc_path?

RPC endpoint path (e.g. /api/rpc) used by the data-frame and close-on-revoke cases to fire admin_session_revoke_all / account_session_revoke_all over the keeper's session channel. When omitted, those cases are skipped — they depend on the standard account + admin actions being mounted on the RPC endpoint.

type string

readonly

origin?

Origin for the stream request. Defaults to base_url.

type string

readonly

max_per_scope?

The backend's per-session audit-stream cap, which the cap case opens one stream past. Defaults to AUDIT_LOG_SSE_MAX_PER_SCOPE, the cap both spines apply unless overridden; null (a backend with the cap disabled) skips the case.

type number | null

readonly

logout_path?

REST logout route path, for the logout close case. Default /api/account/logout.

type string

readonly

password_path?

REST password-change route path, for its close case. Default /api/account/password.

type string

readonly

CrossProcessWsTestOptions
#

testing/cross_backend/ws_round_trip.ts view source

CrossProcessWsTestOptions import type {CrossProcessWsTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_round_trip.js';

inheritance

setup_test

Per-test fixture producer (default_cross_process_setup(handle)). The authenticated cases upgrade on the fresh-per-test keeper's session cookie (open_session_ws).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags; every case gates on capabilities.ws.

type BackendCapabilities

readonly

rpc_path?

RPC endpoint path (e.g. /api/rpc) used by the close-on-revoke case to fire account_session_revoke_all over the keeper's session channel. When omitted, that case is skipped — it depends on the standard account actions being mounted on the RPC endpoint.

type string

readonly

max_message_bytes?

The backend's inbound WebSocket message cap, in bytes — the oversized-message case sends a message just past it. Defaults to DEFAULT_WS_MAX_MESSAGE_BYTES; pass the consumer's value when it raises the server cap.

type number

readonly

logout_path?

REST logout route path, for the logout close case. Default /api/account/logout.

type string

readonly

password_path?

REST password-change route path, for its close case. Default /api/account/password.

type string

readonly

max_tokens?

The backend's per-account API token cap, which the token-cap close case mints past. Defaults to DEFAULT_MAX_TOKENS; null (a backend with the cap disabled) skips the case.

type number | null

readonly

ws_account_roles?

Roles each second account whose socket a revocation case watches is created with, for a backend whose WS upgrade requires a role (an admin-gated /api/ws refuses a roleless account's upgrade). Each must be admin-grantable — the fixture offers it. Default none.

type readonly string[]

readonly

CrossPutFactInput
#

testing/cross_backend/fact_serving.ts view source

CrossPutFactInput import type {CrossPutFactInput} from '@fuzdev/fuz_app/testing/cross_backend/fact_serving.js';

Input for cross_put_fact — _testing_put_fact's params.

content

type string

readonly

content_type?

type string

readonly

holder?

The actor the put is on behalf of — recorded as a holder. Omit for a system put.

type string

readonly

external_url?

Store an external row naming this URL, verbatim and with no bytes, instead of an embedded one.

type string

readonly

CrossSuiteHandleOptions
#

testing/cross_backend/spine_suites.ts view source

CrossSuiteHandleOptions import type {CrossSuiteHandleOptions} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

The suite options a backend's BackendConfig supplies.

capabilities

type BackendCapabilities

readonly

base_url

type string

readonly

rpc_path

type string

readonly

ws_path

type string

readonly

sse_path

type string | undefined

readonly

cookie_name

type string

readonly

CrossSuiteOptions
#

testing/cross_backend/setup.ts view source

CrossSuiteOptions import type {CrossSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Base options shared by every imperative cross-backend parity suite (origin / ready / body_size / cell_* / actor_* / account_lifecycle / app_settings / testing_backdoor / …). The setup_test core is identical across them; each suite extends this with its own path field (rpc_path for RPC-dispatched suites via RpcPathCrossSuiteOptions, ready_path for the readiness probe, etc.), and a suite that actually gates cases on a capability flag extends CapabilityGatedCrossSuiteOptions instead. Lives here in the neutral fixture-types home rather than in any one domain helper, so a non-cell suite never reaches into the cell helpers for its option shape.

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

DAEMON_TOKEN_HEADER
#

auth/daemon_token.ts view source

"X-Daemon-Token" import {DAEMON_TOKEN_HEADER} from '@fuzdev/fuz_app/auth/daemon_token.js';

The X-Daemon-Token header name.

DaemonInfo
#

testing/daemon_info.ts view source

value + type

{ version: number; pid: number; port: number; started: string; app_version: string; } import {DaemonInfo} from '@fuzdev/fuz_app/testing/daemon_info.js';

Daemon info schema for ~/.{name}/run/daemon.json, a strict @fuzdev/fuz_schema object schema.

version

type number

pid

type number

port

type number

started

type string

app_version

type string

DaemonToken
#

auth/daemon_token.ts view source

value + type

string import {DaemonToken} from '@fuzdev/fuz_app/auth/daemon_token.js';

Daemon token format: 43 base64url characters (256 bits).

DaemonTokenRotation
#

DaemonTokenRotationOptions
#

testing/daemon_token_rotation.ts view source

DaemonTokenRotationOptions import type {DaemonTokenRotationOptions} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Options for daemon token rotation.

token_path

Absolute path the token file is written to. Caller computes from its own conventions — e.g. a path derived from PUBLIC_<APP>_DIR for cross-process test setups that isolate the app dir to a tmpdir.

type string

rotation_interval_ms?

Rotation interval in ms. Default: 30000 (30s).

type number

DaemonTokenState
#

auth/daemon_token.ts view source

DaemonTokenState import type {DaemonTokenState} from '@fuzdev/fuz_app/auth/daemon_token.js';

Mutable runtime state for daemon token rotation.

This is runtime state (not AppDeps or *Options) — it changes during operation. Created at server startup, passed to the middleware factory.

current_token

Current valid token.

type string

previous_token

Previous token, still valid during the race window. null before first rotation.

type string | null

rotated_at

When the last rotation occurred.

type Date

keeper_account_id

The account ID of the keeper (resolved at startup, set by on_bootstrap).

type string | null

DaemonTokenWriteDeps
#

testing/daemon_token_rotation.ts view source

DaemonTokenWriteDeps import type {DaemonTokenWriteDeps} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Deps for writing the daemon token to disk.

mkdir

Create a directory. mode applies at creation (no-op where modes don't apply).

type (path: string, options?: { recursive?: boolean | undefined; mode?: number | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; mode?: number | undefined; }
optional
returns Promise<void>

rename

Rename (move) a file.

type (old_path: string, new_path: string): Promise<void>

old_path

type string

new_path

type string
returns Promise<void>

write_text_file

Write text to a file. See WriteFileOptions for mode / exclusive.

type (path: string, content: string, options?: WriteFileOptions | undefined): Promise<void>

path

type string

content

type string

options?

optional
returns Promise<void>

DataExposureTestOptions
#

testing/data_exposure.ts view source

DataExposureTestOptions import type {DataExposureTestOptions} from '@fuzdev/fuz_app/testing/data_exposure.js';

setup_test

Per-test fixture-producing function (per-describe cadence).

type (): Promise<TestFixture>

returns Promise<TestFixture>

surface_source

App surface for schema-level + route-iteration checks. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

sensitive_fields?

Fields that must never appear in any response. Default: sensitive_field_blocklist.

type readonly string[]

admin_only_fields?

Fields that must not appear in non-admin responses. Default: admin_only_field_blocklist.

type readonly string[]

skip_routes?

Routes to skip, in 'METHOD /path' format.

type string[]

Datatable
#

ui/Datatable.svelte view source

accepts children

import Datatable from '@fuzdev/fuz_app/ui/Datatable.svelte';

columns

type DatatableColumn<T>[]

rows

type T[]

row_key

The row's stable identity, used as the keyed-each key — e.g. (row) => row.id.

type (row: T) => PropertyKey
snippet parameters
row T

height?

CSS height for the scrollable region (e.g. '400px'). Omit to size to content.

type string
optional

header?

Override default header-cell rendering. Receives the column.

type Snippet<[column: DatatableColumn<T>]>
optional
snippet parameters
column DatatableColumn<T>

cell?

Override default cell rendering. Receives column, row, and the cell value.

type Snippet<[column: DatatableColumn<T>, row: T, value: T[keyof T]]>
optional
snippet parameters
column DatatableColumn<T>
row T
value T[keyof T]

empty?

Rendered when rows is empty. Defaults to a no data text.

type Snippet<[]>
optional

external types

SvelteHTMLElements['div']

generics

Datatable<T extends Record<string, any> = Record<string, any>>
T
constraint Record<string, any>
default Record<string, any>

DATATABLE_COLUMN_WIDTH_DEFAULT
#

ui/datatable.ts view source

120 import {DATATABLE_COLUMN_WIDTH_DEFAULT} from '@fuzdev/fuz_app/ui/datatable.js';

Default initial column width in pixels.

DATATABLE_MIN_COLUMN_WIDTH
#

ui/datatable.ts view source

50 import {DATATABLE_MIN_COLUMN_WIDTH} from '@fuzdev/fuz_app/ui/datatable.js';

Default minimum column width in pixels.

DatatableColumn
#

ui/datatable.ts view source

DatatableColumn<T> import type {DatatableColumn} from '@fuzdev/fuz_app/ui/datatable.js';

Column definition for a Datatable.

generics

DatatableColumn<T = unknown>
T
default unknown

key

Row data accessor key.

type string & (keyof T)

label

Header label text.

type string

width?

Initial column width in pixels.

type number

min_width?

Minimum column width in pixels.

type number

format?

Format a cell value for display. Falls back to format_value when absent.

type (value: T[keyof T], row: T): string

value

type T[keyof T]

row

type T
returns string

Datetime
#

primitive_schemas.ts view source

value + type

string & $brand<"Datetime">

type string & $brand<"Datetime">

import {Datetime} from '@fuzdev/fuz_app/primitive_schemas.js';

Branded ISO 8601 datetime schema.

external types

z.infer<typeof Datetime>

DatetimeNow
#

primitive_schemas.ts view source

value + type

string & $brand<"Datetime">

type string & $brand<"Datetime">

import {DatetimeNow} from '@fuzdev/fuz_app/primitive_schemas.js';

Datetime that defaults to get_datetime_now() when the input is undefined.

external types

z.infer<typeof DatetimeNow>

Db
#

db/db.ts view source

import {Db} from '@fuzdev/fuz_app/db/db.js';

Database wrapper providing a consistent query and transaction interface.

Construct via create_pg_db() from db/db_pg.ts, create_pglite_db() from db/db_pglite.ts or create_pglet_db_driver() from db/db_pglet.ts for proper transaction support, or via create_db() for URL-based auto-detection.

examples

const {db, close} = await create_db('postgres://...'); const users = await db.query<User>('SELECT * FROM users WHERE active = $1', [true]); await db.transaction(async (tx) => { await tx.query('INSERT INTO users ...'); await tx.query('INSERT INTO audit_log ...'); }); await close();

client

type DbClient

readonly

constructor

type new (options: DbDeps): Db

options

type DbDeps

with_checkout_timeout

A Db over the same pool whose statements check out under timeout_ms instead of the pool's own bound — the audit emitter's longer wait (db/db_pg.ts, "Checkout timeout"). With no pool (PGlite, pglet, a transaction-scoped Db) there is no checkout to bound, and the Db itself is returned.

type (timeout_ms: number): Db

timeout_ms

the checkout bound, in ms

type number
returns Db

a Db sharing this one's pool

query

Execute a query and return all rows.

type <T>(text: string, values?: unknown[] | undefined): Promise<T[]>

text

SQL text with $1, $2, ... parameter placeholders

type string

values?

parameter values bound to the placeholders in text

type unknown[]
optional
returns Promise<T[]>

the result rows, typed as T

query_one

Execute a query and return the first row, or undefined if no rows.

type <T>(text: string, values?: unknown[] | undefined): Promise<T | undefined>

text

SQL text with $1, $2, ... parameter placeholders

type string

values?

parameter values bound to the placeholders in text

type unknown[]
optional
returns Promise<T | undefined>

the first row, or undefined when the result set is empty

transaction

Run a function inside a database transaction.

The callback receives a transaction-scoped Db. Queries inside the callback go through the transaction connection; queries outside use the pool normally. Commits on success, rolls back on error.

type <T>(fn: (tx_db: Db) => Promise<T>): Promise<T>

fn

async function receiving a transaction-scoped Db

type (tx_db: Db) => Promise<T>
returns Promise<T>

the value returned by fn

throws

  • Error - propagated from `fn` after `ROLLBACK`, or from the driver if `BEGIN` / `COMMIT` / `ROLLBACK` itself fails

in_transaction

Whether this Db is scoped to an open transaction — the Db a driver adapter hands a transaction callback, which it builds with no_nested_transaction. A pool-level Db answers false.

The audit emitter reads it to write a success row inside the request's transaction when one is open (auth/audit_emitter.ts).

type boolean

getter

DB_ADMIN_STATEMENT_TIMEOUT_MS
#

http/db_routes.ts view source

5000 import {DB_ADMIN_STATEMENT_TIMEOUT_MS} from '@fuzdev/fuz_app/http/db_routes.js';

Per-statement timeout applied (SET LOCAL statement_timeout) inside the table-list, table-detail, and row-DELETE transactions — no single statement outlives it. (Per statement, not per request: the table listing's one-COUNT(*)-per-table loop can still take several in sequence.) Milliseconds.

DB_CHECKOUT_TIMEOUT_MAX_MS
#

DB_TABLE_ROWS_DEFAULT_LIMIT
#

http/db_routes.ts view source

100 import {DB_TABLE_ROWS_DEFAULT_LIMIT} from '@fuzdev/fuz_app/http/db_routes.js';

Default page size for GET /tables/:name rows.

DB_TABLE_ROWS_LIMIT_MAX
#

http/db_routes.ts view source

1000 import {DB_TABLE_ROWS_LIMIT_MAX} from '@fuzdev/fuz_app/http/db_routes.js';

Maximum page size for GET /tables/:name rows.

DB_UNAVAILABLE_MESSAGE
#

http/jsonrpc_errors.ts view source

"database unavailable" import {DB_UNAVAILABLE_MESSAGE} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

The message every failed pool checkout answers with, on both spines — the Rust fuz_http::DB_UNAVAILABLE_MESSAGE.

DbCheckoutError
#

db/db_checkout_error.ts view source

import {DbCheckoutError} from '@fuzdev/fuz_app/db/db_checkout_error.js';

A pool checkout failed; cause carries the driver's error, and the message repeats it for the server's log (database checkout failed: <cause>). The wire never carries it — the answer is the fixed database unavailable.

inheritance

extends: Error

constructor

type new (options: ErrorOptions): DbCheckoutError

options

type ErrorOptions

DbCheckoutPool
#

db/db_pg.ts view source

DbCheckoutPool import type {DbCheckoutPool} from '@fuzdev/fuz_app/db/db_pg.js';

A pg.Pool from create_pg_pool: a failed checkout throws DbCheckoutError, and query_with_checkout_timeout runs one statement under its own bound.

inheritance

extends: Pool

query_with_checkout_timeout

pool.query with the checkout bounded by timeout_ms in place of the pool's own.

type <T>(timeout_ms: number, text: string, values?: unknown[] | undefined): Promise<{ rows: T[]; }>

timeout_ms

type number

text

type string

values?

type unknown[]
optional
returns Promise<{ rows: T[]; }>

DbClient
#

db/db.ts view source

DbClient import type {DbClient} from '@fuzdev/fuz_app/db/db.js';

Minimal interface that pg, pglite and pglet satisfy.

query

type <T = unknown>(text: string, values?: unknown[] | undefined): Promise<{ rows: T[]; }>

text

type string

values?

type unknown[]
optional
returns Promise<{ rows: T[]; }>

DbDeps
#

db/db.ts view source

DbDeps import type {DbDeps} from '@fuzdev/fuz_app/db/db.js';

Configuration for constructing a Db with transaction support.

transaction is injected by create_db which knows the driver. For pg: acquires a dedicated pool client per transaction. For PGlite: delegates to pglite.transaction(). For pglet: delegates to the adapter's transaction().

client

type DbClient

transaction

type <T>(fn: (tx_db: Db) => Promise<T>): Promise<T>

fn

type (tx_db: Db) => Promise<T>
returns Promise<T>

with_checkout_timeout?

A Db over the same pool whose checkouts are bounded by timeout_ms — supplied by the pg adapter (create_pg_db). Omitted where there is no pool (PGlite, pglet), so Db.with_checkout_timeout returns the Db itself.

type (timeout_ms: number): Db

timeout_ms

type number
returns Db

DbDriverResult
#

db/db.ts view source

DbDriverResult import type {DbDriverResult} from '@fuzdev/fuz_app/db/db.js';

Result of constructing a driver-specific Db.

Returned by create_pg_db(), create_pglite_db() and create_pglet_db_driver(). The close callback is bound to the actual driver — callers never need to know which driver is in use.

db

type Db

close

Close the database connection. Bound to the actual driver at construction.

type (): Promise<void>

returns Promise<void>

DbFactory
#

testing/db.ts view source

DbFactory import type {DbFactory} from '@fuzdev/fuz_app/testing/db.js';

Factory interface for creating test database instances.

name

type string

create

type (): Promise<Db>

returns Promise<Db>

close

type (db: Db): Promise<void>

db

type Db
returns Promise<void>

skip

type boolean

skip_reason?

type string

DbFactoryBuilder
#

testing/db.ts view source

DbFactoryBuilder import type {DbFactoryBuilder} from '@fuzdev/fuz_app/testing/db.js';

Builds a DbFactory from a schema initializer — the shape of every factory builder here.

(call)

type (init_schema: (db: Db) => Promise<void>): DbFactory

init_schema

type (db: Db) => Promise<void>
returns DbFactory

DbRouteDeps
#

http/db_routes.ts view source

DbRouteDeps import type {DbRouteDeps} from '@fuzdev/fuz_app/http/db_routes.js';

Capabilities the db routes need — a narrow structural slice of AppDeps (auth/deps.ts), declared here so http/ stays auth-free. The bound auth/audit_emitter.ts AuditEmitter (and therefore AppDeps.audit / RouteFactoryDeps.audit) satisfies audit structurally.

audit

Awaitable pool audit write. The row-DELETE handler calls it from an emit_after_commit thunk, so the success-only trail row can never claim a delete whose transaction failed at COMMIT, and the row is written on the pool because the request transaction has ended by then. The flush awaits the returned promise.

type { emit_pool: (input: { event_type: "db_admin_row_delete"; account_id: (string & $brand<"Uuid">) | null; ip: string; metadata: { table: string; pk_column: string; id: string; }; }) => Promise<...>; }

DbRouteOptions
#

http/db_routes.ts view source

DbRouteOptions import type {DbRouteOptions} from '@fuzdev/fuz_app/http/db_routes.js';

Per-factory configuration for db routes.

db_type

type DbType

db_name

type string

browsable_tables

The tables the browser exposes — an explicit allowlist gating the table list, table detail, and row-DELETE alike. Required with no "all" escape hatch, so a future secret-bearing table stays unlisted until a consumer names it, and NON_BROWSABLE_TABLES is subtracted even when named. An unlisted table 404s as table_not_found — the same answer as a table that doesn't exist.

type readonly string[]

extra_stats?

Optional callback to provide app-specific stats in the health response.

type (db: Db): Promise<Record<string, unknown>>

db

type Db
returns Promise<Record<string, unknown>>

log?

Optional logger for server-side diagnostics (e.g. FK violation details).

type Logger

non_deletable_tables?

Consumer tables to exclude from row deletion, unioned with the builtin NON_DELETABLE_TABLES (which this never replaces).

type readonly string[]

DbStatus
#

db/status.ts view source

DbStatus import type {DbStatus} from '@fuzdev/fuz_app/db/status.js';

Full database status snapshot.

connected

Whether the database is reachable.

type boolean

error?

Error message if connection failed.

type string

table_count

Number of public tables.

type number

tables

Per-table row counts.

type TableStatus[]

migrations

Per-namespace migration status.

type MigrationStatus[]

DbType
#

db/db.ts view source

DbType

type "postgres" | "pglite-file" | "pglet-memory"

import type {DbType} from '@fuzdev/fuz_app/db/db.js';

Database driver type.

DeclinedOffer
#

auth/role_grant_offer_queries.ts view source

DeclinedOffer import type {DeclinedOffer} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Result of query_role_grant_offer_decline — the declined offer plus the grantor's account_id.

inheritance

from_account_id

Grantor's account_id, resolved via a join on actor so the audit envelope's target_account_id (decline is *to* the grantor) and the post-commit notification target are both addressable without a second round-trip.

type Uuid

default_action_account_rate_limit
#

rate_limiter.ts view source

RateLimiterOptions import {default_action_account_rate_limit} from '@fuzdev/fuz_app/rate_limiter.js';

Default options for per-account action-dispatcher rate limiting: 1200 attempts per 15 minutes. Shared by the HTTP RPC and WebSocket action dispatchers. Permissive — sustained ~80/min is well above any human admin workflow; an oracle probing 10k addresses still finishes in ~2 hours, slow enough to surface in audit. Tighten downstream.

default_action_ip_rate_limit
#

rate_limiter.ts view source

RateLimiterOptions import {default_action_ip_rate_limit} from '@fuzdev/fuz_app/rate_limiter.js';

Default options for per-IP action-dispatcher rate limiting: 600 attempts per 15 minutes. Shared by the HTTP RPC and WebSocket action dispatchers (one budget per action, not per transport). Permissive — catches runaway scripts and egregious oracle probes, but well above human or normal automation pace. Tighten downstream for stricter deployments.

default_audit_factory
#

server/app_backend.ts view source

(params: { db: Db; log: Logger; }): AuditEmitter import {default_audit_factory} from '@fuzdev/fuz_app/server/app_backend.js';

Trivial AuditFactory for consumers that don't compose on_audit_event or audit_log_config. Equivalent to ({db, log}) => create_audit_emitter({db, log}) — exported so the default case stays a single-symbol reference rather than five tokens of boilerplate at every consumer.

Use the inline form when you need to thread on_audit_event / audit_log_config / emit_decorator; the factory composes those three fields itself so there's nothing this constant can pass through.

params

type { db: Db; log: Logger; }

returns

AuditEmitter

DEFAULT_AUDIT_STREAM_ROLE
#

DEFAULT_AUTH_CLEANUP_INTERVAL_MS
#

auth/cleanup.ts view source

number import {DEFAULT_AUTH_CLEANUP_INTERVAL_MS} from '@fuzdev/fuz_app/auth/cleanup.js';

How often start_auth_cleanup runs a pass after its startup pass (10 minutes), unless the consumer passes its own. Equal to the Rust spine's DEFAULT_AUTH_CLEANUP_INTERVAL.

An expired session or offer is already refused by every check that reads it, and a connection closes at its credential's deadline on its own, so the interval bounds two things: how late an offer expiry's audit row lands, and how long dead rows linger.

DEFAULT_BACKOFF_FACTOR
#

actions/socket.svelte.ts view source

1.5 import {DEFAULT_BACKOFF_FACTOR} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Exponential backoff factor: delay = base * factor^(attempt-1).

default_bench_scenarios
#

testing/cross_backend/bench/scenario.ts view source

readonly BenchScenario[]

type readonly BenchScenario[]

import {default_bench_scenarios} from '@fuzdev/fuz_app/testing/cross_backend/bench/scenario.js';

Starter cross-impl scenarios — all on the standard spine surface, so they run on every backend (TS Hono, Rust spine), and all reads, so they're safe to repeat against one bootstrapped keeper without state accumulation.

  • account_verify — the dispatch + auth-resolve floor (no real query work).
  • account_session_list — an authed DB read.
  • audit_log_list — an admin paginated read (the keeper holds ROLE_ADMIN).

login is deliberately omitted: the cross-process test binaries wire a fast TestingArgon2idHasher, so a login scenario would measure dispatch rather than real Argon2 cost — misleading without its own clearly-labeled tier.

DEFAULT_BODY_LIMIT_BYTES
#

DEFAULT_CLOSE_CODE
#

default_cross_process_setup
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle, options?: CrossProcessSetupOptions | undefined): SetupTest import {default_cross_process_setup} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Build a SetupTest against a spawned + bootstrapped backend.

Per-test body (unconditional reset — fresh keeper every test):

  1. Fire _testing_reset via the keeper's daemon-token channel. The action wipes auth tables, seeds a fresh keeper (with extra_keeper_roles applied), seeds any extra_accounts, and returns the new credentials.
  2. Build the TestFixture closing over the new keeper as the fixture's primary account / actor (matching in-process semantics). fixture.extra_accounts[username] exposes any bootstrap-time secondaries.
  3. fixture.create_account() mints additional *post-bootstrap* accounts via the production signup + login flow (invite → signup → login → token). Roles go through offer/accept (production consent path).

No reset: boolean opt-in — every test runs against a freshly bootstrapped keeper. This converges in-process and cross-process keeper lifetimes; mutation-cascade tests (password change, revoke-all) and hardcoded-username signup tests work uniformly.

handle

options?

optional

returns

SetupTest

DEFAULT_DB_AUDIT_CHECKOUT_TIMEOUT_MS
#

db/db_pg.ts view source

30000 import {DEFAULT_DB_AUDIT_CHECKOUT_TIMEOUT_MS} from '@fuzdev/fuz_app/db/db_pg.js';

Default bound on the checkout behind one audit pool write — Rust fuz_db::DEFAULT_AUDIT_CHECKOUT_TIMEOUT.

DEFAULT_DB_CHECKOUT_TIMEOUT_MS
#

db/db_pg.ts view source

5000 import {DEFAULT_DB_CHECKOUT_TIMEOUT_MS} from '@fuzdev/fuz_app/db/db_pg.js';

Default bound on one request's pool checkout — Rust fuz_db::DEFAULT_CHECKOUT_TIMEOUT.

default_error_schema_tightness
#

testing/surface_invariants.ts view source

ErrorSchemaTightnessOptions import {default_error_schema_tightness} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Baseline error schema tightness applied by describe_standard_attack_surface_tests when no config is passed.

Uses min_specificity: 'enum' (the assertion default) with ignore_statuses for middleware-derived status codes that are commonly generic (auth middleware produces multiple error codes at 401/403, and 429 comes from rate limiters), and allowlist seeded with fuz_app_stock_route_tightness_allowlist so fuz_app-shipped routes with heterogeneous generic schemas don't force every consumer to hand-maintain an identical allowlist. Consumers can pass a narrower config with project-specific allowlist entries, or pass null to skip the assertion entirely.

default_format_scope
#

ui/format_scope.ts view source

(args: { scope_id: string | null; role: string; }): string | null import {default_format_scope} from '@fuzdev/fuz_app/ui/format_scope.js';

Default FormatScope — always returns null so callers fall back to the truncated uuid.

args

type { scope_id: string | null; role: string; }

returns

string | null

DEFAULT_HEARTBEAT_INTERVAL
#

actions/socket.svelte.ts view source

30000 import {DEFAULT_HEARTBEAT_INTERVAL} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Idle interval before sending a heartbeat (ms).

DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT
#

default_in_process_setup
#

testing/cross_backend/in_process_setup.ts view source

(options: InProcessSetupOptions): SetupTest import {default_in_process_setup} from '@fuzdev/fuz_app/testing/cross_backend/in_process_setup.js';

Build a SetupTest that creates a fresh TestApp per call via create_test_app and projects it into the TestFixture shape.

Same factory inputs create_test_app already takes — this helper is a projection layer, not a new lifecycle. fuz_app's own src/test/ and consumer suites pass default_in_process_setup({...factory_inputs}) in place of the old per-suite factory-input bundle. The extra_accounts slot (see InProcessSetupOptions) seeds bootstrap-time secondaries directly via create_test_account_with_credentials against the same DB the keeper just landed on — mirrors the cross-process _testing_reset cradle so suite bodies read fixture.extra_accounts[username] uniformly regardless of transport.

The describe-level auth_integration_truncate_tables / WASM database cache lifecycle stays in create_default_db_factory / create_describe_db (testing/db.ts) — default_in_process_setup doesn't manage db state beyond what create_test_app already does.

options

returns

SetupTest

default_in_process_suite_options
#

testing/cross_backend/in_process_setup.ts view source

<const O extends DefaultInProcessSuiteOptions>(options: O): { setup_test: SetupTest; surface_source: AppSurfaceSpec; capabilities: BackendCapabilities; session_options: O["session_options"]; create_route_specs: O["create_route_specs"]; rpc_endpoints: O["rpc_endpoints"]; } import {default_in_process_suite_options} from '@fuzdev/fuz_app/testing/cross_backend/in_process_setup.js';

Build the full in-process suite bundle in a single helper invocation. Output covers {setup_test, surface_source, capabilities} plus every factory input the Tier 1 suites read at their top level (session_options, create_route_specs, rpc_endpoints) — so the call site spreads once and adds only suite-specific extras (roles, skip_routes, input_overrides, db_factories, ...).

// Suite-extras-free call: helper output is the entire options bag. describe_round_trip_validation(default_in_process_suite_options({ session_options, create_route_specs, rpc_endpoints: [rpc_endpoint_spec], })); // With suite-specific extras: spread and add. describe_standard_admin_integration_tests({ ...default_in_process_suite_options({ session_options, create_route_specs, rpc_endpoints, extra_keeper_roles: [ROLE_ADMIN], }), roles, });

Suites that don't read session_options / rpc_endpoints at their top level (round_trip, data_exposure) accept the spread anyway — excess properties on spread sources aren't checked by TS, and the uniform shape keeps consumer call sites mechanical.

options

type O

returns

{ setup_test: SetupTest; surface_source: AppSurfaceSpec; capabilities: BackendCapabilities; session_options: O["session_options"]; create_route_specs: O["create_route_specs"]; rpc_endpoints: O["rpc_endpoints"]; }

generics

default_in_process_suite_options<O extends DefaultInProcessSuiteOptions>
O

DEFAULT_INTEGRATION_ERROR_COVERAGE
#

testing/error_coverage.ts view source

0.2 import {DEFAULT_INTEGRATION_ERROR_COVERAGE} from '@fuzdev/fuz_app/testing/error_coverage.js';

Default minimum error coverage threshold for the standard integration and admin test suites. Conservative — not all error paths are exercisable in the composable suites. Consumers should increase as their test suites mature.

default_login_account_rate_limit
#

rate_limiter.ts view source

RateLimiterOptions import {default_login_account_rate_limit} from '@fuzdev/fuz_app/rate_limiter.js';

Default options for per-account login rate limiting: 10 attempts per 30 minutes.

DEFAULT_LOGIN_FAIL_FLOOR_MS
#

auth/account_routes.ts view source

250 import {DEFAULT_LOGIN_FAIL_FLOOR_MS} from '@fuzdev/fuz_app/auth/account_routes.js';

Default minimum wall-clock time (ms) for a login failure (401) response.

Picked to exceed the p99 of every 401 code path (Argon2id dominates at ~100ms, plus DB + overhead). The handler races failure work against sleep(floor + jitter) via await, so observed response time = max(work, delay). Found-vs-not-found and rate-limit-skipped-vs-not paths converge. Only 401 is padded — 429 stays fast by design to keep rate-limit DoS handling cheap.

DEFAULT_LOGIN_FAIL_JITTER_MS
#

auth/account_routes.ts view source

25 import {DEFAULT_LOGIN_FAIL_JITTER_MS} from '@fuzdev/fuz_app/auth/account_routes.js';

Default uniform jitter window (±ms) layered on the floor.

Random jitter prevents a stable clamp point from leaking whenever a path occasionally exceeds the floor. Math.random is sufficient — we only need unpredictability of the exact delay, not cryptographic guarantees.

default_login_ip_rate_limit
#

rate_limiter.ts view source

RateLimiterOptions import {default_login_ip_rate_limit} from '@fuzdev/fuz_app/rate_limiter.js';

Default options for per-IP auth rate limiting: 5 attempts per 15 minutes. The default for each of the three per-surface IP limiters (login + password change, signup, bootstrap) and the shared cap the Rust spine's DEFAULT_LOGIN_IP_RATE_LIMIT pins.

Deliberately not widened when the buckets became monotone (a success no longer refunds them — see RateLimiter.reset). Widening would have bought NAT'd egress headroom by loosening the one bound that caps credential guessing from a single address; splitting one shared bucket into three per-surface ones buys the same headroom without touching that bound.

DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT
#

actions/connection_registry.ts view source

50 import {DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT} from '@fuzdev/fuz_app/actions/connection_registry.js';

Default per-account WebSocket connection cap, applied by ConnectionRegistry unless overridden via ConnectionRegistryOptions.max_connections_per_account.

Matches the account-wide ceiling the audit SSE cap implies (the default session cap, DEFAULT_MAX_SESSIONS, times AUDIT_LOG_SSE_MAX_PER_SCOPE streams per session) — generous enough that a user's tabs and tools never meet it; only an abusive or leaking client does. The twin of the Rust spine's DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT.

DEFAULT_MAX_GET_URL_LENGTH
#

actions/transports_http.ts view source

4096 import {DEFAULT_MAX_GET_URL_LENGTH} from '@fuzdev/fuz_app/actions/transports_http.js';

The longest path and query a read is sent as a GET with — 4096 characters. Past it the call goes as a POST: a cell_list naming its full id cap runs over it, and proxies refuse a request line past their own limit (nginx's default 8 KiB buffer) with a 414 the RPC client could not read.

DEFAULT_MAX_IN_FLIGHT_PEER_REQUESTS_PER_CONNECTION
#

DEFAULT_MAX_SESSIONS
#

auth/account_route_schema.ts view source

5 import {DEFAULT_MAX_SESSIONS} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Default maximum sessions per account.

The Rust twin pins the same 5 as a const (fuz_auth's DEFAULT_MAX_SESSIONS) rather than a route-state field, because its AccountRouteState has no Default and a new field would break every exhaustive literal in every consumer workspace. So this stays the configurable side of the pair (null disables), and the two ship the same number — retuning means editing both.

DEFAULT_MAX_TOKENS
#

DEFAULT_PEER_REQUEST_TIMEOUT
#

DEFAULT_QUEUE_MAX_SIZE
#

actions/socket.svelte.ts view source

100 import {DEFAULT_QUEUE_MAX_SIZE} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Default bound on buffered requests while disconnected. Overflow rejects.

DEFAULT_RATE_LIMITER_MAX_KEYS
#

rate_limiter.ts view source

100000 import {DEFAULT_RATE_LIMITER_MAX_KEYS} from '@fuzdev/fuz_app/rate_limiter.js';

Default tracked-key cap: bounds worst-case memory under key-enumeration attacks (an attacker rotating source IPs cannot grow the backing map indefinitely between cleanup ticks). Tuned to comfortably fit real traffic for a single-instance deployment while capping memory at a few MB in the worst case.

DEFAULT_RECONNECT_DELAY
#

DEFAULT_RECONNECT_DELAY_MAX
#

actions/socket.svelte.ts view source

10000 import {DEFAULT_RECONNECT_DELAY_MAX} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Max reconnect delay in ms (cap on exponential backoff).

DEFAULT_ROTATION_INTERVAL_MS
#

default_setup_logger
#

dev/setup.ts view source

SetupLogger import {default_setup_logger} from '@fuzdev/fuz_app/dev/setup.js';

Default logger using bracket format.

DEFAULT_SIGNUP_FAIL_FLOOR_MS
#

auth/signup_routes.ts view source

250 import {DEFAULT_SIGNUP_FAIL_FLOOR_MS} from '@fuzdev/fuz_app/auth/signup_routes.js';

Default minimum wall-clock time (ms) for a signup denial (403 / 409) response.

Parallel to login's DEFAULT_LOGIN_FAIL_FLOOR_MS. Without a floor, an attacker can distinguish ERROR_NO_MATCHING_INVITE (cheap — bails before Argon2 + tx) from ERROR_SIGNUP_CONFLICT (Argon2 + tx + rollback) via response time and use the gap as a username-enumeration oracle. Picked to exceed the p99 of every denial code path (Argon2id dominates at ~100ms, plus DB + overhead). 429 stays fast by design (same precedent as login) so rate-limit DoS handling stays cheap.

DEFAULT_SIGNUP_FAIL_JITTER_MS
#

auth/signup_routes.ts view source

25 import {DEFAULT_SIGNUP_FAIL_JITTER_MS} from '@fuzdev/fuz_app/auth/signup_routes.js';

Default uniform jitter window (±ms) layered on the floor.

Random jitter prevents a stable clamp point from leaking whenever a path occasionally exceeds the floor. Math.random is sufficient — we only need unpredictability of the exact delay, not cryptographic guarantees.

DEFAULT_TEST_BACKEND_HOST
#

testing/cross_backend/default_backend_configs.ts view source

"127.0.0.1" import {DEFAULT_TEST_BACKEND_HOST} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

The HOST both builders bind a backend to — loopback IPv4, since localhost can resolve to ::1 and a backend bound there misses a client that connects to 127.0.0.1 (the raw-socket suites, a git hook calling back). base_url stays http://localhost:<port>, which reaches it.

default_test_backend_paths
#

testing/cross_backend/build_test_backend_paths.ts view source

(name: string): TestBackendPaths import {default_test_backend_paths} from '@fuzdev/fuz_app/testing/cross_backend/build_test_backend_paths.js';

The generic path layout under the default, repo-namespaced prefix (default_test_backend_prefix) — what the default backend builders use when given no paths.

name

the backend name (BackendConfig.name)

type string

returns

TestBackendPaths

default_test_backend_prefix
#

testing/cross_backend/build_test_backend_paths.ts view source

(name: string): string import {default_test_backend_prefix} from '@fuzdev/fuz_app/testing/cross_backend/build_test_backend_paths.js';

The default per-backend tmpdir prefix: the running repo's directory name (process.cwd()'s basename — so run the tests from the repo root) plus the backend name, so the same backend name in two repos ('rust' in each) lands in two directories.

name

the backend name (BackendConfig.name)

type string

returns

string

default_test_bootstrap_token
#

testing/cross_backend/default_secrets.ts view source

"test_bootstrap_token_for_cross_be" import {default_test_bootstrap_token} from '@fuzdev/fuz_app/testing/cross_backend/default_secrets.js';

Fixed bootstrap token written to each backend's token_path before spawn. The test binary reads + consumes this via its *_BOOTSTRAP_TOKEN_PATH env var; the harness POSTs the same token to /api/account/bootstrap to mint the keeper account. Any 32+ char string works — the binary just compares bytes, no entropy required for tests.

default_test_cookie_keys
#

default_test_keeper_password
#

default_test_keeper_username
#

DEFAULT_TEST_PASSWORD
#

testing/test_credentials.ts view source

"test-password-123" import {DEFAULT_TEST_PASSWORD} from '@fuzdev/fuz_app/testing/test_credentials.js';

Default password for test-bootstrapped accounts.

DEFAULT_WS_MAX_MESSAGE_BYTES
#

actions/transports.ts view source

number import {DEFAULT_WS_MAX_MESSAGE_BYTES} from '@fuzdev/fuz_app/actions/transports.js';

Default cap on one WebSocket message, in UTF-8 bytes — equal to the default HTTP body cap (DEFAULT_BODY_LIMIT_BYTES), so one JSON-RPC request is bounded the same on both transports. The server closes the socket on a larger inbound message with WS_CLOSE_MESSAGE_TOO_BIG (there's no per-message error reply), and FrontendWebsocketTransport refuses to send one. The twin of the Rust spine's DEFAULT_WS_MAX_MESSAGE_BYTES.

DEFAULT_WS_OUTBOUND_QUEUE_BYTES
#

actions/connection_registry.ts view source

number import {DEFAULT_WS_OUTBOUND_QUEUE_BYTES} from '@fuzdev/fuz_app/actions/connection_registry.js';

Default byte budget of the frames waiting to be written to one connection, applied by ConnectionRegistry unless overridden via ConnectionRegistryOptions.outbound_queue_bytes.

4 MiB — four messages at DEFAULT_WS_MAX_MESSAGE_BYTES as it stands, and a value of its own: it does not move with the inbound message cap. A consumer whose server pushes more than this at a client in one burst — large frames, or long runs of them — sets its own. Same value as the Rust spine's DEFAULT_WS_OUTBOUND_QUEUE_BYTES.

DEFAULT_WS_RECEIVE_TIMEOUT
#

DefaultInProcessSuiteOptions
#

testing/cross_backend/in_process_setup.ts view source

DefaultInProcessSuiteOptions import type {DefaultInProcessSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/in_process_setup.js';

Consumer-facing options for default_in_process_suite_options — the minimal factory inputs both default_in_process_setup and create_test_app_surface_spec consume to produce the {setup_test, surface_source, capabilities} bundle.

session_options

type SessionOptions<string>

create_route_specs

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

rpc_endpoints?

type RpcEndpointsSuiteOption

bootstrap?

Bootstrap config — top-level slot, single source of truth for both surface generation and live dispatch. Same precedent as rpc_endpoints. Discriminated by mode; omit for the default (no bootstrap route mounted).

type BootstrapServerOptions

app_options?

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

extra_keeper_roles?

Additional roles to grant the bootstrapped keeper alongside ROLE_KEEPER — additive, never replaces. The keeper account always holds ROLE_KEEPER (otherwise daemon-token auth breaks); pass extras here for suites that need additional role coverage.

Admin-suite consumers pass [ROLE_ADMIN] so the default keeper can hit admin-gated RPC methods. describe_standard_admin_integration_tests and describe_audit_completeness_tests need this.

type string[]

extra_accounts?

Bootstrap-time secondary accounts seeded alongside the keeper. See ExtraAccountSpec for the cradle-only-bypass rationale. Same shape as the cross-process extra_accounts option — suites read seeded accounts from fixture.extra_accounts[username] regardless of transport.

type readonly ExtraAccountSpec[]

extra_actors?

Additional actor names to seed on the bootstrapped keeper — exposed on fixture.extra_actors. See TestFixture.extra_actors.

type readonly string[]

reject_unknown_audit_metadata_keys?

Refuse undeclared audit metadata keys in the fixture's audit reads — see CrossProcessSetupOptions.reject_unknown_audit_metadata_keys.

type boolean

surface_source?

Pre-built AppSurfaceSpec — overrides the default which calls create_test_app_surface_spec against the same factory inputs. Pass when surface assembly needs fields outside the shared subset (e.g. env_schema, event_specs, ws_endpoints, transform_middleware).

type AppSurfaceSpec

deliver_inbound
#

testing/transports/ws_client.ts view source

(parsed: unknown, received: unknown[], waiters: WsWaiter[], on_request: WsRequestResponder | undefined, reply: (message: unknown) => void): void import {deliver_inbound} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Deliver one parsed inbound frame to a client's receive sink — the shared parse→(respond|push)→resolve core.

A server-initiated request ({method, id}) is answered by on_request when supplied (replying via reply) and is not surfaced as a normal message; everything else — notifications, replies to the client's own requests, and non-JSON frames — is pushed onto received and resolves any matching waiters, exactly as before. With no on_request, every frame (including a server-initiated request) flows to the sink unchanged, so the seam is purely additive.

parsed

type unknown

received

type unknown[]

waiters

type WsWaiter[]

on_request

type (WsRequestResponder) | undefined

reply

type (message: unknown) => void

returns

void

derive_cell_refs
#

db/cell_queries.ts view source

(data: Json): FactHash[] | null import {derive_cell_refs} from '@fuzdev/fuz_app/db/cell_queries.js';

Derive the refs array column value from a cell's data: the string values that are exactly a fact hash, deduped and sorted.

A ref is a whole string value matching the wire fact-hash grammar — blake3: followed by exactly sixty-four lowercase hex characters (is_fact_hash, FACT_HASH_EXACT). A hash inside prose or markup, an uppercase-hex digest, or a blake3: string of any other length is not a ref. Refs are what fact serving admits a read through and what cell ref admission gates on a write, so the grammar is exact: a string either is an address or isn't one. Object keys are not walked — only values. (A fact's own fact_ref edges are never derived from its bytes — they are the refs its put declares.)

Returns null (rather than []) when no refs are present — the column is nullable and the idx_cell_refs partial index is `WHERE refs IS NOT NULL`, so an empty array would force every cell into the index.

Sorts the deduped refs so the stored text[] is deterministic and byte-identical to the Rust twin (fuz_cell's extract_refs); every ref is ASCII, so the code-unit sort here and the byte sort there agree.

data

the cell's data

type Json

returns

FactHash[] | null

the sorted refs, or null when there are none

derive_error_schemas
#

http/error_schemas.ts view source

({ auth, has_input, has_params, has_query, rate_limit, checks_content_type }: DeriveErrorSchemasOptions): Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> import {derive_error_schemas} from '@fuzdev/fuz_app/http/error_schemas.js';

__0

returns

Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

DeriveErrorSchemasOptions
#

http/error_schemas.ts view source

DeriveErrorSchemasOptions import type {DeriveErrorSchemasOptions} from '@fuzdev/fuz_app/http/error_schemas.js';

Derive error schemas from a route's auth requirement, input schema, and rate limit config.

Returns the error schemas that middleware will auto-produce for this route. Route handlers can declare additional error schemas via RouteSpec.errors; explicit entries override auto-derived ones for the same status code.

Derivation rules under the new flat-record auth shape:

auth

type RouteAuth

has_input?

type boolean

has_params?

type boolean

has_query?

type boolean

rate_limit?

type RateLimitKey

checks_content_type?

type boolean

describe_account_lifecycle_cross_tests
#

describe_actor_lookup_cross_tests
#

describe_adversarial_404
#

testing/adversarial_404.ts view source

(options: AdversarialTestOptions): void import {describe_adversarial_404} from '@fuzdev/fuz_app/testing/adversarial_404.js';

Generate adversarial 404 response validation tests.

For each route with params + 404 in error_schemas:

  1. Creates a stub handler returning 404 with the declared error code
  2. Fires a request with valid-format params (nil UUIDs for UUID params)
  3. Validates response status is 404
  4. Validates response body matches the declared 404 Zod schema

options

returns

void

describe_adversarial_auth
#

testing/attack_surface.ts view source

(options: AdversarialTestOptions): void import {describe_adversarial_auth} from '@fuzdev/fuz_app/testing/attack_surface.js';

Generate adversarial HTTP auth enforcement test suites.

Describe blocks:

  • unauthenticated → 401 — every protected route
  • wrong role → 403 — every role route the role apps' session credential reaches, tested with all non-matching roles
  • authenticated without role → 403 — the same routes, no-role context
  • credential-gated routes reject a disallowed credential → 403 — every route declaring auth.credential_types, probed on a channel it refuses
  • correct auth passes guard — every protected route, assert not 401/403

options

returns

void

describe_adversarial_input
#

testing/adversarial_input.ts view source

(options: AdversarialTestOptions): void import {describe_adversarial_input} from '@fuzdev/fuz_app/testing/adversarial_input.js';

Generate adversarial input validation test suites.

Tests input body validation and params validation for all routes. Uses correct auth credentials so auth guards pass and validation middleware is actually exercised.

options

returns

void

describe_app_settings_cross_tests
#

describe_audit_completeness_tests
#

testing/audit_completeness.ts view source

(options: AuditCompletenessTestOptions): void import {describe_audit_completeness_tests} from '@fuzdev/fuz_app/testing/audit_completeness.js';

Composable audit log completeness test suite.

Verifies that every auth mutation route produces the correct audit log event type. Exercises routes via HTTP requests against a real database, then reads events back through the audit_log_list RPC (the production observation path the admin UI consumes).

options

returns

void

throws

  • Error - at setup time when `options.rpc_endpoints` is empty — the mutation-audit tests drive role_grant flow, session/token revoke-all, and invite create/delete through their RPC action specs. Hard-fails via `require_rpc_endpoint_path`.

describe_bearer_auth_cases
#

testing/middleware.ts view source

(suite_name: string, cases: BearerAuthTestCase[]): void import {describe_bearer_auth_cases} from '@fuzdev/fuz_app/testing/middleware.js';

Run a table of bearer auth middleware test cases.

Generates one test() per case inside a describe() block.

suite_name

type string

cases

returns

void

describe_body_size_cross_tests
#

describe_body_size_smuggling_cross_tests
#

describe_cell_crud_cross_tests
#

describe_cell_gated_create_cross_tests
#

describe_cell_grant_role_cross_tests
#

describe_cell_grant_steward_cross_tests
#

describe_cell_moderate_cross_tests
#

testing/cross_backend/cell_moderate.ts view source

(options: RpcPathCapabilityGatedCrossSuiteOptions): void import {describe_cell_moderate_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/cell_moderate.js';

Cross-backend parity for the cell_moderate verb (the `pending → approved | rejected decision and the approved → rejected` takedown) — root-authority-gated.

Builds a moderated public space (admin) + a participant's post (born pending + private under the moderation_required: true policy), then proves both spines agree:

  • a root manager (admin) approves → moderation: 'approved' + visibility: 'public' (the contribution goes live).
  • the author cannot self-approve → 403 cell_moderate_forbidden (the author can *view* their own pending cell, so they reach the gate, and are denied — moderation authority is over the governing root, not the contribution).
  • a non-viewer is 404-masked → a stranger who can't see the pending contribution gets cell_not_found, never learns it exists.
  • reject → moderation: 'rejected', visibility stays private.
  • a cell with no governing root → 400 cell_not_a_contribution for any viewer (owner, admin), the cell left unmoderated; a non-viewer gets the cell_not_found mask first.
  • approval needs a pending contribution, rejection a pending or approved one → else 400 cell_not_pending, the cell left as it was: a root owner a private, born-approved contribution was shared with can't publish it by "approving" it; an approved post is not approved again but can be taken down (rejected, visibility untouched); a rejected post is final.

And that moderation gates reads — a cell's effective audience is its visibility or grants and its moderation admission (unmoderated or approved), so a pending or rejected contribution reaches no one past its author and admins:

  • an author's self-flip to public admits nobody — a pending post its author makes public stays 404 to anon and a fresh non-admin on cell_get, and out of their cell_list; the author and admin still read it.
  • rejection holds a public post back — pending → rejected → public, and approved → rejected (approval made it public, rejection leaves visibility alone), both 404 past the author; a grant made while the post was approved stops admitting once it is rejected, on cell_get and shared_with: 'me'.
  • an approved post is live — the control.
  • relation reads follow — a pending post in a public collection is omitted from anon cell_item_list, beside a live sibling that isn't; at the far end of all four relation lists, a pending post is hidden from anon even flipped public, and a rejected one from its grant holder.
  • fact serving follows — a pending post's fact is 404 to anon, even with the post flipped public (also gated on capabilities.fact_serving).

And the steward arm — the owner of a non-admin root (a parentless non-space kind carrying the same policy, which the test authorizer leaves open) reads the pending posts under it and moderates them:

  • the queue — cell_list({root_id, moderation: 'pending'}) and cell_get (with can_edit / can_grant false) → 200; a stranger 404s.
  • moderation — the root owner approves → 200; the author 403, a stranger 404.
  • the arm stops there — the root owner's cell_clone of the pending post and a contribution under it are both 404.
  • fact serving — the root owner fetches a pending post's fact → 200, anon 404 (also gated on capabilities.fact_serving).

And that cell_grant_create refuses a pending contribution — 403 cell_grant_unapproved for its author and an admin, actor- and role-shaped, while a non-manager keeps the 404 mask; the same grant lands once the post is approved.

Gated on capabilities.cell_gated_create — a pending contribution only exists when the directory authorizer is mounted (reference spine binaries).

options

returns

void

describe_cell_ref_admission_cross_tests
#

describe_cell_relations_cross_tests
#

describe_conformance_table_tests
#

describe_cookie_attributes_cross_tests
#

describe_credential_deadline_cross_tests
#

testing/cross_backend/credential_deadline.ts view source

(options: CredentialDeadlineCrossTestOptions): void import {describe_credential_deadline_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/credential_deadline.js';

Register the credential-deadline suite: a WebSocket on a session or an API token closes with 4001 "Credential expired" at its credential's expiry, an audit stream on a session ends there, and the expiry seam never extends.

options

returns

void

describe_credential_header_robustness_cross_tests
#

describe_cross_process_sse_tests
#

testing/cross_backend/sse_round_trip.ts view source

(options: CrossProcessSseTestOptions): void import {describe_cross_process_sse_tests} from '@fuzdev/fuz_app/testing/cross_backend/sse_round_trip.js';

Register the cross-process SSE round-trip suite. Its cases run over a real streaming fetch: connected-comment, audit data frame, account-wide close-on-revoke, session-scoped close-on-revoke, close on account delete, close on admin token revoke-all, logout, password change and purge, and the per-session cap.

options

returns

void

describe_cross_process_ws_tests
#

testing/cross_backend/ws_round_trip.ts view source

(options: CrossProcessWsTestOptions): void import {describe_cross_process_ws_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_round_trip.js';

Register the cross-process WS round-trip suite over a real upgrade: authed heartbeat round-trip, heartbeat's parameterless-shape contract, an oversized message closing with WS_CLOSE_MESSAGE_TOO_BIG, anonymous-upgrade refusal, disallowed-origin refusal, and — when rpc_path is supplied — each listed revocation (session and token revokes, admin revoke-all, logout, password change, token-cap eviction, account delete and purge) closing the live socket, plus a self-revoking socket reading its reply before its close, and a WS mutation's audit row read back after _testing_drain_effects.

options

returns

void

describe_data_exposure_tests
#

testing/data_exposure.ts view source

(options: DataExposureTestOptions): void import {describe_data_exposure_tests} from '@fuzdev/fuz_app/testing/data_exposure.js';

Composable data exposure test suite.

Three test groups:

  1. Schema-level — walk JSON Schema output/error schemas for sensitive field names
  2. Runtime — fire real requests and check response bodies against blocklists
  3. Cross-privilege — admin routes return 403 for non-admin, error responses contain no sensitive fields

options

returns

void

describe_differential_conformance_tests
#

testing/cross_backend/differential.ts view source

(options: DifferentialConformanceOptions): void import {describe_differential_conformance_tests} from '@fuzdev/fuz_app/testing/cross_backend/differential.js';

Register a describe block sending every case to both backends and asserting their normalized responses are equal, or — for a case in the known-divergence ledger — that they still differ, and only where listed.

options

the cases, the two backends, and the shared surface

returns

void

describe_fact_serving_cross_tests
#

describe_identity_parity_cross_tests
#

describe_login_security_cross_tests
#

describe_origin_conformance_cross_tests
#

testing/cross_backend/origin_conformance.ts view source

(options: RpcPathCrossSuiteOptions): void import {describe_origin_conformance_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

Register the origin conformance suite: every row of the committed table, sent to the backend's _testing_origin_check action over the keeper's daemon-token channel, must get the answer the table names. The action is on every spine test binary, so the suite is ungated.

options

returns

void

describe_origin_cross_tests
#

describe_origin_scope_cross_tests
#

describe_peer_ping_ws_tests
#

testing/cross_backend/peer_ping_ws.ts view source

(options: PeerPingWsTestOptions): void import {describe_peer_ping_ws_tests} from '@fuzdev/fuz_app/testing/cross_backend/peer_ping_ws.js';

Register the server-initiated peer/ping suite — a positive round-trip plus the security negatives the design doc's §Security surface mandates (unsolicited-response rejection, per-connection id isolation, never-reply Timeout, wrong-shape reply rejection, plus client-error forwarding and the HTTP no-transport path). Gated on capabilities.peer_request.

options

returns

void

describe_query_shape_cross_tests
#

describe_rate_limiting_tests
#

testing/rate_limiting.ts view source

(options: RateLimitingTestOptions): void import {describe_rate_limiting_tests} from '@fuzdev/fuz_app/testing/rate_limiting.js';

Standard rate limiting integration test suite.

Creates 2 test groups:

  1. IP rate limiting on login — fires max_attempts + 1 login requests, verifies the last returns 429 with a valid RateLimitError body.
  2. Per-account rate limiting on login — fires max_attempts + 1 login requests with the same username, verifies the last returns 429.

There is deliberately no bearer-auth group: the bearer path carries no rate limiter on either spine, because an API token's entropy — not throttling — is what bounds guessing it. See auth/bearer_auth.ts.

Each test group asserts that required routes exist, failing with a descriptive message if the consumer's route specs are misconfigured.

options

returns

void

describe_ready_cross_tests
#

describe_role_grant_offer_enumeration_cross_tests
#

describe_role_grant_offer_lifecycle_cross_tests
#

describe_role_grant_offer_notification_ws_tests
#

testing/cross_backend/role_grant_offer_notification_ws.ts view source

(options: RoleGrantOfferNotificationWsTestOptions): void import {describe_role_grant_offer_notification_ws_tests} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_notification_ws.js';

Register the role-grant-offer WS notification suite — seven cases over a real upgrade, one per server-initiated notification (received / accepted / declined / retracted / revoke + supersede on both the accept and revoke cascades). Each opens the affected counterparty's socket, drives the lifecycle RPC, and strict-parses the delivered frame against its canonical params schema. Gated on capabilities.ws.

options

returns

void

describe_role_grant_participation_cross_tests
#

describe_role_grant_role_grammar_cross_tests
#

describe_round_trip_validation
#

testing/round_trip.ts view source

(options: RoundTripTestOptions): void import {describe_round_trip_validation} from '@fuzdev/fuz_app/testing/round_trip.js';

Run schema-driven round-trip validation tests.

For each route:

  1. Resolve URL with valid params
  2. Generate a valid request body (or use override)
  3. Pick auth headers matching the route's auth requirement
  4. Fire the request through fixture.transport and validate the response

SSE routes are skipped by Content-Type sniff. Routes returning non-2xx with valid input are still validated against their declared error schemas.

options

returns

void

describe_rpc_adversarial_cross_tests
#

testing/cross_backend/rpc_adversarial.ts view source

(options: RpcAdversarialCrossTestOptions): void import {describe_rpc_adversarial_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/rpc_adversarial.js';

Run the cross-process malformed-envelope and invalid-params suite.

options

the fixture, the declared surface and live mount, the RPC path, and methods to skip

returns

void

describe_rpc_attack_surface_tests
#

testing/rpc_attack_surface.ts view source

(options: RpcAttackSurfaceOptions): void import {describe_rpc_attack_surface_tests} from '@fuzdev/fuz_app/testing/rpc_attack_surface.js';

Run the standard RPC attack surface test suite.

Generates 3 test groups:

  1. Auth enforcement — per-method auth checks via JSON-RPC envelopes
  2. Adversarial envelopes — malformed JSON-RPC requests
  3. Adversarial params — schema-invalid params per method

Skips silently when surface.rpc_endpoints is empty.

options

returns

void

describe_rpc_round_trip_tests
#

testing/rpc_round_trip.ts view source

(options: RpcRoundTripTestOptions): void import {describe_rpc_round_trip_tests} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

Run schema-driven round-trip validation for RPC endpoints.

For each method:

  1. Generate valid params from the action's input schema
  2. Fire a POST request with JSON-RPC envelope
  3. For side_effects: false methods, also fire a GET request
  4. Validate response is well-formed JSON-RPC; successful responses are also validated against the method's declared output schema

Error responses (from missing DB state, etc.) are expected and validated as well-formed JSON-RPC errors. Successful responses are validated against action.spec.output. Two error classes fail loud instead of passing as a valid envelope: method not found (-32601) means the backend is missing a method the local surface advertises (assert_method_implemented), and a crash — HTTP 500, internal_error (-32603), or a ≥500 with no JSON-RPC envelope — means the backend failed on schema-valid input (assert_not_crash). A declared ≥500 code (service_unavailable, timeout) passes as a refusal. A method the suite can't drive generically goes in skip_methods; one whose generated params can't reach its handler gets an input_overrides entry.

options

returns

void

describe_session_cap_cross_tests
#

describe_session_cookie_codec_cross_tests
#

describe_simple_request_cross_tests
#

describe_spine_cross_suites
#

testing/cross_backend/spine_suites.ts view source

(handle: ReconstructedBootstrappedBackendHandle, options: SpineCrossSuitesOptions): void import {describe_spine_cross_suites} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

Run every shipped single-backend cross suite against one spawned backend — see the module doc for what the options add and what stays outside.

handle

the backend, as inject_backend_handle() returns it

options

the declared surface, plus excludes, case exclusions and overrides

returns

void

throws

  • Error - on an exclude with no reason, an unknown suite name, a suite both excluded and overridden, an excluded `testing_backdoor` (or one of its cases, or an override changing its `capabilities.testing_backdoors`), an exclude, override or `exclude_cases` entry for a suite that skips itself or isn't run, a backend not declaring `SPINE_SUITES_REQUIRED_BACKDOORS` (or `_testing_put_fact` with `fact_serving`), or a suite missing an input the handle doesn't carry

describe_sse_route_tests
#

testing/sse_round_trip.ts view source

(options: SseRouteTestOptions): void import {describe_sse_route_tests} from '@fuzdev/fuz_app/testing/sse_round_trip.js';

Run SSE route validation tests.

For each route: opens an authenticated SSE connection, asserts the connected comment, fires the trigger, validates the resulting payload, then asserts close-on-revoke (unless opted out).

options

returns

void

throws

  • Error - at setup time when `options.rpc_endpoints` is empty — the close-on-revoke assertion dispatches `account_session_revoke_all` via RPC. Hard-fails via `require_rpc_endpoint_path`.

describe_standard_admin_integration_tests
#

testing/admin_integration.ts view source

(options: StandardAdminIntegrationTestOptions): void import {describe_standard_admin_integration_tests} from '@fuzdev/fuz_app/testing/admin_integration.js';

Standard admin integration test suite for fuz_app admin routes.

Exercises account listing, role_grant grant/revoke (via RPC), session management, token management, audit log reads, admin-to-admin isolation, and 401/403 error-coverage on the admin REST surface. Output-schema conformance is not in scope — see the module docstring for the suites that cover it.

options

returns

void

throws

  • Error - at setup time when `options.rpc_endpoints` is empty — admin role_grant grant/revoke, session/token revoke-all, and audit-log reads are RPC-only. Hard-fails via `require_rpc_endpoint_path` so consumers see a clear setup error rather than `method not found` mid-suite.

describe_standard_adversarial_headers
#

testing/adversarial_headers.ts view source

(suite_name: string, options: TestMiddlewareStackOptions, allowed_origin: string, extra_cases?: AdversarialHeaderCase[] | undefined): void import {describe_standard_adversarial_headers} from '@fuzdev/fuz_app/testing/adversarial_headers.js';

Create a middleware stack app with standard adversarial header tests.

Convenience wrapper combining create_test_middleware_stack_app and create_standard_adversarial_cases. Generates one test() per case inside a describe() block — asserts body content for both error and success cases, and verifies that mock_validate was (or was not) reached per the case's validate_expectation flag, ensuring earlier middleware actually short-circuits before token validation in the rejection cases.

suite_name

the describe block name

type string

options

middleware stack configuration

allowed_origin

an origin that passes the origin check (used for standard cases)

type string

extra_cases?

additional cases appended after the 7 standard ones

optional

returns

void

describe_standard_attack_surface_tests
#

testing/attack_surface.ts view source

(options: StandardAttackSurfaceOptions): void import {describe_standard_attack_surface_tests} from '@fuzdev/fuz_app/testing/attack_surface.js';

Run the standard attack surface test suite.

Test groups:

  1. Snapshot — live surface matches committed JSON
  2. Determinism — building twice yields identical results
  3. Public routes — bidirectional check (no unexpected, no missing)
  4. Middleware stack — every API route has the full middleware chain
  5. Surface invariants — structural assertions over surface.routes (error schemas, descriptions, duplicates, consistency)
  6. RPC/WS surface invariants — structural assertions over surface.rpc_endpoints + surface.ws_endpoints (descriptions, protocol-action spread, kind ⇔ auth)
  7. Security policy — rate limiting on sensitive routes, no unexpected public mutations, method conventions
  8. Error schema tightness — informational log of generic vs specific error schemas, plus assertion against default_error_schema_tightness by default (opt out with error_schema_tightness: null)
  9. Adversarial auth — unauthenticated/wrong-role/correct-auth enforcement
  10. Adversarial input — input body and params validation
  11. Adversarial 404 — stub 404 handlers, validate response bodies against declared schemas

Consumer test files call this with project-specific options, then add any project-specific assertions in additional describe blocks.

options

returns

void

describe_standard_cross_process_tests
#

testing/cross_backend/standard.ts view source

(options: StandardCrossProcessTestOptions): void import {describe_standard_cross_process_tests} from '@fuzdev/fuz_app/testing/cross_backend/standard.js';

Run the cross-process standard test bundle — integration, admin (when roles provided), round trip, RPC round trip, data exposure. See the module doc for the suites omitted from this bundle and why.

options

returns

void

describe_standard_integration_tests
#

testing/integration.ts view source

(options: StandardIntegrationTestOptions): void import {describe_standard_integration_tests} from '@fuzdev/fuz_app/testing/integration.js';

Standard integration test suite for fuz_app auth routes.

Exercises login/logout, cookie attributes, session security, session revocation, password change (incl. API token revocation), origin verification, bearer auth (incl. browser context discard on mutations), token revocation, cross-account isolation, expired credential rejection, signup invite edge cases, and response body validation.

Each test group asserts that required routes exist, failing with a descriptive message if the consumer's route specs are misconfigured.

The two signup-invite-edge-case tests call invite_create_action_spec (admin-gated) over the fixture's session, so consumers wiring signup + admin actions must thread extra_keeper_roles: [ROLE_ADMIN] through either default_in_process_suite_options or `default_cross_process_setup(handle, {extra_keeper_roles: [ROLE_ADMIN]}). In both modes the fixture's fixture.account` is the fresh keeper, and the extra-keeper-roles list grants the bootstrapped keeper additional roles inline (cross-process via _testing_reset's bootstrap-time seeding; in-process via bootstrap_test_keeper) — no per-role RPC cost, no offer/accept round-trip. The tests run against the production open_signup: false default — the cross-process per-test secondary mint via fixture.create_account() is invite-gated (keeper drives invite_create before signup) so the harness doesn't need to flip the setting. Consumers that don't wire signup or invite_create silently skip these two tests.

options

returns

void

throws

  • Error - at setup time when `options.rpc_endpoints` is empty — the suite hard-fails via `require_rpc_endpoint_path` rather than running tests that would crash mid-suite trying to dispatch `account_verify` / `account_session_*` / `account_token_*`.

describe_testing_backdoor_cross_tests
#

testing/cross_backend/testing_backdoor.ts view source

(options: TestingBackdoorCrossTestOptions): void import {describe_testing_backdoor_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/testing_backdoor.js';

Register the backdoor suite: the declaration check over every known backdoor, then the refused-principal rows over the mounted ones.

options

the fixture, the backend's capabilities, and the optional WS target and extra seams

returns

void

throws

  • Error - on a declaration or extra method `resolve_backdoor_probes` refuses

describe_token_lifetime_cross_tests
#

describe_token_scope_surface_cross_tests
#

describe_transport_composition_cross_tests
#

testing/cross_backend/transport_composition.ts view source

(options: TransportCompositionCrossTestOptions): void import {describe_transport_composition_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

Register the front-door table's cases — one test per cell, each on a fresh fixture.

This is the front-door contract: a backend that wires it answers each transport's probes as the table says — when one request trips several refusals, the one the table names answers.

options

returns

void

describe_ws_action_rate_limit_cross_tests
#

testing/cross_backend/ws_action_rate_limit.ts view source

(options: WsActionRateLimitCrossTestOptions): void import {describe_ws_action_rate_limit_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_action_rate_limit.js';

Register the action rate-limit suite: the account axis over WS, one account bucket across HTTP RPC and WS, the per-IP axis keyed by X-Forwarded-For across both transports, and the account axis at the RPC and WS dispatchers.

options

returns

void

describe_ws_connection_cap_cross_tests
#

describe_ws_envelope_cross_tests
#

testing/cross_backend/ws_envelope.ts view source

(options: WsEnvelopeCrossTestOptions): void import {describe_ws_envelope_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_envelope.js';

Register the WS frame-envelope cases. Gated on capabilities.ws; the in-flight cancel also on capabilities.peer_request.

options

returns

void

describe_ws_inbound_dispatch_cross_tests
#

describe_ws_outbound_overflow_cross_tests
#

testing/cross_backend/ws_outbound_overflow.ts view source

(options: WsOutboundOverflowCrossTestOptions): void import {describe_ws_outbound_overflow_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_outbound_overflow.js';

Register the outbound-overflow parity suite over a real upgrade: a burst within the budget arrives whole, one past it closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW and no response, and the account's other connection is unaffected.

options

returns

void

describe_ws_role_gated_cross_tests
#

testing/cross_backend/ws_role_gated.ts view source

(options: WsRoleGatedCrossTestOptions): void import {describe_ws_role_gated_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_role_gated.js';

Register the role-gated WebSocket suite: a revoked global grant of a gating role closes the account's gated socket with WS_CLOSE_PERMISSION_REVOKED, its ungated socket stays open and still receives the account's pushes, and a re-upgrade is refused at the gate; a scoped revoke of a gating role closes nothing.

That the closed registration is sent nothing more is beyond what a client can observe once it has read the close frame, so the suite does not claim it: each spine's own transport tests pin it.

options

returns

void

detect_format
#

testing/schema_generators.ts view source

(field_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): string | null import {detect_format} from '@fuzdev/fuz_app/testing/schema_generators.js';

Detect format constraints on a field from its input-side JSON Schema (to_field_json_schema). Returns the format string (e.g. 'uuid', 'email'), 'pattern' for a regex, or null.

field_schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

string | null

detect_json_schema_format
#

testing/schema_generators.ts view source

(json: Record<string, unknown>): string | null import {detect_json_schema_format} from '@fuzdev/fuz_app/testing/schema_generators.js';

The format constraint a field's input-side JSON Schema carries: its format (e.g. 'uuid', 'email'), 'pattern' for a regex, or null. A union (anyOf of several non-null branches) answers its first branch carrying one — a value violating every branch is what the caller needs, and the generators check the field refuses the candidate.

json

a field's JSON Schema, as to_field_json_schema returns it

type Record<string, unknown>

returns

string | null

the format, 'pattern', or null

diff_action_manifests
#

testing/cross_backend/action_manifest_parity.ts view source

(a: { methods: { method: string; input: "object" | "void"; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; }[]; }, b: { ...; }): ActionManifestDiff[] import {diff_action_manifests} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest_parity.js';

Structural diff between two manifests — empty array means parity holds.

Order is deterministic: methods in sorted order, with each method's input-shape, side-effect, rate-limit, and auth-field sub-diffs grouped together.

a

b

returns

ActionManifestDiff[]

diff_differential_responses
#

testing/cross_backend/differential_normalize.ts view source

(a: DifferentialResponse, b: DifferentialResponse): DifferentialDiff[] import {diff_differential_responses} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

Every place two normalized responses differ, in a stable order: status, then header names, then the body depth-first with keys sorted. Empty when the responses are equal.

a

one backend's normalized response

b

the other's

returns

DifferentialDiff[]

the differences

diff_migration_trackers
#

testing/schema_parity.ts view source

(a: { entries: { namespace: string; name: string; sequence: number; }[]; }, b: { entries: { namespace: string; name: string; sequence: number; }[]; }): MigrationTrackerDiff[] import {diff_migration_trackers} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structural diff between two migration trackers — empty array means the two spines recorded byte-identical migration identity. Keyed on (namespace, name); sequence mismatches on a shared key are reported too. Deterministic order: shared/missing rows in sorted (namespace, name) order.

a

b

returns

MigrationTrackerDiff[]

diff_schema_snapshots
#

testing/schema_parity.ts view source

(a: { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }, b: { ...; }): SchemaDiff[] import {diff_schema_snapshots} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structural diff between two snapshots — empty array means parity holds.

Order of diffs is deterministic: tables in sorted order (with column/index/constraint sub-diffs grouped per table), then sequences. Consumers can rely on this for stable diff output.

a

b

returns

SchemaDiff[]

differential_path_covers
#

testing/cross_backend/differential_normalize.ts view source

(covered: string, path: string): boolean import {differential_path_covers} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

Whether path is covered or lies under it — body.error.data covers body.error.data.reason and body.error.data[0], not body.error.database.

covered

a declared path

type string

path

type string

returns

boolean

true when path is covered or under it

DIFFERENTIAL_REQUEST_HEADERS
#

testing/cross_backend/differential.ts view source

Readonly<Record<string, string>>

type Readonly<Record<string, string>>

import {DIFFERENTIAL_REQUEST_HEADERS} from '@fuzdev/fuz_app/testing/cross_backend/differential.js';

Headers every HTTP request the differential sends carries, beneath the principal's: accept-encoding: identity, so each backend answers uncompressed and a runtime's own compression (Deno.serve compresses for a request that accepts gzip) never reads as a header difference.

DifferentialBackend
#

testing/cross_backend/differential.ts view source

DifferentialBackend import type {DifferentialBackend} from '@fuzdev/fuz_app/testing/cross_backend/differential.js';

One side of the differential.

name

Short label used in failure output (ts, rust).

type string

readonly

setup_test

Per-test fixture producer for this backend.

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

ws?

The backend's WebSocket endpoint. Set on both backends to send the cases over a socket instead of HTTP — only the cases is_ws_conformance_case admits, and the ledger is checked against those. Set on one alone, the suite throws at registration.

type WsEndpointTarget

readonly

DifferentialConformanceOptions
#

testing/cross_backend/differential.ts view source

DifferentialConformanceOptions import type {DifferentialConformanceOptions} from '@fuzdev/fuz_app/testing/cross_backend/differential.js';

cases

The cases to send to both backends, in order.

type readonly ConformanceCase[]

readonly

backends

The two backends; a and b in diffs are these, in order.

type readonly [DifferentialBackend, DifferentialBackend]

readonly

surface_source

Surface spec — supplies the RouteSpecs for the REST branch.

type AppSurfaceSpec

readonly

rpc_endpoints

RPC endpoints — resolved to find each method's action spec.

type RpcEndpointsSuiteOption

readonly

live_rpc_endpoints?

The RPC endpoints both backends live-mount, for methods off the declared rpc_endpoints — resolved the way the table runner resolves them (resolve_conformance_rpc_endpoints).

type RpcEndpointsSuiteOption

readonly

session_options

Session options — needed to resolve the rpc_endpoints factory form.

type SessionOptions<string>

readonly

principals?

Maps the role_holder / wrong_role principals to seeded usernames.

type ConformancePrincipalConfig

readonly

known_divergences?

The tracked divergences these cases may show.

type readonly KnownDivergence[]

readonly

suite_name?

describe block label. Defaults to 'differential conformance'.

type string

readonly

DifferentialDiff
#

testing/cross_backend/differential_normalize.ts view source

DifferentialDiff import type {DifferentialDiff} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

One place two normalized responses differ. path is dotted from the response root (status, headers.x-example, body.error.data.reason, body.result.accounts[0]); a / b are the values on each side, undefined where a side has nothing there. A header one side sent and the other didn't is headers.<name>, with true on the side that sent it. An object one side lacks is compared key by key, so each of its keys is its own difference (body.error.data.reason, not the whole body.error.data); an empty object against a missing one is a difference at the object's path.

path

type string

readonly

a

type unknown

readonly

b

type unknown

readonly

DifferentialRawResponse
#

DifferentialResponse
#

testing/cross_backend/differential_normalize.ts view source

DifferentialResponse import type {DifferentialResponse} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

The canonical form of one response: what the differential compares across backends.

status

HTTP status.

type number

readonly

body

The JSON body with volatile values placeholdered and keys sorted.

type unknown

readonly

header_names

Sorted response header names, minus VOLATILE_HEADER_NAMES.

type readonly string[]

readonly

dispatch_with_post_commit_rollback
#

http/pending_effects.ts view source

<T>(post_commit_effects: (() => void | Promise<void>)[] | undefined, dispatch: () => T | Promise<T>): Promise<Awaited<T>> import {dispatch_with_post_commit_rollback} from '@fuzdev/fuz_app/http/pending_effects.js';

Run a handler dispatch (the handler plus its wrapping db.transaction), discarding any post_commit_effects it queued via emit_after_commit if it throws. A thrown handler rolls back its transaction, so firing those deferred effects would announce state that never committed. On any throw the queue is truncated back to its pre-dispatch depth — not cleared, so entries a surrounding scope pre-seeded survive — and the error re-thrown unchanged.

The eager pending_effects queue is deliberately never touched here: its pool writes run outside the transaction and intentionally survive rollback (attempt audits). emit_after_commit thus reads as "run iff the wrapping transaction commits."

Both dispatch sites — the REST route wrapper (http/route_spec.ts) and the action dispatcher (actions/perform_action.ts, the RPC + WS path) — wrap their handler call in this so the discard contract lives in one place. The Rust fuz_actions spine pins the same contract.

post_commit_effects is undefined when a handler runs without the app-server pending-effects middleware (bare route / dispatch harnesses): absent ⇒ nothing queued ⇒ nothing to discard.

post_commit_effects

the deferred queue to truncate on throw, or undefined

type (() => void | Promise<void>)[] | undefined

dispatch

invokes the handler (and any wrapping transaction)

type () => T | Promise<T>

returns

Promise<Awaited<T>>

generics

dispatch_with_post_commit_rollback<T>
T

mutates

  • post_commit_effects — truncated to its pre-dispatch depth on throw

dispatch_ws_message
#

testing/ws_round_trip.ts view source

(on_message: (evt: MessageEvent<WSMessageReceive>, ws: WSContext<unknown>) => void, event: MessageEvent<any>, ws: WSContext<unknown>): Promise<...> import {dispatch_ws_message} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Hono types WSEvents.onMessage as () => void | Promise<void>. Awaits only the Promise branch so tests observe full dispatch (auth, validation, handler, send).

on_message

type (evt: MessageEvent<WSMessageReceive>, ws: WSContext<unknown>) => void

event

type MessageEvent<any>

ws

type WSContext<unknown>

returns

Promise<void>

display_db_url
#

db/status.ts view source

(url: string): string import {display_db_url} from '@fuzdev/fuz_app/db/status.js';

Render a database URL for printing from the parse node-postgres dials — new URL, with pg-connection-string's precedence, where a user, host, or port query parameter overrides the URL's own — so the line names the server dialed and no credential can leak however the input spells it.

A postgres:// or postgresql:// URL renders as postgres://[user@]host:port[/dbname]: never the password, and never the query or fragment (sslmode and the like are dropped by design). The scheme always prints as postgres://. The port always prints, 5432 when the URL names none. The user and database name print percent-encoded with encodeURIComponent, so no control character, escape sequence, or bidi override reaches the terminal and an @ can't pose as the separator; each prints only when non-empty. An IP-literal host prints canonically (IPv6 bracketed); a Unix-socket directory prints with each / as %2F. A PGlite file:// or memory:// URL prints as scheme, host, and path.

The whole line is (not a URL; hidden) for any other scheme, when new URL rejects the input, when it holds a malformed percent-escape (%ZZ), when there is no host, when the host is neither an IP literal, a hostname of A-Z a-z 0-9 . _ -, nor a socket directory of those characters and /, when the user holds ?, =, &, /, or #, or when the port isn't a number up to 65535. The host and user rules match the Rust twin's, where they catch tokio-postgres's split at the first @.

Twin of the Rust fuz_db CLI's display_db_url, byte-identical on the URLs both drivers read alike. They part where the drivers do: a host= or port= query parameter overrides the URL's here (tokio-postgres appends it to a list whose first entry the pool dials); a dbname= query parameter, which tokio-postgres honours and node-postgres ignores, and a #… fragment, which tokio-postgres keeps as part of the database name; tokio-postgres also accepts key=value strings, host lists, and an unencoded / or ? in the password (here, the placeholder); an unencoded @ in the userinfo splits at the last @ here, rendering the user and host node-postgres dials, where the Rust twin prints the placeholder; node-postgres decodes the database name with decodeURI, so a reserved-character escape such as %2F stays literal and prints as %252F; an IPv4-mapped IPv6 address serializes differently; and tokio-postgres refuses an unknown query parameter (sslpassword=) that node-postgres passes.

url

the database URL

type string

returns

string

the URL without its password, query, or fragment, or the placeholder

Divergence
#

db/status.ts view source

Divergence

type { kind: "name_mismatch"; position: number; applied: string; expected: string; } | { kind: "binary_older"; applied: number; declared: number; }

import type {Divergence} from '@fuzdev/fuz_app/db/status.js';

A divergence between the recorded migration tracker and the code's list.

Either variant is a state the migration runner refuses to boot against — a re-bootstrap (drop + migrate) is needed. Structured (not a pre-formatted string) so programmatic consumers can branch on kind; format_db_status renders the operator-facing line. The discriminated-union twin of the Rust fuz_db Divergence enum.

DualSpawnGlobalSetupOptions
#

testing/cross_backend/create_dual_spawn_global_setup.ts view source

DualSpawnGlobalSetupOptions import type {DualSpawnGlobalSetupOptions} from '@fuzdev/fuz_app/testing/cross_backend/create_dual_spawn_global_setup.js';

configs

The two backend config factories to spawn. a is spawned first; on its success b is spawned (with a torn down if b throws). Label them in the consuming test (e.g. via assert_schema_snapshots_equal's labels).

type { readonly a: () => BackendConfig; readonly b: () => BackendConfig; }

readonly

provide_keys?

project.provide keys for the two serialized handles (read by inject in the test). Default parity_handle_a / parity_handle_b (the primary parity use, declared on vitest's ProvidedContext by provided_context.ts); override for other dual-spawn gates, augmenting ProvidedContext for those keys yourself.

type { readonly a: string; readonly b: string; }

readonly

EffectErrorContext
#

http/pending_effects.ts view source

EffectErrorContext import type {EffectErrorContext} from '@fuzdev/fuz_app/http/pending_effects.js';

Context passed to on_effect_error when an eager pending effect rejects.

transport

The transport whose request spawned the effect.

type "http" | "ws"

method

HTTP method of the request, or the JSON-RPC method of the WS frame.

type string

path

URL path of the request, or the mount path of the WS endpoint.

type string

Email
#

primitive_schemas.ts view source

value + type

string import {Email} from '@fuzdev/fuz_app/primitive_schemas.js';

Email validation. Every current consumer pairs it with Username (signup, invites, audit log), so the two live together with the other Zod primitives.

Deliberately permissive — a structural shape check (EMAIL_REGEX plus the EMAIL_LENGTH_MAX byte bound), not RFC 5322 conformance or deliverability (real delivery is proven by a confirmation email). Replaces Zod's stricter z.email() so the rule is one explicit regex the Rust spine's is_valid_email mirrors; z.email()'s internal regex (2+ char TLD, no consecutive dots) was brittle to keep in cross-impl parity and rejected addresses like a@b.c. The length bound is the UTF-8 byte count (RFC 5321 measures octets), so a multibyte address is bounded identically to the Rust spine's s.len() rather than diverging on JS's UTF-16 .length. No transform: case is preserved and surrounding whitespace is rejected (not trimmed), so storage is verbatim and the case-insensitive lookup rides the DB-side LOWER(email).

EMAIL_LENGTH_MAX
#

primitive_schemas.ts view source

254 import {EMAIL_LENGTH_MAX} from '@fuzdev/fuz_app/primitive_schemas.js';

Maximum email length in bytes — RFC 5321 §4.5.3.1.3 path-length limit (the limit is octets). Email bounds the UTF-8 byte length, matching the Rust spine's s.len() check; for an all-ASCII address bytes == characters.

emit_after_commit
#

http/pending_effects.ts view source

(ctx: EmitAfterCommitContext, fn: () => void | Promise<void>): void import {emit_after_commit} from '@fuzdev/fuz_app/http/pending_effects.js';

Defer a side effect until after the handler's transaction commits.

Pushes a raw thunk onto ctx.post_commit_effects — the flush middleware (in server/app_server.ts and the per-message WS dispatcher) is the only site that ever invokes fn. This is load-bearing: a previous implementation queued Promise.resolve().then(fn), which JS's microtask scheduler drains before the wrapping await db.query('COMMIT') resumes — fn fired mid-transaction and a rollback would leak a notification for state that never landed.

The thunk shape closes that gap by deferring the work to flush time. The flush owns the per-thunk try/catch + log.error so any directly-pushed thunk (tests included) cannot escape the safety net.

Deferral is only half the contract: a queued thunk is also discarded if the handler's transaction rolls back — via dispatch_with_post_commit_rollback (see its TSDoc). So fn runs iff the wrapping transaction commits, never for state that rolled back. Rollback-resilient writes (attempt audits that must land even when the handler fails) belong on the eager pending_effects queue instead.

ctx

context carrying the post_commit_effects queue

fn

side effect to run after commit; may return void or Promise<void>

type () => void | Promise<void>

returns

void

mutates

  • ctx.post_commit_effects — appends `fn` verbatim

emit_cell_audit
#

auth/cell_action_helpers.ts view source

<T extends string>(ctx: ActionContext | ActionActorContext, audit: AuditEmitter, auth: RequestActorContext, event_type: T, metadata: (T extends "login" | ... 26 more ... | "db_admin_row_delete" ? (AuditMetadataMap[T] & Record<...>) | null : Record<...> | null) | undefined): Promise<...> import {emit_cell_audit} from '@fuzdev/fuz_app/auth/cell_action_helpers.js';

Emit a cell-domain audit event for the caller, with actor_id, account_id, and ip lifted from auth and ctx. metadata is the event's own envelope.

ctx

the action context

audit

the bound audit emitter

auth

the caller

event_type

the cell event

type T

metadata

the event's metadata envelope

type (T extends "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete" ? (AuditMetadataMap[T] & Record<string, unknown>) | null : Record<string, unknown> | null) | undefined

returns

Promise<void>

generics

emit_cell_audit<T extends string>
T
constraint string

throws

  • Error - when the in-transaction INSERT fails, failing the handler

mutates

  • audit_log table — inserts the row through `audit.emit`, in the request transaction

EmitAfterCommitContext
#

http/pending_effects.ts view source

EmitAfterCommitContext import type {EmitAfterCommitContext} from '@fuzdev/fuz_app/http/pending_effects.js';

Minimal structural context required by emit_after_commit — just the deferred queue. Both RouteContext and ActionContext satisfy this. (No log here: the flush middleware owns the per-thunk try/catch + log.error, with a logger of its own.)

post_commit_effects

type (() => void | Promise<void>)[]

EmitDecorator
#

auth/audit_emitter.ts view source

EmitDecorator import type {EmitDecorator} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Wrap the bound emit before it gets captured by emit_role_grant_target's closure and exposed on the returned AuditEmitter. Test instrumentation uses this to record emit invocations against external markers on a frozen emitter, whose slots can't be patched after construction.

Because the inner closure captures the decorated function (not the outer slot reference), emit_role_grant_target also routes through the wrap — the decorator sees role-grant-shape emissions, not just bare emit calls. Production never sets this.

(call)

type (inner: AuditEmitFn): AuditEmitFn

inner

returns AuditEmitFn

empty_owned_database
#

testing/cross_backend/postgres_database.ts view source

(database_url: string, options: EmptyOwnedDatabaseOptions): Promise<void> import {empty_owned_database} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

Drop and recreate the public schema of a database a setup owns, so a backend that does not wipe its own database migrates and bootstraps from nothing on every run — bootstrap refuses a database that already holds an account.

The wipe takes everything, so it is tied to owned_name, the one database the setup owns, rather than to whatever URL reaches it. Create the database first (ensure_database_for_url); a missing one, a server that cannot be reached and a role that cannot connect all fail with what to check.

database_url

a postgres:// URL naming the owned database

type string

options

the owned name, the setup's label, and an optional hint

returns

Promise<void>

throws

  • Error - when the URL names another database, or when the database cannot be emptied (with what to check)

EmptyOwnedDatabaseOptions
#

testing/cross_backend/postgres_database.ts view source

EmptyOwnedDatabaseOptions import type {EmptyOwnedDatabaseOptions} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

owned_name

The one database name the call may empty — the setup's own. A URL naming any other database is refused before anything connects.

type string

readonly

label

Prefixes every error message, naming the setup (e.g. 'schema parity setup').

type string

readonly

hint?

Appended to the fix a connect failure suggests — another way to reach a usable server, such as the env var that points the setup elsewhere.

type string

readonly

ensure_database
#

testing/cross_backend/postgres_database.ts view source

(database: string, server_url?: string | undefined, deps?: EnsureDatabaseDeps): boolean import {ensure_database} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

Create a Postgres database when it is missing, with createdb.

Which server it lands on depends on server_url. Given one, the database is created on the server that URL names: createdb connects through the URL with its database swapped for the postgres maintenance database, so host, port, role and query parameters all come from the URL, and a password goes to createdb in its environment, never its arguments. Without one, createdb reads the libpq environment (PGHOST / PGPORT / PGUSER).

Idempotent and never throws: a database that already exists is the quiet success case (is_createdb_already_exists). Any other failure — no createdb on PATH, no server, a role that may not create databases — is reported through deps.warn with createdb's own stderr, and whatever connects next reports a database that is really missing.

database

the database name

type string

server_url?

a postgres:// URL on the server to create it on; its database name is ignored

type string
optional

deps?

where a failure is reported, console.warn by default

optional default default_ensure_database_deps

returns

boolean

whether the database exists now (created, or already there)

ensure_database_for_url
#

testing/cross_backend/postgres_database.ts view source

(database_url: string, deps?: EnsureDatabaseDeps | undefined): boolean import {ensure_database_for_url} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

Create the database a Postgres URL names, on the server it names, when it is missing (ensure_database). A URL that is not postgres: — memory://, file:// — or that names no database is left alone.

database_url

the URL a backend connects to

type string

deps?

where a failure is reported, console.warn by default

optional

returns

boolean

whether the URL named a Postgres database to ensure

EnsureDatabaseDeps
#

EnumTypeSnapshot
#

testing/schema_introspect.ts view source

value + type

{ labels: string[]; } import {EnumTypeSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Enum-type metadata — the labels of a CREATE TYPE ... AS ENUM, captured in pg_enum.enumsortorder (declaration) order. Order is significant: a Postgres enum's labels are an ordered set, and reordering them is a schema change, so the parity diff compares the arrays positionally.

labels

Enum labels in declared order.

type string[]

env_schema_to_surface
#

http/surface.ts view source

(schema: ZodObject<$ZodLooseShape, $strip>): AppSurfaceEnv[] import {env_schema_to_surface} from '@fuzdev/fuz_app/http/surface.js';

Convert env schema to surface entries using .meta() metadata, read through each field's wrappers — schema_meta_value for the description, the wider fail-safe schema_sensitivity walk for secrets.

schema

Zod object schema with .meta() on fields

type ZodObject<$ZodLooseShape, $strip>

returns

AppSurfaceEnv[]

EnvDeps
#

runtime/deps.ts view source

EnvDeps import type {EnvDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Environment variable access.

env_get

Get an environment variable value.

type (name: string): string | undefined

name

type string
returns string | undefined

env_set

Set an environment variable.

type (name: string, value: string): void

name

type string

value

type string
returns void

EnvValidationError
#

env/load.ts view source

import {EnvValidationError} from '@fuzdev/fuz_app/env/load.js';

Error thrown when environment validation fails.

Contains structured information for apps to format their own error messages.

inheritance

extends: Error

raw

The raw env values that were read.

type Record<string, string | undefined>

readonly

zod_error

The Zod validation error.

type $ZodError<unknown>

readonly

all_undefined

True if every env var was undefined (nothing loaded).

type boolean

readonly

constructor

type new (raw: Record<string, string | undefined>, zod_error: $ZodError<unknown>): EnvValidationError

raw

type Record<string, string | undefined>

zod_error

type $ZodError<unknown>

format_issues

Format Zod validation issues as human-readable strings.

type (): string[]

returns string[]

array of formatted issue strings like "PORT: Expected number"

ERROR_ACCOUNT_NOT_FOUND
#

http/error_schemas.ts view source

"account_not_found" import {ERROR_ACCOUNT_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Token references a deleted account.

ERROR_ACCOUNT_VANISHED
#

http/error_schemas.ts view source

"account_vanished" import {ERROR_ACCOUNT_VANISHED} from '@fuzdev/fuz_app/http/error_schemas.js';

Authentication validated an account, but a follow-up read in the authorization phase came back null — the account or its named actor row was deleted between the credential check and the dispatcher's build_request_context / build_account_context step. Torn read, not a missing-actor invariant violation. Surfaced as 500 so the operator sees the race signal; clients can retry. Distinct from ERROR_ACCOUNT_NOT_FOUND (stale token referencing a long-deleted account, raised at credential validation) and ERROR_NO_ACTORS_ON_ACCOUNT (the actor list enumerated empty).

ERROR_ACTOR_NOT_ON_ACCOUNT
#

http/error_schemas.ts view source

"actor_not_on_account" import {ERROR_ACTOR_NOT_ON_ACCOUNT} from '@fuzdev/fuz_app/http/error_schemas.js';

Supplied acting field does not name an actor on the authenticated account.

ERROR_ACTOR_REQUIRED
#

http/error_schemas.ts view source

"actor_required" import {ERROR_ACTOR_REQUIRED} from '@fuzdev/fuz_app/http/error_schemas.js';

Multi-actor account requires the request to carry an explicit acting field naming the actor the request is acting as, so the dispatcher's authorization phase doesn't pick a default actor silently. Returned with the available actors so the client can prompt.

ERROR_ACTOR_SEARCH_SCOPE_REQUIRED
#

auth/actor_search_action_specs.ts view source

"actor_search_scope_required" import {ERROR_ACTOR_SEARCH_SCOPE_REQUIRED} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

Reason: scope_ids was empty and the caller is not admin. Distinct from standard invalid_params issues so the visiones picker can surface a specific "pick a scope first" message rather than echoing Zod issues.

ERROR_ALREADY_BOOTSTRAPPED
#

http/error_schemas.ts view source

"already_bootstrapped" import {ERROR_ALREADY_BOOTSTRAPPED} from '@fuzdev/fuz_app/http/error_schemas.js';

Bootstrap lock already acquired — system already bootstrapped.

ERROR_AUTHENTICATION_REQUIRED
#

http/error_schemas.ts view source

"authentication_required" import {ERROR_AUTHENTICATION_REQUIRED} from '@fuzdev/fuz_app/http/error_schemas.js';

No valid session or bearer token.

ERROR_CANNOT_DELETE_KEEPER
#

auth/admin_action_specs.ts view source

"cannot_delete_keeper" import {ERROR_CANNOT_DELETE_KEEPER} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

data.reason (403) on account_delete / account_purge when the target account holds an active keeper role_grant. The keeper account is never deletable or purgeable through the API: auth resolution and daemon-token resolution both pivot on the keeper account, so tombstoning or cascading it away would brick keeper/daemon auth with no recovery path (the keeper role is not web-revocable, and account_purge itself requires keeper auth). Keeper-account removal stays out-of-band (bootstrap / DB surgery). Mirrors the Rust ERROR_CANNOT_DELETE_KEEPER.

ERROR_CANNOT_DELETE_LAST_ADMIN
#

auth/admin_action_specs.ts view source

"cannot_delete_last_admin" import {ERROR_CANNOT_DELETE_LAST_ADMIN} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

data.reason (403) on account_delete / account_purge when the target is the sole remaining active admin — removing it would leave the system with no account that can authenticate into the admin surface (and account_undelete is itself admin-gated), and no web path re-grants admin. Soft-deleted admins don't count toward the tally (they can't log in), and the count locks the rows it counted, so two removals racing for the last two admins can't both pass. Mirrors the Rust ERROR_CANNOT_DELETE_LAST_ADMIN.

ERROR_CANNOT_REVOKE_KEEPER_ADMIN
#

auth/role_grant_offer_action_specs.ts view source

"cannot_revoke_keeper_admin" import {ERROR_CANNOT_REVOKE_KEEPER_ADMIN} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — role_grant_revoke targeted the keeper account's global admin grant. A 403: no web path grants admin to the keeper again (bootstrap is one-shot and keeper is not web-grantable), so revoking it would lock the keeper out of the admin surface for good. Twin of the Rust ERROR_CANNOT_REVOKE_KEEPER_ADMIN.

ERROR_CANNOT_REVOKE_LAST_ADMIN
#

auth/role_grant_offer_action_specs.ts view source

"cannot_revoke_last_admin" import {ERROR_CANNOT_REVOKE_LAST_ADMIN} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — role_grant_revoke targeted a global admin grant whose revoke would leave no active admin account. A 403, counted as account_delete's last-admin guard counts (active accounts, active actors), less the grant being revoked, under the same row locks. Twin of the Rust ERROR_CANNOT_REVOKE_LAST_ADMIN.

ERROR_CELL_CREATE_FORBIDDEN
#

auth/cell_action_specs.ts view source

"cell_create_forbidden" import {ERROR_CELL_CREATE_FORBIDDEN} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — the parent-aware CellCreateAuthorize denied the create for a viewable parent (or a root creation): "you see it, you can't contribute here." A 403 forbidden, distinct from the create-path 404 mask used when the parent itself isn't viewable (twin of the Rust ERROR_CELL_CREATE_FORBIDDEN).

ERROR_CELL_FIELD_LIST_REQUIRES_SOURCE_OR_TARGET
#

auth/cell_field_action_specs.ts view source

"cell_field_list_requires_source_or_target" import {ERROR_CELL_FIELD_LIST_REQUIRES_SOURCE_OR_TARGET} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Error reason — cell_field_list got neither or both of source_id and target_id.

ERROR_CELL_GET_REQUIRES_ID_OR_PATH
#

auth/cell_action_specs.ts view source

"cell_get_requires_id_or_path" import {ERROR_CELL_GET_REQUIRES_ID_OR_PATH} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — input shape for cell_get lacked both id and path.

ERROR_CELL_GRANT_NOT_AUTHORIZED
#

ERROR_CELL_GRANT_NOT_FOUND
#

auth/cell_grant_action_specs.ts view source

"cell_grant_not_found" import {ERROR_CELL_GRANT_NOT_FOUND} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Error reason — grant id did not resolve, or caller may not see it.

ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER
#

auth/cell_grant_action_specs.ts view source

"cell_grant_principal_is_owner" import {ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Error reason — cell_grant_create principal resolves to the cell's owner. Owner access is implicit (cell.created_by); a self-grant row would shadow it without changing access and create a confusing self-leave path.

ERROR_CELL_GRANT_UNAPPROVED
#

auth/cell_grant_action_specs.ts view source

"cell_grant_unapproved" import {ERROR_CELL_GRANT_UNAPPROVED} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Error reason — cell_grant_create on a cell whose moderation doesn't admit an audience (pending, rejected, or an unknown marker). A grant would extend an audience the cell doesn't have yet.

ERROR_CELL_GRANT_UNKNOWN_ROLE
#

auth/cell_grant_action_specs.ts view source

"cell_grant_unknown_role" import {ERROR_CELL_GRANT_UNKNOWN_ROLE} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Error reason — role-shaped principal references a role string not registered in the role schema. Would produce a dead grant row that no role_grant could match.

ERROR_CELL_ITEM_LIST_REQUIRES_PARENT_OR_CHILD
#

auth/cell_item_action_specs.ts view source

"cell_item_list_requires_parent_or_child" import {ERROR_CELL_ITEM_LIST_REQUIRES_PARENT_OR_CHILD} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Error reason — cell_item_list got neither or both of parent_id and child_id.

ERROR_CELL_ITEM_POSITION_TAKEN
#

auth/cell_item_action_specs.ts view source

"cell_item_position_taken" import {ERROR_CELL_ITEM_POSITION_TAKEN} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Error reason — (parent_id, position) collision on cell_item_insert or cell_item_move. Surfaces when two clients computed the same fractional-indexing key (rare given helper-side jitter, which is on by default; the safety net for the residual race). A client that opts out with {jitter: false} gets bare deterministic mids, so two of them racing the same bracket collide every time and this stops being rare — that mode is for reproducible callers (seeders, fixtures), not concurrent ones. Client refreshes its bracket and retries.

ERROR_CELL_KIND_EMPTY
#

auth/cell_action_specs.ts view source

"cell_kind_empty" import {ERROR_CELL_KIND_EMPTY} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell_create was given an empty-string kind. kind is a non-empty capability tag or absent (null = typeless cell); "" (a tag that tags nothing) is rejected fail-loud so kind stays a clean null | non-empty-string (twin of the Rust ERROR_CELL_KIND_EMPTY).

ERROR_CELL_KIND_IN_DATA
#

auth/cell_action_specs.ts view source

"cell_kind_in_data" import {ERROR_CELL_KIND_IN_DATA} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — a kind key was supplied inside data. kind is a top-level column (cell.kind), not content metadata; accepting it inside data would create two sources of truth. Rejected fail-loud at the create / update / clone-patch boundary so the column stays canonical.

ERROR_CELL_LIST_CREATED_BY_REQUIRES_AUTH
#

auth/cell_action_specs.ts view source

"cell_list_created_by_requires_auth" import {ERROR_CELL_LIST_CREATED_BY_REQUIRES_AUTH} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — null-auth cell_list caller passed a created_by filter. The filter is a soft account-id enumeration vector ("does account X have any public cells?"), so we require an authenticated caller to use it.

ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH
#

auth/cell_action_specs.ts view source

"cell_list_shared_with_requires_auth" import {ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — null-auth cell_list caller passed shared_with: 'me'. The filter resolves to the caller's account + role_grants, which only exist for an authenticated session.

ERROR_CELL_MODERATE_FORBIDDEN
#

auth/cell_action_specs.ts view source

"cell_moderate_forbidden" import {ERROR_CELL_MODERATE_FORBIDDEN} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell_moderate caller can view the contribution but lacks moderation authority over its governing root (not admin / not the root's manager). A 403 forbidden. The author can view their own pending contribution, so they reach this gate — and are denied here, which is the anti-self-approval guard (twin of the Rust ERROR_CELL_MODERATE_FORBIDDEN).

ERROR_CELL_NOT_A_CONTRIBUTION
#

auth/cell_action_specs.ts view source

"cell_not_a_contribution" import {ERROR_CELL_NOT_A_CONTRIBUTION} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell_moderate target has no governing root (root_id is null): it's a root or an unparented cell, not a gated contribution, so there is nothing to moderate. A 400 invalid_params (twin of the Rust ERROR_CELL_NOT_A_CONTRIBUTION).

ERROR_CELL_NOT_FOUND
#

auth/cell_action_specs.ts view source

"cell_not_found" import {ERROR_CELL_NOT_FOUND} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell id did not resolve, or caller can't view it.

ERROR_CELL_NOT_PENDING
#

auth/cell_action_specs.ts view source

"cell_not_pending" import {ERROR_CELL_NOT_PENDING} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell_moderate names a transition the target's moderation doesn't allow: an approval of a contribution that is not 'pending' (approved or rejected already, or never moderated), or a rejection of one that is neither 'pending' nor 'approved' (rejected already, or never moderated). A 400 invalid_params, answered to a caller holding moderation authority over the governing root (the 404 mask and the 403 come first). Without it an approval would force any reachable contribution public (twin of the Rust ERROR_CELL_NOT_PENDING).

ERROR_CELL_PATH_ADMIN_ONLY
#

auth/cell_action_specs.ts view source

"cell_path_admin_only" import {ERROR_CELL_PATH_ADMIN_ONLY} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — caller is not an admin and supplied a path write.

ERROR_CELL_PATH_TAKEN
#

auth/cell_action_specs.ts view source

"cell_path_taken" import {ERROR_CELL_PATH_TAKEN} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — a path write collided with an existing active cell's path. path is globally unique on active rows (idx_cell_path_unique); the create / update handlers translate the unique-index violation into this conflict (409) reason rather than leaking a raw internal error. Soft-deleted rows free their path (the index is partial on deleted_at IS NULL), so reusing a deleted cell's path does not collide.

ERROR_CELL_POLICY_INVALID
#

auth/cell_action_specs.ts view source

"cell_policy_invalid" import {ERROR_CELL_POLICY_INVALID} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — the conventional refusal a CellCreateAuthorize.validate_policy throws for a policy it can't interpret. The spine never answers it itself (twin of the Rust ERROR_CELL_POLICY_INVALID).

ERROR_CELL_POLICY_MANAGE_ONLY
#

auth/cell_action_specs.ts view source

"cell_policy_manage_only" import {ERROR_CELL_POLICY_MANAGE_ONLY} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — a cell_policy_set caller can view the root but can't manage it (not admin, not the owner — an editor grant included). A 403 forbidden; a caller who can't view the root gets the 404 mask first (twin of the Rust ERROR_CELL_POLICY_MANAGE_ONLY).

ERROR_CELL_POLICY_ROOT_ONLY
#

auth/cell_action_specs.ts view source

"cell_policy_root_only" import {ERROR_CELL_POLICY_ROOT_ONLY} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — a policy write (a cell_create carrying policy, or cell_policy_set) targeted a cell with a parent. Only a root carries a contribution policy. A 400 invalid_params (twin of the Rust ERROR_CELL_POLICY_ROOT_ONLY).

ERROR_CELL_POLICY_UNSUPPORTED
#

auth/cell_action_specs.ts view source

"cell_policy_unsupported" import {ERROR_CELL_POLICY_UNSUPPORTED} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — a non-null policy write found nothing to interpret it: no authorize_create is mounted, or the mounted one has no validate_policy. The policy is opaque to the spine, so the write is refused rather than stored inert. A 400 invalid_params (twin of the Rust ERROR_CELL_POLICY_UNSUPPORTED).

ERROR_CELL_REF_NOT_HELD
#

auth/cell_action_specs.ts view source

"cell_ref_not_held" import {ERROR_CELL_REF_NOT_HELD} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — a non-admin write's data introduces a fact ref the acting actor does not hold. A 403 forbidden.

A ref is new when the write's derived refs carry a hash its base doesn't — nothing on cell_create, the cell's current refs on cell_update, the source row's refs on each row cell_clone writes — and held when the acting actor has put the fact's bytes (a fact_holder row). The answer reads holder rows only, so it is the same for a fact that is stored and one that isn't. The gate is auth/cell_ref_admission.ts (twin of the Rust ERROR_CELL_REF_NOT_HELD).

ERROR_CELL_VISIBILITY_MANAGE_ONLY
#

auth/cell_action_specs.ts view source

"cell_visibility_manage_only" import {ERROR_CELL_VISIBILITY_MANAGE_ONLY} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — caller tried to write cell.visibility without the manage tier (can_manage_cell = admin / owner). Editor-grant holders may edit data but cannot flip a cell's visibility — that is a manage-tier-only operation.

ERROR_CREDENTIAL_TYPE_REQUIRED
#

http/error_schemas.ts view source

"credential_type_required" import {ERROR_CREDENTIAL_TYPE_REQUIRED} from '@fuzdev/fuz_app/http/error_schemas.js';

Route requires a credential type the request didn't arrive on. Symmetric with ERROR_INSUFFICIENT_PERMISSIONS + required_roles: the body carries required_credential_types: ReadonlyArray<string> — what the route demanded, not what arrived. Today the only credential gate is keeper (['daemon_token']); future gates (agent_token, group_actor_token) reuse the same literal and label themselves through the array.

ERROR_DATABASE_CONNECTION_FAILED
#

http/error_schemas.ts view source

"database_connection_failed" import {ERROR_DATABASE_CONNECTION_FAILED} from '@fuzdev/fuz_app/http/error_schemas.js';

Database health-check query failed (connectivity or query error).

ERROR_DUPLICATE_REQUEST_ID
#

actions/ws_socket_session.ts view source

"duplicate_request_id" import {ERROR_DUPLICATE_REQUEST_ID} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

The data.reason of the invalid_request a WebSocket request gets when its id names a request still running on the same socket. The duplicate is not dispatched; the running request keeps its id, so its reply, and a cancel naming that id, stay its own. An id is free again once its request has been answered.

ERROR_FORBIDDEN_ORIGIN
#

http/error_schemas.ts view source

"forbidden_origin" import {ERROR_FORBIDDEN_ORIGIN} from '@fuzdev/fuz_app/http/error_schemas.js';

Request origin not in allowlist.

ERROR_FOREIGN_KEY_VIOLATION
#

http/error_schemas.ts view source

"foreign_key_violation" import {ERROR_FOREIGN_KEY_VIOLATION} from '@fuzdev/fuz_app/http/error_schemas.js';

DELETE blocked by a foreign key constraint.

ERROR_INSUFFICIENT_PERMISSIONS
#

http/error_schemas.ts view source

"insufficient_permissions" import {ERROR_INSUFFICIENT_PERMISSIONS} from '@fuzdev/fuz_app/http/error_schemas.js';

Authenticated but missing required role.

ERROR_INVALID_CONTENT_LENGTH
#

http/error_schemas.ts view source

"invalid_content_length" import {ERROR_INVALID_CONTENT_LENGTH} from '@fuzdev/fuz_app/http/error_schemas.js';

Request Content-Length header is not a plain run of ASCII digits.

ERROR_INVALID_CREDENTIALS
#

http/error_schemas.ts view source

"invalid_credentials" import {ERROR_INVALID_CREDENTIALS} from '@fuzdev/fuz_app/http/error_schemas.js';

Username or password is wrong (intentionally vague for enumeration prevention).

ERROR_INVALID_EVENT_TYPE
#

http/error_schemas.ts view source

"invalid_event_type" import {ERROR_INVALID_EVENT_TYPE} from '@fuzdev/fuz_app/http/error_schemas.js';

Query parameter event_type is not a valid audit event type.

ERROR_INVALID_JSON_BODY
#

http/error_schemas.ts view source

"invalid_json_body" import {ERROR_INVALID_JSON_BODY} from '@fuzdev/fuz_app/http/error_schemas.js';

Request body is not valid JSON or not an object.

ERROR_INVALID_QUERY_PARAMS
#

http/error_schemas.ts view source

"invalid_query_params" import {ERROR_INVALID_QUERY_PARAMS} from '@fuzdev/fuz_app/http/error_schemas.js';

URL query params failed Zod validation.

ERROR_INVALID_REQUEST_BODY
#

http/error_schemas.ts view source

"invalid_request_body" import {ERROR_INVALID_REQUEST_BODY} from '@fuzdev/fuz_app/http/error_schemas.js';

Request body failed Zod validation.

ERROR_INVALID_ROUTE_PARAMS
#

http/error_schemas.ts view source

"invalid_route_params" import {ERROR_INVALID_ROUTE_PARAMS} from '@fuzdev/fuz_app/http/error_schemas.js';

URL path params failed Zod validation.

ERROR_INVALID_TOKEN
#

http/error_schemas.ts view source

"invalid_token" import {ERROR_INVALID_TOKEN} from '@fuzdev/fuz_app/http/error_schemas.js';

Bearer token failed validation (missing, malformed, or revoked).

ERROR_INVITE_ACCOUNT_EXISTS_EMAIL
#

http/error_schemas.ts view source

"invite_account_exists_email" import {ERROR_INVITE_ACCOUNT_EXISTS_EMAIL} from '@fuzdev/fuz_app/http/error_schemas.js';

An account already exists with this invite's email.

ERROR_INVITE_ACCOUNT_EXISTS_USERNAME
#

http/error_schemas.ts view source

"invite_account_exists_username" import {ERROR_INVITE_ACCOUNT_EXISTS_USERNAME} from '@fuzdev/fuz_app/http/error_schemas.js';

An account already exists with this invite's username.

ERROR_INVITE_DUPLICATE
#

http/error_schemas.ts view source

"invite_duplicate" import {ERROR_INVITE_DUPLICATE} from '@fuzdev/fuz_app/http/error_schemas.js';

An unclaimed invite already exists for this email or username.

ERROR_INVITE_NOT_FOUND
#

http/error_schemas.ts view source

"invite_not_found" import {ERROR_INVITE_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Invite not found (for delete operations).

ERROR_KEEPER_ACCOUNT_NOT_FOUND
#

http/error_schemas.ts view source

"keeper_account_not_found" import {ERROR_KEEPER_ACCOUNT_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Keeper account ID set but account row not found.

ERROR_LIMIT_INVALID
#

http/error_schemas.ts view source

"limit_invalid" import {ERROR_LIMIT_INVALID} from '@fuzdev/fuz_app/http/error_schemas.js';

limit on a paged read refused — out of [1, max], or not an integer.

ERROR_NO_ACTORS_ON_ACCOUNT
#

http/error_schemas.ts view source

"no_actors_on_account" import {ERROR_NO_ACTORS_ON_ACCOUNT} from '@fuzdev/fuz_app/http/error_schemas.js';

Authenticated account exists but has no actors. Server invariant violation — signup / bootstrap always create an actor in the same transaction. Surfaced from the dispatcher's authorization phase as a 500 so the operator sees the corruption signal rather than a confusing 4xx. Distinct from ERROR_ACCOUNT_VANISHED: the actor list was enumerated successfully and came back empty.

ERROR_NO_MATCHING_INVITE
#

http/error_schemas.ts view source

"no_matching_invite" import {ERROR_NO_MATCHING_INVITE} from '@fuzdev/fuz_app/http/error_schemas.js';

No unclaimed invite matches the signup credentials.

ERROR_OFFSET_INVALID
#

http/error_schemas.ts view source

"offset_invalid" import {ERROR_OFFSET_INVALID} from '@fuzdev/fuz_app/http/error_schemas.js';

offset on a paged read refused — negative, or not a safe integer.

ERROR_OUTCOME_INVALID
#

http/error_schemas.ts view source

"outcome_invalid" import {ERROR_OUTCOME_INVALID} from '@fuzdev/fuz_app/http/error_schemas.js';

audit_log_list's outcome filter refused — not 'success' or 'failure'.

ERROR_PARAMS_PROTO_KEY
#

http/error_schemas.ts view source

"params_proto_key" import {ERROR_PARAMS_PROTO_KEY} from '@fuzdev/fuz_app/http/error_schemas.js';

An RPC call's params hold an own __proto__ key, at any depth — refused by the dispatcher (invalid_params) before the input schema runs. See json_has_own_proto_key.

ERROR_PAYLOAD_TOO_LARGE
#

http/error_schemas.ts view source

"payload_too_large" import {ERROR_PAYLOAD_TOO_LARGE} from '@fuzdev/fuz_app/http/error_schemas.js';

Request body exceeds the maximum allowed size.

ERROR_PURGE_NOT_CONFIRMED
#

auth/admin_action_specs.ts view source

"purge_not_confirmed" import {ERROR_PURGE_NOT_CONFIRMED} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

data.reason on account_purge when confirm: true is absent. Fail-loud: the irreversible purge refuses to run without explicit confirmation. Mirrors the Rust ERROR_PURGE_NOT_CONFIRMED.

ERROR_RATE_LIMIT_EXCEEDED
#

http/error_schemas.ts view source

"rate_limit_exceeded" import {ERROR_RATE_LIMIT_EXCEEDED} from '@fuzdev/fuz_app/http/error_schemas.js';

Rate limiter rejected the request.

error_reason
#

ERROR_RESPONSE_OUTPUT_INVALID
#

actions/action_event.ts view source

"response_output_invalid" import {ERROR_RESPONSE_OUTPUT_INVALID} from '@fuzdev/fuz_app/actions/action_event.js';

error.data.reason on the internal_error a client reports when a response's result fails spec.output — a remote bug, distinct from the invalid_params / validation_error a caller's own bad input produces.

ERROR_ROLE_GRANT_BUILTIN_SCOPED
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_builtin_scoped" import {ERROR_ROLE_GRANT_BUILTIN_SCOPED} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — a grant path was asked to bind a builtin role to a scope (builtin roles are global-only, see is_builtin_role). Refused by role_grant_assign, role_grant_offer_create, and role_grant_offer_accept.

ERROR_ROLE_GRANT_NOT_FOUND
#

http/error_schemas.ts view source

"role_grant_not_found" import {ERROR_ROLE_GRANT_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Role grant ID not found or not owned by the target actor.

ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_actor_account_mismatch" import {ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — role_grant_offer_create was called with a to_actor_id that does not belong to to_account_id.

ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_actor_mismatch" import {ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — actor-targeted offer was accepted by an actor other than to_actor_id.

ERROR_ROLE_GRANT_OFFER_EXPIRED
#

ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_not_authorized" import {ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — caller is not authorized to offer this role (default policy: caller lacks the role; consumer authorize callback may add further policy).

ERROR_ROLE_GRANT_OFFER_NOT_FOUND
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_not_found" import {ERROR_ROLE_GRANT_OFFER_NOT_FOUND} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — offer does not exist or belongs to a different recipient (404-over-403 IDOR mask).

ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_role_not_grantable" import {ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — the offered role does not include 'admin' in its RoleSpec.grant_paths (nobody may offer it via this surface).

ERROR_ROLE_GRANT_OFFER_SELF_TARGET
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_self_target" import {ERROR_ROLE_GRANT_OFFER_SELF_TARGET} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — caller tried to offer themselves a role_grant.

ERROR_ROLE_GRANT_OFFER_TERMINAL
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_terminal" import {ERROR_ROLE_GRANT_OFFER_TERMINAL} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — offer is declined, retracted, or superseded.

ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE
#

auth/self_service_role_action_specs.ts view source

"role_not_self_service_eligible" import {ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

Error reason — caller asked to self-toggle a role outside the configured allowlist.

ERROR_ROLE_NOT_WEB_GRANTABLE
#

http/error_schemas.ts view source

"role_not_web_grantable" import {ERROR_ROLE_NOT_WEB_GRANTABLE} from '@fuzdev/fuz_app/http/error_schemas.js';

Admin tried to grant a role that is not web-grantable.

ERROR_ROW_NOT_FOUND
#

http/error_schemas.ts view source

"row_not_found" import {ERROR_ROW_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Row with the given PK value not found.

ERROR_SERVICE_UNAVAILABLE
#

http/error_schemas.ts view source

"service_unavailable" import {ERROR_SERVICE_UNAVAILABLE} from '@fuzdev/fuz_app/http/error_schemas.js';

A pool checkout failed — the database is unavailable (503). The flat REST reason of the JSON-RPC service_unavailable a DbCheckoutError maps to; the Rust spine's fuz_http::SERVICE_UNAVAILABLE_REASON.

ERROR_SIGNUP_CONFLICT
#

http/error_schemas.ts view source

"signup_conflict" import {ERROR_SIGNUP_CONFLICT} from '@fuzdev/fuz_app/http/error_schemas.js';

Signup conflict — username or email already taken (intentionally vague for enumeration prevention).

ERROR_SINCE_SEQ_INVALID
#

http/error_schemas.ts view source

"since_seq_invalid" import {ERROR_SINCE_SEQ_INVALID} from '@fuzdev/fuz_app/http/error_schemas.js';

audit_log_list's since_seq cursor refused — negative, or not a safe integer.

ERROR_TABLE_NO_PRIMARY_KEY
#

http/error_schemas.ts view source

"table_no_primary_key" import {ERROR_TABLE_NO_PRIMARY_KEY} from '@fuzdev/fuz_app/http/error_schemas.js';

Table has no single-column primary key, so there is no column the row-DELETE can target: either no primary key at all, or a composite one (a single-column WHERE on one member of a composite key over-matches).

ERROR_TABLE_NOT_DELETABLE
#

http/error_schemas.ts view source

"table_not_deletable" import {ERROR_TABLE_NOT_DELETABLE} from '@fuzdev/fuz_app/http/error_schemas.js';

Table is excluded from row deletion by policy — the audit trail and the framework's singleton bookkeeping rows (bootstrap_lock, app_settings, schema_version), whose invariants live in the domain layer rather than in a generic storage endpoint. See http/db_routes.ts's NON_DELETABLE_TABLES.

ERROR_TABLE_NOT_FOUND
#

http/error_schemas.ts view source

"table_not_found" import {ERROR_TABLE_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Table name not found in information_schema.

ERROR_TESTING_FLOOD_NO_CONNECTION
#

ERROR_TOKEN_FILE_MISSING
#

http/error_schemas.ts view source

"token_file_missing" import {ERROR_TOKEN_FILE_MISSING} from '@fuzdev/fuz_app/http/error_schemas.js';

Bootstrap token file not found on disk.

ERROR_TOKEN_LIFETIME_INVALID
#

auth/account_action_specs.ts view source

"token_lifetime_invalid" import {ERROR_TOKEN_LIFETIME_INVALID} from '@fuzdev/fuz_app/auth/account_action_specs.js';

error.data.reason on any refusal of account_token_create's lifetime — missing, null, an unknown kind or key, a days out of range. The field's declared invalid_reason.

ERROR_TOKEN_SCOPE_INVALID
#

auth/account_action_specs.ts view source

"token_scope_invalid" import {ERROR_TOKEN_SCOPE_INVALID} from '@fuzdev/fuz_app/auth/account_action_specs.js';

error.data.reason on any refusal of account_token_create's scope — missing, null, an unknown kind or key, a malformed methods, an over-cap document. The field's declared invalid_reason.

ERROR_TOKEN_SCOPE_REQUIRED
#

http/error_schemas.ts view source

"token_scope_required" import {ERROR_TOKEN_SCOPE_REQUIRED} from '@fuzdev/fuz_app/http/error_schemas.js';

The credential is admitted on this channel, but the specific token's scope does not grant the requested capability.

The body carries required_scope: string in the stable <section>:<id> capability format — rpc:<method> for an action, surface:<name> for a non-RPC surface. Distinct from credential_type_required (which is about the *channel*) and from insufficient_permissions (about the *account's* roles): this one says the account could do it but this token was minted narrower.

ERROR_UNSUPPORTED_CONTENT_TYPE
#

http/error_schemas.ts view source

"unsupported_content_type" import {ERROR_UNSUPPORTED_CONTENT_TYPE} from '@fuzdev/fuz_app/http/error_schemas.js';

A POST whose Content-Type the route refuses: anything but application/json on a JSON route, a CORS-safelisted type or none on a raw_body one. See http/simple_request.ts.

ErrorCoverageCollector
#

testing/error_coverage.ts view source

import {ErrorCoverageCollector} from '@fuzdev/fuz_app/testing/error_coverage.js';

Tracks which route × status (and route × status × code) combinations have been exercised in tests.

Use record() to log an observed status (optionally with the body's error code), or assert_and_record() to combine response validation with tracking (auto-extracts body.error from the response when present). After all tests, call uncovered() to find declared error paths never exercised.

An observation recorded without a code still satisfies "any-code" coverage requirements for the same status — i.e., if a caller records just the status, all declared codes for that status are considered covered. Per-code tracking is additive: callers who know the body's error value should pass it to get precise per-code gap reporting on routes with literal/enum error schemas.

observed

Observed keys: "METHOD /spec-path:STATUS" or "METHOD /spec-path:STATUS:CODE".

Both shapes coexist — the code-less key marks the status as covered at any code; a code-bearing key adds per-code precision.

type Set<string>

readonly

record

Record an observed error status (optionally with the body's error code) for a route.

Resolves the concrete request path back to the spec template path (e.g., /api/accounts/abc → /api/accounts/:id). When code is provided, it is stored alongside the status for per-code coverage tracking.

type (route_specs: RouteSpec[], method: string, path: string, status: number, code?: string | undefined): void

route_specs

type RouteSpec[]

method

type string

path

request path (may be concrete)

type string

status

type number

code?

observed body error code (pass when the route's error schema declares specific codes via z.literal or z.enum)

type string
optional
returns void

mutates

  • this.observed — adds the resolved `"METHOD /spec-path:STATUS"` key (and the `:CODE` variant when `code` is provided).

assert_and_record

Validate a response against its route spec and record the status.

Wraps assert_response_matches_spec and records the status code. For error responses, auto-extracts body.error from the JSON body (via a cloned response, so the original stream stays usable) and records it for per-code coverage. Pass an explicit code to override the auto-extracted value or when the body was already consumed.

type (route_specs: RouteSpec[], method: string, path: string, response: Response, code?: string | undefined): Promise<void>

route_specs

type RouteSpec[]

method

type string

path

type string

response

type Response

code?

observed body error code (override; if omitted and the response body is a JSON object with a string error field, that value is auto-extracted)

type string
optional
returns Promise<void>

throws

  • Error - if the response body fails the route spec's declared schemas (propagated from `assert_response_matches_spec`).

mutates

  • this.observed — via `record` after `assert_response_matches_spec` succeeds.

uncovered

Find declared error paths that were never observed.

Computes the declared set from merge_error_schemas for each route spec. For statuses whose error schema names specific codes (via z.literal or z.enum), reports per-code rows; otherwise reports one row per status. A status-only observation (no code) satisfies all declared codes for that status — the "any-code" rule.

type (route_specs: RouteSpec[], options?: CoverageFilterOptions | undefined): UncoveredEntry[]

route_specs

type RouteSpec[]

options?

optional
returns UncoveredEntry[]

ErrorCoverageOptions
#

ErrorSchemaAuditEntry
#

testing/surface_invariants.ts view source

ErrorSchemaAuditEntry import type {ErrorSchemaAuditEntry} from '@fuzdev/fuz_app/testing/surface_invariants.js';

A single entry in the error schema tightness audit report.

method

type string

route_path

type string

status

type string

specificity

type ErrorSchemaSpecificity

error_codes

The literal value or enum values, if specific.

type string[] | null

ErrorSchemaSpecificity
#

testing/surface_invariants.ts view source

ErrorSchemaSpecificity

type "literal" | "enum" | "generic"

import type {ErrorSchemaSpecificity} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Specificity level of an error schema's error field.

ErrorSchemaTightnessOptions
#

testing/surface_invariants.ts view source

ErrorSchemaTightnessOptions import type {ErrorSchemaTightnessOptions} from '@fuzdev/fuz_app/testing/surface_invariants.js';

min_specificity?

Minimum specificity level. Error schemas below this threshold fail. Default: 'enum'.

type ErrorSchemaSpecificity

ignore_statuses?

HTTP status codes to skip (e.g., middleware-injected codes).

type number[]

allowlist?

Routes to skip, in 'METHOD /path' format.

type string[]

events_to_surface
#

EventSpec
#

realtime/sse.ts view source

EventSpec import type {EventSpec} from '@fuzdev/fuz_app/realtime/sse.js';

Spec for a push event — declares params schema, description, and channel.

method

Event method name, used as the JSON-RPC notification method.

type string

params

Zod schema for the notification params payload.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

description

Human-readable description for surface output and docs.

type string

channel?

Channel this event broadcasts on. Omit for cross-channel events.

type string

expect_output
#

testing/cross_backend/cross_rpc.ts view source

<T>(r: RpcResult, schema: ZodType<T, unknown, $ZodTypeInternals<T, unknown>>): T import {expect_output} from '@fuzdev/fuz_app/testing/cross_backend/cross_rpc.js';

Assert the call succeeded and the result matches the verb's declared output schema — the wire-shape parity gate. Returns the parsed output.

r

schema

type ZodType<T, unknown, $ZodTypeInternals<T, unknown>>

returns

T

generics

expect_output<T>
T

expected_invalid_reason
#

testing/adversarial_input_cases.ts view source

(input_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, test_case: InputTestCase): string | undefined import {expected_invalid_reason} from '@fuzdev/fuz_app/testing/adversarial_input_cases.js';

The error.data.reason an RPC refusal of test_case owes: the declared invalid_reason of the field it breaks, resolved as the dispatcher resolves a failure on that field (resolve_invalid_reason), or undefined — no reason — for an undeclared field or a whole-body case.

input_schema

the input schema the case was generated from

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

test_case

a case generate_input_test_cases produced from it

returns

string | undefined

the owed reason, or undefined when the refusal carries none

ExpectedSchema
#

db/schema_ready.ts view source

ExpectedSchema import type {ExpectedSchema} from '@fuzdev/fuz_app/db/schema_ready.js';

Expected schema: table name → sorted column names, from a fresh bootstrap.

[key: string]

type readonly string[]

EXPIRED_SESSION_OFFSET_SECONDS
#

testing/cross_backend/setup.ts view source

-60 import {EXPIRED_SESSION_OFFSET_SECONDS} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Backdating offset (seconds) the mint_expired_session seam passes to mint_test_session / _testing_mint_session. A minute in the past is comfortably past NOW() for the DB-row expiry gate without depending on clock precision.

EXPIRES_IN_MS_SQL
#

auth/credential_deadline.ts view source

"(EXTRACT(EPOCH FROM (expires_at - NOW())) * 1000)::float8" import {EXPIRES_IN_MS_SQL} from '@fuzdev/fuz_app/auth/credential_deadline.js';

SQL expression for a row's remaining lifetime in milliseconds, measured on the database clock — NULL when expires_at is NULL (an eternal API token). float8, so every driver reads it as a JS number.

ExtraAccountSpec
#

testing/cross_backend/setup.ts view source

ExtraAccountSpec import type {ExtraAccountSpec} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Spec for a bootstrap-time secondary account seeded alongside the keeper by _testing_reset (cross-process) or create_test_app (in-process). Used for accounts whose required roles aren't admin-grantable via offer/accept — primarily ROLE_KEEPER (whose RoleSpec.grant_paths is bootstrap-only), where the only way to land the grant is at the bootstrap-equivalent setup step.

For admin-grantable roles, prefer fixture.create_account({roles}) — that goes through the production offer/accept handlers and observes audit + WS fan-out. extra_accounts is the cradle-only bypass; the runtime has no equivalent action.

username

type string

readonly

password_value?

type string

readonly

roles

type readonly string[]

readonly

extract_action_result
#

actions/action_event_helpers.ts view source

(event: ActionEvent<string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">): Result<...> import {extract_action_result} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

Pull the terminal Result from an action event.

data.error populated → error path (covers both explicit failed and the unhandled receive_error / send_error case where no handler was registered for the error phase). step === 'handled' → success path.

event

type ActionEvent<string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">

returns

Result

throws

  • Error - if the event is in a non-terminal state (programming error — callers should check `is_action_complete` first)

extract_declared_error_codes
#

testing/error_coverage.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): string[] | null import {extract_declared_error_codes} from '@fuzdev/fuz_app/testing/error_coverage.js';

Extract declared error code values from an error response schema.

Recognizes schemas shaped like z.object({error: z.literal(...)}) or z.object({error: z.enum([...])}) (incl. looseObject/strictObject). Returns the set of declared code values, or null if the schema doesn't expose a literal/enum error field (e.g., bare ApiError with z.string()).

Used by coverage reporting to split a single declared status into per-code rows when the route's error schema names specific codes.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

string[] | null

FACT_CACHE_CONTROL
#

server/fact_security_headers.ts view source

"private, max-age=300" import {FACT_CACHE_CONTROL} from '@fuzdev/fuz_app/server/fact_security_headers.js';

Cache-Control for fact responses — private, five minutes: the window a cached read outlives a revoked view.

FACT_COLUMNS
#

FACT_CONTENT_SECURITY_POLICY
#

server/fact_security_headers.ts view source

"default-src 'none'; sandbox" import {FACT_CONTENT_SECURITY_POLICY} from '@fuzdev/fuz_app/server/fact_security_headers.js';

Content-Security-Policy for fact responses. A fact stored as (or sniffable to) text/html would otherwise execute as stored XSS for any reader of a referencing cell. default-src 'none'; sandbox neutralizes script execution and sub-resource loads even when a fact is rendered inline; harmless for directly-served images (they load no sub-resources).

fact_disk_path
#

db/file_fact_url.ts view source

(hash: FactHash): { shard: string; rest: string; } import {fact_disk_path} from '@fuzdev/fuz_app/db/file_fact_url.js';

Split a FactHash into its on-disk <shard>/<rest> parts — the first 2 hex chars of the digest (shard subdir) + the remaining 62. The single source of truth for the disk layout, so the write path (put / put_stream) and the URL minted into the fact row can't disagree. Mirrors the Rust fact_disk_path in fuz_fact.

hash

type FactHash

returns

{ shard: string; rest: string; }

FACT_DROP_TABLES
#

db/fact_ddl.ts view source

readonly ["fact_holder", "memo", "fact_ref", "fact"] import {FACT_DROP_TABLES} from '@fuzdev/fuz_app/db/fact_ddl.js';

Tables created by FACT_MIGRATION_NS, in drop order (children first).

FACT_EMBEDDED_THRESHOLD_DEFAULT
#

db/fact_store.ts view source

number import {FACT_EMBEDDED_THRESHOLD_DEFAULT} from '@fuzdev/fuz_app/db/fact_store.js';

Default embedded-vs-referenced cutoff (1 MiB).

FACT_HOLDER_COLUMNS
#

db/fact_holder_queries.ts view source

readonly ["actor_id", "hash", "created_at"] import {FACT_HOLDER_COLUMNS} from '@fuzdev/fuz_app/db/fact_holder_queries.js';

The fact_holder table's columns.

Twin of the Rust fuz_fact::FACT_HOLDER_COLUMNS (same names, same order). No query reads whole rows — admission reads hashes only — so the const is the drift guard's anchor rather than a projection source.

FACT_MIGRATION_NS
#

db/fact_ddl.ts view source

MigrationNamespace import {FACT_MIGRATION_NS} from '@fuzdev/fuz_app/db/fact_ddl.js';

Migration namespace consumed by run_migrations — the spine's own, so splice this object itself (a copy is refused under the reserved name).

fact_security_header_directives
#

server/fact_security_headers.ts view source

(): string[] import {fact_security_header_directives} from '@fuzdev/fuz_app/server/fact_security_headers.js';

The exact nginx add_header … always; lines the internal facts location must carry, rendered from FACT_SECURITY_HEADERS. always makes nginx add the header on every status, not just 2xx/3xx. The X-Accel validator refuses a facts location missing any of them.

returns

string[]

one add_header directive per header, each ending in always;

FACT_SECURITY_HEADERS
#

server/fact_security_headers.ts view source

{ readonly 'X-Content-Type-Options': "nosniff"; readonly 'Content-Security-Policy': "default-src 'none'; sandbox"; } import {FACT_SECURITY_HEADERS} from '@fuzdev/fuz_app/server/fact_security_headers.js';

The fact hardening headers, in render order. `Content-Disposition: attachment` is deliberately absent — it would force-download and break legitimate inline image rendering.

FACT_TMP_DIRNAME
#

db/fact_disk_storage.ts view source

".tmp" import {FACT_TMP_DIRNAME} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Subdirectory under facts_dir for in-flight atomic temp files.

FACT_TMP_ORPHAN_MAX_AGE_MS
#

db/fact_disk_storage.ts view source

number import {FACT_TMP_ORPHAN_MAX_AGE_MS} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Default age (1 hour) past which a .tmp/* file is considered orphaned.

FACT_X_CONTENT_TYPE_OPTIONS
#

server/fact_security_headers.ts view source

"nosniff" import {FACT_X_CONTENT_TYPE_OPTIONS} from '@fuzdev/fuz_app/server/fact_security_headers.js';

X-Content-Type-Options for fact responses — block MIME sniffing so an application/octet-stream fact can't be sniffed into executable HTML.

FactDiskStorageDeps
#

db/fact_disk_storage.ts view source

FactDiskStorageDeps

type Pick<FsReadDeps, "stat" | "readdir" | "read_file"> & Pick<FsWriteDeps, "mkdir" | "rename" | "write_file" | "fsync"> & Pick<FsStreamDeps, "write_file_stream"> & Pick<FsRemoveDeps, "remove">

import type {FactDiskStorageDeps} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Filesystem capabilities the disk CAS needs, drawn from runtime/deps.ts. A full RuntimeDeps (Node or Deno) satisfies this; each function below picks the narrow subset it actually uses.

stat

Get file/directory stats, or null if path doesn't exist.

type (path: string): Promise<StatResult | null>

path

type string
returns Promise<StatResult | null>

readdir

List directory entries (names, not full paths). Throws if the directory does not exist.

type (path: string): Promise<string[]>

path

type string
returns Promise<string[]>

read_file

Read a file as bytes. Throws if the file does not exist.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

mkdir

Create a directory. mode applies at creation (no-op where modes don't apply).

type (path: string, options?: { recursive?: boolean | undefined; mode?: number | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; mode?: number | undefined; }
optional
returns Promise<void>

rename

Rename (move) a file.

type (old_path: string, new_path: string): Promise<void>

old_path

type string

new_path

type string
returns Promise<void>

write_file

Write bytes to a file.

type (path: string, data: Uint8Array<ArrayBufferLike>): Promise<void>

path

type string

data

type Uint8Array<ArrayBufferLike>
returns Promise<void>

fsync

Flush a file's data to stable storage (fsync). Call on a temp file after writing it and *before* rename-ing it into place when the renamed path is later served without re-verification — otherwise a host crash after the rename can surface a torn/zero file as authentic content. The fact disk CAS (db/fact_disk_storage.ts) is the one such path; it twins the Rust fuz_fact §fsync posture (data-sync before rename; the parent-dir fsync stays deliberately waived — a lost dirent is regenerable under content addressing). Real runtimes open the path, fsync, and close; create_mock_runtime no-ops (it models no durability).

type (path: string): Promise<void>

path

type string
returns Promise<void>

write_file_stream

Write a ReadableStream of bytes to a file, consuming it with backpressure (peak memory is one chunk). Creates or truncates path. Throws on any I/O error; a partially-written file may remain, so callers needing atomicity write to a temp path then rename.

type (path: string, data: ReadableStream<Uint8Array<ArrayBufferLike>>): Promise<void>

path

type string

data

type ReadableStream<Uint8Array<ArrayBufferLike>>
returns Promise<void>

remove

Remove a file or directory.

type (path: string, options?: { recursive?: boolean | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; }
optional
returns Promise<void>

FactHashSchema
#

primitive_schemas.ts view source

ZodString & ZodType<FactHash, string, $ZodTypeInternals<FactHash, string>>

type ZodString & ZodType<FactHash, string, $ZodTypeInternals<FactHash, string>>

import {FactHashSchema} from '@fuzdev/fuz_app/primitive_schemas.js';

Wire-form schema for a blake3:-prefixed fact hash, whose output is FactHash from @fuzdev/fuz_util/hash_schemas.ts — the type fact_hash_bytes, fact_hash_stream, and FactStore use — so a parsed value passes straight to them. Client-side (cell payloads) and server-side (DB-row hashes) consumers reuse this same schema.

The output type is a cast over a plain regex check: Zod's .brand('FactHash') would output Zod's own brand, which doesn't fit fuz_util's fuz_schema-branded FactHash, and a .transform or z.custom that produced the right type makes z.toJSONSchema throw, as neither can be represented in JSON Schema. The cast costs nothing at runtime and keeps the ZodString API and the pattern in the generated JSON Schema, so .meta(), .nullable(), and z.array() compose as before.

FactHolderRow
#

db/fact_holder_queries.ts view source

FactHolderRow import type {FactHolderRow} from '@fuzdev/fuz_app/db/fact_holder_queries.js';

Row shape of fact_holder.

actor_id

type Uuid

hash

type FactHash

created_at

type Date

FactMetaRow
#

db/fact_queries.ts view source

FactMetaRow import type {FactMetaRow} from '@fuzdev/fuz_app/db/fact_queries.js';

Subset returned by metadata-only queries (no bytes payload).

hash

type FactHash

external_url

type string | null

content_type

type string | null

size

type number | string

created_at

type Date

FactPayloadTooLargeError
#

db/fact_store_errors.ts view source

import {FactPayloadTooLargeError} from '@fuzdev/fuz_app/db/fact_store_errors.js';

The streamed upload exceeded the byte cap. Thrown by put_stream when its mid-stream counter passes max_bytes — the backstop for a chunked or mis-declared Content-Length that the cheap header pre-check can't catch. A consumer route maps this to 413.

inheritance

extends: Error

bytes_read

Bytes read before the cap tripped (may exceed max_bytes by one chunk).

type number

readonly

max_bytes

type number

readonly

constructor

type new (bytes_read: number, max_bytes: number): FactPayloadTooLargeError

bytes_read

type number

max_bytes

type number

FactRow
#

db/fact_queries.ts view source

FactRow import type {FactRow} from '@fuzdev/fuz_app/db/fact_queries.js';

Row shape for SELECT … FROM fact.

hash

type FactHash

bytes

type Uint8Array<ArrayBufferLike> | null

external_url

type string | null

content_type

type string | null

size

type number | string

created_at

type Date

FactServingCrossTestOptions
#

testing/cross_backend/fact_serving.ts view source

FactServingCrossTestOptions import type {FactServingCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/fact_serving.js';

The fact suite adds one optional knob to the shared cell options: a setup variant whose keeper carries a second actor. Only the multi-actor case needs it; the rest of the suite runs single-actor, so wiring it is opt-in. Omit it and the multi-actor case silently skips.

inheritance

setup_test_multi_actor?

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

FactStorageFullError
#

db/fact_store_errors.ts view source

import {FactStorageFullError} from '@fuzdev/fuz_app/db/fact_store_errors.js';

The disk filled mid-stream (ENOSPC). Thrown by put_stream when the temp-file write fails for lack of space — the real disk-full guarantee that a best-effort free-space preflight can't promise (chunked uploads, TOCTOU races). A consumer route maps this to 507.

inheritance

extends: Error

constructor

type new (cause?: unknown): FactStorageFullError

cause?

type unknown
optional

FailFloor
#

auth/fail_floor.ts view source

FailFloor

type "login" | "signup"

import type {FailFloor} from '@fuzdev/fuz_app/auth/fail_floor.js';

Which denial-timing floor a disabled-floor banner names.

FakeHonoContextOptions
#

testing/ws_round_trip.ts view source

FakeHonoContextOptions import type {FakeHonoContextOptions} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

credential_type

type BuiltinCredentialType

role?

A single role to grant via create_test_request_context.

type string

auth_session_id?

The raw session token a session credential carries. Default 's1'.

type string

api_token_id?

The api_token credential's token id. Default a fresh uuid.

type string

request_context?

Override the RequestContext outright (for multi-role or custom account/actor fixtures). Takes precedence over role.

type RequestContext

FakeWs
#

testing/ws_round_trip.ts view source

FakeWs import type {FakeWs} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

A WSContext paired with capture arrays. Use sends to assert on outgoing frames; use closes to assert on revocation / close; use socket to model a client that stopped reading and to assert on a cut.

ws

type WSContext<unknown>

sends

type string[]

closes

type { code?: number | undefined; reason?: string | undefined; }[]

socket

The fake runtime socket (ws.raw).

type FakeWsSocket

FakeWsSocket
#

testing/ws_round_trip.ts view source

FakeWsSocket import type {FakeWsSocket} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

The runtime socket behind a FakeWs — what WSContext.raw exposes, shaped like Node's ws: a bufferedAmount the outbound writer reads for backpressure, and a terminate it cuts a stalled socket with.

bufferedAmount

Bytes "handed to the socket and not yet written". Grows by each sent frame's UTF-8 length while stalled; set it (e.g. to 0) to model the client reading.

type number

stalled

While true, sent frames stay in bufferedAmount — a client that is not reading.

type boolean

terminated

Whether terminate was called — the socket was cut with no close frame.

type boolean

terminate

Cut the socket. Records terminated and empties bufferedAmount.

type (): void

returns void

FamilyDefaultCapabilities
#

testing/cross_backend/capabilities.ts view source

FamilyDefaultCapabilities import type {FamilyDefaultCapabilities} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

A family's default capabilities: every flag but the MountFlags and the testing_backdoors list, which each backend config states itself. Spread one and add every mount flag and the backdoor list:

capabilities: { ...rust_default_capabilities, ws: true, sse: true, ready: true, peer_request: true, cell_crud: false, cell_relations: false, cell_gated_create: false, fact_serving: false, fact_serving_admin_hash: false, ws_account_actions: false, ws_role_gated: false, testing_backdoors: ['_testing_reset', '_testing_drain_effects'], }

account_lifecycle

The account-lifecycle admin verbs (account_delete soft-delete, account_undelete reactivation, account_purge keeper hard-delete) are live-mounted on the backend's RPC path. Gates the dedicated describe_account_lifecycle_cross_tests suite.

Unlike cells / fact-serving / ws / sse, these verbs are on the standard declared surface — they live in create_admin_actions, so create_spine_surface_spec carries them and the spec-derived round-trip + attack-surface suites already auto-enumerate their wire shape + auth. This flag gates the *behavioral* parity the generic round-trip can't provide: it can't drive verbs that delete their own subject (soft-delete → undelete round-trip, keeper-confirmed purge, the keeper-guard refusal), so the dedicated cross suite adds them.

type boolean

readonly

account_status

The account surface serves GET /api/account/status (account info + bootstrap_available flag). Bundled into create_account_route_specs, so any backend mounting the account routes serves it — true for every spine. Gates the account status response body case in describe_standard_integration_tests: when true the case asserts the route is present (fail-loud on 404, no silent skip); when false it skips explicitly (a backend that deliberately omits the route).

type boolean

readonly

ws_handshake_pipelining

The backend's HTTP server accepts WebSocket frames written in the same TCP write as the upgrade request, handing them to the socket once the handshake completes. Gates the frames sent with the handshake case in describe_cross_process_ws_tests — the deterministic form of "frames that arrive before the connection is admitted wait for admission", since those frames are in the backend's hands before it can have admitted anything.

true for Node (ws), Deno, and the Rust spine (hyper hands the leftover bytes to the upgraded stream); false for a Bun backend that upgrades after an asynchronous check, as one behind auth does — Bun.serve answers such a request 400 (observed on Bun 1.4.3). A backend without it still runs the suite's client-library case (requests sent the moment the socket opens), which pins the same contract without forcing the ordering.

type boolean

readonly

ws_refusal_body

A refused WebSocket upgrade carries its refusal body — the flat {error, ...} the same refusal answers over plain HTTP. Not a gate: it forks how describe_transport_composition_cross_tests reads its WS rows. true, an empty refusal body fails the case; false, an empty body is allowed (the case pins the status alone) and a non-empty one is still asserted.

false on Node: @hono/node-ws writes a refused upgrade to the raw socket with Content-Length: 0, dropping the response body. true on Deno, Bun, and the Rust spine, which send the response as built.

type boolean

readonly

FetchDeps
#

runtime/deps.ts view source

FetchDeps import type {FetchDeps} from '@fuzdev/fuz_app/runtime/deps.js';

HTTP fetch capability.

fetch

Fetch a URL. Same signature as the global fetch.

type { (input: URL | RequestInfo, init?: RequestInit | undefined): Promise<Response>; (input: string | URL | Request, init?: RequestInit | undefined): Promise<...>; }

FetchTransport
#

testing/transports/fetch_transport.ts view source

FetchTransport import type {FetchTransport} from '@fuzdev/fuz_app/testing/transports/fetch_transport.js';

The transport shape: callable as RpcTestTransport plus a cookies() accessor that returns the current jar state. The accessor exists so ws_transport can thread the session cookie onto the WS upgrade without an HTTP round trip.

inheritance

cookies

Snapshot of every cookie currently in the jar, formatted as full Set-Cookie values (name=value). Used by ws_transport to compose the Cookie header on the upgrade request.

type (): readonly string[]

readonly
returns readonly string[]

FetchTransportOptions
#

testing/transports/fetch_transport.ts view source

FetchTransportOptions import type {FetchTransportOptions} from '@fuzdev/fuz_app/testing/transports/fetch_transport.js';

Construction options for create_fetch_transport.

base_url

Base URL the binary is reachable at — e.g. http://localhost:8788.

type string

readonly

initial_cookies?

Initial cookie values to seed the jar. Pass the Set-Cookie values captured from a prior bootstrap() call to keep the keeper session across a transport-recreation boundary. Each entry is a full Set-Cookie value (the same string Headers.getSetCookie() returns).

type readonly string[]

readonly

origin?

Origin header threaded onto every request when the caller hasn't set one. Defaults to base_url; backends running with ALLOWED_ORIGINS=http://localhost:* accept http://localhost:<port> matching the spawned binary.

Pass null to disable the default — useful for bearer-only probes that must not look like browser-context requests (the auth middleware silently discards bearer credentials when Origin/Referer is present). Callers can still set Origin per-call via init.headers.

type string | null

readonly

FieldJson
#

auth/cell_field_action_specs.ts view source

value + type

{ source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; created_at: string; } import {FieldJson} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Wire-format for a cell_field row. ISO created_at, branded UUIDs.

source_id

type string & $brand<"Uuid">

name

type string

target_id

type string & $brand<"Uuid">

created_at

type string

file_fact_url_addresses
#

db/file_fact_url.ts view source

(parsed: { shard: string; rest: string; }, hash: FactHash): boolean import {file_fact_url_addresses} from '@fuzdev/fuz_app/db/file_fact_url.js';

Whether a parsed file:<shard>/<rest> URL is the disk address of hash — shard + rest is its hex.

The shape check (parse_file_fact_url) says a URL is safe to join onto the facts directory; this says it names hash's own file. A reader checks both before it serves the file as hash's bytes: a row whose external_url names another fact's file would otherwise serve those bytes under this hash, past this hash's admission. Twin of the Rust ParsedFactUrl::addresses (fuz_fact).

parsed

type { shard: string; rest: string; }

hash

type FactHash

returns

boolean

FILE_FACT_URL_PATTERN
#

db/file_fact_url.ts view source

RegExp import {FILE_FACT_URL_PATTERN} from '@fuzdev/fuz_app/db/file_fact_url.js';

Anchored, capture-group form: ^file:(<shard>)/(<rest>)$.

FileFactUrl
#

db/file_fact_url.ts view source

value + type

string & $brand<"FileFactUrl">

type string & $brand<"FileFactUrl">

import {FileFactUrl} from '@fuzdev/fuz_app/db/file_fact_url.js';

Branded URL form. Construct only via parse_file_fact_url or mint_file_fact_url — those are the validated boundaries. Direct string literals don't satisfy the brand.

external types

z.infer<typeof FileFactUrl>

filter_credential_gated_routes
#

http/surface_query.ts view source

(surface: AppSurface): AppSurfaceRoute[] import {filter_credential_gated_routes} from '@fuzdev/fuz_app/http/surface_query.js';

Filter routes gated to a credential channel (any non-empty auth.credential_types), whichever channel that is.

Superset of filter_keeper_routes: keeper is the daemon_token gate, and the same axis points the other way on the session-only routes (the credential-minting actions, POST /logout / POST /password, the audit SSE stream). Adversarial runners need the whole bucket, since every route in it answers credential_type_required to a channel it does not admit.

surface

returns

AppSurfaceRoute[]

filter_keeper_routes
#

filter_protected_routes
#

filter_public_routes
#

filter_role_routes
#

filter_routes_with_input
#

filter_routes_with_params
#

filter_routes_with_query
#

filter_visible_target_ids
#

auth/cell_relation_visibility.ts view source

(deps: QueryDeps, auth: RequestContext | null, target_ids: readonly (string & $brand<"Uuid">)[]): Promise<Set<string & $brand<"Uuid">>> import {filter_visible_target_ids} from '@fuzdev/fuz_app/auth/cell_relation_visibility.js';

Return the subset of target_ids the caller may view.

Soft-deleted targets and ids with no matching cell are absent from the result (treated as not-viewable). Grants are loaded only for authenticated callers — null auth admits solely via the public branch of can_view_cell, so the grant load is skipped entirely.

deps

query deps

auth

request context, or null for unauthenticated callers

type RequestContext | null

target_ids

candidate cell ids (duplicates are harmless)

type readonly Uuid[]

returns

Promise<Set<Uuid>>

the set of ids the caller may view

find_auth_route
#

testing/integration_helpers.ts view source

(specs: RouteSpec[], suffix: "/login" | "/logout" | "/password" | "/verify" | "/signup" | "/bootstrap", method: RouteMethod): RouteSpec | undefined import {find_auth_route} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Find a REST auth route by suffix and method.

Decouples tests from consumer route prefix (/api/account/login, /api/auth/login, etc.). suffix must be one of rest_auth_route_suffixes — throws otherwise so an RPC-only method path (e.g. /sessions/revoke-all) fails loudly at the call site instead of silently returning undefined.

specs

type RouteSpec[]

suffix

method

returns

RouteSpec | undefined

throws

  • Error - if `suffix` is not in `rest_auth_route_suffixes`.

find_route_spec
#

testing/integration_helpers.ts view source

(specs: RouteSpec[], method: string, path: string): RouteSpec | undefined import {find_route_spec} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Find a route spec matching the given method and path.

Supports both exact matches and parameterized paths (:param segments).

specs

type RouteSpec[]

method

type string

path

request path (exact or with concrete param values)

type string

returns

RouteSpec | undefined

find_rpc_action
#

testing/rpc_helpers.ts view source

(rpc_endpoints: readonly RpcEndpointSpec[], method: string): { path: string; action: RpcAction; } | undefined import {find_rpc_action} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Find the RpcAction for a method within a set of RPC endpoint specs. Returns both the endpoint path and the matched action. undefined when the method is not registered.

rpc_endpoints

type readonly RpcEndpointSpec[]

method

type string

returns

{ path: string; action: RpcAction; } | undefined

find_rpc_method
#

testing/rpc_helpers.ts view source

(rpc_endpoints: readonly AppSurfaceRpcEndpoint[], method: string): { path: string; method_spec: AppSurfaceRpcMethod; } | undefined import {find_rpc_method} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Find the generated surface entry for a method — the shape returned by generate_app_surface (JSON-serializable, useful for schema assertions at the boundary of a consumer test).

rpc_endpoints

type readonly AppSurfaceRpcEndpoint[]

method

type string

returns

{ path: string; method_spec: AppSurfaceRpcMethod; } | undefined

FINGERPRINT_HEADERS
#

testing/cross_backend/conformance_table.ts view source

readonly string[] import {FINGERPRINT_HEADERS} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Response headers that fingerprint the backend implementation or framework. Neither spine emits these; the runner asserts they stay absent on EVERY conformance response so a framework upgrade or consumer middleware that adds (say) Server: to one spine can't silently become a backend-identifying oracle. Lowercased — matched against the lowercased-key snapshot headers_to_record produces.

flush_pending_effects
#

http/pending_effects.ts view source

(effects: readonly Promise<void>[], log: Logger, on_rejection?: ((reason: unknown) => void) | undefined): Promise<void> import {flush_pending_effects} from '@fuzdev/fuz_app/http/pending_effects.js';

Drain an eager pending_effects queue: Promise.allSettled the in-flight handles, route every rejection through log.error, and fan out to on_rejection when supplied (production wires this to on_effect_error for monitoring).

Returned promise resolves once every effect has settled. Never rejects. No-op when effects is empty (common on read-only requests).

Symmetric with flush_post_commit_effects for the deferred queue.

effects

type readonly Promise<void>[]

log

type Logger

on_rejection?

type (reason: unknown) => void
optional

returns

Promise<void>

flush_post_commit_effects
#

http/pending_effects.ts view source

(effects: readonly (() => void | Promise<void>)[], log: Logger): Promise<void> import {flush_post_commit_effects} from '@fuzdev/fuz_app/http/pending_effects.js';

Drain a post_commit_effects queue: invoke each thunk under try/catch, collect any returned promises, and Promise.allSettled them. Synchronous throws and async rejections are routed through log.error so one failing effect cannot starve siblings.

Returned promise resolves once every thunk has finished. Never rejects.

effects

type readonly (() => void | Promise<void>)[]

log

type Logger

returns

Promise<void>

flush_request_effects
#

http/pending_effects.ts view source

(pending_effects: readonly Promise<void>[], post_commit_effects: readonly (() => void | Promise<void>)[], log: Logger, on_rejection?: ((reason: unknown) => void) | undefined): Promise<...> import {flush_request_effects} from '@fuzdev/fuz_app/http/pending_effects.js';

Drain both queues of one request or WS frame — the single flush every transport runs after its handler returns.

The deferred thunks are invoked before the eager writes are awaited, so a revocation's connection close never sits behind a slow audit INSERT; a thunk that needs a write awaits it itself. Eager rejections are logged and passed to on_rejection (the server's on_effect_error). The caller picks whether to await the returned promise — HTTP awaits it in test mode only, WS awaits it per frame after the reply is sent. Never rejects.

pending_effects

the eager queue of in-flight pool writes

type readonly Promise<void>[]

post_commit_effects

the deferred thunks queued by emit_after_commit

type readonly (() => void | Promise<void>)[]

log

receives every rejection and thunk failure

type Logger

on_rejection?

optional fan-out for each eager rejection

type (reason: unknown) => void
optional

returns

Promise<void>

ForeignKeyError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "foreign_key_violation"; } import {ForeignKeyError} from '@fuzdev/fuz_app/http/error_schemas.js';

Foreign key violation error — returned when a delete is blocked by references.

error

type "foreign_key_violation"

[key: string]

type unknown

format_action_manifest_diffs
#

format_allowed_origins_error
#

http/origin.ts view source

(error: AllowedOriginsError): string import {format_allowed_origins_error} from '@fuzdev/fuz_app/http/origin.js';

Render one refusal for a boot error — the entry in double quotes with every character but visible ASCII and the space escaped as \uXXXX (and " and \ backslash-escaped), so an env value can't put a control, bidi, or other non-printing character in a log, nor close the quotes it is shown in.

error

one of parse_allowed_origins's errors

returns

string

the reason as one line

format_audit_metadata
#

ui/ui_format.ts view source

(event_type: string, metadata: Record<string, unknown> | null): string import {format_audit_metadata} from '@fuzdev/fuz_app/ui/ui_format.js';

Format audit event metadata for display based on event type.

event_type

builtin or consumer-registered audit event type

type string

metadata

type Record<string, unknown> | null

returns

string

format_cross_impl_comparison
#

testing/cross_backend/bench/bench_report.ts view source

(entries: readonly CrossImplComparisonEntry[]): string import {format_cross_impl_comparison} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

Render the comparison entries as one line each. The prefix lists the reference first to match benchmark_stats_compare's "First" (= the a arg = reference) / "Second" (= the b arg = backend) in the recommendation text — compare_cross_impl passes (reference, backend).

entries

type readonly CrossImplComparisonEntry[]

returns

string

format_cross_impl_json
#

testing/cross_backend/bench/bench_report.ts view source

(result: CrossImplBenchResult): string import {format_cross_impl_json} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

Self-describing JSON artifact: one entry per backend × scenario with the percentiles (raw-sample tail), the resolved budget, and iteration count. Diffable and reviewable without a TS runtime; the seed for the deferred static-docs comparison surface.

result

returns

string

format_cross_impl_markdown
#

format_datetime_local
#

ui/ui_format.ts view source

(timestamp: string | number | Date): string import {format_datetime_local} from '@fuzdev/fuz_app/ui/ui_format.js';

Format a timestamp as an absolute datetime string for title attributes.

timestamp

type string | number | Date

returns

string

readable absolute datetime like "2026-03-21 14:30:00 UTC"

format_db_status
#

db/status.ts view source

(status: DbStatus): string import {format_db_status} from '@fuzdev/fuz_app/db/status.js';

Format a DbStatus as the body of the human-facing operator report.

A tables: count with one aligned name N rows line per table, then a migrations: header (always printed, even with no namespaces) and one aligned line per namespace: its state (up-to-date, PENDING <n>, or DIVERGED), applied/declared, and the latest applied name ((none) when nothing is applied). A diverged namespace adds a diverged: line, a pending one a pending: line naming the tail. A failed connection renders the single line connection: FAILED (<error>). Every line ends in a newline.

The body only — the database status / url: header belongs to the CLI script, which holds the URL a DbStatus never sees. For a connected status the output is byte-identical with the Rust twin's format_db_status; both test suites pin the same fixture report.

status

the status to format

returns

string

multi-line string suitable for console output

format_differential_diffs
#

testing/cross_backend/differential_normalize.ts view source

(diffs: readonly DifferentialDiff[], labels: { readonly a: string; readonly b: string; }): string import {format_differential_diffs} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

Render diffs one per line for an assertion message.

diffs

the differences

type readonly DifferentialDiff[]

labels

names for the two sides

type { readonly a: string; readonly b: string; }

returns

string

one indented path: a=… b=… line per diff

format_env_display_value
#

env/mask.ts view source

(value: unknown, secret: boolean): string import {format_env_display_value} from '@fuzdev/fuz_app/env/mask.js';

Format an env value for display, masking secrets.

value

the env value to format

type unknown

secret

whether the value is secret and should be masked

type boolean

returns

string

display string — masked placeholder for secrets, string values as-is, non-strings JSON-stringified

format_migration_tracker_diffs
#

testing/schema_parity.ts view source

(diffs: readonly MigrationTrackerDiff[], labels?: SchemaDiffLabels): string import {format_migration_tracker_diffs} from '@fuzdev/fuz_app/testing/schema_parity.js';

Render migration-tracker diffs as a human-readable multi-line string. Empty diffs produce an empty string.

diffs

type readonly MigrationTrackerDiff[]

labels?

optional default {}

returns

string

format_rate_limit_message
#

ui/auth_state.svelte.ts view source

(retry_after: unknown): string import {format_rate_limit_message} from '@fuzdev/fuz_app/ui/auth_state.svelte.js';

The message a 429 shows, from its retry_after (seconds).

A short wait reads as one — the spines answer retry_after: 1 when the cap is held only by attempts still in flight — rather than rounding up to a minute; a missing value reads as a minute.

retry_after

the response's retry_after, when it carried one

type unknown

returns

string

format_relative_time
#

ui/ui_format.ts view source

(timestamp: string | number | Date, now?: number): string import {format_relative_time} from '@fuzdev/fuz_app/ui/ui_format.js';

Format a timestamp as a relative time string.

timestamp

type string | number | Date

now?

type number
optional default Date.now()

returns

string

human-friendly relative time (e.g. "2m ago", "3h ago", "5d ago", "2mo ago", "1y ago")

format_route_key
#

http/surface_query.ts view source

(route: AppSurfaceRoute): string import {format_route_key} from '@fuzdev/fuz_app/http/surface_query.js';

Format a route as 'METHOD /path' (e.g. 'GET /health').

route

returns

string

format_schema_diffs
#

testing/schema_parity.ts view source

(diffs: readonly SchemaDiff[], labels?: SchemaDiffLabels): string import {format_schema_diffs} from '@fuzdev/fuz_app/testing/schema_parity.js';

Render a diff list as a human-readable multi-line string. Empty diffs produce an empty string.

diffs

type readonly SchemaDiff[]

labels?

optional default {}

returns

string

format_schema_drift
#

db/schema_ready.ts view source

(drift: SchemaDriftResult): string import {format_schema_drift} from '@fuzdev/fuz_app/db/schema_ready.js';

Render a drift result as a one-issue-per-line operator string.

drift

returns

string

format_scope_context
#

ui/format_scope.ts view source

{ get: () => () => FormatScope; set: (value?: (() => FormatScope) | undefined) => () => FormatScope; } import {format_scope_context} from '@fuzdev/fuz_app/ui/format_scope.js';

Svelte context carrying a getter for the consumer's FormatScope. Provisioned by provide_admin_rpc_contexts from its format_scope option. Default getter returns default_format_scope so unprovisioned trees render the truncated uuid.

format_uptime
#

ui/ui_format.ts view source

(ms: number): string import {format_uptime} from '@fuzdev/fuz_app/ui/ui_format.js';

Format milliseconds as a human-friendly uptime string.

ms

type number

returns

string

human-friendly duration (e.g. "45s", "12m", "3h 15m", "2d 5h")

format_value
#

ui/ui_format.ts view source

(value: unknown): string import {format_value} from '@fuzdev/fuz_app/ui/ui_format.js';

Format an arbitrary value for table cell display.

value

type unknown

returns

string

format_zod_issues
#

testing/schema_generators.ts view source

(error: ZodError<unknown>): string import {format_zod_issues} from '@fuzdev/fuz_app/testing/schema_generators.js';

Format Zod issues as path: message pairs for a generator's error message.

error

the failed parse's error

type ZodError<unknown>

returns

string

the issues, comma-separated

FormatScope
#

ui/format_scope.ts view source

FormatScope import type {FormatScope} from '@fuzdev/fuz_app/ui/format_scope.js';

Render a {scope_id, role} pair as a human label. Return null to fall back to the truncated scope uuid (truncate_uuid; or a caller-chosen global_label when scope_id is null).

Returning null for unknown scope ids (stale cache, revoked resource) is the recommended pattern — components show the truncated uuid rather than a misleading blank.

(call)

type (args: { scope_id: string | null; role: string; }): string | null

args

type { scope_id: string | null; role: string; }
returns string | null

FormState
#

ui/form_state.svelte.ts view source

import {FormState} from '@fuzdev/fuz_app/ui/form_state.svelte.js';

generics

FormState<TField extends string = string>
TField
constraint string
default string

form

Creates a form attachment that handles the Enter key (advance focus to the next input, or submit from the last one) and tracks field touched state via delegated focusout.

Fields are identified by their name attribute.

type (): Attachment<HTMLFormElement>

returns Attachment<HTMLFormElement>

throws

  • Error - in DEV when called while already attached, or when an input loses focus without a `name` attribute

is_touched

Whether a field has been blurred at least once.

type (field: TField): boolean

field

type TField
returns boolean

show

Whether to show validation errors for a field. Returns true if the field has been blurred or a submit attempt was made.

type (field: TField): boolean

field

type TField
returns boolean

touch

Programmatically marks a field as touched without requiring a blur event.

type (field: TField): void

field

type TField
returns void

mutates

  • this

focus

Focuses the named input within the form.

type (field: TField): void

field

type TField
returns void

attempt

Marks the form as having been submitted, causing all field errors to show.

type (): void

returns void

mutates

  • this

reset

Resets all touched and attempted state.

type (): void

returns void

mutates

  • this

attempted

Whether a submit attempt has been made.

type boolean

getter

freeze_origin_carve_outs
#

auth/middleware.ts view source

(carve_outs: readonly MiddlewareCarveOut[]): readonly MiddlewareCarveOut[] import {freeze_origin_carve_outs} from '@fuzdev/fuz_app/auth/middleware.js';

Copy origin carve-outs into a frozen array of frozen {path, reason} records, so a caller that mutates its own array or objects after assembly cannot change what was validated or what the check skips.

carve_outs

the caller's carve-outs

type readonly MiddlewareCarveOut[]

returns

readonly MiddlewareCarveOut[]

the frozen copy

FrontendHttpTransport
#

actions/transports_http.ts view source

import {FrontendHttpTransport} from '@fuzdev/fuz_app/actions/transports_http.js';

Thin fetch adapter for the JSON-RPC endpoint. POST by default; a request for a read — its spec (lookup_action_spec) has `side_effects: false — is a GET with method, id, and params` in the query string (matching create_rpc_endpoint's GET convention), unless its input carries a sensitive value (schema_has_sensitive_field, checked once per spec) or its path and query run past max_get_url_length. A notification, and a call for a method without a spec, is always a POST. Always reports ready.

On non-OK HTTP responses, a body that is a JSON-RPC error response for this request (same id, or null when the server couldn't read one) is returned as-is, so the server's code, message, and data reach the caller — the status → code map is lossy (queue_overflow and rate_limited share 429). Any other body (HTML from a proxy, empty, malformed) gets a synthesized envelope whose code comes from http_status_to_jsonrpc_error_code. A 2xx whose body isn't a JSON-RPC response (an HTML fallback page, say) becomes an internal_error naming the status and content type.

inheritance

implements: Transport

transport_name

type "frontend_http_rpc"

readonly

constructor

type new (options: FrontendHttpTransportOptions): FrontendHttpTransport

options

send

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: TransportSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

is_ready

type (): boolean

returns boolean

FrontendHttpTransportOptions
#

actions/transports_http.ts view source

FrontendHttpTransportOptions import type {FrontendHttpTransportOptions} from '@fuzdev/fuz_app/actions/transports_http.js';

url

The JSON-RPC endpoint, absolute or relative to the page.

type string

headers?

Extra headers, merged over the JSON content-type and accept defaults case-insensitively — a POST without application/json is refused (415). A GET carries no content-type.

type Record<string, string>

lookup_action_spec

The spec of a method, which decides how its calls are sent: a request for a read (side_effects: false) is a GET, unless its input carries a 'secret' or 'personal' value at any depth (schema_has_sensitive_field) — a GET puts the params in the URL, which proxies and server logs record, so that input stays in a POST body. Every other call is a POST, including one for a method the lookup doesn't know. create_frontend_rpc_client passes its registry's lookup; a hand-built transport passes the same one its dispatcher environment uses.

type (method: string): { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; } | undefined

method

type string
returns ActionSpecUnion | undefined

max_get_url_length?

The longest path and query a read is sent as a GET with; a longer one is a POST.

type number

default DEFAULT_MAX_GET_URL_LENGTH

FrontendRpcClient
#

actions/frontend_rpc_client.ts view source

FrontendRpcClient<TApi> import type {FrontendRpcClient} from '@fuzdev/fuz_app/actions/frontend_rpc_client.js';

Bundle returned by create_frontend_rpc_client.

generics

FrontendRpcClient<TApi>
TApi

api

Typed throwing Proxy. await api.method(input) returns the unwrapped value or throws an Error with {code, data} from the JSON-RPC error. Default for call sites that don't inspect errors.

type ThrowingApi<TApi>

api_result

Typed Result-shaped Proxy. await api_result.method(input) returns Result<{value}, {error: JsonrpcErrorObject}>. Use when call sites inspect error.data.reason without try/catch, or when Error allocation per {ok: false} would be wasteful.

type TApi

peer

Underlying peer — exposed for consumers that need to register more transports or send raw messages.

type ActionDispatcher

socket

The ws.client the WebSocket lane runs over, or null without one.

type WebsocketRpcConnection | null

environment

Action environment — exposed for consumers that need to share it (e.g. attach a notification handler registry).

type ActionEventEnvironment

FrontendRpcClientWsOptions
#

actions/frontend_rpc_client.ts view source

FrontendRpcClientWsOptions import type {FrontendRpcClientWsOptions} from '@fuzdev/fuz_app/actions/frontend_rpc_client.js';

The WebSocket lane of create_frontend_rpc_client.

client

The WebSocket client — typically a FrontendWebsocketClient over to_same_origin_ws_url('/api/ws'). The caller owns its lifecycle (connect() in the browser, close()).

type WebsocketRpcConnection

queue?

The peer's default queue — true for a client-authoritative app whose requests should wait out a disconnect in the socket's durable queue rather than fail. Per call, RpcClientCallOptions.queue overrides it.

type boolean

default false

max_message_bytes?

See FrontendWebsocketTransportOptions.max_message_bytes.

type number | null

FrontendWebsocketClient
#

actions/socket.svelte.ts view source

import {FrontendWebsocketClient} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Reactive WebSocket client implementing WebsocketConnection.

Construct with a URL and optional config; call connect() to open the socket and begin auto-reconnect. Register message/error handlers via add_message_handler / add_error_handler — both return unsubscribe functions. FrontendWebsocketTransport consumes this as its connection.

Session-revocation close codes (WS_CLOSE_SESSION_REVOKED) put the client in a permanently-closed state; reconnecting would just loop on 401.

A connection-limit close (WS_CLOSE_CONNECTION_LIMIT) leaves the client closed with superseded set and no reconnect scheduled — the server closed this socket to admit a newer one on the same account, and reconnecting would close that one in turn. An explicit connect() reopens.

A permission-revoked close (WS_CLOSE_PERMISSION_REVOKED) leaves the client closed with no reconnect scheduled, and neither revoked nor superseded set: the credential is still good, but the account lost the role the endpoint requires, so a reconnect would be refused at its role gate. Pending and queued requests reject forbidden. last_close_code says why; an explicit connect() reopens, for when the role has been granted again.

inheritance

implements: WebsocketConnection,Disposable

ws

type WebSocket | null

$state.raw

status

type SocketStatus

$state.raw

reconnect_count

type number

$state.raw

current_reconnect_delay

type number

$state.raw

last_connect_time

Epoch ms of the most recent successful open. Never cleared on close.

type number | null

$state.raw

last_close_time

Epoch ms of the most recent close event or client-initiated close.

type number | null

$state.raw

last_close_code

Close code from the most recent close. Initial null means "never closed."

type number | null

$state.raw

last_close_reason

Reason string from the most recent close event (may be empty).

type string | null

$state.raw

last_send_time

Epoch ms of the most recent frame written to the socket — requests, notifications, heartbeats, and cancels alike. null until the first send.

type number | null

$state.raw

last_receive_time

Epoch ms of the most recent frame received — responses (including ones request consumes), notifications, and requests alike. null until the first receive.

type number | null

$state.raw

last_send_error

The error thrown by the most recent attempted send(), or null if the most recent attempt succeeded or none has been attempted yet. Populated when the underlying ws.send throws (e.g., buffer full, serialization error); reset to null on the next successful send. Not touched when send() short-circuits because the socket is not connected — consult connected for that case. Wrappers surfacing per-message failure reasons can read this after a false return from send().

type Error | null

$state.raw

connected

type boolean

readonly $derived

will_reconnect

Whether the socket is down but opening on its own — connecting, or reconnecting (waiting out a scheduled reconnect) — so a request queued now is sent without anyone calling connect(). false when connected, and when closed or initial: after a close that schedules no reconnect (superseded, permission revoked, revoked, auto-reconnect off), disconnect(), or cancel_reconnect().

type boolean

readonly $derived

constructor

type new (url: string, options?: FrontendWebsocketClientOptions): FrontendWebsocketClient

url

type string

options?

optional default {}

set_reconnect

Swap the auto-reconnect policy in place. Accepts the same shape as the constructor's reconnect option: false disables reconnect, true or null/omitted restores the defaults, or a config object customizes specific fields (missing fields fall back to defaults, not "keep current" — each call defines the whole policy atomically, same as the constructor).

In-flight reconnect schedules are monotonically shortened: the effective total wait from arm-time never exceeds what the new policy prescribes. If the new target is already past the time already elapsed, the reconnect fires immediately (on the next tick). The wait is never extended.

Turning reconnect off while a reconnect timer is pending cancels that timer and transitions status to closed (since the lie of 'reconnecting' would be visible to UI indicators). Turning it back on does not synthesize a reconnect — wait for the next close.

type (reconnect?: boolean | FrontendWebsocketReconnectOptions | null): void

reconnect?

type boolean | FrontendWebsocketReconnectOptions | null
optional default null
returns void

mutates

  • this — replaces the reconnect policy fields; may also reset `status`, `reconnect_count`, and `current_reconnect_delay` when an in-flight reconnect timer is cancelled or rescheduled

set_heartbeat

Swap the heartbeat policy in place. Accepts the same shape as the constructor's heartbeat option: false disables the timer, true or null/omitted restores the defaults, or a config object customizes specific fields (missing fields fall back to defaults, not "keep current" — each call defines the whole policy atomically, same as the constructor and set_reconnect).

When connected, the live timer is restarted immediately so the new interval / receive_timeout take effect without a reconnect; when disconnected, just stashes the policy for the next open.

type (heartbeat?: boolean | FrontendWebsocketHeartbeatOptions | null): void

heartbeat?

type boolean | FrontendWebsocketHeartbeatOptions | null
optional default null
returns void

mutates

  • this — replaces the heartbeat policy fields; restarts the live heartbeat timer when connected

cancel_reconnect

Cancel a scheduled reconnect without closing the client or disabling auto-reconnect. Transitions status from reconnecting → closed and resets the backoff counters — the next close still triggers a fresh reconnect cycle under the current policy. No-op when no reconnect is pending.

Use this when UI state asks "stop trying for now" without the finality of disconnect (which also rejects pending/queued requests and clears heartbeat) or the policy change of set_reconnect(false) (which disables future reconnects). The queue stays intact so that calling connect later flushes buffered work.

type (): void

returns void

mutates

  • this — clears the pending reconnect timer, sets `status` to `closed`, resets `reconnect_count` and `current_reconnect_delay`

connect

Open the WebSocket. No-op on SSR, or if the session has been revoked. Cancels any pending reconnect and tears down any existing connection first; an open prior socket is closed with a normal-closure code. Reopens after a superseded close, clearing the flag.

type (): void

returns void

mutates

  • this — replaces `ws`, clears `superseded`, sets `status` to `connecting` (or `closed` on construction failure), and on construction failure may schedule a reconnect (mutating `reconnect_count` / `current_reconnect_delay`)

disconnect

Close the WebSocket, cancel any pending reconnect, and reset the reconnect backoff counters. Puts the client in closed status; call connect() to reopen. Safe to call more than once.

type (code?: number): void

code?

type number
optional default DEFAULT_CLOSE_CODE
returns void

mutates

  • this — clears `ws`, heartbeat, and reconnect timers; sets `status` to `closed`; rejects every pending and queued request with `service_unavailable`

[Symbol.dispose]

Explicit-resource-management hook — supports using client = new FrontendWebsocketClient(url).

type (): void

returns void

send

type (data: object): boolean

data

type object
returns boolean

request

Promise-based JSON-RPC over the socket. Auto-assigns a monotonic request id (or uses an explicit one supplied via options.id — used by FrontendWebsocketTransport which delegates to this method and has its own peer-minted UUID), tracks the pending promise, and resolves when the server sends a matching response.

An undefined params is omitted from the frame rather than sent as {} — the absent form is the parameterless call every server admits for a z.void() input, including servers that refuse {}.

Callers supplying an explicit options.id are responsible for uniqueness — the pending map is keyed by id, and a duplicate silently overwrites the prior entry. Auto-minted ids are monotonic and never collide with themselves or with peer-minted UUIDs (the types differ: integer vs string).

While the socket is disconnected, the request is buffered in a bounded queue (default-on, DEFAULT_QUEUE_MAX_SIZE) and flushed on reopen. Pass {queue: false} to reject immediately when disconnected — used internally by the heartbeat, which must not fight the queue for the disconnect-detection slot.

On AbortSignal fire: rejects the local promise *and* sends the shared cancel notification (cancel_action_spec.method) so the server-side dispatcher can abort the matching handler's ctx.signal. Suppressed for queued-but-never-sent (server doesn't know about it) and response-beat-cancel races.

type <R = unknown>(method: string, params?: unknown, options?: { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; }): Promise<R>

method

type string

params?

type unknown
optional

options?

type { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; }
optional default {}
returns Promise<R>

throws

  • ThrownJsonrpcError - on the returned promise — never thrown synchronously. Rejection codes: - `unauthenticated` — session revoked (entry check or close code) - `request_cancelled` — caller's `AbortSignal` fired - `queue_overflow` — durable queue full - `service_unavailable` — socket not connected / closed / torn down mid-flight, including a `superseded` close - `forbidden` — in flight or queued at a `WS_CLOSE_PERMISSION_REVOKED` close - `internal_error` — `ws.send` threw (serialization, buffer full) - server's wire code verbatim — JSON-RPC error frame from peer

mutates

  • this — inserts the new pending entry into `#pending` (or buffers into `#queue` when disconnected) and may bump `#next_request_id`, `last_send_time`, `last_send_error`

add_message_handler

type (handler: SocketMessageHandler): () => void

handler

returns () => void

add_error_handler

type (handler: SocketErrorHandler): () => void

handler

returns () => void

url

type string

getter

revoked

Whether the server has permanently closed the session. Once true, all connect() calls are no-ops. Distinct from status:'closed', which reflects any closed state (incl. user-initiated disconnect()).

type boolean

getter

superseded

Whether the server closed this client's socket to admit a newer connection on the same account (WS_CLOSE_CONNECTION_LIMIT) and it hasn't been reopened since. While true the client is closed and schedules no reconnect — reconnecting on its own would supersede a newer socket in turn, and clients past the cap would close each other in a loop.

Unlike revoked this is not terminal: the credential is still good, and the next connect() clears it and reopens — only connect() does; disconnect() leaves it set. Requests in flight or queued at the close reject with service_unavailable; ones made afterwards buffer in the durable queue like any other call on a closed client. Reactive, so UI can say why the connection is closed and offer the reconnect.

socket_status_to_async_status reads a superseded client as initial, the same as one that was never connected. An app that connects on its own when its status reads initial must also check superseded, or it rebuilds the loop.

type boolean

getter

FrontendWebsocketClientOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketClientOptions import type {FrontendWebsocketClientOptions} from '@fuzdev/fuz_app/actions/socket.svelte.js';

reconnect?

Auto-reconnect policy. false disables reconnect entirely; true or omit for default timing; pass an object to customize.

type boolean | FrontendWebsocketReconnectOptions | null

heartbeat?

Activity-aware heartbeat. true/null/omit for defaults; false disables the timer entirely (only do this if the server side is also running without heartbeat); pass an object to tune interval / receive_timeout.

type boolean | FrontendWebsocketHeartbeatOptions | null

queue?

Durable queue for FrontendWebsocketClient.request. true or omit for defaults; false disables buffering (requests while disconnected reject immediately). Raw FrontendWebsocketClient.send is never queued — use request() for RPC semantics.

type boolean | FrontendWebsocketQueueOptions

log?

Optional logger for diagnostic messages.

type Logger | null

FrontendWebsocketHeartbeatOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketHeartbeatOptions import type {FrontendWebsocketHeartbeatOptions} from '@fuzdev/fuz_app/actions/socket.svelte.js';

interval?

Idle duration (ms) after which a heartbeat is sent — when either nothing has been sent or nothing has been received for this long, so both ends' silence detectors stay fed. Traffic in both directions defers it, so chatty connections never emit extras. Defaults to DEFAULT_HEARTBEAT_INTERVAL.

type number

receive_timeout?

Receive-silence (ms) after which the client closes the socket with WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT, letting auto-reconnect kick in. Defaults to DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT; either way it's raised to at least HEARTBEAT_RECEIVE_TIMEOUT_MIN_FACTOR × interval (see resolve_heartbeat_receive_timeout), so a long interval alone is safe.

type number

FrontendWebsocketQueueOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketQueueOptions import type {FrontendWebsocketQueueOptions} from '@fuzdev/fuz_app/actions/socket.svelte.js';

max_size?

Maximum number of requests held while the socket is disconnected. Enqueue past this rejects the new call with a queue_overflow error. Defaults to DEFAULT_QUEUE_MAX_SIZE.

type number

FrontendWebsocketReconnectOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketReconnectOptions import type {FrontendWebsocketReconnectOptions} from '@fuzdev/fuz_app/actions/socket.svelte.js';

delay?

Base reconnect delay in ms. Defaults to 1000.

type number

delay_max?

Max reconnect delay in ms (cap on exponential backoff). Defaults to 10000.

type number

factor?

Exponential backoff factor. Defaults to 1.5.

type number

FrontendWebsocketTransport
#

actions/transports_ws.ts view source

import {FrontendWebsocketTransport} from '@fuzdev/fuz_app/actions/transports_ws.js';

Thin adapter over WebsocketRpcConnection (canonical implementation: FrontendWebsocketClient). Routes inbound server-pushed requests and notifications into the supplied receive callback and sends the request response back over the socket; an inbound peer/ping is answered by the built-in responder before receive. Responses to requests *we* sent are owned by the connection's own request() pending map and are ignored here. An oversized outbound message (max_message_bytes) fails with an error envelope instead of being sent (see FrontendWebsocketTransportOptions).

inheritance

implements: Transport

transport_name

type "frontend_websocket_rpc"

readonly

constructor

type new (connection: WebsocketRpcConnection, receive: (data: unknown) => Promise<unknown>, options?: FrontendWebsocketTransportOptions | undefined): FrontendWebsocketTransport

connection

receive

type (data: unknown) => Promise<unknown>

options?

optional

send

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: TransportSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

is_ready

type (): boolean

returns boolean

dispose

Detach the inbound message and error handlers registered on the connection. Idempotent — subsequent calls no-op. Does not close the underlying connection (that lifecycle is owned by the caller).

type (): void

returns void

mutates

  • this — clears the two stored unsubscribe references after invoking them

queues

A request sent with queue: true waits in the connection's durable queue while disconnected — but only while the connection will_reconnect on its own. After a close that won't reopen (superseded, permission revoked, disconnect(), auto-reconnect off) this is false, so Transports.get_transport falls back to a ready transport (HTTP) rather than leave the request waiting on a connect() nobody may call.

type boolean

getter

FrontendWebsocketTransportOptions
#

actions/transports_ws.ts view source

FrontendWebsocketTransportOptions import type {FrontendWebsocketTransportOptions} from '@fuzdev/fuz_app/actions/transports_ws.js';

max_message_bytes?

Largest outbound message, in UTF-8 bytes of its JSON encoding. A larger request or notification is refused with an invalid_request error (data.reason payload_too_large) without being sent — the server closes the socket on an oversized one (failing every request in flight on it) rather than replying with an error. Defaults to DEFAULT_WS_MAX_MESSAGE_BYTES, the server's default cap; a consumer that raises the server's max_message_bytes passes the same value here. null skips the measurement.

type number | null

default DEFAULT_WS_MAX_MESSAGE_BYTES

FsReadDeps
#

runtime/deps.ts view source

FsReadDeps import type {FsReadDeps} from '@fuzdev/fuz_app/runtime/deps.js';

File system read operations.

stat

Get file/directory stats, or null if path doesn't exist.

type (path: string): Promise<StatResult | null>

path

type string
returns Promise<StatResult | null>

read_text_file

Read a file as text. Throws if the file does not exist.

type (path: string): Promise<string>

path

type string
returns Promise<string>

read_file

Read a file as bytes. Throws if the file does not exist.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

read_text_from_offset

Read text starting from a byte offset. Throws if the file does not exist.

Returns content, bytes_read, and file_size so callers can detect truncation (when file_size < offset) and tail incrementally without re-reading the whole file.

type (path: string, offset: number): Promise<ReadTextFromOffsetResult>

path

type string

offset

type number
returns Promise<ReadTextFromOffsetResult>

readdir

List directory entries (names, not full paths). Throws if the directory does not exist.

type (path: string): Promise<string[]>

path

type string
returns Promise<string[]>

FsRemoveDeps
#

runtime/deps.ts view source

FsRemoveDeps import type {FsRemoveDeps} from '@fuzdev/fuz_app/runtime/deps.js';

File system remove operations.

remove

Remove a file or directory.

type (path: string, options?: { recursive?: boolean | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; }
optional
returns Promise<void>

FsSecureReadDeps
#

runtime/deps.ts view source

FsSecureReadDeps import type {FsSecureReadDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Hardened read for secret files (bootstrap tokens, keys) — the TS twin of the Rust spine's fuz_sys::secure_file::load_secure_file.

read_secure_file

Read a small secret file with fail-loud checks: rejects symlinks, non-regular files (FIFO, device, directory — real runtimes only; the mock has no file types), any group/other-accessible mode (must be 0600/0400; skipped where POSIX modes don't apply), and files over a 4 KiB cap. Permission checks run on the open descriptor where the platform allows, so the file can't be swapped between check and read. Throws on any refusal — a refused secret must never degrade to an empty read.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

FsStreamDeps
#

runtime/deps.ts view source

FsStreamDeps import type {FsStreamDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Streaming file I/O — read a file as a byte stream, or write a byte stream to a file, both bounded in memory (peak ≈ one chunk, not the whole file).

Kept separate from FsReadDeps / FsWriteDeps so the whole-buffer read_file / write_file consumers and their partial test stubs are unaffected; only the full runtime factories implement these. Used for GB-scale artifact transfer (the fuzf file get / put path) where buffering the whole file would OOM the client.

read_file_stream

Open a file as a ReadableStream of its bytes — read incrementally, so peak memory is one chunk rather than the whole file. Throws if the file does not exist. Use as a streaming upload body or for an incremental hash pass over a large file.

type (path: string): Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>

path

type string
returns Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>

write_file_stream

Write a ReadableStream of bytes to a file, consuming it with backpressure (peak memory is one chunk). Creates or truncates path. Throws on any I/O error; a partially-written file may remain, so callers needing atomicity write to a temp path then rename.

type (path: string, data: ReadableStream<Uint8Array<ArrayBufferLike>>): Promise<void>

path

type string

data

type ReadableStream<Uint8Array<ArrayBufferLike>>
returns Promise<void>

FsWriteDeps
#

runtime/deps.ts view source

FsWriteDeps import type {FsWriteDeps} from '@fuzdev/fuz_app/runtime/deps.js';

File system write operations.

mkdir

Create a directory. mode applies at creation (no-op where modes don't apply).

type (path: string, options?: { recursive?: boolean | undefined; mode?: number | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; mode?: number | undefined; }
optional
returns Promise<void>

write_text_file

Write text to a file. See WriteFileOptions for mode / exclusive.

type (path: string, content: string, options?: WriteFileOptions | undefined): Promise<void>

path

type string

content

type string

options?

optional
returns Promise<void>

write_file

Write bytes to a file.

type (path: string, data: Uint8Array<ArrayBufferLike>): Promise<void>

path

type string

data

type Uint8Array<ArrayBufferLike>
returns Promise<void>

rename

Rename (move) a file.

type (old_path: string, new_path: string): Promise<void>

old_path

type string

new_path

type string
returns Promise<void>

fsync

Flush a file's data to stable storage (fsync). Call on a temp file after writing it and *before* rename-ing it into place when the renamed path is later served without re-verification — otherwise a host crash after the rename can surface a torn/zero file as authentic content. The fact disk CAS (db/fact_disk_storage.ts) is the one such path; it twins the Rust fuz_fact §fsync posture (data-sync before rename; the parent-dir fsync stays deliberately waived — a lost dirent is regenerable under content addressing). Real runtimes open the path, fsync, and close; create_mock_runtime no-ops (it models no durability).

type (path: string): Promise<void>

path

type string
returns Promise<void>

FullSpineMount
#

testing/cross_backend/full_spine_mount.ts view source

FullSpineMount import type {FullSpineMount} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

The spine binary's RPC and WS action sets, built from one action list.

rpc_actions

Every action on the RPC endpoint — build_full_spine_rpc_actions's list.

type RpcAction[]

readonly

ws_actions

The WS endpoint's actions: protocol_actions first, then every RPC action not already among them (peer/ping is on both lists). The same handler instances as rpc_actions.

type Action<ActionSpecUnion>[]

readonly

FullSpineMountOptions
#

testing/cross_backend/full_spine_mount.ts view source

FullSpineMountOptions import type {FullSpineMountOptions} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

daemon_token_state

Daemon-token runtime state threaded into create_testing_actions — the _testing_reset handler mutates keeper_account_id after re-seeding. Pass the same instance the daemon-token middleware reads. For the coverage test (method enumeration only, handlers never run) any stub state satisfies it.

type DaemonTokenState

readonly

notification_sender?

WS notification sender for the role-grant-offer fan-out. Pass the SAME ConnectionRegistry the WS endpoint registers connections against (the registry is the fan-out target). Omitted for enumeration.

type NotificationSender | null

readonly

fuz_app_stock_route_tightness_allowlist
#

testing/surface_invariants.ts view source

readonly string[] import {fuz_app_stock_route_tightness_allowlist} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Routes shipped by fuz_app whose error schemas require a tightness exemption.

Currently empty — every fuz_app-shipped route (account login/password/ bootstrap/signup, db health/tables/:name/tables/:name/rows/:id) was tightened in place to z.enum([...]) / z.literal(...) against every emit-site error code.

Kept as a forward-compatibility hook: when new stock routes ship with heterogeneous error surfaces that need an interim generic schema, add them here instead of forcing every consumer to hand-maintain the entry.

Paths assume the standard /api/account + /api/db prefixes used by every fuz_app consumer. Merged into default_error_schema_tightness.allowlist so consumers calling assert_error_schema_tightness directly inherit the exemptions; the standard attack-surface suite also prepends these entries underneath any consumer-supplied allowlist so project-specific entries are additive.

fuz_auth_guard_resolver
#

auth/auth_guard_resolver.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): AuthGuards import {fuz_auth_guard_resolver} from '@fuzdev/fuz_app/auth/auth_guard_resolver.js';

Standard auth guard resolver for fuz_app.

Reads each axis of the RouteAuth shape and emits the corresponding middleware:

  • account === 'required' or actor === 'required' → pre-authorization require_auth
  • credential_types?.length → pre-authorization require_credential_types(types)
  • required_scope → pre-authorization require_token_scope(capability)
  • roles?.length → post-authorization require_role(roles) (multi-role any-of)

Guards run in declaration order, which is the coarse-to-fine order above: presence first (an anonymous caller holds neither a credential type nor a scope to check), then the channel, then the scope, then the role — the last being the only one that needs the resolved RequestContext.

auth

returns

AuthGuards

throws

  • Error - if `auth.required_scope` is not a well-formed capability string (via `require_token_scope`) — `RouteAuth` types it as a plain string to keep `http/` free of an `auth/` import, so this is where the value is checked. The check is on *shape*, not membership: the vocabulary is deliberately open so a consumer can name its own surface, and a `surface:` name decides nothing (rule 3 is all-or-nothing), so the closed set would only have been a spelling check on a diagnostic string. fuz_app's own declarations are pinned to the surfaces it actually mounts by the surface census.
  • Error - if `auth.required_scope` sits on an unrestricted route (`account: 'none' && actor: 'none'`). The same holder reaches that route by dropping the credential, so the guard reads as a control and enforces nothing — the shape `compile_action_registry` refuses on an `ActionSpec` for the same reason. Extends `RouteAuth`'s leaf invariant, which the route pipeline never parses.

fuz_session_config
#

FuzAuthActionSpecRegistry
#

auth/all_action_spec_registries.ts view source

FuzAuthActionSpecRegistry import type {FuzAuthActionSpecRegistry} from '@fuzdev/fuz_app/auth/all_action_spec_registries.js';

One named entry in the registry-of-registries.

name

Stable identifier matching the source bundle name ('admin', 'role_grant_offer', etc.).

type string

specs

The bundle's spec array — kept readonly here even when the source declares it mutable.

type readonly RequestResponseActionSpec[]

generate_action_event_datas
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_action_event_datas} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the ActionEventDatas interface — one ActionEvent*Data variant per method, parameterized by the spec's kind:

  • request_response → ActionEventRequestResponseData<method, input, output>
  • remote_notification → ActionEventRemoteNotificationData<method, input>
  • local_call → ActionEventLocalCallData<method, input, output>

Adds the per-kind data type imports (only the kinds that appear in specs).

specs

type readonly ActionSpecUnion[]

imports

options?

type { same_file?: boolean | undefined; collections_path?: string | undefined; include_protocol_actions?: boolean | undefined; }
optional

returns

string

generate_action_inputs_outputs
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_action_inputs_outputs} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit ActionInputs + ActionOutputs runtime consts and matching interfaces. The runtime consts reference specs.{method}_action_spec.input / .output; the interfaces use z.infer.

Adds import {z} from 'zod'; and the * as specs namespace import.

specs

type readonly ActionSpecUnion[]

imports

options?

type { specs_module?: string | undefined; qualify_spec?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string)...
optional

returns

string

generate_action_method_enums
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_action_method_enums} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit one or more z.enum([...]) declarations for action method names — ActionMethod, RequestResponseActionMethod, RemoteNotificationActionMethod, LocalCallActionMethod, FrontendActionMethod, BackendActionMethod, FrontendRequestResponseMethod, BackendRequestResponseMethod, BroadcastActionMethod. Pairs each runtime const with a z.infer type alias under the same identifier.

Protocol-action methods (heartbeat, cancel) are filtered out by default — pass include_protocol_actions: true if a consumer genuinely wants them on their typed surface. Empty kinds are skipped so the helper never emits z.enum([]) (zod runtime-throws on that).

Adds import {z} from 'zod'; to imports only when at least one block is emitted (idempotent).

specs

type readonly ActionSpecUnion[]

imports

options?

type { emit?: ReadonlySet<ActionMethodEnumKind> | undefined; include_protocol_actions?: boolean | undefined; }
optional

returns

string

generate_action_specs_record
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_action_specs_record} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the ActionSpecs runtime const + interface + the `action_specs: Array<ActionSpecUnion> value bundling every spec. Adds the * as specs` namespace import + the ActionSpecUnion type import.

specs

type readonly ActionSpecUnion[]

imports

options?

type { specs_module?: string | undefined; qualify_spec?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string)...
optional

returns

string

generate_api_token
#

auth/api_token.ts view source

(): { token: string; id: string; token_hash: string; } import {generate_api_token} from '@fuzdev/fuz_app/auth/api_token.js';

Generate a new API token with its hash and public id.

The raw token is returned exactly once — callers must present it to the user immediately.

returns

{ token: string; id: string; token_hash: string; }

the raw token, a public id, and the blake3 hash for storage

generate_app_surface
#

http/surface.ts view source

(options: GenerateAppSurfaceOptions): AppSurface import {generate_app_surface} from '@fuzdev/fuz_app/http/surface.js';

Generate a JSON-serializable attack surface from middleware, route specs, and optional env/event metadata.

options

returns

AppSurface

generate_backend_actions_api
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_backend_actions_api} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit BOTH the typed BackendActionsApi interface AND the broadcast_action_specs runtime array. The interface is shaped for create_broadcast_api: backend-initiated remote_notification methods, each (input) => Promise<void>. The array bundles the matching specs as a ReadonlyArray<ActionSpecUnion>.

Filter: kind === 'remote_notification' && initiator !== 'frontend', additionally excluding methods that are the target of another spec's streams field. Streams targets (e.g. completion_progress, ollama_progress) are request-scoped notifications invoked via ctx.notify inside their parent handler — they're never callable through the broadcast API. The discriminator is ActionSpec.streams, not a manual exclusion list.

Adds the * as specs namespace import (from specs_module), the ActionInputs type import (from collections_path), and the ActionSpecUnion type import.

Method signature shape today is (input) => Promise<void> — matches the fire-and-forget runtime of create_broadcast_api. Generalizing it per kind, as FrontendActionsApi signatures are, is deferred until a second backend runtime constructor lands.

specs

type readonly ActionSpecUnion[]

imports

options?

type { specs_module?: string | undefined; collections_path?: string | undefined; qualify_spec?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; ... 8 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string) | undefined; include_protocol_a...
optional

returns

string

generate_cookie_key
#

generate_daemon_token
#

auth/daemon_token.ts view source

(): string import {generate_daemon_token} from '@fuzdev/fuz_app/auth/daemon_token.js';

Generate a new daemon token (256-bit random, base64url).

returns

string

a 43-character base64url string

generate_frontend_action_handlers
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_frontend_action_handlers} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the FrontendActionHandlers interface — wraps generate_phase_handlers with the TypedActionEvent action-event type and standard 1-tab per-method indentation. Pairs with generate_typed_action_event_alias (emits the matching TypedActionEvent alias) — call both in the same gen producer.

specs

type readonly ActionSpecUnion[]

imports

options?

type { collections_path?: string | undefined; include_protocol_actions?: boolean | undefined; }
optional

returns

string

generate_frontend_actions_api
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_frontend_actions_api} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the FrontendActionsApi interface — one method signature per spec. Optionally filter the spec set (e.g. omit additional methods alongside the default protocol-action filter) via method_filter.

Imports are registered per emitted line — only what the spec set actually references shows up on imports. A spec set with no async methods skips RpcClientCallOptions; one with no inputs skips ActionInputs; sync local_call methods with sync_returns_value: true (the default) skip Result / JsonrpcErrorObject.

The interface name is fixed at FrontendActionsApi — the symmetric counterpart of BackendActionsApi. Earlier consumer-named variants (MyActionsApi, VisionesActionsApi) were retired in API review III to make the side-of-the-wire intent visible at every call site. If a consumer needs a different name they hand-roll the interface (the helper's job is the standard symmetric shape).

specs

type readonly ActionSpecUnion[]

imports

options?

type { method_filter?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => boolean) | undefined; collections_path?: ...
optional

returns

string

generate_input_test_cases
#

testing/adversarial_input_cases.ts view source

(input_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, options?: InputTestCaseOptions | undefined): InputTestCase[] import {generate_input_test_cases} from '@fuzdev/fuz_app/testing/adversarial_input_cases.js';

Generate adversarial test cases for a route's input schema.

Produces focused, non-redundant cases:

  • Whole-body: send array instead of object, extra unknown key
  • Missing required fields (without defaults)
  • One wrong-type value per field
  • Null for required non-nullable fields
  • One format violation per constrained field
  • String length (empty, one under minLength, one over maxLength), numeric (out of range, non-integer, off a multipleOf grid) and array-length (empty, one under minItems, one over maxItems) boundaries

Format and boundaries come from the field's input-side JSON Schema (to_field_json_schema), so a field carrying a .transform() or pipe and a .nullable() / .nullish() field get them too. A bound only a .refine() enforces generates nothing — JSON Schema can't express it.

Each per-field case is single-fault: valid except for the field it breaks. It changes one field of a valid base body — options.base when given, else one built by generate_valid_object — or, for a field that base omits, of the valid body generate_valid_object_with_field builds around it, so a case can't also break an object-level rule (adding scope_kind without its paired scope_id, or slug beside an exactly-one-of id). A field no valid body carries keeps its wrong-type and null cases on the base and gets no format or boundary cases, which would test no bound. A per-field case is emitted only when the field refuses its value, so no case is accidentally valid.

input_schema

the input schema to attack

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

options?

a base body to seed the cases, and a label for errors

optional

returns

InputTestCase[]

throws

  • Error - if the base body fails `input_schema.safeParse` — a seeded base the schema refuses, or a generated one that surfaces broken generation logic — naming the issues rather than producing nonsense adversarial cases.

generate_params_test_cases
#

testing/adversarial_input_cases.ts view source

(params_schema: ZodObject<$ZodLooseShape, $strip>): ParamsTestCase[] import {generate_params_test_cases} from '@fuzdev/fuz_app/testing/adversarial_input_cases.js';

Generate adversarial test cases for a route's params schema.

Params are always strings from URL segments. Only generates cases for format-constrained fields (uuid, pattern) since unconstrained string params accept any string value, and only a violation the field refuses. Each case breaks one field of a valid params record.

params_schema

type ZodObject<$ZodLooseShape, $strip>

returns

ParamsTestCase[]

throws

  • Error - if no valid params record can be generated

generate_phase_handlers
#

actions/action_codegen.ts view source

(spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, executor: "frontend" | "backend", imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_phase_handlers} from '@fuzdev/fuz_app/actions/action_codegen.js';

Generates the phase handlers for an action spec using the unified ActionEvent type with the new phase/step type parameters.

Returns '' when the spec contributes no phases on the given executor side (e.g. a backend-only local_call asked for 'frontend'). Upstream wrappers compose blocks with .filter(Boolean) so empty entries are dropped from the generated handler map. The earlier shape was ${method}?: never, which read as "calling this here is a type error" but in practice produced useless rows on FrontendActionHandlers for methods that don't belong on this side at all — drop the row instead so the typed surface only carries methods the executor actually handles.

spec

executor

type "frontend" | "backend"

imports

options?

type { action_event_type?: string | undefined; collections_path?: string | undefined; }
optional

returns

string

generate_position_styles
#

ui/position_helpers.ts view source

(position?: Position, align?: Alignment, offset?: string): Record<string, string> import {generate_position_styles} from '@fuzdev/fuz_app/ui/position_helpers.js';

Generates CSS positioning styles for UI elements.

position?

optional default 'center'

align?

optional default 'center'

offset?

distance from the position (CSS value)

type string
optional default '0'

returns

Record<string, string>

throws

  • UnreachableError - if `position` is not a known `Position` value

generate_query_test_cases
#

testing/adversarial_input_cases.ts view source

(query_schema: ZodObject<$ZodLooseShape, $strip>): QueryTestCase[] import {generate_query_test_cases} from '@fuzdev/fuz_app/testing/adversarial_input_cases.js';

Generate adversarial test cases for a route's query schema.

Query params are always strings from the URL. Generates cases for:

  • Missing required fields the field refuses to go without (a preprocessed field with a default accepts its absence)
  • Format violations on constrained fields (uuid, pattern) the field refuses

Each case breaks one field of a valid query record.

query_schema

type ZodObject<$ZodLooseShape, $strip>

returns

QueryTestCase[]

throws

  • Error - if no valid query record can be generated

generate_random_base64url
#

crypto.ts view source

(byte_length?: number): string import {generate_random_base64url} from '@fuzdev/fuz_app/crypto.js';

Generate a cryptographically random base64url string.

byte_length?

number of random bytes (default 32 = 256 bits)

type number
optional default 32

returns

string

base64url-encoded string without padding

generate_rpc_auth_conformance_cases
#

testing/cross_backend/conformance_rpc_auth.ts view source

(options: RpcAuthConformanceOptions): { name: string; request: { method: string; as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | ... 7 more ... | "expired_session"; params?: unknown; params_json?: string | undefined; acting?: "principal" | ... 2 more ... | undefined; verb?: "GET" | ... 1 more ... | undefined; }; expect: { ...; }; note?: string | undefined; xfail?: { ...; } | undefined; }[] import {generate_rpc_auth_conformance_cases} from '@fuzdev/fuz_app/testing/cross_backend/conformance_rpc_auth.js';

Generate the per-method auth rows for every declared RPC method, then every live-mounted one the declared surface lacks.

options

the declared surface, the live mount, and any methods to skip

returns

ConformanceCase[]

the conformance rows, grouped by method in declaration order, then live-mount order

generate_rpc_params_test_cases
#

testing/rpc_envelope_cases.ts view source

(input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, options?: InputTestCaseOptions | undefined): InputTestCase[] import {generate_rpc_params_test_cases} from '@fuzdev/fuz_app/testing/rpc_envelope_cases.js';

Generate the schema-invalid params cases for one RPC method's input.

generate_input_test_cases minus its structural cases: a non-object body is a non-object params, which the JSON-RPC envelope refuses (invalid_request) before params validation runs, and the envelope cases cover it. Every case returned owes 400 invalid_params.

input

the method's input schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

options?

a valid base params to seed the cases (InputTestCaseOptions.base), and the method name for error messages

optional

returns

InputTestCase[]

the cases, each a params object

throws

  • Error - if the base `params`, seeded or generated, fails the input schema

generate_session_token
#

auth/session_queries.ts view source

(): string import {generate_session_token} from '@fuzdev/fuz_app/auth/session_queries.js';

Generate a cryptographically random session token.

returns

string

a 32-byte base64url-encoded token

generate_typed_action_event_alias
#

actions/action_codegen.ts view source

(imports: ImportBuilder, options?: { collections_path?: string | undefined; metatypes_path?: string | undefined; } | undefined): string import {generate_typed_action_event_alias} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the fixed-shape TypedActionEvent alias used by FrontendActionHandlers to narrow ActionEvent.data against the consumer's generated ActionEventDatas map. Registers the four fuz_app type imports it needs (ActionEvent, ActionEventPhase, ActionEventStep, ActionEventDatas) plus the ActionMethod type import — sourced from collections_path and metatypes_path respectively.

Pair with generate_action_method_enums (emits ActionMethod into metatypes_path) and generate_action_event_datas (emits ActionEventDatas into collections_path).

imports

options?

type { collections_path?: string | undefined; metatypes_path?: string | undefined; }
optional

returns

string

generate_valid_body
#

testing/schema_generators.ts view source

(input_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): Record<string, unknown> | undefined import {generate_valid_body} from '@fuzdev/fuz_app/testing/schema_generators.js';

Generate a valid request body for a route's input schema.

input_schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

Record<string, unknown> | undefined

a generated body that passes safeParse, or undefined for null / non-object schemas

throws

  • Error - if the generated body fails `input_schema.safeParse` — catches broken `generate_valid_value` logic early with a descriptive Zod-issues summary instead of a confusing 400 in downstream tests.

generate_valid_object
#

testing/schema_generators.ts view source

(object_schema: ZodObject<$ZodLooseShape, $strip>, schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): { value: Record<...>; result: ZodSafeParseResult<...>; } import {generate_valid_object} from '@fuzdev/fuz_app/testing/schema_generators.js';

Generate a valid object for an object schema: its required and defaulted fields first; when schema still refuses it, each optional field alone on top of those, in declaration order, taking the first that parses; and failing that, the optional fields added one at a time, in declaration order, until it parses. The fill covers a .refine() over optional fields (at least one of, exactly one of, both of), which the required-only object can't satisfy, and the single-field pass keeps an unrelated optional field declared ahead of the refined ones (an acting, a note) out of the object.

Shared by the top-level body generators and generate_valid_value's nested-object case, so a refined object validates the same way at any depth.

object_schema

the unwrapped object whose fields are generated

type ZodObject<$ZodLooseShape, $strip>

schema

the schema the object must satisfy, refinements included (object_schema or a schema wrapping it)

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

{ value: Record<string, unknown>; result: ZodSafeParseResult<unknown>; }

the generated object and its last safeParse result — the caller decides whether a failure throws

generate_valid_object_with_field
#

testing/schema_generators.ts view source

(object_schema: ZodObject<$ZodLooseShape, $strip>, schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, base: Record<string, unknown>, field: ZodFieldInfo): Record<...> | null import {generate_valid_object_with_field} from '@fuzdev/fuz_app/testing/schema_generators.js';

A valid object that carries field, built from base, a valid object of object_schema — the body a case breaking field changes, so the case breaks that field and nothing else. base itself when it carries the field. Otherwise the field takes a generated value and joins base, with the optional fields base omits filled as generate_valid_object fills them (a pair rule: adding scope_kind needs scope_id); failing that, it replaces base's optional fields, refilled the same way around it (an exactly-one-of rule: adding slug means dropping id).

object_schema

the unwrapped object base is an instance of

type ZodObject<$ZodLooseShape, $strip>

schema

the schema the object must satisfy, refinements included

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

base

a valid object, the generated or seeded base

type Record<string, unknown>

field

the field the object must carry

returns

Record<string, unknown> | null

the object, or null when no object the generator can build carries the field and passes schema

generate_valid_string_param
#

testing/schema_generators.ts view source

(field: ZodFieldInfo, field_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): string | null import {generate_valid_string_param} from '@fuzdev/fuz_app/testing/schema_generators.js';

A string a URL-sourced field accepts — a route param or a query value, which arrive as strings whatever the schema parses them into. The nil UUID for a UUID field; else the first the field accepts of test_value, the generated valid value written as a string, 1, 0, true and false, the field's JSON Schema default or lower bound written as a string (a preprocessed integer whose minimum is above one), and a string synthesized from the field's constraints.

field

the field's info, from zod_extract_fields

field_schema

the field's schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

string | null

the string, or null when no candidate passes

generate_valid_value
#

testing/schema_generators.ts view source

(field: ZodFieldInfo, field_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): unknown import {generate_valid_value} from '@fuzdev/fuz_app/testing/schema_generators.js';

Generate a valid-ish value for a field based on its base type.

field

field_schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

unknown

GenerateAppSurfaceOptions
#

http/surface.ts view source

GenerateAppSurfaceOptions import type {GenerateAppSurfaceOptions} from '@fuzdev/fuz_app/http/surface.js';

route_specs

type RouteSpec[]

middleware_specs

type MiddlewareSpec[]

env_schema?

type ZodObject<$ZodLooseShape, $strip>

event_specs?

type EventSpec[]

rpc_endpoints?

type RpcEndpointSpec[]

ws_endpoints?

Mounted WS endpoints (the same array create_app_server.ws_endpoints auto-mounts). Each entry's actions surface into AppSurface.ws_endpoints[i].methods for attack-surface tests + startup logging. Each carries its effective allowed_origins — resolve_ws_endpoints fills in the server's list where a spec omits it — so the surface records the exact patterns the upgrade gate matches.

type readonly ResolvedWsEndpointSpec[]

get_app_dir
#

testing/daemon_info.ts view source

(runtime: Pick<EnvDeps, "env_get">, name: string): string | null import {get_app_dir} from '@fuzdev/fuz_app/testing/daemon_info.js';

Get an app's home directory path (~/.{name}).

runtime

runtime with env_get capability

type Pick<EnvDeps, "env_get">

name

application name (e.g., "tx", "zzz")

type string

returns

string | null

path to the app directory, or null if $HOME is not set

get_audit_metadata_validation_failures
#

auth/audit_log_queries.ts view source

(): number import {get_audit_metadata_validation_failures} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Number of audit metadata validation failures observed since process start.

returns

number

get_audit_unknown_event_type_failures
#

auth/audit_log_queries.ts view source

(): number import {get_audit_unknown_event_type_failures} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Number of audit unknown-event-type failures observed since process start.

returns

number

get_audit_write_failures
#

auth/audit_emitter.ts view source

(): number import {get_audit_write_failures} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Number of audit rows an emitter failed to write since process start.

returns

number

get_client_ip
#

http/client_ip.ts view source

(c: Context<any, any, {}>): string import {get_client_ip} from '@fuzdev/fuz_app/http/client_ip.js';

Client IP resolved by the trusted-proxy middleware, or 'unknown' if unset.

c

type Context<any, any, {}>

returns

string

get_daemon_info_path
#

testing/daemon_info.ts view source

(runtime: Pick<EnvDeps, "env_get">, name: string): string | null import {get_daemon_info_path} from '@fuzdev/fuz_app/testing/daemon_info.js';

Get the daemon info file path (~/.{name}/run/daemon.json).

runtime

runtime with env_get capability

type Pick<EnvDeps, "env_get">

name

application name

type string

returns

string | null

path to daemon.json, or null if $HOME is not set

get_datetime_now
#

primitive_schemas.ts view source

(): string & $brand<"Datetime"> import {get_datetime_now} from '@fuzdev/fuz_app/primitive_schemas.js';

Returns an ISO 8601 datetime string for the current time.

returns

Datetime

get_executor_phases
#

actions/action_codegen.ts view source

(spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, executor: "frontend" | "backend"): ("send_request" | ... 7 more ... | "execute")[] import {get_executor_phases} from '@fuzdev/fuz_app/actions/action_codegen.js';

Phases an executor can handle for the given spec — kind + initiator → set of phases.

spec

executor

type "frontend" | "backend"

returns

ActionEventPhase[]

get_handler_return_type
#

actions/action_codegen.ts view source

(spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, phase: "send_request" | ... 7 more ... | "execute", imports: ImportBuilder, collections_path?: string): string import {get_handler_return_type} from '@fuzdev/fuz_app/actions/action_codegen.js';

Gets the handler return type for a specific phase and spec. Adds an ActionOutputs import (from collections_path) when the phase carries an output (request_response receive_request, local_call execute).

spec

phase

imports

collections_path?

type string
optional default DEFAULT_COLLECTIONS_PATH

returns

string

get_initial_phase
#

get_public_account_id
#

auth/request_context.ts view source

(c: Context<any, any, {}>): string | null import {get_public_account_id} from '@fuzdev/fuz_app/auth/request_context.js';

The account id a public route — one that answers anonymous callers and reads the credential (reads_credential) — may act on: null for the anonymous caller and for a scope-narrowed API token. Rule 3 in its anonymous-admitting disposition — the downgrade the Rust AuthGate::public_request_context applies.

A narrowed token reaches no non-RPC surface that reads account data (token_scope_admits_non_rpc). On a route that refuses anonymous callers that is a 403 (auth.required_scope); on one that answers them, a 403 would enforce nothing — the holder drops the Authorization header and reads the anonymous answer anyway — so the narrowed token *is* the anonymous caller there. Read the credential through this on any reads_credential route instead of get_resolved_auth directly, so the downgrade is one decision rather than an && hand-rolled per handler.

The RPC endpoint is the exception: its dispatcher gates each method against the scope, so it reads the full credential.

c

the request context, after the auth middleware chain

type Context<any, any, {}>

returns

string | null

the account id, or null for an anonymous caller or a narrowed token

get_request_context
#

auth/request_context.ts view source

(c: Context<any, any, {}>): RequestContext | null import {get_request_context} from '@fuzdev/fuz_app/auth/request_context.js';

Get the request context from a Hono context, or null if unauthenticated.

c

the Hono context

type Context<any, any, {}>

returns

RequestContext | null

the request context, or null

get_resolved_auth
#

auth/resolved_auth.ts view source

(c: Context<any, any, {}>): ResolvedAuth | null import {get_resolved_auth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

The credential the auth middleware resolved for this request.

c

the request context, after the auth middleware chain

type Context<any, any, {}>

returns

ResolvedAuth | null

the resolved credential, or null for an unauthenticated request

get_route_error_schema
#

testing/assertions.ts view source

(lookup: Map<string, Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>>, route: AppSurfaceRoute, status: number): ZodType<...> | undefined import {get_route_error_schema} from '@fuzdev/fuz_app/testing/assertions.js';

Look up the merged error schema for a route+status from a pre-built schema lookup.

lookup

map from "METHOD /path" to merged error schemas

type Map<string, Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>>

route

the surface route to look up

status

HTTP status code

type number

returns

ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | undefined

the Zod schema for that route+status, or undefined when no error schema is declared for the status code

get_route_input
#

http/route_spec.ts view source

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> import {get_route_input} from '@fuzdev/fuz_app/http/route_spec.js';

Get validated input from the Hono context.

Call after the input validation middleware has run. Pass the route's input Zod schema to infer the typed shape directly:

const input = get_route_input(c, my_route_spec.input);

Or pass an explicit type argument when the schema isn't in scope:

const input = get_route_input<MyInput>(c);

c

type Context<any, any, {}>

schema

type S

overloads

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S>

Get validated input from the Hono context.

Call after the input validation middleware has run. Pass the route's input Zod schema to infer the typed shape directly:

const input = get_route_input(c, my_route_spec.input);

Or pass an explicit type argument when the schema isn't in scope:

const input = get_route_input<MyInput>(c);
c Context<any, any, {}>
schema S
returns output<S>
<T = unknown>(c: Context<any, any, {}>): T
c Context<any, any, {}>
returns T

returns

output<S>

generics

get_route_input<S extends z.ZodType>
S
constraint z.ZodType

get_route_params
#

http/route_spec.ts view source

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> import {get_route_params} from '@fuzdev/fuz_app/http/route_spec.js';

Get validated URL path params from the Hono context.

Call after the params validation middleware has run. Pass the route's params schema to infer the typed shape, or supply an explicit type argument. See get_route_input for the two call shapes.

c

type Context<any, any, {}>

schema

type S

overloads

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S>

Get validated URL path params from the Hono context.

Call after the params validation middleware has run. Pass the route's params schema to infer the typed shape, or supply an explicit type argument. See get_route_input for the two call shapes.

c Context<any, any, {}>
schema S
returns output<S>
<T = unknown>(c: Context<any, any, {}>): T
c Context<any, any, {}>
returns T

returns

output<S>

generics

get_route_params<S extends z.ZodType>
S
constraint z.ZodType

get_route_query
#

http/route_spec.ts view source

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> import {get_route_query} from '@fuzdev/fuz_app/http/route_spec.js';

Get validated URL query params from the Hono context.

Call after the query validation middleware has run. Pass the route's query schema to infer the typed shape, or supply an explicit type argument. See get_route_input for the two call shapes.

c

type Context<any, any, {}>

schema

type S

overloads

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S>

Get validated URL query params from the Hono context.

Call after the query validation middleware has run. Pass the route's query schema to infer the typed shape, or supply an explicit type argument. See get_route_input for the two call shapes.

c Context<any, any, {}>
schema S
returns output<S>
<T = unknown>(c: Context<any, any, {}>): T
c Context<any, any, {}>
returns T

returns

output<S>

generics

get_route_query<S extends z.ZodType>
S
constraint z.ZodType

get_session_cookie
#

get_spine_suite_backdoors
#

testing/cross_backend/spine_suites.ts view source

internal

(name: "cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | "body_size" | ... 32 more ... | "ws_round_trip"): readonly ("_testing_reset" | ... 8 more ... | "_testing_shorten_credential_expiry")[] import {get_spine_suite_backdoors} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

The backdoors every case of name drives beyond SPINE_SUITES_REQUIRED_BACKDOORS — a backend not declaring one skips the suite.

name

the suite

returns

readonly TestingBackdoorMethod[]

its SuiteEntry.backdoors, empty for a suite needing none

GetTransportOptions
#

actions/transports.ts view source

GetTransportOptions import type {GetTransportOptions} from '@fuzdev/fuz_app/actions/transports.js';

Options for Transports.get_transport.

queue?

The call is a request sent with queue: true — a transport that queues is chosen even while not ready, so the request waits for it.

type boolean

grant_key
#

ui/admin_accounts_state.svelte.ts view source

(account_id: string & $brand<"Uuid">, role: string, to_actor_id?: (string & $brand<"Uuid">) | null | undefined): string import {grant_key} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

Compose the grant keyed-slot key for an offer. Account-grain offers key on ${account_id}:${role}; actor-targeted offers add the actor suffix so the two variants can be in flight simultaneously without colliding on per-row spinners.

account_id

type Uuid

role

type string

to_actor_id?

type Uuid | null
optional

returns

string

GRANT_PATH_ADMIN
#

auth/grant_path_schema.ts view source

"admin" import {GRANT_PATH_ADMIN} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Admin-mediated grant — role_grant_offer_create plus admin-direct flows.

GRANT_PATH_BOOTSTRAP
#

auth/grant_path_schema.ts view source

"bootstrap" import {GRANT_PATH_BOOTSTRAP} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Bootstrap grant — one-shot flow during the keep's first-run bootstrap.

GRANT_PATH_NAME_REGEX
#

GRANT_PATH_SELF_SERVICE
#

auth/grant_path_schema.ts view source

"self_service" import {GRANT_PATH_SELF_SERVICE} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Self-service grant — caller toggles their own role_grant via self_service_role_set.

GRANT_PATH_SYSTEM
#

auth/grant_path_schema.ts view source

"system" import {GRANT_PATH_SYSTEM} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

System-mediated grant — signup hooks, automation, internal service flows.

GRANT_SCOPE_PAIRED_MESSAGE
#

auth/role_grant_offer_action_specs.ts view source

"'scope_kind' and 'scope_id' must be given together (both for a scoped grant, neither for a global one)" import {GRANT_SCOPE_PAIRED_MESSAGE} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

The message a half-scoped grant input fails with — the grant inputs' refine, and the UI's pre-RPC guard.

GrantJson
#

auth/cell_grant_action_specs.ts view source

value + type

{ id: string & $brand<"Uuid">; cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; actor_id: (string & $brand<"Uuid">) | null; role: string | null; scope_id: (string & $brand<...>) | null; granted_by: (string & $brand<...>) | null; created_at: string; } import {GrantJson} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Wire-format for a cell_grant row. Mirrors CellJson's shape — ISO-string created_at, branded UUIDs, principal columns surfaced as-is. Caller inspects actor_id xor role to render the right principal label.

id

type string & $brand<"Uuid">

cell_id

type string & $brand<"Uuid">

level

type "viewer" | "editor"

actor_id

type (string & $brand<"Uuid">) | null

role

type string | null

scope_id

type (string & $brand<"Uuid">) | null

granted_by

type (string & $brand<"Uuid">) | null

created_at

type string

GrantPathMeta
#

auth/grant_path_schema.ts view source

GrantPathMeta import type {GrantPathMeta} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Per-grant-path metadata. description is admin-UI-facing copy (mirrors RoleSpec.description and ScopeKindMeta.description). Open shape so v2 can extend without a breaking change.

description?

type string

GrantPathName
#

GrantPathSchemaResult
#

auth/grant_path_schema.ts view source

GrantPathSchemaResult import type {GrantPathSchemaResult} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

The result of create_grant_path_schema — a Zod schema and metadata map.

GrantPath

Zod schema that validates grant-path name strings against the registered set (builtins + consumer-declared). Use at I/O boundaries (admin UIs, codegen) and as the construction-time check inside create_role_schema for every RoleSpec.grant_paths entry.

type ZodType<string, unknown, $ZodTypeInternals<string, unknown>>

grant_paths

Map of every registered grant-path to its metadata. Keyed by name. Read at startup by admin / codegen surfaces.

type ReadonlyMap<string, GrantPathMeta>

HandlerForSpec
#

actions/action_types.ts view source

HandlerForSpec<TSpec> import type {HandlerForSpec} from '@fuzdev/fuz_app/actions/action_types.js';

Conditional handler shape for rpc_action — picks the narrowest ctx.auth type the dispatcher's runtime guarantee allows:

  • auth.actor === 'required' → ActorActionHandler (ctx.auth: RequestActorContext).
  • auth.account === 'required' && auth.actor === 'none' → AuthActionHandler (ctx.auth: RequestContext).
  • else (public, optional axes) → ActionHandler (ctx.auth: RequestContext | null).

The bracketed form [T] extends ['required'] defeats distributive conditionals so a degraded AuthAxisState union (when the spec was typed without preserving its literal) falls through to the loosest tier instead of collapsing to the narrowest.

generics

HandlerForSpec<TSpec extends RequestResponseActionSpec>
TSpec

has_any_scoped_role
#

auth/request_context.ts view source

(ctx: RequestContext | null, roles: readonly string[], scope_id: string | null, now?: Date): boolean import {has_any_scoped_role} from '@fuzdev/fuz_app/auth/request_context.js';

Whether the request context holds an active role_grant for any role in roles at scope_id. Empty roles short-circuits to false — documents intent at the call site ("zero roles trivially admit no-one"). Same scope and null-tolerance semantics as has_scoped_role.

ctx

the request context, or null for unauthenticated callers

type RequestContext | null

roles

the roles that would admit the caller (any-of)

type readonly string[]

scope_id

the scope to check (null for global)

type string | null

now?

current time (defaults to new Date(), pass for testability)

type Date
optional default new Date()

returns

boolean

true iff the actor holds an active role_grant for any role in roles at the requested scope

has_global_role
#

auth/account_schema.ts view source

(role_grants: readonly { role: string; scope_id: string | null; revoked_at?: string | null | undefined; expires_at: string | null; }[], role: string, now?: Date): boolean import {has_global_role} from '@fuzdev/fuz_app/auth/account_schema.js';

Whether role_grants holds a live global grant of role — scope_id is null and the grant passes is_role_grant_active.

This is the question a role gate asks. A scoped grant confers its role on one resource only, and a scoped grant of a builtin role (admin, keeper) confers nothing, so matching on the role name alone would read a grant for one resource as authority everywhere. Server-side the same rule is has_scoped_role(ctx, role, null) over a request context and query_account_has_global_role in SQL; this is the form for a bare list of grants — AuthState.role_grants, an admin listing row.

The scope match is scope_id === null, so a grant decoded without the field is not read as global. Client-side the answer shapes the UI and nothing else: the server re-checks every request.

role_grants

the grants to search, e.g. AuthState.role_grants

type readonly { role: string; scope_id: string | null; revoked_at?: string | null | undefined; expires_at: string | null; }[]

role

the role to check

type string

now?

current time (defaults to new Date(), pass for testability)

type Date
optional default new Date()

returns

boolean

true iff a live grant of role with no scope is present

has_scoped_role
#

auth/request_context.ts view source

(ctx: RequestContext | null, role: string, scope_id: string | null, now?: Date): boolean import {has_scoped_role} from '@fuzdev/fuz_app/auth/request_context.js';

Whether the request context holds an active role_grant for role at scope_id.

Walks the in-memory ctx.role_grants snapshot loaded once per request by the route-spec / RPC dispatcher's authorization phase (when the route declares acting?: ActingActor or has role_grant-requiring auth); zero DB roundtrip per check. The "freshness" framing of a SQL re-query is illusory because the race window is between predicate and the actual mutation, not predicate and authorization load. Closing that race needs a transactional re-check inside the UPDATE/INSERT, which neither style provides.

Null-tolerant — null ctx (unauthenticated) and account-grain contexts (actor: null, empty role_grants) both return false, which lets the helper drop into public ({account: 'none', actor: 'none'}) and account-grain ({account: 'required', actor: 'none'}) handlers without a manual narrow. See cell_authorize for the resource-side analog.

has_scoped_role(ctx, ROLE_ADMIN, null) is the admin check everywhere admin confers authority (builtin roles are global-only, see is_builtin_role).

scope_id semantics: in-memory role_grant.scope_id is string | null, so JS === matches the SQL IS NOT DISTINCT FROM semantics exactly:

  • scope_id === null matches global role_grants (scope_id IS NULL).
  • scope_id === '<uuid>' matches role_grants bound to that exact scope.

ctx

the request context, or null for unauthenticated callers

type RequestContext | null

role

the role to check

type string

scope_id

the scope to check (null for global)

type string | null

now?

current time (defaults to new Date(), pass for testability and hot-path efficiency)

type Date
optional default new Date()

returns

boolean

true iff the actor holds an active role_grant for the role at the requested scope

has_test_context_preset
#

hono_context.ts view source

(c: Context<any, any, {}>): boolean import {has_test_context_preset} from '@fuzdev/fuz_app/hono_context.js';

Whether a test harness pre-baked this request's context — the key holds TEST_CONTEXT_PRESET itself. The skip every transport makes of the live authorization phase and the admission re-reads.

c

the Hono context

type Context<any, any, {}>

returns

boolean

true only when the key holds the TEST_CONTEXT_PRESET symbol

hash_api_token
#

auth/api_token.ts view source

(token: string): string import {hash_api_token} from '@fuzdev/fuz_app/auth/api_token.js';

Hash an API token for storage using blake3.

token

the raw API token

type string

returns

string

hex-encoded blake3 hash

hash_password
#

auth/password_argon2.ts view source

(password: string): Promise<string> import {hash_password} from '@fuzdev/fuz_app/auth/password_argon2.js';

Hash a password using Argon2id.

password

the plaintext password to hash

type string

returns

Promise<string>

the Argon2id hash string

hash_session_token
#

auth/session_queries.ts view source

(token: string): string & $brand<"SessionId"> import {hash_session_token} from '@fuzdev/fuz_app/auth/session_queries.js';

Hash a session token to its storage key using blake3.

The sole minting point for SessionId — hash_blake3 returns bare hex, so the brand is applied here, where the value gains its meaning.

token

the raw session token

type string

returns

SessionId

hex-encoded blake3 hash

headers_to_record
#

testing/rpc_helpers.ts view source

(headers: Headers): Record<string, string> import {headers_to_record} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Snapshot a Headers object into a plain Record with lowercased keys (Headers iteration normalizes the casing already). Multiple values for one header collapse to the comma-joined form Headers exposes; Set-Cookie is the lone platform exception these call sites never assert on. Lets a response's headers travel back through RpcCallResult for header-level assertions (no-fingerprint, expect.headers).

headers

type Headers

returns

Record<string, string>

HEALTH_PATH
#

HealthResponse
#

http/health_schema.ts view source

value + type

{ status: "ok"; } import {HealthResponse} from '@fuzdev/fuz_app/http/health_schema.js';

The liveness route's response body.

status

type "ok"

heartbeat_action
#

actions/heartbeat.ts view source

Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | {...

type Action<ActionSpecUnion>

import {heartbeat_action} from '@fuzdev/fuz_app/actions/heartbeat.js';

Protocol-action tuple — spread into the server's actions array for dispatch (or via protocol_actions from actions/protocol.ts) so the dispatcher resolves the heartbeat handler. The frontend-side spread happens via protocol_action_specs — the client doesn't run the echo handler, but the spec must be in ActionRegistry so create_rpc_client types app.api.heartbeat() against the shared spec.

heartbeat_action_spec
#

actions/heartbeat.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodVoid; output: ZodObject<{}, $strict>; async: true; description: string; } import {heartbeat_action_spec} from '@fuzdev/fuz_app/actions/heartbeat.js';

ActionSpec for the shared heartbeat. Account-required, actor-none — upgrade-time auth has already admitted the socket; heartbeats don't need role gating or actor resolution. side_effects: false keeps it orthogonal to state changes.

heartbeat_handler
#

actions/heartbeat.ts view source

(): Record<string, never> import {heartbeat_handler} from '@fuzdev/fuz_app/actions/heartbeat.js';

Handler — nullary echo. Stateless, suitable for high-frequency pings.

returns

Record<string, never>

HEARTBEAT_RECEIVE_TIMEOUT_MIN_FACTOR
#

actions/socket.svelte.ts view source

2 import {HEARTBEAT_RECEIVE_TIMEOUT_MIN_FACTOR} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Minimum ratio of the heartbeat receive timeout to the heartbeat interval.

A heartbeat goes out once a direction has been idle for interval, and the check runs every interval / 2, so on an idle socket it can leave up to 1.5 × interval after the last receive — and its response needs a round trip on top. A receive timeout under 2 × interval can close a healthy idle socket before its heartbeat is answered, so it's clamped up to this.

HeldFactPutOptions
#

db/fact_store.ts view source

HeldFactPutOptions import type {HeldFactPutOptions} from '@fuzdev/fuz_app/db/fact_store.js';

Options for HeldFactStore.put / put_stream: the @fuzdev/fuz_util FactPutOptions plus the required holder.

holder is the actor that brought these bytes, recorded in fact_holder whether or not the fact already existed. null is a system put (seeds, server-side imports) that records no holder — a cell write can newly reference such a fact only as a global admin or as an actor that puts the same bytes itself (refs a cell already carries stay admitted through update and clone). Required, so a put on behalf of a caller can't silently skip the possession record. Twin of the Rust PutOptions::holder.

inheritance

extends: FactPutOptions

holder

type Uuid | null

HeldFactStore
#

db/fact_store.ts view source

HeldFactStore import type {HeldFactStore} from '@fuzdev/fuz_app/db/fact_store.js';

A fact store whose puts record possession: the @fuzdev/fuz_util FactStore reads, plus put / put_stream taking HeldFactPutOptions with its required holder.

The holder is required — a store typed by the generic FactStore cannot record possession, so cell ref admission (auth/cell_ref_admission.ts) could never admit a ref to a fact put through it. AppDeps.fact_store is typed by this interface; PgFactStore implements it.

inheritance

extends: Omit<FactStore, 'put' | 'put_stream'>

put

Idempotent like FactStore.put; records options.holder (when non-null) on a fresh insert and a dedup hit alike.

type (bytes: Uint8Array<ArrayBufferLike>, options: HeldFactPutOptions): Promise<FactHash>

bytes

type Uint8Array<ArrayBufferLike>

options

returns Promise<FactHash>

put_stream

Idempotent like FactStore.put_stream; records options.holder (when non-null) on a fresh insert and a dedup hit alike.

type (stream: ReadableStream<Uint8Array<ArrayBufferLike>>, max_bytes: number, options: HeldFactPutOptions): Promise<...>

stream

type ReadableStream<Uint8Array<ArrayBufferLike>>

max_bytes

type number

options

returns Promise<PutStreamOutcome>

HostWildcard
#

http/origin_wildcard.ts view source

HostWildcard

type { kind: "partial"; } | { kind: "whole"; labels: number; }

import type {HostWildcard} from '@fuzdev/fuz_app/http/origin_wildcard.js';

How much of a pattern's host its * labels leave open.

http_status_to_jsonrpc_error_code
#

http/jsonrpc_errors.ts view source

(status: number): -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">) import {http_status_to_jsonrpc_error_code} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Map an HTTP status code to a JSON-RPC error code.

Returns internal_error (-32603) for unrecognized status codes.

status

type number

returns

JsonrpcErrorCode

HTTP_STATUS_TO_JSONRPC_ERROR_CODE
#

http/jsonrpc_errors.ts view source

Readonly<Record<number, -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)>>

type Readonly<Record<number, JsonrpcErrorCode>>

import {HTTP_STATUS_TO_JSONRPC_ERROR_CODE} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Maps HTTP status codes to JSON-RPC error codes (reverse mapping), over the base set only — built once at module load, before any consumer code is registered, and frozen.

When multiple error codes map to the same HTTP status (e.g. parse_error and invalid_request both map to 400), the last one wins. Use for best-effort HTTP → JSON-RPC translation.

http_transport
#

testing/rpc_helpers.ts view source

(app: { request: (input: string, init: RequestInit) => Response | Promise<Response>; }): RpcTestTransport import {http_transport} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Adapt a Hono-style app into an RpcTestTransport.

app

type { request: (input: string, init: RequestInit) => Response | Promise<Response>; }

returns

RpcTestTransport

IdentityParityCrossTestOptions
#

testing/cross_backend/identity_parity.ts view source

IdentityParityCrossTestOptions import type {IdentityParityCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/identity_parity.js';

Options for the identity-primitive parity suite.

setup_test

Per-test fixture producer (in-process or cross-process).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

login_path?

REST login route path. Default /api/account/login (the spine convention).

type string

readonly

signup_path?

REST signup route path. Default /api/account/signup (the spine convention).

type string

readonly

bootstrap_path?

REST bootstrap route path. Default /api/account/bootstrap (the spine convention); null skips the bootstrap case, for a surface that mounts no bootstrap route.

type string | null

readonly

password_path?

REST password-change route path, for the malformed-body case. Default /api/account/password (the spine convention); null skips that case.

type string | null

readonly

ImportBuilder
#

actions/action_codegen.ts view source

import {ImportBuilder} from '@fuzdev/fuz_app/actions/action_codegen.js';

Manages imports for generated code, building them on demand. Automatically optimizes type-only imports to use import type syntax.

Why this matters:

  • import type statements are completely removed during compilation
  • Mixed imports like import { type A, B } cannot be safely removed
  • This ensures optimal tree-shaking and smaller bundle sizes

examples

const imports = new ImportBuilder(); imports.add_types('./types.ts', 'Foo', 'Bar'); imports.add('./utils.ts', 'helper'); imports.add_type('./utils.ts', 'HelperOptions'); imports.add('./action_specs.ts', '* as specs'); // Generates: // import type {Foo, Bar} from './types.ts'; // import {helper, type HelperOptions} from './utils.ts'; // import * as specs from './action_specs.ts';

imports

type Map<string, Map<string, ImportItem>>

add

Add a value import. Accepts * as ns strings as namespace imports.

type (from: string, what: string): this

from

type string

what

type string
returns this

this for chaining

mutates

  • this — inserts into the internal `imports` map

add_type

Add a type-only import.

type (from: string, what: string): this

from

type string

what

type string
returns this

this for chaining

mutates

  • this — inserts into the internal `imports` map (downgrade to type is suppressed if already registered as a value)

add_many

type (from: string, ...items: string[]): this

from

type string

items

type string[]
returns this

add_types

type (from: string, ...items: string[]): this

from

type string

items

type string[]
returns this

build

Generate the import statements. When every import from a module is a type, emits import type {…} so the whole statement disappears at compile time.

type (): string

returns string

has_imports

type (): boolean

returns boolean

preview

Build the same statement list as build without joining — for inspection in tests.

type (): string[]

returns string[]

clear

Clear all imports.

type (): this

returns this

this for chaining

mutates

  • this — empties the internal `imports` map

import_count

type number

getter

IN_FLIGHT_RETRY_AFTER_SECONDS
#

rate_limiter.ts view source

1 import {IN_FLIGHT_RETRY_AFTER_SECONDS} from '@fuzdev/fuz_app/rate_limiter.js';

retry_after reported while a key is at its cap only because of attempts still in flight. Those settle within a request's lifetime, so the earliest moment worth retrying is the next second, not the oldest recorded attempt's expiry. Mirrors the Rust spine's IN_FLIGHT_RETRY_AFTER_SECONDS.

in_process_capabilities
#

testing/cross_backend/capabilities.ts view source

BackendCapabilities import {in_process_capabilities} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

Capability declarations for the in-process Hono transport. Nearly every flag is true because in-process testing exercises the full backend with no missing optional behaviors. The exceptions are the flags whose cases are cross-process-only: peer_request (describe_peer_ping_ws_tests needs a real bound socket with an on_request responder via create_ws_transport, so the in-process driver never runs it — the transport itself supports server-initiated requests), ws_handshake_pipelining (raw bytes on a real socket), ws_account_actions (a real socket's reply-then-close ordering), cell_gated_create, and ws_role_gated. Cross-process consumers declare each flag explicitly per backend.

inject_backend_handle
#

testing/cross_backend/provided_context.ts view source

(): ReconstructedBootstrappedBackendHandle import {inject_backend_handle} from '@fuzdev/fuz_app/testing/cross_backend/provided_context.js';

The backend the project's globalSetup spawned, rebuilt from the provided backend_handle. Call at a test file's top level.

returns

ReconstructedBootstrappedBackendHandle

inject_parity_handles
#

testing/cross_backend/provided_context.ts view source

(): InjectedParityHandles import {inject_parity_handles} from '@fuzdev/fuz_app/testing/cross_backend/provided_context.js';

The two backends a dual-spawn project provides as parity_handle_a / parity_handle_b, rebuilt. Call at a test file's top level.

returns

InjectedParityHandles

InjectedParityHandles
#

InProcessSetupOptions
#

testing/cross_backend/in_process_setup.ts view source

InProcessSetupOptions import type {InProcessSetupOptions} from '@fuzdev/fuz_app/testing/cross_backend/in_process_setup.js';

Options for default_in_process_setup. Extends CreateTestAppOptions with the same extra_accounts slot the cross-process variant accepts — both transports observe the same bootstrap-time secondary set so suite bodies can read fixture.extra_accounts[username] uniformly.

inheritance

extra_accounts?

Additional accounts seeded at this transport's bootstrap-equivalent step. See ExtraAccountSpec for the cradle-only-bypass rationale. Most suites pass undefined / []; the ROLE_KEEPER probe (in describe_standard_admin_integration_tests) is the primary user.

type readonly ExtraAccountSpec[]

readonly

extra_actors?

Additional actor names to seed on the bootstrapped keeper — exposed on fixture.extra_actors. See CrossProcessSetupOptions.extra_actors / TestFixture.extra_actors. Seeded directly against the live backend DB (in-process has no wire hop).

type readonly string[]

readonly

reject_unknown_audit_metadata_keys?

Refuse undeclared audit metadata keys in the fixture's audit reads — see CrossProcessSetupOptions.reject_unknown_audit_metadata_keys.

type boolean

readonly

input_schema_declares_acting
#

http/auth_shape.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {input_schema_declares_acting} from '@fuzdev/fuz_app/http/auth_shape.js';

Whether a schema declares the canonical acting?: ActingActor field. Reference-equality on the exported ActingActor schema — consumer schemas with unrelated acting fields don't trip this check.

Peels through Zod wrappers (optional, nullable, default, transform, pipe, prefault) via zod_unwrap_to_object so a spec authored as z.optional(z.strictObject({acting: ActingActor})) or z.strictObject({acting: ActingActor}).default({}) still trips the predicate.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

InputTestCase
#

testing/adversarial_input_cases.ts view source

InputTestCase import type {InputTestCase} from '@fuzdev/fuz_app/testing/adversarial_input_cases.js';

One adversarial body for an input schema, and the error code it owes.

label

type string

body

type unknown

field?

The one top-level field the case breaks; absent on a whole-body case (a non-object body, an extra unknown key). An RPC refusal of the case owes the field's declared invalid_reason (expected_invalid_reason).

type string

expected_error

type "invalid_request_body" | "invalid_json_body"

InputTestCaseOptions
#

testing/adversarial_input_cases.ts view source

InputTestCaseOptions import type {InputTestCaseOptions} from '@fuzdev/fuz_app/testing/adversarial_input_cases.js';

base?

A valid body to build the cases from, in place of the generated one — for an input whose valid shape the generator can't reach, or one whose generated body the handler can't run. Each case is this body with one field broken, so it must pass the input schema itself: one that doesn't throws.

type Record<string, unknown>

label?

Names the input in error messages — the RPC method or route it belongs to.

type string

install_audit_drift_guard
#

testing/audit_drift_guard.ts view source

(): void import {install_audit_drift_guard} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Register per-test beforeEach + afterEach hooks that catch any audit emission with a metadata shape that fails its audit_metadata_schemas entry, an event_type not present in the active AuditLogConfig, or a row the emitter failed to write.

The production validation in query_audit_log is fail-open — it bumps process-wide counters and proceeds, so a regression that emits an undeclared metadata field or a typo'd event-type lands a row that passes downstream queries but breaks forensics. The emitter's pool write is fail-open too: a refused INSERT is logged and counted, the request succeeds, and the row is gone. Tests that exercise audit emits should fail loudly when either happens.

Call at the top of every describe / describe_db block that fires audit writes through deps.audit.emit. Resets counters before each test and asserts zero on completion. A test that makes a write fail on purpose calls reset_audit_write_failures (auth/audit_emitter.ts) once it has asserted the count.

Pair with await_pending_effects: true (the default for create_test_app) so fire-and-forget audit writes have completed by the time the after-each check observes counter state.

returns

void

Invite
#

auth/invite_schema.ts view source

Invite import type {Invite} from '@fuzdev/fuz_app/auth/invite_schema.js';

Invite row from the database.

id

type Uuid

email

type string | null

username

type string | null

claimed_by

type Uuid | null

claimed_at

type string | null

created_at

type string

created_by

type Uuid | null

INVITE_COLUMNS
#

auth/invite_queries.ts view source

readonly ["id", "email", "username", "claimed_by", "claimed_at", "created_at", "created_by"] import {INVITE_COLUMNS} from '@fuzdev/fuz_app/auth/invite_queries.js';

The full invite column set, named explicitly so a row read fails loud on schema drift — invite rows ride the invite_create / invite_list RPC responses raw, so a SELECT * would silently carry a dropped or leftover column into the strict-validated wire shapes (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class; the Rust twin names columns at every invite site). Keep in sync with Invite and the migration chain's end state.

invite_create_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ email: ZodOptional<ZodNullable<ZodString>>; username: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: t... import {invite_create_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

invite_delete_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ invite_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string... import {invite_delete_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

INVITE_INDEXES
#

invite_list_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ acting: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>>; output: ZodObject<...>; async: true; description: string; r... import {invite_list_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds admin-side scraping of the invite table — bounded by table size, but every row carries email + username + creator/claimer identifiers worth defense-in-depth against an admin mutation oracle running scripted reads alongside invite_create.

INVITE_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS invite (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n email TEXT,\n username TEXT,\n claimed_by UUID REFERENCES account(id) ON DELETE SET NULL,\n claimed_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n created_by UUID REFERENCES actor(id) ON DELETE SET NU... import {INVITE_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

InviteCreateInput
#

auth/admin_action_specs.ts view source

value + type

{ email?: string | null | undefined; username?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {InviteCreateInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for invite_create. At least one of email / username must be provided.

email?

type string | null

username?

type string | null

acting?

type string & $brand<"Uuid">

InviteCreateOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; invite: { id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; }; } import {InviteCreateOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for invite_create.

ok

type true

invite

type InviteJson

InviteDeleteInput
#

auth/admin_action_specs.ts view source

value + type

{ invite_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {InviteDeleteInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for invite_delete.

invite_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

InviteDeleteOutput
#

InviteJson
#

auth/invite_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; } import {InviteJson} from '@fuzdev/fuz_app/auth/invite_schema.js';

Zod schema for client-safe invite data.

id

type string & $brand<"Uuid">

email

type string | null

username

type string | null

claimed_by

type (string & $brand<"Uuid">) | null

claimed_at

type string | null

created_at

type string

created_by

type (string & $brand<"Uuid">) | null

InviteListInput
#

auth/admin_action_specs.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; } import {InviteListInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for invite_list.

acting?

type string & $brand<"Uuid">

InviteListOutput
#

auth/admin_action_specs.ts view source

value + type

{ invites: { id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; created_by_username: string | null; claimed_by_username: string | null; }[]; } import {InviteListOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for invite_list. Uses the enriched row including creator/claimer usernames.

invites

type InviteWithUsernamesJson[]

InviteWithUsernamesJson
#

auth/invite_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; created_by_username: string | null; claimed_by_username: string | null; } import {InviteWithUsernamesJson} from '@fuzdev/fuz_app/auth/invite_schema.js';

Zod schema for invite data with resolved creator/claimer usernames.

id

type string & $brand<"Uuid">

email

type string | null

username

type string | null

claimed_by

type (string & $brand<"Uuid">) | null

claimed_at

type string | null

created_at

type string

created_by

type (string & $brand<"Uuid">) | null

created_by_username

type string | null

claimed_by_username

type string | null

IP_LITERAL_CHARS
#

http/ip_canonical.ts view source

RegExp import {IP_LITERAL_CHARS} from '@fuzdev/fuz_app/http/ip_canonical.js';

Allowed character set for a bare IP literal.

Covers the union of IPv4 (digits + .), IPv6 (hex digits + :), and IPv4-mapped IPv6 forms (::ffff:127.0.0.1). Anything outside this set — brackets, whitespace, control bytes, letters g–z — disqualifies the input from parsing.

Same regex http/proxy.ts's validate_ip_strict uses; exported here so both modules can share one source of truth.

ipv6_bigint_to_canonical
#

http/ip_canonical.ts view source

(bits: bigint): string import {ipv6_bigint_to_canonical} from '@fuzdev/fuz_app/http/ip_canonical.js';

Convert a 128-bit IPv6 binary value into its RFC 5952 canonical string form.

  • IPv4-mapped (groups[0..5] = 0, groups[5] = 0xffff) emits the ::ffff:a.b.c.d dotted form per RFC 5952 §5.
  • Otherwise: lowercase hex with no leading zeros per group (§4.1), the longest run of consecutive zero groups (≥ 2 groups) is replaced with :: (§4.2.1, §4.2.3), and on equal-length runs the first one wins (§4.2.3). Single-zero groups stay as 0 (§4.2.2).

Pure helper exported for the test suite to exercise the canonicalization invariants directly without a full parse_ipv6_bits round-trip.

bits

the 128-bit IPv6 value as bigint. Must satisfy 0n <= bits < 2n ** 128n; throws RangeError otherwise. Silent truncation would mask caller bugs since the bit-extraction loop only consumes the low 128 bits.

type bigint

returns

string

throws

  • RangeError - when `bits` is negative or exceeds 128 bits

is_action_complete
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): boolean import {is_action_complete} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_action_spec
#

actions/action_spec.ts view source

(value: unknown): value is { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<...>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; } import {is_action_spec} from '@fuzdev/fuz_app/actions/action_spec.js';

Structural type guard for any ActionSpecUnion variant — checks kind is one of the three known values.

value

type unknown

returns

boolean

is_api_token_shaped
#

auth/api_token.ts view source

(raw: string): boolean import {is_api_token_shaped} from '@fuzdev/fuz_app/auth/api_token.js';

Whether raw has the shape of a raw API token — the bearer path's precheck, so a value that cannot be a token costs no database work. A true says nothing about whether the token exists. Twin of the Rust spine's is_api_token_shaped.

raw

the value from the Authorization: Bearer header

type string

returns

boolean

true when raw matches API_TOKEN_REGEX

is_browser_context
#

http/origin.ts view source

(c: Context<any, any, {}>): boolean import {is_browser_context} from '@fuzdev/fuz_app/http/origin.js';

True when the request looks like it originated from a browser context — it carries an Origin or Referer header. Browsers attach these automatically on the state-changing surface; loopback / CLI clients don't. Uses !== undefined so an empty-string header still counts as browser context. Checks Referer too (not just Origin) because some browser requests send only Referer. Twin of the Rust spine's bearer_auth::is_browser_context.

The bearer (auth/bearer_auth.ts) and daemon-token (auth/daemon_token_middleware.ts) middleware both silently discard their credential when this is true: a stolen bearer token can't be replayed from a browser, and the loopback daemon token never legitimately carries an Origin. Distinct from verify_request_source — that gates browser requests against an allowlist; this only detects browser context.

c

type Context<any, any, {}>

returns

boolean

is_builtin_role
#

auth/role_schema.ts view source

(name: string): name is "admin" | "keeper" import {is_builtin_role} from '@fuzdev/fuz_app/auth/role_schema.js';

Whether name is a builtin role (keeper / admin).

Builtin roles are global-only: no grant path binds one to a scope (role_grant_assign, role_grant_offer_create, and role_grant_offer_accept refuse it), and every gate that reads one checks the global grant, so a scoped builtin row confers nothing.

name

the role name to check

type string

returns

boolean

true for keeper or admin

is_cors_simple_content_type
#

http/simple_request.ts view source

(content_type: string | undefined): boolean import {is_cors_simple_content_type} from '@fuzdev/fuz_app/http/simple_request.js';

True when a browser could have sent a POST with this Content-Type cross-origin without a preflight: none, or a CORS-safelisted one.

Matches more than browsers do (a value they'd refuse to call safelisted — one with odd bytes, say — counts as safelisted here), so it only ever refuses too much, never too little.

content_type

the request's Content-Type header, if any

type string | undefined

returns

boolean

is_createdb_already_exists
#

testing/cross_backend/postgres_database.ts view source

(stderr: string, database: string): boolean import {is_createdb_already_exists} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

Whether createdb's stderr is its refusal of a database that already exists — Postgres's database "<name>" already exists, which createdb prints after database creation failed: ERROR:.

stderr

what createdb wrote to stderr

type string

database

the database it was asked to create

type string

returns

boolean

is_credential_gated_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_credential_gated_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route declares any credential-type gate (auth.credential_types?.length).

auth

returns

boolean

is_daemon_running
#

testing/daemon_info.ts view source

(runtime: CommandDeps, pid: number): Promise<boolean> import {is_daemon_running} from '@fuzdev/fuz_app/testing/daemon_info.js';

Check if a process is running by PID.

runtime

runtime with command execution capability

pid

process ID to check

type number

returns

Promise<boolean>

true if the process is running

is_db_checkout_error
#

db/db_checkout_error.ts view source

(err: unknown): boolean import {is_db_checkout_error} from '@fuzdev/fuz_app/db/db_checkout_error.js';

Whether err is a DbCheckoutError from any copy of fuz_app, recognized by its realm-global brand.

err

the thrown value

type unknown

returns

boolean

true for a failed pool checkout

is_empty_input_handler_guarded
#

schema_meta.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {is_empty_input_handler_guarded} from '@fuzdev/fuz_app/schema_meta.js';

Whether schema carries empty_input_handler_guarded: true in its .meta().

schema

an action's input schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

whether its handler refuses the {} the schema admits

is_enospc_error
#

db/fact_store_errors.ts view source

(err: unknown): boolean import {is_enospc_error} from '@fuzdev/fuz_app/db/fact_store_errors.js';

Whether a thrown value is a Node filesystem ENOSPC (no space left on device). Used by the streaming disk write to translate the raw FS error into a FactStorageFullError.

err

type unknown

returns

boolean

is_grant_scope_paired
#

auth/role_grant_offer_action_specs.ts view source

(input: { scope_kind?: string | null | undefined; scope_id?: string | null | undefined; }): boolean import {is_grant_scope_paired} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Whether a grant input's scope_kind + scope_id are paired — both absent (a global grant) or both present (a scoped one), the role_grant_scope_kind_paired / role_grant_offer_scope_kind_paired CHECKs. The grant inputs refine on it so a half-scoped call is a -32602 rather than a constraint-violation 500.

input

the grant input's scope pair

type { scope_kind?: string | null | undefined; scope_id?: string | null | undefined; }

returns

boolean

true when both are set or both are absent

is_iso8601_seconds_live
#

timestamp.ts view source

(stamp: string, now_ms: number): boolean import {is_iso8601_seconds_live} from '@fuzdev/fuz_app/timestamp.js';

Whether the second a wire timestamp names has not yet ended.

The one in-memory expiry rule over a truncated stamp: stamp stands for [stamp, stamp + ISO8601_SECOND_MS), so this admits until that whole second is over and can never report an expiry the server's raw-column expires_at > NOW() gate would still accept. A stamp that does not parse is never live.

stamp

a second-precision wire timestamp

type string

now_ms

the current time in epoch milliseconds

type number

returns

boolean

true while now_ms lies before the end of stamp's second

is_json_content_type
#

http/simple_request.ts view source

(content_type: string | undefined): boolean import {is_json_content_type} from '@fuzdev/fuz_app/http/simple_request.js';

True when Content-Type is application/json (any parameters, such as charset, allowed).

content_type

the request's Content-Type header, if any

type string | undefined

returns

boolean

is_jsonrpc_error_code
#

http/jsonrpc.ts view source

(value: unknown): value is -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">) import {is_jsonrpc_error_code} from '@fuzdev/fuz_app/http/jsonrpc.js';

Whether value is a valid JSON-RPC error code — the codes JsonrpcErrorCode parses, but integer-only in the server range. A numeric code alone doesn't make an error a JSON-RPC one: a DOMException's code is a small positive number.

value

the candidate code

type unknown

returns

boolean

whether value is one of the standard codes or an integer in the server range

is_jsonrpc_error_response
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; } import {is_jsonrpc_error_response} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC error response (has error + id).

message

type unknown

returns

boolean

is_jsonrpc_notification
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } import {is_jsonrpc_notification} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC notification (has method, no id).

message

type unknown

returns

boolean

is_jsonrpc_object
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { jsonrpc: "2.0"; } import {is_jsonrpc_object} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC object (has jsonrpc: '2.0').

message

type unknown

returns

boolean

is_jsonrpc_request
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } import {is_jsonrpc_request} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC request (has method + id).

message

type unknown

returns

boolean

is_jsonrpc_request_id
#

http/jsonrpc_helpers.ts view source

(id: unknown): id is string | number import {is_jsonrpc_request_id} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a valid JSON-RPC request id (string or finite number).

id

type unknown

returns

boolean

is_jsonrpc_response
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } import {is_jsonrpc_response} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC success response (has result + id).

message

type unknown

returns

boolean

is_keeper_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_keeper_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route is the keeper bucket — a credential gate admitting daemon_token. Knows the literal directly, because keeper is a named authority level rather than one channel among many: it is the ceiling sessions and api tokens cannot reach.

Narrower than is_credential_gated_auth, which is true of every channel gate — including the session-only gates pointing the other way (the credential-minting actions, POST /logout / POST /password, the audit SSE stream). Reach for the general predicate when the question is "does a channel gate answer here"; reach for this one only when the question is about keeper specifically.

auth

returns

boolean

is_loopback_host
#

testing/cross_backend/testing_server_core.ts view source

(host: string): boolean import {is_loopback_host} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Loopback bind hosts — the only ones the test binary may serve on. It ships deterministic dev secrets (fixed cookie keys + bootstrap token in default_secrets.ts), so binding any network-reachable interface would let anyone who knows those fixed keys forge cookies against it. An allowlist (not an 0.0.0.0/:: blocklist) closes the gap a concrete LAN/public interface IP — e.g. --host 192.168.1.50 — would otherwise slip through. Covers localhost, the IPv4 loopback 127.0.0.0/8, and IPv6 ::1.

host

type string

returns

boolean

is_no_rebuild_requested
#

testing/cross_backend/cargo_binary.ts view source

(value: string | undefined): boolean import {is_no_rebuild_requested} from '@fuzdev/fuz_app/testing/cross_backend/cargo_binary.js';

Whether an NO_REBUILD_ENV value asks to skip the build — only an explicit 1 / true, so FUZ_TESTING_NO_REBUILD=0 still builds.

value

the variable's value, undefined when unset

type string | undefined

returns

boolean

is_notification
#

testing/transports/ws_client.ts view source

(method: string): (msg: unknown) => boolean import {is_notification} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Predicate matching a JSON-RPC notification with the given method name.

method

type string

returns

(msg: unknown) => boolean

is_notification_send
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ... 3 more ... | ({ ...; } & { ...; }) import {is_notification_send} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_notification_send_with_parsed_input
#

actions/action_event_helpers.ts view source

<TMethod extends string = string>(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ({ ...; } & { ...; }) import {is_notification_send_with_parsed_input} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

generics

is_notification_send_with_parsed_input<TMethod extends string = string>
TMethod
constraint string
default string

is_notification_with
#

testing/transports/ws_client.ts view source

<P>(method: string, match: (params: P) => boolean): (msg: unknown) => msg is JsonrpcNotificationFrame<P> import {is_notification_with} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Type-guard combinator: match a notification whose typed params satisfies match. Collapses the common test pattern of casting msg to JsonrpcNotificationFrame<P> in every predicate body.

const match_roster_for = (id: Uuid) => is_notification_with<RosterChangedParams>( WORLD_METHODS.roster_changed, (params) => params.character_id === id && !params.removed, ); const roster = await client.wait_for(match_roster_for(char_id));

method

type string

match

type (params: P) => boolean

returns

(msg: unknown) => msg is JsonrpcNotificationFrame<P>

generics

is_notification_with<P>
P

is_null_schema
#

http/schema_helpers.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {is_null_schema} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a schema is exactly z.null().

Uses instanceof rather than runtime parsing to avoid false positives from z.nullable(z.string()) or similar schemas that accept null but also accept other values.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

is_pg_database_url
#

db/create_db.ts view source

(database_url: string): boolean import {is_pg_database_url} from '@fuzdev/fuz_app/db/create_db.js';

Whether database_url names a Postgres server — the one driver with a pool.

database_url

a database URL

type string

returns

boolean

true for postgres:// and postgresql://

is_pg_unique_violation
#

db/pg_error.ts view source

(error: unknown): boolean import {is_pg_unique_violation} from '@fuzdev/fuz_app/db/pg_error.js';

Check if an error is a PostgreSQL unique constraint violation (error code 23505).

error

the caught error

type unknown

returns

boolean

true if the error is a unique constraint violation

is_plain_authenticated_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_plain_authenticated_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route is plain authenticated — account === 'required' with no role gate and no credential gate: any credential the auth middleware resolved gets in.

A channel gate takes a route out of this bucket, which is why the account-grain routes one might expect here are not — POST /logout, POST /password, and the account_* credential-lifecycle actions all declare credential_types: ['session'] and answer to is_credential_gated_auth instead.

auth

returns

boolean

is_port_accepting
#

testing/cross_backend/spawn_backend.ts view source

(base_url: string): Promise<boolean> import {is_port_accepting} from '@fuzdev/fuz_app/testing/cross_backend/spawn_backend.js';

Whether something accepts TCP connections at base_url's host and port. A refused connection, an unresolvable host or no answer within PORT_PROBE_TIMEOUT_MS all read as free.

base_url

an http:// or https:// URL; a missing port is the scheme's default

type string

returns

Promise<boolean>

is_post_content_type_refused
#

http/simple_request.ts view source

(content_type: string | undefined, raw_body: boolean): boolean import {is_post_content_type_refused} from '@fuzdev/fuz_app/http/simple_request.js';

True when a POST route refuses this Content-Type: a JSON route anything but application/json, a raw_body route a CORS-safelisted type or none.

content_type

the request's Content-Type header, if any

type string | undefined

raw_body

the route's RouteSpec.raw_body

type boolean

returns

boolean

is_postgres_url
#

testing/cross_backend/postgres_database.ts view source

(url: string): boolean import {is_postgres_url} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

Whether url parses as a postgres: / postgresql: URL. URL.canParse alone admits a scheme-less localhost:5432/db, which it reads as protocol localhost:.

url

the candidate connection string

type string

returns

boolean

is_protocol_action_method
#

actions/action_codegen.ts view source

(method: string): method is "heartbeat" | "cancel" | "peer/ping" import {is_protocol_action_method} from '@fuzdev/fuz_app/actions/action_codegen.js';

Type predicate for filtering protocol-action methods out of a typed FrontendActionsApi method_filter. Avoids the (... as never) cast required to call Array.prototype.includes on the readonly tuple at narrow string types.

method

type string

returns

boolean

examples

generate_frontend_actions_api(specs, imports, { method_filter: (s) => !is_protocol_action_method(s.method), });

is_public_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_public_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route is fully public — both account and actor axes are 'none'. Public routes skip the dispatcher's authorization phase entirely (per registry-time invariant 4 they also cannot declare roles or credential gates).

auth

returns

boolean

is_raw_ws_close
#

is_request_response
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ActionEventRequestResponseData import {is_request_response} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_response_for
#

testing/transports/ws_client.ts view source

(id: string | number): (msg: unknown) => boolean import {is_response_for} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Predicate matching a JSON-RPC response frame (success or error) for the given request id.

id

type number | string

returns

(msg: unknown) => boolean

is_role_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_role_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route declares any role gate (auth.roles?.length).

auth

returns

boolean

is_role_grant_active
#

auth/account_schema.ts view source

(p: { revoked_at?: string | null | undefined; expires_at: string | null; }, now?: Date): boolean import {is_role_grant_active} from '@fuzdev/fuz_app/auth/account_schema.js';

Whether a role grant is live, from its wire fields — not revoked, and not past expires_at.

Server-side this is belt-and-suspenders: every role_grant a request context holds came through query_role_grant_find_active_for_actor, whose role_grant_active_sql predicate (auth/role_grant_sql.ts) is the authoritative gate on both spines (the Rust twin's in-memory RoleGrantRow carries no timestamps at all and re-checks nothing).

expires_at arrives second-truncated, so the expiry check is is_iso8601_seconds_live (timestamp.ts): it admits through the end of the second the stamp names, so the predicate can never report a grant expired *before* the SQL gate would — a bare expires_at > now would deny an admitted grant for up to a second.

p

the grant's revocation + expiry fields

type { revoked_at?: string | null | undefined; expires_at: string | null; }

now?

current time (defaults to new Date(), pass for testability and hot-path efficiency)

type Date
optional default new Date()

returns

boolean

true when the grant is neither revoked nor expired

is_route_scope_capability
#

auth/token_scope.ts view source

(value: string): boolean import {is_route_scope_capability} from '@fuzdev/fuz_app/auth/token_scope.js';

Is value a capability a route spec may declare as auth.required_scope?

Only the surface:<name> arm: a route is a non-RPC surface, and rule 3 is all-or-nothing there. The rpc:<method> arm is the dispatcher's per-method gate (perform_action), which a route never reaches — so a route declaring one would put a method name in front of a REST handler that no method gate guards, and is refused at registration.

Callers turn false into a registration-time throw: RouteAuth.required_scope is typed as a plain string so http/ needs no auth/ import, and this is the single point where that string becomes a mounted guard, so it is where the value gets checked. The identifier is checked for shape (TOKEN_SURFACE_NAME_REGEX), not membership — a consumer names its own surface.

value

the declared capability string, e.g. surface:audit_stream

type string

returns

boolean

whether value is a well-formed surface:<name> capability

is_send_request
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ... 3 more ... | ({ ...; } & { ...; }) import {is_send_request} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_send_request_with_parsed_input
#

actions/action_event_helpers.ts view source

<TMethod extends string = string>(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ({ ...; } & { ...; }) import {is_send_request_with_parsed_input} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

generics

is_send_request_with_parsed_input<TMethod extends string = string>
TMethod
constraint string
default string

is_strict_object_schema
#

http/schema_helpers.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {is_strict_object_schema} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a schema is a strict object (z.strictObject()).

Strict objects set catchall to ZodNever to reject unknown keys. Regular z.object() has catchall: undefined (strips unknown keys in Zod 4).

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

is_token_surface
#

auth/token_scope.ts view source

(value: string): value is "db_admin" | "fact_bare" | "audit_stream" | "ws_upgrade" | "app_surface" | "account_verify" import {is_token_surface} from '@fuzdev/fuz_app/auth/token_scope.js';

Is value one of the surfaces the spine mounts?

Membership, not well-formedness — is_route_scope_capability is the format check the guard resolver runs on a declaration, and it is deliberately looser. Used where the *spine's* own surfaces are the question: the direct-call denial helpers, and the census assertion that fuz_app declares no surface it doesn't mount.

value

type string

returns

boolean

is_trusted_ip
#

http/proxy.ts view source

(ip: string, proxies: ParsedProxy[]): boolean import {is_trusted_ip} from '@fuzdev/fuz_app/http/proxy.js';

Check whether ip matches any entry in the trusted proxy list.

Normalizes ip before matching (lowercase, IPv4-mapped IPv6 stripped). Uses validate_ip_strict to reject malformed input — without strict validation, Hono's lax distinctRemoteAddr would let an entry like '203.0.113.1:8080' (false-positive 'IPv6') reach convertIPv6ToBinary in the CIDR-match branch and throw.

ip

type string

proxies

returns

boolean

is_unauthenticated_refusal
#

testing/integration_helpers.ts view source

(status: number, reason: unknown): boolean import {is_unauthenticated_refusal} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Whether a response says the request carried no valid credential: a 401 whose reason is authentication_required, or that names no reason. A 401 naming another reason is a handler's own answer to an authenticated call — invalid_credentials for a wrong current password — and is not this.

status

the HTTP status

type number

reason

the refusal's reason: a REST body's error, a JSON-RPC error's data.reason

type unknown

returns

boolean

is_void_schema
#

http/schema_helpers.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {is_void_schema} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a schema is exactly z.void().

RPC action specs use z.void() to declare a parameterless method — JSON-RPC 2.0 forbids params: null (params must be omitted or be a Structured value), so z.void() is the correct schema for "no params" and the dispatcher maps absent params to undefined for these specs.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

is_ws_conformance_case
#

testing/cross_backend/conformance_table.ts view source

(c: { name: string; request: { method: string; as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"; params?: unknown; params_json?: string | undefined; acting?: "principal" | ... 2 more ... | undefined; verb?: "GET" | ... 1 more ... | undefined; }; expect: { ...; }; note?: string | undefined; xfail?: { ...; } | undefined; }): boolean import {is_ws_conformance_case} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Whether a case can run over a WebSocket: an RPC method (not a REST auth route), sent as POST (a GET row pins the HTTP query path), pinning no header value (a frame has no headers; an absence pin holds vacuously), as a principal that can hold a socket.

c

the case

returns

boolean

whether the WS leg runs it

ISO8601_SECOND_MS
#

timestamp.ts view source

1000 import {ISO8601_SECOND_MS} from '@fuzdev/fuz_app/timestamp.js';

The width of one wire timestamp, in milliseconds.

A wire stamp is truncated, not rounded, so the instant it names lies anywhere in [stamp, stamp + ISO8601_SECOND_MS). A predicate that must not fire early against a stamp — "has this expiry passed?" — adds this to reach the end of the second the stamp stands for.

ISO8601_SECONDS
#

timestamp.ts view source

RegExp import {ISO8601_SECONDS} from '@fuzdev/fuz_app/timestamp.js';

The canonical wire timestamp shape as a regex — second-precision UTC, exactly 20 characters.

Lives in library code rather than a test helper so the unit suites, the db suites, and the cross-backend suites all pin the same pattern the server renders.

iso8601_timestamp_column
#

db/sql_columns.ts view source

(alias: string, column: string): string import {iso8601_timestamp_column} from '@fuzdev/fuz_app/db/sql_columns.js';

Build a `to_char({alias}.{column} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')` fragment for embedding in a SELECT projection, via a ColumnExpr override.

The format literal is the spine's canonical timestamp wire shape — second-precision UTC ISO-8601, exactly 20 characters — byte-identical to fuz_db::iso8601_timestamp_column, so the two backends serialize the same instant to the same bytes. to_iso8601_seconds (timestamp.ts) is the same shape for stamps minted in TS rather than read from Postgres.

alias

the table alias or name to qualify the column with, or the empty string for an unqualified reference

type string

column

the timestamp column name

type string

returns

string

the to_char(…) fragment

iso8601_timestamp_expr
#

db/sql_columns.ts view source

<T extends string>(columns: readonly T[], timestamp_columns: readonly NoInfer<T>[]): (alias: string) => ColumnExpr import {iso8601_timestamp_expr} from '@fuzdev/fuz_app/db/sql_columns.js';

Build a table's timestamp override: a ColumnExpr factory that projects each of timestamp_columns through iso8601_timestamp_column and declines every other column. The result takes the row qualifier — the empty string for a bare-table read, the alias for a JOIN — so one definition per query module serves every read in it.

timestamp_columns is typed against columns (and checked at build time), so a misspelled name is a compile error and a stale one throws at module load rather than silently leaving a column projected as a Date.

columns

the table's *_COLUMNS const

type readonly T[]

timestamp_columns

the members of columns that carry timestamps

type readonly NoInfer<T>[]

returns

(alias: string) => ColumnExpr

(alias) => ColumnExpr

generics

iso8601_timestamp_expr<T extends string>
T
constraint string

throws

  • Error - when a name in `timestamp_columns` is not in `columns`

ItemJson
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; } import {ItemJson} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Wire-format for a cell_item row.

position is branded CellItemPosition so consumers that round-trip the value back into a position_after / position input field don't need a cast at every call site. Wire ingress is validated by the CellItemPosition Zod schema (alphabet + length); wire egress trusts the DB CHECK constraint that backs cell_item.position, so the server-side to_item_json casts a raw string from CellItemRow.

parent_id

type string & $brand<"Uuid">

position

type string & $brand<"CellItemPosition">

child_id

type string & $brand<"Uuid">

created_at

type string

json_has_own_proto_key
#

http/schema_helpers.ts view source

(value: unknown): boolean import {json_has_own_proto_key} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check whether a parsed JSON value holds an own __proto__ key in any object, at any depth — what the dispatcher refuses in a request's params (ERROR_PARAMS_PROTO_KEY).

JSON.parse defines "__proto__" as an ordinary own key, but the first plain assignment that copies it (target[key] = value, as any shallow copy or merge loop does) hits the prototype setter instead, so stored JSON carrying one is a prototype-pollution sink for whoever reads it back. An own constructor or prototype key is inert on a plain object and is not checked.

Walks with an explicit stack rather than recursion: JSON.parse accepts nesting deep enough to overflow the call stack, and the walk is linear in the value's nodes, which the request body limit bounds. The Rust spine's fuz_http::json_has_own_proto_key is the twin.

value

a value as JSON.parse produced it

type unknown

returns

boolean

whether any object in value has an own __proto__ key

jsonrpc_error_code_to_http_status
#

http/jsonrpc_errors.ts view source

(code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)): ContentfulStatusCode import {jsonrpc_error_code_to_http_status} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Map a JSON-RPC error code to an HTTP status code.

Returns 500 for unrecognized codes (a consumer code never passed to register_jsonrpc_error_code answers internal server error). The return is narrowed to Hono's ContentfulStatusCode so call sites can pass the result to c.json(body, status) without as any — 499 (nginx "client closed request") is non-standard and gets absorbed by the cast here rather than at every dispatcher branch.

code

returns

ContentfulStatusCode

jsonrpc_error_code_to_name
#

http/jsonrpc_errors.ts view source

(code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)): JsonrpcErrorName import {jsonrpc_error_code_to_name} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Map a JSON-RPC error code to its canonical name ('not_found', 'forbidden', etc.). Falls back to 'internal_error' for codes outside the standard taxonomy so REST emitters that read this for their error field always have a stable string to emit.

code

returns

JsonrpcErrorName

JSONRPC_ERROR_CODE_TO_NAME
#

http/jsonrpc_errors.ts view source

Readonly<Record<number, JsonrpcErrorName>>

type Readonly<Record<number, JsonrpcErrorName>>

import {JSONRPC_ERROR_CODE_TO_NAME} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Reverse map of JSONRPC_ERROR_CODES — JSON-RPC error code → name.

Used by REST emitters that need a stable string identifier for the code in their flat-shape error body ({error: '<name>', ...}) without inventing a separate vocabulary. Built once at module load from the canonical JSONRPC_ERROR_CODES map so the two cannot drift.

Consumer-defined codes outside the standard taxonomy are not present — registering one with register_jsonrpc_error_code gives it a status, not a name; jsonrpc_error_code_to_name falls back to 'internal_error' so the REST shape always carries some reason rather than undefined.

JSONRPC_ERROR_CODES
#

http/jsonrpc_errors.ts view source

Readonly<Record<JsonrpcErrorName, -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)>>

type Readonly<Record<JsonrpcErrorName, -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)>>

import {JSONRPC_ERROR_CODES} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Standard JSON-RPC error codes (5) plus general application codes (10).

Extensible — consumers add domain-specific codes to their own objects by casting as JsonrpcErrorCode, and declare each one's HTTP status with register_jsonrpc_error_code. Application codes use the -32000 to -32099 range reserved by the JSON-RPC spec.

Frozen with Object.freeze to convert accidental mutation (test cross-contamination, cast escapes) into loud TypeErrors. Spread into a fresh object to extend.

jsonrpc_error_messages
#

http/jsonrpc_errors.ts view source

Readonly<Record<JsonrpcErrorName, (...args: any[]) => { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }>>

type Readonly<Record<JsonrpcErrorName, (...args: any[]) => { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }>>

import {jsonrpc_error_messages} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Named constructors for JsonrpcErrorObject values.

Each function creates a JSON-RPC error object with the correct code and a sensible default message. Used by the catch layer in apply_route_specs to build response bodies.

Frozen so tests must compose new objects rather than monkey-patch.

jsonrpc_errors
#

http/jsonrpc_errors.ts view source

{ readonly parse_error: (...args: any[]) => ThrownJsonrpcError; readonly invalid_request: (...args: any[]) => ThrownJsonrpcError; readonly method_not_found: (...args: any[]) => ThrownJsonrpcError; ... 11 more ...; readonly request_cancelled: (...args: any[]) => ThrownJsonrpcError; } import {jsonrpc_errors} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Named constructors for ThrownJsonrpcError.

Usage: throw jsonrpc_errors.not_found('user') or throw jsonrpc_errors.forbidden().

JSONRPC_INTERNAL_ERROR
#

JSONRPC_INVALID_PARAMS
#

JSONRPC_INVALID_REQUEST
#

JSONRPC_METHOD_NOT_FOUND
#

JSONRPC_PARSE_ERROR
#

JSONRPC_SERVER_ERROR_END
#

http/jsonrpc.ts view source

-32099 import {JSONRPC_SERVER_ERROR_END} from '@fuzdev/fuz_app/http/jsonrpc.js';

End of the server-defined error code range (-32099).

JSONRPC_SERVER_ERROR_START
#

http/jsonrpc.ts view source

-32000 import {JSONRPC_SERVER_ERROR_START} from '@fuzdev/fuz_app/http/jsonrpc.js';

Start of the server-defined error code range (-32000).

JSONRPC_VERSION
#

JsonrpcErrorCode
#

http/jsonrpc.ts view source

value + type

-32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)

type -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)

import {JsonrpcErrorCode} from '@fuzdev/fuz_app/http/jsonrpc.js';

A valid JSON-RPC error code — one of the 5 standard codes or a server-defined code in the -32000 to -32099 range.

JsonrpcErrorName
#

http/jsonrpc_errors.ts view source

JsonrpcErrorName

type "parse_error" | "invalid_request" | "method_not_found" | "invalid_params" | "internal_error" | "unauthenticated" | "forbidden" | "not_found" | "conflict" | "validation_error" | "rate_limited" | "service_unavailable" | "timeout" | "queue_overflow" | "request_cancelled"

import type {JsonrpcErrorName} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Names of standard and general application JSON-RPC error codes.

JsonrpcErrorObject
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; } import {JsonrpcErrorObject} from '@fuzdev/fuz_app/http/jsonrpc.js';

Error object within a JSON-RPC error response.

code

type -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)

message

type string

data?

type unknown

[key: string]

type unknown

JsonrpcErrorResponse
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; } import {JsonrpcErrorResponse} from '@fuzdev/fuz_app/http/jsonrpc.js';

A response that indicates an error occurred.

jsonrpc

type "2.0"

id

type string | number | null

error

type JsonrpcErrorObject

[key: string]

type unknown

JsonrpcErrorResponseFrame
#

testing/transports/ws_client.ts view source

JsonrpcErrorResponseFrame<D> import type {JsonrpcErrorResponseFrame} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

generics

JsonrpcErrorResponseFrame<D = unknown>
D
default unknown

jsonrpc

type "2.0"

id

type number | string

error

type { code: number; message: string; data?: D | undefined; }

JsonrpcMcpMeta
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; } import {JsonrpcMcpMeta} from '@fuzdev/fuz_app/http/jsonrpc.js';

MCP metadata object — loose to allow additional properties and .extend.

[key: string]

type unknown

JsonrpcMessage
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { ...; } | { ...; }

type JsonrpcRequest | JsonrpcResponse | JsonrpcErrorResponse | JsonrpcNotification

import {JsonrpcMessage} from '@fuzdev/fuz_app/http/jsonrpc.js';

Any valid JSON-RPC message (request, notification, response, or error response).

JsonrpcMessageFromClientToServer
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }

type JsonrpcRequest | JsonrpcNotification

import {JsonrpcMessageFromClientToServer} from '@fuzdev/fuz_app/http/jsonrpc.js';

Messages a client can send to a server (request or notification).

JsonrpcMessageFromServerToClient
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; } | { ...; }

type JsonrpcResponse | JsonrpcErrorResponse | JsonrpcNotification

import {JsonrpcMessageFromServerToClient} from '@fuzdev/fuz_app/http/jsonrpc.js';

Messages a server can send to a client (notification, response, or error response).

JsonrpcMethod
#

http/jsonrpc.ts view source

value + type

string import {JsonrpcMethod} from '@fuzdev/fuz_app/http/jsonrpc.js';

A JSON-RPC method name.

JsonrpcNotification
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } import {JsonrpcNotification} from '@fuzdev/fuz_app/http/jsonrpc.js';

A notification which does not expect a response.

jsonrpc

type "2.0"

method

type string

params?

type JsonrpcNotificationParams

[key: string]

type unknown

JsonrpcNotificationFrame
#

testing/transports/ws_client.ts view source

JsonrpcNotificationFrame<P> import type {JsonrpcNotificationFrame} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

generics

JsonrpcNotificationFrame<P = unknown>
P
default unknown

jsonrpc

type "2.0"

method

type string

params

type P

JsonrpcNotificationParams
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; } import {JsonrpcNotificationParams} from '@fuzdev/fuz_app/http/jsonrpc.js';

Notification params — loose object. Per-action schemas validate _meta content.

[key: string]

type unknown

JsonrpcProgressToken
#

http/jsonrpc.ts view source

value + type

string | number

type string | number

import {JsonrpcProgressToken} from '@fuzdev/fuz_app/http/jsonrpc.js';

A progress token, used to associate progress notifications with the original request.

JsonrpcRequest
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } import {JsonrpcRequest} from '@fuzdev/fuz_app/http/jsonrpc.js';

A request that expects a response.

jsonrpc

type "2.0"

id

type string | number

method

type string

params?

type JsonrpcRequestParams

[key: string]

type unknown

JsonrpcRequestId
#

http/jsonrpc.ts view source

value + type

string | number

type string | number

import {JsonrpcRequestId} from '@fuzdev/fuz_app/http/jsonrpc.js';

A uniquely identifying id for a request in JSON-RPC. Like MCP, excludes null.

JsonrpcRequestParams
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; } import {JsonrpcRequestParams} from '@fuzdev/fuz_app/http/jsonrpc.js';

Request params — loose object. Per-action schemas validate _meta content.

[key: string]

type unknown

JsonrpcRequestParamsMeta
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; progressToken?: string | number | undefined; } import {JsonrpcRequestParamsMeta} from '@fuzdev/fuz_app/http/jsonrpc.js';

Request params metadata — extends MCP meta with optional progress token.

progressToken?

If specified, the caller is requesting out-of-band progress notifications for this request. The value is an opaque token attached to subsequent notifications. The receiver is not obligated to provide these notifications.

type string | number

[key: string]

type unknown

JsonrpcResponse
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } import {JsonrpcResponse} from '@fuzdev/fuz_app/http/jsonrpc.js';

A successful (non-error) response to a request.

jsonrpc

type "2.0"

id

type string | number

result

type JSONType

[key: string]

type unknown

JsonrpcResponseOrError
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; }

type JsonrpcResponse | JsonrpcErrorResponse

import {JsonrpcResponseOrError} from '@fuzdev/fuz_app/http/jsonrpc.js';

A successful response or an error response.

JsonrpcResult
#

http/jsonrpc.ts view source

value + type

JSONType

type JSONType

import {JsonrpcResult} from '@fuzdev/fuz_app/http/jsonrpc.js';

Result — any JSON value per JSON-RPC 2.0 §5. Per-action spec.output is the actual contract; the envelope only asserts presence + JSON-ness.

z.json() is required (not implicitly optional like z.unknown() / z.any(), which would let an error envelope {jsonrpc, id, error} parse successfully against the success envelope and break union discrimination).

JsonrpcServerErrorCode
#

http/jsonrpc.ts view source

value + type

number & $brand<"JsonrpcServerErrorCode">

type number & $brand<"JsonrpcServerErrorCode">

import {JsonrpcServerErrorCode} from '@fuzdev/fuz_app/http/jsonrpc.js';

A server-defined error code in the -32000 to -32099 range.

external types

z.infer<typeof JsonrpcServerErrorCode>

JsonrpcSuccessResponseFrame
#

testing/transports/ws_client.ts view source

JsonrpcSuccessResponseFrame<R> import type {JsonrpcSuccessResponseFrame} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

generics

JsonrpcSuccessResponseFrame<R = unknown>
R
default unknown

jsonrpc

type "2.0"

id

type number | string

result

type R

KeeperHeaderProvider
#

testing/integration_helpers.ts view source

KeeperHeaderProvider import type {KeeperHeaderProvider} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Header-builder triple shared by TestApp (in-process) and TestFixture (cross-backend fixture protocol). Both satisfy this shape structurally — pick_auth_headers accepts either without a cast.

create_session_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_daemon_token_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

KeyedAsyncSlot
#

ui/keyed_async_slot.svelte.ts view source

import {KeyedAsyncSlot} from '@fuzdev/fuz_app/ui/keyed_async_slot.svelte.js';

Reactive container for many concurrent async operations keyed by K.

generics

KeyedAsyncSlot<K, T = void, E = string>
K
T
default void
E
default string

constructor

type new <K, T = void, E = string>(options?: KeyedAsyncSlotOptions<T, E>): KeyedAsyncSlot<K, T, E>

options?

optional default {}

has

Reactive — true once run(key, ...) has been called and the entry hasn't been deleted.

type (key: K): boolean

key

type K
returns boolean

get

Direct access to the underlying AsyncSlot for key, or undefined if no run() has been issued for it yet. Reactive on map population and on the slot's $state.raw fields.

Prefer the sugar getters (loading, error) for templates; reach for get(key) when you need error_data, data, or to call abort() / set() / reset() on the underlying slot.

type (key: K): AsyncSlot<T, E> | undefined

key

type K
returns AsyncSlot<T, E> | undefined

loading

Reactive — false for keys that have never been used.

type (key: K): boolean

key

type K
returns boolean

error

Reactive — null when the key has no entry or hasn't failed.

type (key: K): E | null

key

type K
returns E | null

failed

Reactive — false for keys that have never been used.

type (key: K): boolean

key

type K
returns boolean

succeeded

Reactive — false for keys that have never been used.

type (key: K): boolean

key

type K
returns boolean

keys

Reactive iterator over every key with state.

type (): IterableIterator<K>

returns IterableIterator<K, any, any>

values

Reactive iterator over every slot.

type (): IterableIterator<AsyncSlot<T, E>>

returns IterableIterator<AsyncSlot<T, E>, any, any>

entries

Reactive iterator over [key, slot] pairs.

type (): IterableIterator<[K, AsyncSlot<T, E>]>

returns IterableIterator<[K, AsyncSlot<T, E>], any, any>

run

Run an async operation for key. Lazily creates an AsyncSlot for the key on first use, inheriting the constructor's map_error / preserve_error_on_retry options.

Supersession is scoped to key: a second run(key, ...) aborts the first's signal AND drops its commit. Calls on different keys are fully independent (each has its own AbortController).

type (key: K, fn: (signal: AbortSignal) => Promise<T>, options?: RunOptions | undefined): Promise<T | undefined>

key

type K

fn

type (signal: AbortSignal) => Promise<T>

options?

optional
returns Promise<T | undefined>

the resolved value on success; undefined on failure, abort, or supersession.

abort

Abort the in-flight run for key, if any. No-op when the key has no entry. The slot stays in the map at its prior resolved status — call delete to remove the entry entirely.

type (key: K, reason?: unknown): void

key

type K

reason?

type unknown
optional
returns void

abort_all

Abort every in-flight run. Resolved entries stay in the map — call reset to clear them too.

type (reason?: unknown): void

reason?

type unknown
optional
returns void

delete

Abort the in-flight run for key (if any) and remove the entry from the map. After delete(key), has(key) returns false and the sugar getters report the no-entry defaults — typically how a UI dismisses a per-row error indicator.

type (key: K): boolean

key

type K
returns boolean

true if the key had an entry.

mutates

  • this

reset

Abort every in-flight run and clear the map. The keyed slot looks like a fresh instance afterwards.

type (): void

returns void

mutates

  • this

size

Total number of keys with state (pending OR resolved). Reactive.

type number

getter

KeyedAsyncSlotOptions
#

ui/keyed_async_slot.svelte.ts view source

KeyedAsyncSlotOptions<T, E> import type {KeyedAsyncSlotOptions} from '@fuzdev/fuz_app/ui/keyed_async_slot.svelte.js';

Constructor options for KeyedAsyncSlot. Propagated to every child AsyncSlot at lazy creation time.

initial from AsyncSlotOptions is deliberately omitted — keyed slots have no per-key seed concept (the entries don't exist until run() creates them).

generics

KeyedAsyncSlotOptions<T, E = string>
T
E
default string

map_error?

Convert a caught throw into the error value stored in AsyncSlot.error. Default extracts Error.message (falling back to 'Request failed' for non-Error throws). Pass to_rpc_error_message to unwrap JSON-RPC data.reason codes.

type (e: unknown): E

e

type unknown
returns E

preserve_error_on_retry?

When true, the previous error / error_data survive the start of a new run() until the next success (or another failure overwrites them). Useful for retry UX that wants to keep the failure message visible alongside an inline spinner. Default false — run() clears the error at the start so the pending state reads "no current error."

type boolean

Keyring
#

auth/keyring.ts view source

Keyring import type {Keyring} from '@fuzdev/fuz_app/auth/keyring.js';

Opaque keyring that encapsulates secret keys. Only exposes sign/verify operations, never the raw keys.

sign

Sign a value with HMAC SHA-256.

type (value: string): Promise<string>

value

type string
returns Promise<string>

signed value in format: value.signature

verify

Verify a signed value and extract the original. Tries all keys in order to support key rotation.

type (signed_value: string): Promise<{ value: string; key_index: number; } | null>

signed_value

type string
returns Promise<{ value: string; key_index: number; } | null>

object with value and key_index, or null if invalid

KnownDivergence
#

testing/cross_backend/differential.ts view source

KnownDivergence

type ({ readonly case_name: string | readonly string[]; readonly method?: undefined; } & { readonly tracking_id: string; readonly reason: string; readonly what_differs: readonly (string | KnownDivergencePath)[]; }) | ({ readonly method: string | readonly string[]; readonly case_name?: undefined; } & { readonly tracking_id: string; readonly reason: string; readonly what_differs: readonly (string | KnownDivergencePath)[]; })

import type {KnownDivergence} from '@fuzdev/fuz_app/testing/cross_backend/differential.js';

A tracked, accepted difference between the two backends.

what_differs lists the normalized-response paths that differ (DifferentialDiff.path — status, headers.<name>, body.error.data.reason), each optionally with the value each side shows (KnownDivergencePath). A matched case may differ there and nowhere else. An entry naming cases must still differ on each of them; one naming methods, on at least one of its cases.

A pin is compared with the normalized leaf value at the differing path: a placeholdered value never matches the raw one, an object pin must list its keys sorted, and a pin on a parent path never matches, since diffs are reported at their leaves.

KnownDivergenceMatch
#

testing/cross_backend/differential.ts view source

KnownDivergenceMatch

type { readonly case_name: string | readonly string[]; readonly method?: undefined; } | { readonly method: string | readonly string[]; readonly case_name?: undefined; }

import type {KnownDivergenceMatch} from '@fuzdev/fuz_app/testing/cross_backend/differential.js';

What a ledger entry matches: cases by name, or every case on a method — one, or a list.

KnownDivergencePath
#

testing/cross_backend/differential.ts view source

KnownDivergencePath import type {KnownDivergencePath} from '@fuzdev/fuz_app/testing/cross_backend/differential.js';

One path a known divergence differs at, with what each side must show there.

path covers itself and everything under it (differential_path_covers). a / b, when given, are the value that side's diff must carry at the differing path — undefined meaning the side has nothing there — so a divergence that changes shape (the side that lacked a field starts sending a different one, or the other side's value moves) is no longer covered and fails. Omit a side to leave its value open. A bare string is a path with both sides open.

A pin is compared with the normalized leaf value at the differing path: a placeholdered value never matches the raw one, an object pin must list its keys sorted, and a pin on a parent path never matches, since diffs are reported at their leaves.

path

type string

readonly

a?

type unknown

readonly

b?

type unknown

readonly

list_audit_events
#

testing/cross_backend/audit_cross_helpers.ts view source

(fixture: AuditReadFixture, params: Record<string, unknown>, rpc_path?: string): Promise<{ id: string & $brand<"Uuid">; seq: number; event_type: string; ... 9 more ...; target_username: string | null; }[]> import {list_audit_events} from '@fuzdev/fuz_app/testing/cross_backend/audit_cross_helpers.js';

Drain the backend's audit writes (cross_drain_effects), then list audit rows as the keeper, parse them against AuditLogListOutput, and parse each row's metadata against its event type's schema (assert_audit_event_metadata), with unknown keys refused when fixture.reject_unknown_audit_metadata_keys is set. The wire schema leaves metadata a loose record, so without this a spine's metadata drift reaches no assertion.

fixture

the per-test fixture; the list runs on the keeper's session

params

audit_log_list params, e.g. {event_type: 'login', outcome: 'failure'}

type Record<string, unknown>

rpc_path?

the RPC endpoint both actions are mounted on

type string
optional default SPINE_RPC_PATH

returns

Promise<AuditLogEventWithUsernamesJson[]>

the listed events

list_roles_with_grant_path
#

auth/role_schema.ts view source

(role_specs: ReadonlyMap<string, RoleSpec>, grant_path: string): string[] import {list_roles_with_grant_path} from '@fuzdev/fuz_app/auth/role_schema.js';

Filter helper: list every role whose grant_paths includes the given path.

role_specs

type ReadonlyMap<string, RoleSpec>

grant_path

type string

returns

string[]

load_env
#

env/load.ts view source

<T extends z.ZodObject>(schema: T, get_env: (key: string) => string | undefined): output<T> import {load_env} from '@fuzdev/fuz_app/env/load.js';

Load and validate env vars against a Zod schema.

schema

Zod object schema defining expected env vars

type T

get_env

function to read an env var by key

type (key: string) => string | undefined

returns

output<T>

validated env object

generics

load_env<T extends z.ZodObject>
T
constraint z.ZodObject

throws

  • EnvValidationError - if Zod validation fails

load_env_file
#

env/dotenv.ts view source

(runtime: Pick<FsReadDeps, "read_text_file">, path: string): Promise<Record<string, string> | null> import {load_env_file} from '@fuzdev/fuz_app/env/dotenv.js';

Load and parse an env file.

Returns null only when the file does not exist. Other read errors (permission denied, I/O failure, etc.) are re-thrown so callers can distinguish "no file" from "couldn't read".

runtime

runtime with read_text_file capability

type Pick<FsReadDeps, "read_text_file">

path

path to env file

type string

returns

Promise<Record<string, string> | null>

parsed env record, or null if file doesn't exist

throws

  • Error - if reading fails for any reason other than `ENOENT` / `NotFound`

load_expected_schema
#

http/common_routes.ts view source

(url: string | URL): ExpectedSchema import {load_expected_schema} from '@fuzdev/fuz_app/http/common_routes.js';

Load a consumer's committed expected_schema.json fixture (cached by URL).

The spine ships the readiness *mechanism* but not the *expectation* — the expected column map is per-consumer (each adds its own tables), so the consumer commits the fixture and passes the loaded map to create_ready_route_spec. Call with an import.meta.url-relative URL:

create_ready_route_spec({ expected: load_expected_schema(new URL('./expected_schema.json', import.meta.url)), log: deps.log, });

The fixture is regenerated against a fresh bootstrap by the consumer's gen-time test (see testing/schema_ready_fixture.ts), so it can't silently fall behind the migration chain.

url

the fixture location (a file URL or path)

type URL | string

returns

ExpectedSchema

load_origin_conformance
#

testing/cross_backend/origin_conformance.ts view source

(): { parse: ({ raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | ... 11 more ... | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; })[]; match: { ...; }[]; } import {load_origin_conformance} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

Read and validate the committed conformance table.

returns

OriginConformance

the parsed table

throws

  • Error - when the file doesn't match `OriginConformance`

load_secure_file_node
#

runtime/secure_file.ts view source

(path: string): Promise<Uint8Array<ArrayBufferLike>> import {load_secure_file_node} from '@fuzdev/fuz_app/runtime/secure_file.js';

Read a secret file with fail-loud checks (Node implementation).

  • O_NOFOLLOW atomically rejects symlinks during open (no TOCTOU window)
  • O_NONBLOCK keeps the open from waiting on a FIFO that has no writer (it has no effect on a regular file); the regular-file check on the open descriptor then refuses it, a device node, or a directory
  • O_NOCTTY keeps a terminal device at the path from becoming the process's controlling terminal in the moment before that check refuses it
  • the permission check runs on the open descriptor, not the path, so the file can't be swapped between check and read — any group/other access (not 0600/0400) is refused
  • a size cap bounds the read; the read itself is limited to cap + 1 so a file growing between stat and read is still caught

path

path to the secret file

type string

returns

Promise<Uint8Array<ArrayBufferLike>>

the file's bytes

throws

  • Error - on a missing file, symlink, non-regular file, permissive mode, oversized file, or I/O failure

LocalCallActionSpec
#

actions/action_spec.ts view source

value + type

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } import {LocalCallActionSpec} from '@fuzdev/fuz_app/actions/action_spec.js';

Local calls can wrap synchronous or asynchronous actions, and are the escape hatch for remote APIs that do not support SAES.

method

type string

initiator

type "frontend" | "backend" | "both"

side_effects

type boolean

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

output

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

async

type boolean

description

type string

kind

type "local_call"

auth

type null

streams?

Names the notification method this action emits as request-scoped progress. Forward-compatible handshake — transport-agnostic, does not imply a specific delivery mechanism. Registry-time validation (e.g., ensuring the named method is a remote_notification spec) is a consumer-side concern.

type string

error_reasons?

Error reason codes this action may surface via error.data.reason on failure. Declarative metadata mirroring the streams precedent — codegen, UI form-state matching, and docs read it off the spec instead of scanning handler implementations. Reuses the same as const string constants the handler throws (e.g. ERROR_ROLE_GRANT_OFFER_*) so call sites can import either side. Optional — actions that surface only standard transport errors leave it unset.

type readonly string[]

rate_limit?

Rate limit key the RPC dispatcher consults for this action. Optional — actions without it skip the rate-limit hook entirely.

  • 'ip' — keyed on the resolved client IP (get_client_ip(c)).
  • 'account' — keyed on the post-auth account id (request_context.account.id). Registration-time error if paired with auth.account !== 'required' (no account to key on).
  • 'both' — both checks run; either can block.

Throttle-requests semantics — every invocation records, regardless of outcome (different from REST login's throttle-failures, which resets on success). Suits admin mutation oracles (invite_create account- existence probe) where the *successful* invocation is the threat.

Today only RequestResponseActionSpec is consulted by the RPC dispatcher. The field is on the base for shape symmetry with streams and error_reasons; remote_notification / local_call dispatchers don't read it.

type "both" | "account" | "ip"

log_db_factory_status
#

testing/db.ts view source

(factories: DbFactory[]): void import {log_db_factory_status} from '@fuzdev/fuz_app/testing/db.js';

Log factory status to console.

factories

type DbFactory[]

returns

void

log_env_validation_error
#

env/load.ts view source

(error: EnvValidationError, label?: string | undefined): void import {log_env_validation_error} from '@fuzdev/fuz_app/env/load.js';

Log formatted env validation issues to stderr.

Handles the common case: labels each Zod issue with an optional prefix. Callers who want app-specific "getting started" instructions should check error.all_undefined before calling this.

error

the env validation error

label?

optional prefix for log lines (e.g., 'server', 'env')

type string
optional

returns

void

log_startup_summary
#

server/startup.ts view source

(surface: AppSurface, log: Logger, env_values?: Record<string, unknown> | undefined): void import {log_startup_summary} from '@fuzdev/fuz_app/server/startup.js';

Log a startup summary from an AppSurface.

Logs route count, middleware count, env breakdown (when non-empty), and event/channel counts (when non-empty). When env_values is provided, non-secret values are logged and secrets are masked with ***.

surface

log

type Logger

env_values?

optional env values to log (secrets are masked)

type Record<string, unknown>
optional

returns

void

LogDeps
#

runtime/deps.ts view source

LogDeps import type {LogDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Warning/diagnostic output.

warn

Log a warning message.

type (...args: unknown[]): void

args

type unknown[]
returns void

LOGIN_FAIL_FLOOR_DISABLED_BANNER
#

auth/fail_floor.ts view source

"!!! LOGIN FAILURE TIMING FLOOR DISABLED — test-only mode. A production server must never print this." import {LOGIN_FAIL_FLOOR_DISABLED_BANNER} from '@fuzdev/fuz_app/auth/fail_floor.js';

The stderr line a process prints when the login failure floor is off — the Rust spine's text.

LOGIN_RATE_LIMIT_ENABLED_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_LOGIN_RATE_LIMIT_ENABLED" import {LOGIN_RATE_LIMIT_ENABLED_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var both spine binaries read to enable their login rate limiters ('true' on / unset off). The cross-language contract for the login-security cross project: the TS binary reads it via runtime.env_get (a test-only flag, not in BaseServerEnv); the Rust testing_spine_stub reads it via std::env::var — so one backend-config option drives both impls (ts_spine_backend_config and rust_spine_stub_backend_config set it).

LoginForm
#

ui/LoginForm.svelte view source

import LoginForm from '@fuzdev/fuz_app/ui/LoginForm.svelte';

username_label?

Label and placeholder for the username field — set when the surface accepts only one of username/email so the UX matches.

type string
optional default 'username or email'

redirect_on_login?

Path to navigate to on successful login.

type string
optional default resolve('/')

LoginInput
#

auth/account_route_schema.ts view source

value + type

{ username: string; password: string; } import {LoginInput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Input for POST /login. Accepts a username or email in the username field.

username

type string

password

type string

LoginOutput
#

auth/account_route_schema.ts view source

value + type

{ ok: true; } import {LoginOutput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Output for POST /login. Session cookie is the operative side effect.

ok

type true

LoginSecurityCrossTestOptions
#

testing/cross_backend/login_security.ts view source

LoginSecurityCrossTestOptions import type {LoginSecurityCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/login_security.js';

Options for the login-security parity suite.

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

login_path?

REST login route path. Default /api/account/login (the spine convention).

type string

readonly

password_path?

REST password-change route path. Default /api/account/password.

type string

readonly

concurrent_transactions?

Whether the backend serves concurrent logins — each its own database transaction — at once, for the burst-of-correct-logins case. Default false: a backend on one in-process connection (pglet-wasm, PGlite) refuses a second top-level transaction while one runs, so concurrent successful logins answer 500 there whatever the limiter does.

type boolean

readonly

base_url?

The backend's base URL, for the repeated-X-Forwarded-For case, which needs a raw socket to send two header lines. null (the default) skips that case.

type string | null

readonly

LogoutButton
#

ui/LogoutButton.svelte view source

accepts children

import LogoutButton from '@fuzdev/fuz_app/ui/LogoutButton.svelte';

onclick?

type (MouseEventHandler<HTMLButtonElement>) | null
optional

children?

type Snippet<[]>
optional

external types

Omit<ComponentProps<typeof PendingButton>, 'pending' | 'onclick' | 'children'>

LogoutInput
#

auth/account_route_schema.ts view source

value + type

null import {LogoutInput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Input for POST /logout. Session identity flows through the cookie.

LogoutOutput
#

auth/account_route_schema.ts view source

value + type

{ ok: true; username: string; } import {LogoutOutput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Output for POST /logout. Includes the revoked account's username for UI redraw.

ok

type true

username

type string

make_cross_backend_project
#

testing/cross_backend/make_cross_backend_project.ts view source

({ name, global_setup, include, exclude, group_order, test_timeout, hook_timeout }: CrossBackendProjectOptions): { extends: true; test: { name: string; include: string[]; exclude: string[]; globalSetup: string[]; isolate: false; fileParallelism: false; sequence: { ...; }; testTimeout: number; hookTimeout: number; }; } import {make_cross_backend_project} from '@fuzdev/fuz_app/testing/cross_backend/make_cross_backend_project.js';

Build a single cross-backend vitest project config. Spread the results into test.projects in the consumer's vite.config.ts. isolate: false + fileParallelism: false because a project shares one spawned backend across its files, and testTimeout / hookTimeout are always emitted so a spawned-backend case is never held to vitest's 5 s / 10 s defaults.

__0

returns

{ extends: true; test: { name: string; include: string[]; exclude: string[]; globalSetup: string[]; isolate: false; fileParallelism: false; sequence: { groupOrder: number; }; testTimeout: number; hookTimeout: number; }; }

make_default_rust_backend_config
#

testing/cross_backend/default_backend_configs.ts view source

(opts: MakeDefaultRustBackendConfigOptions): BackendConfig import {make_default_rust_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

Shared builder for Rust-family backends. Owns the common env baseline (RUST_LOG, HOST, port, real Postgres, cookie keys, bootstrap token path, the FUZ_TESTING_RESET_DB_ON_STARTUP=true self-wipe gate) plus the 120s startup window for cargo's first-run build cost.

opts

returns

BackendConfig

make_default_ts_backend_config
#

testing/cross_backend/default_backend_configs.ts view source

(opts: MakeDefaultTsBackendConfigOptions): BackendConfig import {make_default_ts_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

Shared builder for TS-family backends (Deno + Node). Owns the common env baseline (NODE_ENV, HOST, PORT, in-memory pglet, cookie keys, bootstrap token path) so per-backend factories only declare what genuinely differs.

opts

returns

BackendConfig

MakeDefaultRustBackendConfigOptions
#

testing/cross_backend/default_backend_configs.ts view source

MakeDefaultRustBackendConfigOptions import type {MakeDefaultRustBackendConfigOptions} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

name

Diagnostic label; also names the tmpdir (default_test_backend_paths) when paths is omitted.

type string

readonly

port

TCP port the binary listens on.

type number

readonly

start_command

argv passed to the spawn (first entry is the binary).

type readonly string[]

readonly

database_url

Required — Rust needs real Postgres (an in-process WASM database isn't reachable from tokio-postgres). Consumers typically supply 'postgres://localhost/{repo}_test_{name}'.

type string

readonly

extra_env?

Merged on top of the generic env baseline; later keys win.

type Readonly<Record<string, string>>

readonly

capabilities

Every capability, stated: spread rust_default_capabilities and add each MountFlag and the testing_backdoors list. Required, so a mount flag or the backdoor list left out is a type error rather than a suite that silently skips.

type BackendCapabilities

readonly

paths?

Pre-computed paths; defaults to default_test_backend_paths(name).

type TestBackendPaths

readonly

bootstrap_overrides?

Override individual bootstrap fields (username/password/token).

type Partial<BackendBootstrapConfig>

readonly

port_env_var?

Env-var name the binary reads for its port. Defaults to 'PORT'. Consumers whose binary reads a different name (e.g. 'ZZZ_PORT') override.

type string

readonly

rust_log?

Initial value for RUST_LOG. Defaults to 'info'. Consumers pass their binary-specific module filter (e.g. 'info,zzz_server=info,testing_zzz_server=info').

type string

readonly

cookie_name?

Session cookie name the binary uses. Defaults to 'fuz_session'. Must match the consumer's session config (see the TS builder's note).

type string

readonly

daemon_token_dir_env?

Env var naming the directory the binary writes its daemon token under ({dir}/run/daemon_token), e.g. 'MY_APP_DAEMON_TOKEN_DIR'. Given, it is set to the directory that layout puts the token at the bootstrap's daemon_token_path (to_daemon_token_dir) — the file spawn_backend reads — and a path in another layout throws. Omit it for a binary that derives the path otherwise.

type string

readonly

MakeDefaultTsBackendConfigOptions
#

testing/cross_backend/default_backend_configs.ts view source

MakeDefaultTsBackendConfigOptions import type {MakeDefaultTsBackendConfigOptions} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

name

Diagnostic label; also names the tmpdir (default_test_backend_paths) when paths is omitted.

type string

readonly

port

TCP port the binary listens on.

type number

readonly

start_command

argv passed to the spawn (first entry is the binary).

type readonly string[]

readonly

database_url?

Defaults to 'memory://' (in-memory pglet).

type string

readonly

extra_env?

Merged on top of the generic env baseline; later keys win.

type Readonly<Record<string, string>>

readonly

capabilities

Every capability, stated: spread ts_default_capabilities and add each MountFlag and the testing_backdoors list. Required, so a mount flag or the backdoor list left out is a type error rather than a suite that silently skips.

type BackendCapabilities

readonly

paths?

Pre-computed paths; defaults to default_test_backend_paths(name).

type TestBackendPaths

readonly

bootstrap_overrides?

Override individual bootstrap fields (username/password/token).

type Partial<BackendBootstrapConfig>

readonly

port_env_var?

Env-var name the binary reads for its port. Defaults to 'PORT'. Consumers whose binary reads a different name (e.g. 'ZZZ_PORT') override.

type string

readonly

cookie_name?

Session cookie name the binary's create_session_config uses. Defaults to 'fuz_session'. Must match the consumer's session config — the harness threads the _testing_reset-returned keeper cookie into its jar under this name, so a mismatch surfaces as 401s on the create_account path (e.g. fuz_forge uses 'fuz_forge_session').

type string

readonly

daemon_token_dir_env?

Env var naming the directory the binary writes its daemon token under ({dir}/run/daemon_token), e.g. 'MY_APP_DAEMON_TOKEN_DIR'. Given, it is set to the directory that layout puts the token at the bootstrap's daemon_token_path (to_daemon_token_dir) — the file spawn_backend reads — and a path in another layout throws. Omit it for a binary that derives the path otherwise.

type string

readonly

MASKED_VALUE
#

env/mask.ts view source

"***" import {MASKED_VALUE} from '@fuzdev/fuz_app/env/mask.js';

Placeholder displayed in place of secret values.

MAX_INBOUND_DISPATCHES_PER_CONNECTION
#

actions/ws_socket_session.ts view source

128 import {MAX_INBOUND_DISPATCHES_PER_CONNECTION} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

Max requests one socket may have dispatching at once. A request that arrives past it is answered queue_overflow with its id (WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE) and not dispatched; the socket stays open, and the requests already running keep answering.

A runtime adapter can hand frames over without awaiting the previous one's dispatch (@hono/node-ws does not await onMessage), so without a ceiling one socket could hold any number of handlers — and their pooled database work — at once. Shedding rather than queueing keeps the socket reading, so a reply to a server-initiated request still reaches the handler awaiting it. Generous, so ordinary pipelining never meets it. Same value as the Rust spine's MAX_INBOUND_DISPATCHES_PER_CONNECTION.

Counted are the requests that passed envelope validation and are not yet answered — the entries of the socket's cancellation map. Notifications, replies to server-initiated requests, and refused frames never count.

MAX_PRE_ADMISSION_FRAMES
#

actions/ws_socket_session.ts view source

256 import {MAX_PRE_ADMISSION_FRAMES} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

Max inbound frames one socket may have queued while it is still opening — between the handshake and the end of the credential re-check plus on_socket_open. One more closes the socket with WS_CLOSE_POLICY_VIOLATION.

Frames sent before the socket is open wait rather than dispatch (nothing runs on a credential that has not been re-read), and a runtime adapter hands them over with no backpressure, so the wait needs a bound. This is the count half; PRE_ADMISSION_QUEUE_BYTES_FACTOR is the byte half.

Sized so a client flushing its durable queue on reopen never meets it at the default DEFAULT_QUEUE_MAX_SIZE (actions/socket.svelte.ts). A consumer that raises the client's queue above this cap makes a full flush overflow it: the socket closes with WS_CLOSE_POLICY_VIOLATION, the flushed requests fail, and the client reconnects.

MAX_SECURE_FILE_SIZE
#

runtime/secure_file.ts view source

4096 import {MAX_SECURE_FILE_SIZE} from '@fuzdev/fuz_app/runtime/secure_file.js';

Maximum size for secure files. Prevents DoS from unexpectedly large files; 4 KiB is generous for token/key material. Matches the Rust twin's MAX_SECURE_FILE_SIZE.

MAX_TOKEN_SCOPE_BYTES
#

MenuLink
#

merge_error_schemas
#

http/schema_helpers.ts view source

(spec: { method: RouteMethod; auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }; ... 4 more ...; errors?: Partial<...> | undefined; }, middleware_errors?: Partial<...> | ... 1 more ... | undefined): Partial<...> | null import {merge_error_schemas} from '@fuzdev/fuz_app/http/schema_helpers.js';

Merge auto-derived, middleware, and explicit error schemas for a route spec.

Merge order: derived -> middleware -> explicit route errors. Later layers override earlier ones for the same status code.

spec

the route spec (reads method, auth, input, params, query, rate_limit, errors)

type { method: RouteMethod; auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }; ... 4 more ...; errors?: Partial<...> | undefined; }

middleware_errors?

errors contributed by middleware whose path matches the route

type Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> | null
optional

returns

Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> | null

merged error schemas, or null if empty

MethodCoverageEntry
#

testing/cross_backend/method_coverage.ts view source

MethodCoverageEntry import type {MethodCoverageEntry} from '@fuzdev/fuz_app/testing/cross_backend/method_coverage.js';

One row of the live-RPC-method coverage manifest.

method

The RPC method name (action.spec.method).

type string

readonly

tier

How this method is covered.

type MethodCoverageTier

readonly

capability?

The BackendCapabilities flag gating this method's cross suite, when it is capability-gated. Typed against the real interface so a stale flag is a compile error. Omit for ungated off-surface families (always-mounted, the suite runs unconditionally) and for declared / backdoor tiers.

type "ws" | "sse" | "cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "fact_serving_admin_hash" | "ready" | "peer_request" | "ws_account_actions" | "ws_role_gated" | "testing_backdoors" | "account_lifecycle" | "account_status" | "ws_handshake_pipelining" | "ws_refusal_body"

readonly

suite?

The cross-backend suite (or test file) that covers this method. Required for off_surface — the manifest's whole point is to name the suite a method without auto-enumeration relies on. Optional for declared (auto-enumerated) / backdoor (infra).

type string

readonly

note?

Optional free-text note printed nowhere — documentation for the reader.

type string

readonly

MethodCoverageTier
#

testing/cross_backend/method_coverage.ts view source

MethodCoverageTier

type "declared" | "off_surface" | "backdoor"

import type {MethodCoverageTier} from '@fuzdev/fuz_app/testing/cross_backend/method_coverage.js';

How a live-mounted RPC method earns its cross-backend coverage.

middleware_applies
#

http/schema_helpers.ts view source

(mw_path: string, route_path: string): boolean import {middleware_applies} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a middleware path pattern applies to a route path.

Supports Hono-style patterns:

  • /api/* matches /api/anything
  • /api/zap/* matches /api/zap/runs but not /api/account/login
  • Exact match: /health matches /health

mw_path

type string

route_path

type string

returns

boolean

middleware_paths_overlap
#

http/schema_helpers.ts view source

(a: string, b: string): boolean import {middleware_paths_overlap} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if two middleware path patterns can match a common route path — the pattern-against-pattern form of middleware_applies, over the same grammar (*, an exact path, or a /prefix/* that also matches /prefix).

a

one Hono-style pattern

type string

b

the other

type string

returns

boolean

middleware_spec_applies
#

http/schema_helpers.ts view source

(spec: { readonly path: string; readonly carve_outs?: readonly { readonly path: string; }[] | null | undefined; }, route_path: string): boolean import {middleware_spec_applies} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a middleware layer runs on a route path: its path pattern applies (middleware_applies) and none of its carve_outs does.

spec

the layer's path and optional carve_outs

type { readonly path: string; readonly carve_outs?: readonly { readonly path: string; }[] | null | undefined; }

route_path

the route path to check

type string

returns

boolean

MiddlewareCarveOut
#

http/middleware_spec.ts view source

MiddlewareCarveOut import type {MiddlewareCarveOut} from '@fuzdev/fuz_app/http/middleware_spec.js';

Paths a middleware layer explicitly does not run on, and why — the named exception to a layer that covers every path by default (the origin check).

path

Hono-style pattern (/hooks/*, /health), matched like MiddlewareSpec.path.

type string

readonly

reason

Why these paths are safe without the layer — required, so a carve-out is an argument, not an omission.

type string

readonly

MiddlewareSpec
#

http/middleware_spec.ts view source

MiddlewareSpec import type {MiddlewareSpec} from '@fuzdev/fuz_app/http/middleware_spec.js';

A named middleware layer.

name

type string

path

type string

handler

type MiddlewareHandler

errors?

Error response schemas this middleware can produce, keyed by HTTP status code.

type Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

carve_outs?

Paths under path the layer does not run on. The handler enforces them itself (Hono mounts by path alone); the field is what keeps the generated surface from claiming the layer's errors on those routes.

type readonly MiddlewareCarveOut[]

Migration
#

db/migrate.ts view source

Migration import type {Migration} from '@fuzdev/fuz_app/db/migrate.js';

A single migration: a name + an up function applied inside a transaction.

Throw from up to roll back the entire chain.

name

type string

up

type (db: Db): Promise<void>

db

type Db
returns Promise<void>

MigrationError
#

db/migrate.ts view source

import {MigrationError} from '@fuzdev/fuz_app/db/migrate.js';

Tagged error thrown by run_migrations and baseline.

Branch on .kind; the message carries an operator-facing remediation hint.

inheritance

extends: Error

kind

type MigrationErrorKind

readonly

namespace?

type string

readonly

at_index?

type number

readonly

unknown_names?

type readonly string[]

readonly

constructor

type new (kind: MigrationErrorKind, message: string, context?: MigrationErrorContext | undefined): MigrationError

kind

message

type string

context?

optional

MigrationErrorContext
#

db/migrate.ts view source

MigrationErrorContext import type {MigrationErrorContext} from '@fuzdev/fuz_app/db/migrate.js';

Structured context passed alongside a MigrationError.

namespace?

type string

at_index?

type number

unknown_names?

type readonly string[]

cause?

type unknown

MigrationErrorKind
#

db/migrate.ts view source

MigrationErrorKind

type "namespace-reserved" | "binary-older-than-db" | "name-divergence-at-N" | "migration-failed" | "baseline-name-not-in-code" | "baseline-name-out-of-order" | "baseline-namespace-already-populated"

import type {MigrationErrorKind} from '@fuzdev/fuz_app/db/migrate.js';

Tagged error vocabulary for run_migrations and baseline.

Callers branch on .kind rather than matching error messages — message text is for operators, not control flow.

MigrationNamespace
#

db/migrate.ts view source

MigrationNamespace import type {MigrationNamespace} from '@fuzdev/fuz_app/db/migrate.js';

A named group of ordered migrations.

A consumer builds one as a plain literal under a name of its own; the spine's namespaces (reserved_migration_namespaces) come from create_spine_migration_namespace. Array index = position in the chain. Pre-stable: bodies, names, and positions can change between versions (consumers re-bootstrap on upgrade).

namespace

type string

migrations

type Migration[]

MigrationResult
#

db/migrate.ts view source

MigrationResult import type {MigrationResult} from '@fuzdev/fuz_app/db/migrate.js';

Result of running migrations for a single namespace.

namespace

type string

applied_names

Migrations applied in this run, in sequence-ascending (execution) order.

type string[]

MigrationStatus
#

db/status.ts view source

MigrationStatus import type {MigrationStatus} from '@fuzdev/fuz_app/db/status.js';

Migration status for a single namespace.

namespace

type string

applied_names

Names of migrations recorded in the tracker, sequence-ascending.

type string[]

pending_names

Names of code migrations not yet applied (suffix of the code array).

type string[]

up_to_date

Whether applied_names is the full code array with no name divergence (no pending work, no diverged history).

type boolean

divergence?

The first applied/code divergence, if any. Absent when the applied names are a clean prefix of the code's list (the only state the runner boots against). Present means a divergent bootstrap history — a re-bootstrap (drop + migrate) is needed.

type Divergence

MigrationTracker
#

testing/schema_introspect.ts view source

value + type

{ entries: { namespace: string; name: string; sequence: number; }[]; } import {MigrationTracker} from '@fuzdev/fuz_app/testing/schema_introspect.js';

The full schema_version tracker as a deterministically-ordered list, wrapped in an object so it round-trips cleanly as a JSON-RPC result. Sorted by (namespace, sequence) on capture.

entries

type MigrationTrackerEntry[]

MigrationTrackerDiff
#

testing/schema_parity.ts view source

MigrationTrackerDiff

type { readonly kind: "tracker_row_only_in"; readonly where: "a" | "b"; readonly namespace: string; readonly name: string; } | { readonly kind: "tracker_sequence_differs"; readonly namespace: string; readonly name: string; readonly a: number; readonly b: number; }

import type {MigrationTrackerDiff} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structured migration-identity drift entry. Keyed on (namespace, name) — the schema_version PK — so a name rename and a partitioning change both surface as tracker_row_only_in, and a re-order surfaces as tracker_sequence_differs.

MigrationTrackerEntry
#

testing/schema_introspect.ts view source

value + type

{ namespace: string; name: string; sequence: number; } import {MigrationTrackerEntry} from '@fuzdev/fuz_app/testing/schema_introspect.js';

A single schema_version tracker row — the migration-identity primitive.

Where SchemaSnapshot is provenance-agnostic (it captures the resulting tables and deliberately *excludes* the tracker), this is the tracker: the (namespace, name, sequence) the migration runner records per applied migration. sequence carries order; name carries identity (the PK is (namespace, name)). The cross-impl gate diffs these between the two bootstrapped spines so a migration-name or partitioning drift — invisible to the schema snapshot — is a gated fact, not a latent interop break.

namespace

Migration namespace (e.g. fuz_auth, fuz_cell).

type string

name

Migration name within the namespace (e.g. full_cell_schema).

type string

sequence

Per-namespace apply order, starting at 0.

type number

MinimalActionEnvironment
#

testing/ws_round_trip.ts view source

import {MinimalActionEnvironment} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Minimal ActionEventEnvironment for tests that instantiate an ActionDispatcher without pulling in the full runtime. Pre-loads a spec map from the supplied list.

inheritance

executor

type "frontend" | "backend"

constructor

type new (specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[]): MinimalActionEnvironment

specs

type readonly ActionSpecUnion[]

lookup_action_handler

type (): undefined

returns undefined

lookup_action_spec

type (method: string): { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; } | undefined

method

type string
returns ActionSpecUnion | undefined

mint_file_fact_url
#

db/file_fact_url.ts view source

(shard: string, rest: string): string & $brand<"FileFactUrl"> import {mint_file_fact_url} from '@fuzdev/fuz_app/db/file_fact_url.js';

Construct a canonical file:<shard>/<rest> URL. The writer side (db/fact_disk_storage.ts) assembles the shape from a freshly-computed hash via fact_disk_path; this helper centralizes the literal so a future shape change is a single edit.

shard

type string

rest

type string

returns

FileFactUrl

mint_narrowed_api_token
#

testing/cross_backend/narrowed_api_token.ts view source

(options: MintNarrowedApiTokenOptions): Promise<string> import {mint_narrowed_api_token} from '@fuzdev/fuz_app/testing/cross_backend/narrowed_api_token.js';

Mint a narrowed api token and return its raw bearer value.

options

returns

Promise<string>

the raw token, for an Authorization: Bearer header

throws

  • Error - when the mint is refused, naming the status and error

mint_test_session
#

testing/app_server.ts view source

(options: MintTestSessionOptions): Promise<{ session_cookie: string; }> import {mint_test_session} from '@fuzdev/fuz_app/testing/app_server.js';

Mint a real auth_session row for an existing account and return a validly-signed session cookie value referencing it. Test-only — the forge behind the cross-backend expiry conformance cases (the expired_session principal): pass a negative expires_in_seconds to produce an *expired server-side session* whose signed cookie envelope is still well-formed. Both the TS _testing_mint_session action and the in-process fixture.mint_expired_session() seam call this so the write semantics match across transports.

options

returns

Promise<{ session_cookie: string; }>

mutates

  • options.db — inserts one auth_session row.

MintNarrowedApiTokenOptions
#

testing/cross_backend/narrowed_api_token.ts view source

MintNarrowedApiTokenOptions import type {MintNarrowedApiTokenOptions} from '@fuzdev/fuz_app/testing/cross_backend/narrowed_api_token.js';

app

Transport the mint goes over — it must carry an allowed Origin (the default rpc_call one does).

type RpcCallApp

readonly

rpc_path

RPC endpoint path account_token_create is mounted on.

type string

readonly

session_headers

Session credential headers of the account that owns the token — the mint is session-only.

type Record<string, string>

readonly

methods

The RPC methods the token admits; every other method and every non-RPC surface refuses it.

type readonly string[]

readonly

name?

Token label. Default 'narrowed test token'.

type string

readonly

MintTestSessionOptions
#

testing/app_server.ts view source

MintTestSessionOptions import type {MintTestSessionOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Options for mint_test_session.

db

type Db

keyring

type Keyring

session_options

type SessionOptions<string>

account_id

Account the minted session belongs to.

type string

expires_in_seconds

Session lifetime offset in seconds applied to NOW() for the auth_session.expires_at row. A negative value backdates the row so the authoritative DB-row expiry gate (query_session_get_valid — WHERE expires_at > NOW()) rejects it, while the returned cookie's own signed payload stays valid (future). Resolution therefore passes the cookie-payload check in parse_session and is refused at the DB-row gate — the gate the in-process payload-expiry tests never reach and the one that structurally needs a server-side mint.

type number

MissingColumns
#

db/schema_ready.ts view source

MissingColumns import type {MissingColumns} from '@fuzdev/fuz_app/db/schema_ready.js';

Columns the live DB is missing for a table the expected schema declares.

table

type string

columns

type string[]

MockExitError
#

runtime/mock.ts view source

import {MockExitError} from '@fuzdev/fuz_app/runtime/mock.js';

Error thrown when mock runtime.exit() is called.

Tests can catch this to verify exit behavior.

inheritance

extends: Error

code

type number

readonly

constructor

type new (code: number): MockExitError

code

type number

MockRuntime
#

runtime/mock.ts view source

MockRuntime import type {MockRuntime} from '@fuzdev/fuz_app/runtime/mock.js';

Mock RuntimeDeps with observable state for assertions.

inheritance

extends: RuntimeDeps

mock_env

Mock environment variables.

type Map<string, string>

mock_fs

Mock file system (path -> content).

type Map<string, string>

mock_fs_bytes

Mock binary file system (path -> bytes).

type Map<string, Uint8Array<ArrayBufferLike>>

mock_fs_modes

Recorded POSIX modes (path -> mode), written by write_text_file's mode option. A file with no recorded mode reads as 0o644 (the default-umask hazard) — so read_secure_file refuses it unless the writer asked for 0o600 explicitly, mirroring the real runtimes.

type Map<string, number>

mock_dirs

Mock directories that exist.

type Set<string>

exit_calls

Exit calls recorded (exit codes).

type number[]

command_calls

Commands executed. Captures options when passed so tests can assert cwd/timeout/signal.

type { cmd: string; args: string[]; options?: RunCommandOptions | undefined; }[]

command_inherit_calls

Commands executed with inherit.

type { cmd: string; args: string[]; }[]

stdout_writes

Stdout writes recorded.

type string[]

mock_command_results

Mock command results (cmd -> result).

type Map<string, CommandResult>

stdin_buffer

Stdin buffer for input simulation.

type Uint8Array<ArrayBufferLike> | null

fetch_calls

Fetch calls recorded.

type { input: string | URL | Request; init?: RequestInit | undefined; }[]

mock_fetch_responses

Mock fetch responses (URL substring -> Response).

type Map<string, Response>

MountFlag
#

testing/cross_backend/capabilities.ts view source

"ws" | "sse" | "cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "fact_serving_admin_hash" | "ready" | "peer_request" | "ws_account_actions" | "ws_role_gated"

type "ws" | "sse" | "cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "fact_serving_admin_hash" | "ready" | "peer_request" | "ws_account_actions" | "ws_role_gated"

import type {MountFlag} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

The capabilities that describe a backend's own mounts, never a runtime trait: what a consumer chooses to serve. They have no family default — state each flag in your backend config's capabilities; a missing one would silently skip its suites, so leaving one out is a type error.

Pick keeps the list honest: a name that is not a BackendCapabilities flag fails to compile.

needs_actor
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {needs_actor} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route declares an actor axis ('optional' or 'required'). Equivalent to "the dispatcher's authorization phase may resolve an actor for this request" — which by registry-time invariant 2 also means the input (or query, on REST GETs) declares acting?: ActingActor.

auth

returns

boolean

next_credential_deadline_delay
#

auth/credential_deadline.ts view source

(deadline: number, now: number): number | null import {next_credential_deadline_delay} from '@fuzdev/fuz_app/auth/credential_deadline.js';

The next timer toward deadline from now: what remains, capped at CREDENTIAL_DEADLINE_TIMER_CAP_MS, or null once the deadline is reached.

deadline

the monotonic deadline

type number

now

the monotonic clock reading

type number

returns

number | null

the next delay in ms, or null when the deadline has passed

NginxFactsValidation
#

server/x_accel.ts view source

NginxFactsValidation import type {NginxFactsValidation} from '@fuzdev/fuz_app/server/x_accel.js';

Result of the facts-location check.

ok

true when every rule passes.

type boolean

errors

Fatal issues, each naming the offending location.

type string[]

no_nested_transaction
#

db/db.ts view source

<T>(fn: (tx_db: Db) => Promise<T>): Promise<T> import {no_nested_transaction} from '@fuzdev/fuz_app/db/db.js';

Sentinel transaction function for transaction-scoped Db instances.

Used by driver adapters when constructing the inner Db passed to transaction callbacks.

fn

type (tx_db: Db) => Promise<T>

returns

Promise<T>

throws

  • Error - always — nested transactions are not supported

NO_REBUILD_ENV
#

testing/cross_backend/cargo_binary.ts view source

"FUZ_TESTING_NO_REBUILD" import {NO_REBUILD_ENV} from '@fuzdev/fuz_app/testing/cross_backend/cargo_binary.js';

Env var skipping the build, for iteration on a known-current binary: 1 or true skips it, any other value (0, false, empty) builds.

NoActorsOnAccountError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "no_actors_on_account"; } import {NoActorsOnAccountError} from '@fuzdev/fuz_app/http/error_schemas.js';

error

type "no_actors_on_account"

[key: string]

type unknown

NON_BROWSABLE_TABLES
#

http/db_routes.ts view source

readonly string[] import {NON_BROWSABLE_TABLES} from '@fuzdev/fuz_app/http/db_routes.js';

Tables the browser can never expose, whatever browsable_tables names — the credential floor, subtracted from the consumer's allowlist and not overridable:

  • account — the one surface that would return the argon2 password-hash corpus in bulk (every other read path excludes the hash by projection).
  • auth_session / api_token — credential digests plus per-account session/token metadata.
  • bootstrap_lock — the first-admin gate; its row is load-bearing state, not content.

A table on the floor answers exactly like one that doesn't exist (404 table_not_found) so the browser doesn't confirm its presence.

NON_DELETABLE_TABLES
#

http/db_routes.ts view source

readonly string[] import {NON_DELETABLE_TABLES} from '@fuzdev/fuz_app/http/db_routes.js';

Tables this endpoint refuses to delete rows from, whatever their key shape.

Two kinds, one rule — a generic storage endpoint has no business deleting a row whose meaning lives in the domain layer:

  • audit_log — the trail and the tamper path would otherwise be the same surface. It is also part of how revocation propagates: the revocation guard (create_revocation_guard) closes live connections by listening to audit events, so a raw row delete here is invisible to it. bootstrap_lock — deleting the singleton leaves check_bootstrap_status advertising an open bootstrap window on the next boot that bootstrap_account.ts's atomic flip then always refuses.
  • app_settings — deleting the singleton makes every settings read throw.
  • schema_version — the migration tracker. Already refused by the single-column-primary-key rule (its key is (namespace, name)), but that is incidental: naming it here keeps the protection if the key shape ever changes.

Consumers extend this with DbRouteOptions.non_deletable_tables — the builtin set is a floor, never replaced.

noop_connection_closer
#

normalize_differential_response
#

normalize_ip
#

http/proxy.ts view source

(ip: string): string import {normalize_ip} from '@fuzdev/fuz_app/http/proxy.js';

Normalize an IP address for consistent matching and storage.

Delegates to canonicalize_ip from http/ip_canonical.ts — collapses RFC 5952-equivalent IPv6 forms (::1, ::0001, 0:0:0:0:0:0:0:1) into a single key, emits IPv4-mapped IPv6 in dotted form, and strips the ::ffff: prefix from dotted IPv4-mapped values so the bucket collapses to plain IPv4.

  • Lowercases for case-insensitive IPv6 comparison.
  • Idempotent: calling twice produces the same result.
  • Safe on non-IP strings: normalize_ip('unknown') returns 'unknown'. Malformed inputs ('attacker:controlled', '::1\n', '203.0.113.1:8080') pass through unchanged so downstream validate_ip_strict can still reject them — canonicalization never erases the malformed-form signal.

ip

type string

returns

string

NotificationSender
#

auth/role_grant_offer_notifications.ts view source

NotificationSender import type {NotificationSender} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Narrow structural capability for sending a JSON-RPC notification to every socket bound to an account.

ConnectionRegistry satisfies this interface — its send_to_account(account_id, message) signature accepts the broader JsonrpcMessageFromServerToClient type, which is contravariantly compatible with JsonrpcNotification here. The interface stays local so handlers don't couple to the concrete registry, and tests can inject a capturing stub with no WS machinery.

Returns the number of sockets the notification was sent to — callers typically ignore it (used by telemetry / tests).

send_to_account

type (account_id: string & $brand<"Uuid">, message: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }): number

account_id

type Uuid

message

returns number

OFFER_GRANTOR_USERNAME
#

testing/cross_backend/role_grant_offer_enumeration.ts view source

"offer_grantor" import {OFFER_GRANTOR_USERNAME} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_enumeration.js';

Username of the bootstrap-seeded single-actor admin grantor. The entrypoint seeds it via `extra_accounts: [{username: OFFER_GRANTOR_USERNAME, roles: [ROLE_ADMIN]}]`. A *separate* account is required (an account can't offer to itself), and it must be single-actor so its role_grant_offer_create resolves without an acting selector — which rules out the now-multi-actor keeper and fixture.create_account (whose internal invite_create runs as the multi-actor keeper and would hit actor_required).

offer_pending_predicate
#

auth/role_grant_sql.ts view source

(alias: string): string import {offer_pending_predicate} from '@fuzdev/fuz_app/auth/role_grant_sql.js';

The still-pending predicate for a role grant offer — every OFFER_TERMINAL_COLUMNS entry IS NULL, AND-joined.

Expiry is not part of it: an expired offer that no sweep has terminated is still pending here, and each read that must refuse one adds its own expires_at condition.

alias

the table alias qualifying the columns, or '' for unqualified columns

type string

returns

string

the predicate, with no surrounding parentheses

OFFER_TERMINAL_COLUMNS
#

auth/role_grant_sql.ts view source

readonly ["accepted_at", "declined_at", "retracted_at", "superseded_at"] import {OFFER_TERMINAL_COLUMNS} from '@fuzdev/fuz_app/auth/role_grant_sql.js';

The role_grant_offer timestamp columns that each end an offer. An offer is pending while every one is null.

omit_columns
#

db/sql_columns.ts view source

<T extends string>(columns: readonly T[], ...omitted: NoInfer<T>[]): readonly T[] import {omit_columns} from '@fuzdev/fuz_app/db/sql_columns.js';

Drop named columns from a *_COLUMNS const, preserving projection order — for a read that deliberately narrows the row (a client-safe listing, a metadata read that skips a payload column).

With an as const column array the omitted names are checked at compile time; the runtime check covers plain string[] consts (a consumer's).

columns

the column names

type readonly T[]

omitted

column names to leave out

type NoInfer<T>[]

returns

readonly T[]

the columns without the omitted ones, in projection order

generics

omit_columns<T extends string>
T
constraint string

throws

  • Error - when an omitted name isn't in the list — a typo here would silently keep the column it meant to hide

open_session_ws
#

testing/cross_backend/ws_endpoint.ts view source

(target: WsEndpointTarget, account: WsSessionAccount, options: OpenSessionWsOptions): Promise<WsTransportClient> import {open_session_ws} from '@fuzdev/fuz_app/testing/cross_backend/ws_endpoint.js';

Open a real-upgrade socket on target as account's session: its session cookie on the upgrade, target.origin (or base_url) as the Origin.

The cookie is the static one account.create_session_headers carries, not whatever fixture.transport's jar holds now, so a case that rotates the session through fixture.transport first upgrades on the stale cookie.

target

the backend's WebSocket endpoint

account

the account whose session the socket upgrades on

options

whether to wait for admission, plus transport extras

returns

Promise<WsTransportClient>

the open socket

throws

  • Error - if the account carries no session cookie, or the upgrade (or the admission heartbeat) fails

OpenSessionWsOptions
#

testing/cross_backend/ws_endpoint.ts view source

OpenSessionWsOptions import type {OpenSessionWsOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_endpoint.js';

Options for open_session_ws.

inheritance

extends: Pick< WsTransportOptions, 'default_timeout_ms' | 'on_request' | 'headers' >

admitted

Round-trip heartbeat before resolving, so the backend has admitted the connection (create_admitted_ws_transport) — needed by a case that waits for a push or relies on registration order. false resolves once the socket is open (create_ws_transport), for a case that only round-trips or counts its own requests.

type boolean

readonly

OpenSignupToggle
#

ORIGIN_CONFORMANCE_URL
#

ORIGIN_MIDDLEWARE_PATH
#

auth/middleware.ts view source

"*" import {ORIGIN_MIDDLEWARE_PATH} from '@fuzdev/fuz_app/auth/middleware.js';

The Hono path pattern the origin check mounts on — every path. A request to any route, the static fallback included, carrying a foreign Origin is refused; one with no Origin (a probe, a CLI client) passes. The only way out is a named carve-out with a reason (AuthMiddlewareOptions.origin_carve_outs). The Rust spine's fuz_http::compose_stack checks every route group the same way.

origin_pattern_of
#

http/origin.ts view source

(regexp: RegExp): OriginPattern | undefined import {origin_pattern_of} from '@fuzdev/fuz_app/http/origin.js';

The pattern regexp was parsed from, when parse_allowed_origins (of any fuz_app copy) built it.

regexp

a matcher from an allowlist

type RegExp

returns

OriginPattern | undefined

its OriginPattern, or undefined for a matcher built another way

origin_wildcard_kind
#

http/origin_wildcard.ts view source

(pattern: OriginPattern): OriginWildcard | null import {origin_wildcard_kind} from '@fuzdev/fuz_app/http/origin_wildcard.js';

Which wildcards pattern holds, null for an exact entry.

pattern

a parsed allowlist entry

returns

OriginWildcard | null

its wildcard shape, or null

origin_wildcard_warning
#

http/origin_wildcard.ts view source

(pattern: OriginPattern): string | null import {origin_wildcard_warning} from '@fuzdev/fuz_app/http/origin_wildcard.js';

What a wildcard in pattern admits — the text admit_origin_wildcards warns or refuses with; null for an exact entry. The TS spine mounts no Host check and adds no note, so this is the Rust text with OriginWildcardOptions::DEFAULT.

pattern

a parsed allowlist entry

returns

string | null

the text, or null

OriginConformance
#

testing/cross_backend/origin_conformance.ts view source

value + type

{ parse: ({ raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | ... 11 more ... | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; })[]; match: { ...; }[]; } import {OriginConformance} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

The conformance table.

parse

type OriginConformanceParseRow[]

match

type OriginConformanceMatchRow[]

OriginConformanceCheck
#

OriginConformanceCrossTestOptions
#

OriginConformanceError
#

testing/cross_backend/origin_conformance.ts view source

value + type

{ kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | ... 7 more ... | "no_origins"; index?: number | undefined; } import {OriginConformanceError} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

One expected refusal of a parse row.

kind

type "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port" | "no_origins"

index?

type number

OriginConformanceMatchRow
#

testing/cross_backend/origin_conformance.ts view source

value + type

{ patterns: string; origin: string | null; allowed: boolean; note?: string | undefined; } import {OriginConformanceMatchRow} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

A match row: a valid allowlist, one Origin value or null, and the answer.

patterns

type string

origin

type string | null

allowed

type boolean

note?

type string

OriginConformanceParseRow
#

testing/cross_backend/origin_conformance.ts view source

value + type

{ raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | ... 10 more ... | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; }

type { raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port" | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; }

import {OriginConformanceParseRow} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

A parse row: the value parses (ok: true), or is refused with errors.

OriginCrossTestOptions
#

testing/cross_backend/origin.ts view source

RpcPathCrossSuiteOptions import type {OriginCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/origin.js';

Options for the origin parity suite. The standard RPC-dispatched cross-suite shape (setup_test / rpc_path); aliases the shared RpcPathCrossSuiteOptions rather than minting a duplicate. Origin middleware is on every spine, so no case is capability-gated and the flag bundle is not on the shape.

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

OriginPattern
#

http/origin.ts view source

OriginPattern import type {OriginPattern} from '@fuzdev/fuz_app/http/origin.js';

One validated allowlist entry, as parse_allowed_origins parsed it — the twin of the Rust fuz_http::OriginPattern.

entry

The entry as written (trimmed).

type string

readonly

regexp

The matcher should_allow_origin tests.

type RegExp

readonly

host

The host as parsed, lowercased: .-joined labels (a * label as *), or a bracketed IPv6 literal.

type string

readonly

host_labels

The host's labels, lowercased; null for a bracketed IPv6 host, which is exact.

type readonly string[] | null

readonly

port

No port, :* (any port, or none), or one exact port.

type "absent" | "any" | "exact"

readonly

OriginPatternErrorKind
#

http/origin.ts view source

value + type

"empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | ... 6 more ... | "default_port"

type "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port"

import {OriginPatternErrorKind} from '@fuzdev/fuz_app/http/origin.js';

Why one allowlist entry was refused. The wire names both spines emit — the Rust spine's OriginPatternError::kind returns the same strings.

OriginScopeCrossTestOptions
#

testing/cross_backend/origin_scope.ts view source

OriginScopeCrossTestOptions import type {OriginScopeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/origin_scope.js';

Options for the origin-scope parity suite.

inheritance

probe_path?

A public path outside /api the backend serves with 200. Default /ready.

type string

readonly

base_url?

The backend's base URL, for the over-cap row: given one, the request head is written to the socket alone, declaring a length one byte over the cap, so the 413 has to arrive before any body byte. null (the default) sends the whole body through the fixture's in-process transport.

type string | null

readonly

unrouted_status?

The status the backend's fallback answers an unrouted GET with, once the origin check has passed it. Default 404; a backend serving an SPA shell from its fallback passes 200.

type number

readonly

OriginWildcard
#

OriginWildcardEntry
#

http/origin_wildcard.ts view source

OriginWildcardEntry import type {OriginWildcardEntry} from '@fuzdev/fuz_app/http/origin_wildcard.js';

One wildcard entry an allowlist holds, with what it admits.

entry

type string

readonly

warning

type string

readonly

OrphanFactsListResult
#

db/fact_queries.ts view source

OrphanFactsListResult import type {OrphanFactsListResult} from '@fuzdev/fuz_app/db/fact_queries.js';

Summary + sample shape returned by query_orphan_facts_list. The sample is a small page (default 20 rows) shown in the admin panel so the operator has *some* visibility into what they're about to delete. Total count and total_size_bytes are over the full orphan set (matching the same predicate the delete handler will run).

count

type number

total_size_bytes

type number

sample

type { hash: FactHash; size: number; created_at: string; external_url: string | null; }[]

OUTBOUND_FRAME_OVERHEAD_BYTES
#

realtime/outbound_budget.ts view source

64 import {OUTBOUND_FRAME_OVERHEAD_BYTES} from '@fuzdev/fuz_app/realtime/outbound_budget.js';

What a waiting frame is charged against its budget on top of its payload bytes.

It stands for what a queued frame costs beyond its text, so a frame with little or no payload is not free: a budget of B never holds more than B divided by this many frames, whatever their size. A fixed charge, not a measurement of memory use. Same value as the Rust spine's constant.

OutboundBudget
#

realtime/outbound_budget.ts view source

import {OutboundBudget} from '@fuzdev/fuz_app/realtime/outbound_budget.js';

The accounting for one connection's waiting frames (module doc).

admit charges a frame that fits, release frees the charge of one that is no longer waiting, and clear frees them all.

max_bytes

The budget, in bytes.

type number

readonly

constructor

type new (max_bytes: number): OutboundBudget

max_bytes

the budget, in bytes; 0 admits one frame at a time

type number

throws

  • Error - when `max_bytes` is not a non-negative safe integer

admit

Charge a frame of frame_bytes payload bytes if it fits: when nothing is waiting, or when the charges waiting plus its own stay within the budget.

type (frame_bytes: number): boolean

frame_bytes

the frame's payload length in UTF-8 bytes

type number
returns boolean

whether the frame was admitted and charged

mutates

  • this — adds the frame's charge when it is admitted

release

Free the charge of one admitted frame that is no longer waiting.

type (frame_bytes: number): void

frame_bytes

the payload length the frame was admitted with

type number
returns void

mutates

  • this — removes the frame's charge

clear

Free every charge — the owner discarded what was waiting.

type (): void

returns void

mutates

  • this — removes every charge

to_overflow

What is waiting, for the log line of a frame that did not fit.

type (frame_bytes: number): OutboundOverflow

frame_bytes

the payload length of the frame that did not fit

type number

queued_bytes

Payload bytes waiting, without the per-frame overhead.

type number

getter

queued_frames

Frames waiting.

type number

getter

OutboundOverflow
#

realtime/outbound_budget.ts view source

OutboundOverflow import type {OutboundOverflow} from '@fuzdev/fuz_app/realtime/outbound_budget.js';

What was waiting when a frame did not fit its connection's budget.

queued_bytes

Payload bytes waiting, without the per-frame overhead the budget also charges.

type number

queued_frames

Frames waiting.

type number

frame_bytes

Payload bytes of the frame that did not fit, without its overhead.

type number

OutputTail
#

testing/cross_backend/spawn_backend.ts view source

OutputTail import type {OutputTail} from '@fuzdev/fuz_app/testing/cross_backend/spawn_backend.js';

A byte-bounded tail of a stream's output: keeps the last max_bytes written, dropping the oldest.

push

type (chunk: Buffer<ArrayBufferLike>): void

readonly

chunk

type Buffer<ArrayBufferLike>
returns void

text

The kept bytes as UTF-8, prefixed with a marker when older output was dropped.

type (): string

readonly
returns string

ParamsTestCase
#

testing/adversarial_input_cases.ts view source

ParamsTestCase import type {ParamsTestCase} from '@fuzdev/fuz_app/testing/adversarial_input_cases.js';

One adversarial params set for a route's params schema.

label

type string

params

type Record<string, string>

parse_acting
#

http/auth_shape.ts view source

(value: unknown): string | undefined import {parse_acting} from '@fuzdev/fuz_app/http/auth_shape.js';

Read an acting selector out of an unvalidated value.

The authorization phase runs before input validation on both dispatchers (see http/CLAUDE.md §Validation pipeline), so it reads the selector raw.

A malformed selector reads as omitted, not as an error. Safe because acting only ever picks among actors the resolved account already owns — a value that names nothing grants nothing — and because the typed input schema rejects it a step later, so the caller still gets a 400. Refusing here instead would put an input-shape complaint ahead of the authority gates, which is the ordering this pipeline exists to avoid.

The dispatchers pass through here so that rule has one home: the action dispatcher's read_acting and the REST authorization handler's read_route_acting. Twin of the Rust dispatcher's read_acting. The status route and the WS upgrade read the raw ?acting= through it too; neither has an input schema behind it, so there a malformed selector is final (the status route answers account-only).

value

the raw acting field, from params / query / body

type unknown

returns

string | undefined

the actor id, or undefined when absent or malformed

parse_allowed_origins
#

http/origin.ts view source

(raw: string): Result<{ origins: RegExp[]; patterns: OriginPattern[]; }, { errors: AllowedOriginsError[]; }> import {parse_allowed_origins} from '@fuzdev/fuz_app/http/origin.js';

Parses an allowlist value (conventionally the FUZ_ALLOWED_ORIGINS env var) into regex matchers for request source verification. Origin allowlisting for locally-running services — not the CSRF layer (that's SameSite: strict on session cookies plus http/simple_request.ts).

Entries are comma-separated and trimmed of ASCII whitespace. Each is written the way a browser writes an Origin — scheme://host[:port] — where a whole host label or the port may be *:

  • Exact origins: https://api.fuz.dev, http://localhost:3000
  • Wildcard labels: https://*.fuz.dev (each * is exactly one label, in any position — api.fuz.dev matches, fuz.dev and a.b.fuz.dev don't)
  • Wildcard ports: http://localhost:* (any port or none)
  • IPv6 addresses: http://[::1]:3000, https://[2001:db8::1] (exact)
  • Combined: https://*.fuz.dev:*

Refused, nothing normalized: anything but printable ASCII (punycode an international hostname), a scheme other than http or https (null, file://, chrome-extension://), a path, query, fragment or trailing slash, userinfo, a character no hostname holds, a partial wildcard, an IPv4 address not written as four canonical decimal numbers (127.1), an IPv6 address not in the form a browser sends, a port outside 1..=65535 or with leading zeros, the scheme's default port, and an empty entry. Every refused entry is reported, not just the first.

raw

the comma-separated allowlist value

type string

returns

Result

the matchers and the patterns they were parsed from, or every refusal — no_origins when the value is blank

parse_db_name
#

dev/setup.ts view source

(url: string): string | null import {parse_db_name} from '@fuzdev/fuz_app/dev/setup.js';

Extract the database name from a PostgreSQL URL.

url

type string

returns

string | null

the database name, or null if the URL is invalid or has no path

parse_dotenv
#

env/dotenv.ts view source

(content: string): Record<string, string> import {parse_dotenv} from '@fuzdev/fuz_app/env/dotenv.js';

Parse a dotenv-format string into a record.

Values wrapped in "..." have \\ → \, \" → ", \n → newline, and \r → carriage-return decoded (symmetric with the writer, update_env_content). Values wrapped in '...' are taken literally — no escape processing. Unquoted values are unchanged.

Inline comments are stripped after a closing quote (e.g. KEY="v" # c → v) and after whitespace on unquoted values (e.g. KEY=v # c → v). Unquoted values keep # literal when no whitespace precedes it so URL fragments like KEY=https://x.com#frag round-trip unchanged.

A leading export on a line is ignored, so a shell-sourceable .env (export KEY=value) parses identically to a plain KEY=value.

Trailing whitespace on unquoted values is lost (the raw value is trimmed); wrap the value in "..." or '...' to preserve surrounding spacing.

content

dotenv file content

type string

returns

Record<string, string>

parsed key-value pairs

parse_file_fact_url
#

db/file_fact_url.ts view source

(url: string): { url: string & $brand<"FileFactUrl">; shard: string; rest: string; } | null import {parse_file_fact_url} from '@fuzdev/fuz_app/db/file_fact_url.js';

Validate a string against the canonical shape. Returns the branded URL plus its parsed parts, or null on shape mismatch — callers decide whether that's a 404 (read), a skip (GC), or a hard reject (write).

url

type string

returns

{ url: string & $brand<"FileFactUrl">; shard: string; rest: string; } | null

parse_ipv4_bits
#

http/ip_canonical.ts view source

(ip: string): number | null import {parse_ipv4_bits} from '@fuzdev/fuz_app/http/ip_canonical.js';

Parse a dotted-quad IPv4 address to its 32-bit value.

Four decimal octets, each 0–255 with no leading zeros (01.2.3.4 is refused, as Rust's Ipv4Addr::from_str refuses it).

ip

type string

returns

number | null

the address as an unsigned 32-bit number, or null when ip isn't one

parse_ipv6_bits
#

http/ip_canonical.ts view source

(ip: string): bigint | null import {parse_ipv6_bits} from '@fuzdev/fuz_app/http/ip_canonical.js';

Parse an IPv6 address to its 128-bit value.

The grammar of Rust's Ipv6Addr::from_str: eight groups of one to four hex digits (either case), or fewer around a single :: standing for one or more zero groups, the last two groups optionally written as a dotted-quad IPv4 address. No zone (%eth0) — callers strip it first.

ip

type string

returns

bigint | null

the address as a bigint in [0, 2^128), or null when ip isn't one

parse_proxy_entry
#

http/proxy.ts view source

(entry: string): ParsedProxy import {parse_proxy_entry} from '@fuzdev/fuz_app/http/proxy.js';

Parse a trusted proxy entry string into a structured form.

Accepts plain IPs ('127.0.0.1', '::1') and CIDR notation ('10.0.0.0/8', 'fe80::/10'). Plain IPs are normalized (lowercase, IPv4-mapped IPv6 stripped) and validated. CIDR prefixes are validated against address family bounds. A /0 prefix is refused for either family: it trusts every client as a proxy, so the client's own X-Forwarded-For would choose the IP every per-IP limiter keys on.

entry

IP address or CIDR notation

type string

returns

ParsedProxy

throws

  • Error - on invalid IP, invalid CIDR network, NaN/negative/over-range prefix, or a `/0` prefix

parse_response_error
#

ui/ui_fetch.ts view source

(response: Response, fallback?: string | undefined): Promise<string> import {parse_response_error} from '@fuzdev/fuz_app/ui/ui_fetch.js';

Safely extract an error message from a non-ok response.

Handles responses with non-JSON bodies (e.g. HTML 404 pages) that would throw on response.json().

response

type Response

fallback?

message when no .error field is found

type string
optional

returns

Promise<string>

parse_rpc_call_result
#

testing/rpc_helpers.ts view source

(raw: RpcCallRawResult, method: string): RpcCallResult import {parse_rpc_call_result} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Parse a raw JSON-RPC response into rpc_call's discriminated result.

raw

the response rpc_call_raw returned

method

the method called, for the error message

type string

returns

RpcCallResult

the success or error branch

throws

  • Error - if the body is neither a valid `JsonrpcResponse` nor `JsonrpcErrorResponse` envelope

parse_session
#

auth/session_cookie.ts view source

<TIdentity>(signed_value: string | undefined, keyring: Keyring, options: SessionOptions<TIdentity>, now_seconds?: number | undefined): Promise<ParsedSession<...> | null | undefined> import {parse_session} from '@fuzdev/fuz_app/auth/session_cookie.js';

Parse a signed session cookie value.

The signed value format is ${encode(identity)}:${expires_at}. Tries all keys in order to support key rotation.

signed_value

the raw cookie value (signed)

type string | undefined

keyring

key ring for verification

type Keyring

options

session configuration with decode logic

type SessionOptions<TIdentity>

now_seconds?

current time in seconds (for testing)

type number
optional

returns

Promise<ParsedSession<TIdentity> | null | undefined>

ParsedSession if valid, null if invalid/expired, undefined if empty/missing

generics

parse_session<TIdentity>
TIdentity

parse_stored_token_scope
#

auth/token_scope.ts view source

(value: unknown): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } | null import {parse_stored_token_scope} from '@fuzdev/fuz_app/auth/token_scope.js';

Read a stored api_token.scope value back into a TokenScope, or null when the document is unreadable.

The DB driver may hand scope back already-parsed (JSONB → object) or as text depending on backend, so both are normalized here rather than at each call site. Lives here rather than beside the queries so the pure scope module owns every parse — and so mocking the query module doesn't drag this along.

value

type unknown

returns

TokenScope | null

parse_token_scope
#

auth/token_scope.ts view source

(raw: string): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } | null import {parse_token_scope} from '@fuzdev/fuz_app/auth/token_scope.js';

Parse a stored document, enforcing the size cap and the schema.

Fail-closed: returns null on an unreadable document, and every caller on the enforcement path treats null as "refuse the credential" rather than "allow everything". fuz_forge's hydrate path deliberately widens on a DB blip — safe there only because a role gate still applies. Here the scope *is* the gate, so widening on a parse failure would hand a narrowed token full authority exactly when something is already wrong.

raw

type string

returns

TokenScope | null

ParsedProxy
#

http/proxy.ts view source

ParsedProxy

type { type: "ip"; address: string; } | { type: "cidr"; network: bigint; prefix: number; address_type: "IPv4" | "IPv6"; }

import type {ParsedProxy} from '@fuzdev/fuz_app/http/proxy.js';

A parsed proxy entry — either an exact IP or a CIDR range.

ParsedSession
#

auth/session_cookie.ts view source

ParsedSession<TIdentity> import type {ParsedSession} from '@fuzdev/fuz_app/auth/session_cookie.js';

Result of parsing a signed session cookie.

generics

ParsedSession<TIdentity>
TIdentity

identity

The decoded identity.

type TIdentity

key_index

Index of the key that verified the signature (0 = primary).

type number

expires_at

The signed expiry, in seconds since the Unix epoch — later than the parse.

type number

PARTICIPATION_ROLE
#

testing/cross_backend/test_cell_gated_create_authorize.ts view source

"participant" import {PARTICIPATION_ROLE} from '@fuzdev/fuz_app/testing/cross_backend/test_cell_gated_create_authorize.js';

The app-role a space policy references as min_role (besides admin) — the participant role both reference spines register. Mirrors the Rust policy's "participant" literal.

Password
#

auth/password.ts view source

value + type

string import {Password} from '@fuzdev/fuz_app/auth/password.js';

Password for account creation or password change — enforces current length policy. Also usable for client-side UX validation.

PASSWORD_LENGTH_MAX
#

auth/password.ts view source

300 import {PASSWORD_LENGTH_MAX} from '@fuzdev/fuz_app/auth/password.js';

Maximum password length, in UTF-16 code units. Caps hashing cost to prevent DoS via oversized passwords.

PASSWORD_LENGTH_MIN
#

auth/password.ts view source

12 import {PASSWORD_LENGTH_MIN} from '@fuzdev/fuz_app/auth/password.js';

Minimum password length, in UTF-16 code units (OWASP recommendation).

PasswordChangeInput
#

auth/account_route_schema.ts view source

value + type

{ current_password: string; new_password: string; } import {PasswordChangeInput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Input for POST /password. current_password is minimally validated; new_password enforces the full policy.

current_password

type string

new_password

type string

PasswordChangeOutput
#

auth/account_route_schema.ts view source

value + type

{ ok: true; sessions_revoked: number; tokens_revoked: number; } import {PasswordChangeOutput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Output for POST /password. Counts are returned so the UI can summarize the revoke-all cascade.

ok

type true

sessions_revoked

type number

tokens_revoked

type number

PasswordHashDeps
#

auth/password.ts view source

PasswordHashDeps import type {PasswordHashDeps} from '@fuzdev/fuz_app/auth/password.js';

Injectable password hashing dependencies.

Groups all three password operations for injection in route factories and other callers. Use Pick<PasswordHashDeps, ...> when only a subset is needed:

examples

// Login handler only needs verification password: Pick<PasswordHashDeps, 'verify_password' | 'verify_dummy'>; // Bootstrap only needs hashing password: Pick<PasswordHashDeps, 'hash_password'>;

hash_password

type (password: string): Promise<string>

password

type string
returns Promise<string>

verify_password

type (password: string, password_hash: string): Promise<boolean>

password

type string

password_hash

type string
returns Promise<boolean>

verify_dummy

type (password: string): Promise<boolean>

password

type string
returns Promise<boolean>

PasswordProvided
#

auth/password.ts view source

value + type

string import {PasswordProvided} from '@fuzdev/fuz_app/auth/password.js';

Password submitted for login or verification — minimal validation for forward-compatibility if length requirements change.

PayloadTooLargeError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "payload_too_large"; } import {PayloadTooLargeError} from '@fuzdev/fuz_app/http/error_schemas.js';

Payload too large error — returned when the request body exceeds the size limit.

error

type "payload_too_large"

[key: string]

type unknown

peer_ping_action
#

actions/peer_ping.ts view source

RpcAction import {peer_ping_action} from '@fuzdev/fuz_app/actions/peer_ping.js';

Protocol-action tuple — spread into the server's actions array (via protocol_actions from actions/protocol.ts) so the dispatcher resolves the peer/ping handler on both the WS endpoint and (for the no-transport refusal) the HTTP RPC endpoint. A plain RpcAction literal — the handler's input/output are already pinned by peer_ping_handler's signature. Usable directly in the spine's RpcAction[] full mount and the Action[] protocol bundle alike.

peer_ping_action_spec
#

actions/peer_ping.ts view source

{ method: string; kind: "request_response"; initiator: "both"; auth: { account: "none"; actor: "none"; }; side_effects: false; input: ZodDefault<ZodObject<{ nonce: ZodOptional<ZodNumber>; timeout_ms: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; async: true; rate_limit: "ip"; description: string; } import {peer_ping_action_spec} from '@fuzdev/fuz_app/actions/peer_ping.js';

ActionSpec for the shared peer/ping. initiator: 'both' (the client→server invocation drives a server→client request); auth: public (liveness is non-sensitive; the upgrade authenticated the socket); side_effects: false (no state change — the handler only round-trips a ping).

Public is not ungoverned: rate_limit: 'ip' is the one throttle an unauthenticated method can be given (there is no account to key on). It bounds anonymous fan-out and keeps the class at parity with the Rust spec's RateLimitClass::Ip — there the no-transport refusal costs two pool checkouts per call; here the public auth shape skips the authorization phase, so the refusal is free and the class is carried for the bound alone.

peer_ping_handler
#

actions/peer_ping.ts view source

(input: { nonce?: number | undefined; timeout_ms?: number | undefined; }, ctx: ActionContext): Promise<{ nonce: number; protocol_version: number; }> import {peer_ping_handler} from '@fuzdev/fuz_app/actions/peer_ping.js';

Handler — initiates a peer/ping request back to the originating client, awaits the reply, validates it against PingResponse, and returns it.

input

ctx

returns

Promise<PingResponse>

throws

  • ThrownJsonrpcError - with `data.reason` of `peer_no_transport` (HTTP), `peer_timeout` / `peer_connection_gone` / `peer_too_many_in_flight` (transport failures), the client's own envelope (`client_error`), or `peer_ping_invalid_reply` / `peer_ping_nonce_mismatch` (bad echo).

PEER_PING_METHOD
#

peer_ping_responder
#

actions/peer_ping.ts view source

(params: unknown): { nonce: number; protocol_version: number; } import {peer_ping_responder} from '@fuzdev/fuz_app/actions/peer_ping.js';

Reusable responder for an inbound *server→client* peer/ping request — the mirror of peer_ping_handler, run by the client (frontend). Validates the request params and echoes a PingResponse carrying the issued nonce. Pure + transport-agnostic (the production twin of the cross-process test transport's echo_responder); FrontendWebsocketTransport wires it so a real frontend answers the server's liveness probe with zero consumer plumbing. Falls back to nonce: 0 on a malformed request — the server then surfaces a nonce mismatch, the correct outcome for a bad probe.

params

the inbound request's params (PingRequestParams shape)

type unknown

returns

PingResponse

the PingResponse echo to send back as the reply's result

PEER_PROTOCOL_VERSION
#

actions/peer_ping.ts view source

1 import {PEER_PROTOCOL_VERSION} from '@fuzdev/fuz_app/actions/peer_ping.js';

Wire-protocol version reported in peer/ping replies (PingResponse.protocol_version). Twin of the Rust spine's value — bump in lockstep with a breaking peer-protocol change. The far side validates the reply's *shape*, not this value, so it's informational telemetry today, but it's the hook a future protocol negotiation reads.

PeerPingWsTestOptions
#

testing/cross_backend/peer_ping_ws.ts view source

PeerPingWsTestOptions import type {PeerPingWsTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/peer_ping_ws.js';

Configuration for describe_peer_ping_ws_tests.

inheritance

setup_test

Per-test fixture producer (default_cross_process_setup(handle, ...)).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags; every case gates on capabilities.peer_request.

type BackendCapabilities

readonly

PeerRequestError
#

actions/peer_request.ts view source

PeerRequestError

type { kind: "timeout"; } | { kind: "connection_gone"; } | { kind: "too_many_in_flight"; } | { kind: "client_error"; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }

import type {PeerRequestError} from '@fuzdev/fuz_app/actions/peer_request.js';

Why a server→client peer request did not yield a client success reply.

  • timeout — the client did not answer within the deadline.
  • connection_gone — the socket closed (or was never registered) before a reply.
  • too_many_in_flight — the per-connection in-flight cap was hit.
  • client_error — the client answered with a JSON-RPC error; error is forwarded verbatim so the initiating handler can surface the client's own code / message / data.

PeerRequestOptions
#

actions/peer_request.ts view source

PeerRequestOptions import type {PeerRequestOptions} from '@fuzdev/fuz_app/actions/peer_request.js';

Per-call options for a server→client peer request.

timeout_ms?

Deadline (ms) before the request resolves timeout. Defaults to the registry's default_timeout_ms (DEFAULT_PEER_REQUEST_TIMEOUT). Untrusted remote-supplied values should be clamped shorten-only (never lengthen the server's hold on a pooled connection) — see actions/peer_ping.ts.

type number

PeerRequestOutcome
#

actions/peer_request.ts view source

PeerRequestOutcome

type { ok: true; value: JSONType; } | { ok: false; error: PeerRequestError; }

import type {PeerRequestOutcome} from '@fuzdev/fuz_app/actions/peer_request.js';

Outcome of a server→client peer request: the client's success result, or a PeerRequestError.

PendingOfferSummaryJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; role: string; scope_kind: string | null; scope_id: (string & $brand<"Uuid">) | null; from_actor_id: string & $brand<"Uuid">; from_username: string; created_at: string; expires_at: string; } import {PendingOfferSummaryJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for a pending role_grant offer surfaced in admin account listings.

Deliberately narrower than RoleGrantOfferJson: omits message and decline_reason so cross-admin visibility of the listing does not expose grantor-authored text that the audit log also withholds. Full offer payloads remain available through the offer-specific RPC surface and the audit log when admins need them.

from_username is resolved server-side so multi-admin deployments can see at a glance whose pending offer is blocking a "+ {role}" button; the resolution runs inside the listing query's parallel batch.

id

type string & $brand<"Uuid">

role

type string

scope_kind

type string | null

scope_id

type (string & $brand<"Uuid">) | null

from_actor_id

type string & $brand<"Uuid">

from_username

type string

created_at

type string

expires_at

type string

PendingPeerRequests
#

actions/peer_request.ts view source

import {PendingPeerRequests} from '@fuzdev/fuz_app/actions/peer_request.js';

Correlation registry for in-flight server→client requests, nested by connection then by the server-issued request id.

The per-connection nesting makes the in-flight count and the close-time drain O(1) and is the isolation boundary — a reply on connection B can never settle a request issued on connection A (it lands in a different inner map). An inner map is removed as soon as it empties, so an idle connection holds no entry. The registry never sends on the socket; the caller does the send between register and the first await, and routes inbound replies back via resolve.

constructor

type new (options?: PendingPeerRequestsOptions | undefined): PendingPeerRequests

options?

optional

register

Register a pending request for connection_id with a deadline, returning its server-issued s{n} id and the outcome promise (settled by a reply, the deadline, or drain). Returns null when the connection is at its in-flight cap — the caller maps that to too_many_in_flight. The promise executor runs synchronously, so the entry is registered before this returns.

type (connection_id: string & $brand<"Uuid">, timeout_ms?: number | undefined): { id: string | number; outcome: Promise<PeerRequestOutcome>; } | null

connection_id

type Uuid

timeout_ms?

type number
optional
returns { id: string | number; outcome: Promise<PeerRequestOutcome>; } | null

the allocated id + outcome promise, or null at the cap

mutates

  • this — inserts an entry into `#pending`

resolve

Settle the pending request matching an inbound reply. A success response resolves {ok: true, value: result}; an error response resolves {ok: false, error: {kind: 'client_error', error}} (forwarded verbatim).

Returns false when no entry matches — an unsolicited, cross-connection, or already-settled reply — so the caller drops it.

type (connection_id: string & $brand<"Uuid">, response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { ...; }; }): boolean

connection_id

type Uuid

response

returns boolean

whether a pending request was settled

mutates

  • this — clears the matched entry from `#pending`

settle

Force-settle one pending request with an explicit outcome (the send-failure path uses this for connection_gone). Clears the timer, drops the entry, resolves the promise. Idempotent — a no-op if the entry is already gone.

type (connection_id: string & $brand<"Uuid">, id: string | number, outcome: PeerRequestOutcome): void

connection_id

type Uuid

id

outcome

returns void

mutates

  • this — clears the entry from `#pending`

drain

Settle every pending request on a closing connection as connection_gone. O(1) — drops the connection's inner map in one hop.

type (connection_id: string & $brand<"Uuid">): void

connection_id

type Uuid
returns void

mutates

  • this — removes the connection's entry from `#pending`

size

In-flight request count — for connection_id when given, else across all connections. Telemetry / tests.

type (connection_id?: (string & $brand<"Uuid">) | undefined): number

connection_id?

type Uuid
optional
returns number

PendingPeerRequestsOptions
#

PendingSubscription
#

realtime/sse_registry.ts view source

PendingSubscription import type {PendingSubscription} from '@fuzdev/fuz_app/realtime/sse_registry.js';

A registered subscription awaiting admission — what SseRegistry.subscribe_pending returns and SseRegistry.admit takes.

Its entry is already in the registry, so every close_by_identity can reach it, but nothing is delivered to it and it does not count toward the per-scope cap until it is admitted.

unsubscribe

Remove the registration, pending or admitted. Idempotent. Every path that refuses a pending subscription calls it, and an admitted stream's on_close listener does.

type (): void

returns void

perform_action
#

actions/perform_action.ts view source

(input: PerformActionInput, deps: PerformActionDeps): Promise<PerformActionResult> import {perform_action} from '@fuzdev/fuz_app/actions/perform_action.js';

The shared dispatch core. Pure data — no Hono context, no socket. Each transport calls into this with pre-parsed inputs and binds the result to its wire shape.

Phase order: 401 → authz → 403 → 429 → 400 → handler. On the test-preset path the dispatcher skips the live authorization phase and uses the supplied pre-baked context for post-authorization checks; the 401 still fires when the harness omits auth.

input

deps

returns

Promise<PerformActionResult>

PerformActionDeps
#

actions/perform_action.ts view source

PerformActionDeps import type {PerformActionDeps} from '@fuzdev/fuz_app/actions/perform_action.js';

Per-deps inputs to perform_action. Each transport supplies its own pool-level Db and rate limiters; the dispatcher wraps in a transaction iff spec.side_effects is true.

Pool-resilient fire-and-forget effects (audit writes) run through AppDeps.audit.emit from the action factory's closure — the dispatcher never sees the audit emitter. The bound emitter owns the pool.

db

Pool-level DB. The dispatcher wraps in db.transaction for side_effects: true actions.

type Db

pending_effects

Eager fire-and-forget pool-write queue, flushed by the transport's try/finally via flush_pending_effects.

type Promise<void>[]

post_commit_effects

Deferred post-commit thunks pushed via emit_after_commit, flushed by the transport's try/finally after the handler returns.

type (() => void | Promise<void>)[]

log

Logger threaded into ActionContext.log.

type Logger

action_ip_rate_limiter

Per-IP limiter (shared across transports). null disables.

type RateLimiter | null

action_account_rate_limiter

Per-account limiter (shared across transports). null disables.

type RateLimiter | null

PerformActionInput
#

actions/perform_action.ts view source

PerformActionInput import type {PerformActionInput} from '@fuzdev/fuz_app/actions/perform_action.js';

Per-call inputs to perform_action. Each transport assembles this from its wire envelope + connection identity.

action

The resolved spec + handler (transport does method lookup).

type RpcAction

raw_params

Raw params from the wire envelope (post-JsonrpcRequest.parse, pre-spec.input.safeParse).

type unknown

request_id

JSON-RPC request id — echoed onto the response.

type JsonrpcRequestId

auth

The credential the request authenticated with (get_resolved_auth), or null for anonymous. Its account is what the authorization phase resolves the acting actor against, its type and scope what the post-authorization gates read, and its type and session hash what the handler sees on ActionContext.

type ResolvedAuth | null

client_ip

Resolved client IP ('unknown' if upstream couldn't resolve).

type string

signal

Per-request abort signal. HTTP: c.req.raw.signal. WS: AbortSignal.any([socket, request]).

type AbortSignal

notify

Send a request-scoped notification. HTTP: DEV-warn-and-drop. WS: socket-scoped.

type (method: string, params: unknown): void

method

type string

params

type unknown
returns void

connection_id?

Stable per-socket id on WS; undefined on HTTP.

type Uuid

request_client?

Initiate a server→client request on the originating WS socket and await the typed reply (ActionPeer). Present only on WebSocket dispatch; undefined on HTTP RPC (no return socket). Handlers must handle its absence — e.g. peer/ping surfaces peer_no_transport.

type (method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<PeerRequestOutcome>

method

type string

params

type JsonrpcRequestParams | undefined

options?

optional
returns Promise<PeerRequestOutcome>

preset?

Test-harness escape hatch. When set, the live authorization phase is skipped and request_context is used directly for post-authorization checks + handler dispatch. Production callers leave this undefined.

type { request_context: RequestContext | null; }

PerformActionResult
#

actions/perform_action.ts view source

PerformActionResult

type { kind: "ok"; result: unknown; } | { kind: "error"; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; status: number; }

import type {PerformActionResult} from '@fuzdev/fuz_app/actions/perform_action.js';

Discriminated result of perform_action. Each transport binds this to its wire shape: HTTP RPC folds the error into a JSON-RPC envelope and returns via c.json; WS sends the response over the socket.

PERIODIC_INTERVAL_MS_MAX
#

periodic.ts view source

number import {PERIODIC_INTERVAL_MS_MAX} from '@fuzdev/fuz_app/periodic.js';

The longest interval start_periodic accepts: the largest delay a timer honors (a signed 32-bit count of milliseconds, about 24.8 days). Past it setTimeout fires after one millisecond instead, which would turn a long interval into a pass loop.

PeriodicTask
#

periodic.ts view source

PeriodicTask import type {PeriodicTask} from '@fuzdev/fuz_app/periodic.js';

A running periodic task — the result of start_periodic.

stop

End the schedule. No pass starts once this is called, and the returned promise resolves when the pass in progress, if any, has finished — it never rejects. Await it before tearing down what the pass uses (the database, say). The wait has no bound of its own; a shutdown with a deadline races it. Safe to call more than once.

type (): Promise<void>

returns Promise<void>

PermissionError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "insufficient_permissions"; required_roles: readonly string[]; } import {PermissionError} from '@fuzdev/fuz_app/http/error_schemas.js';

Permission error — returned by require_role() and the dispatcher's post-authorization role gate when the actor's role_grants don't include any of the route's auth.roles.

required_roles carries the full disjunction the route declared (auth.roles from the new flat-record shape). Single-role specs surface as a one-element array; multi-role disjunctions show every admittable role so clients can render targeted copy ("requires admin or steward").

error

type "insufficient_permissions"

required_roles

type readonly string[]

[key: string]

type unknown

pg_error_code
#

db/pg_error.ts view source

(error: unknown): string | null import {pg_error_code} from '@fuzdev/fuz_app/db/pg_error.js';

Extract the error-code string from a caught error, for SQLSTATE checks.

A failed pool checkout (DbCheckoutError) is read through to its cause, so a connect the server refused (3D000 for a missing database, 28P01 for a bad password) keeps its code.

Other error families also carry string codes (Node FS errors: ENOENT, ENOSPC, …) — compare the result against specific SQLSTATE values, never against truthiness.

error

the caught error

type unknown

returns

string | null

the code string, or null when the value isn't an Error or carries no string code

PgFactStore
#

db/fact_store.ts view source

import {PgFactStore} from '@fuzdev/fuz_app/db/fact_store.js';

PG-backed fact store. Delegates to db/fact_queries.ts for I/O and adds the lifecycle layer described in the module doc. Implements HeldFactStore: the @fuzdev/fuz_util FactStore reads, puts taking HeldFactPutOptions.

inheritance

implements: HeldFactStore

constructor

type new (options: PgFactStoreDeps): PgFactStore

options

put

Store fully-buffered bytes, routing by size: <= embedded_threshold into the PG bytes column; larger into the disk CAS (when disk_root + fs are configured) at <facts_dir>/<shard>/<rest> with a file: URL. Oversize without a disk root throws: the store has nowhere to put bytes it cannot embed. Idempotent — ON CONFLICT DO NOTHING + content-addressed disk filenames make a re-write harmless (it renames verified bytes over the same file). Refs are exactly options.refs (none when omitted). options.holder, when set, is recorded as a holder on a fresh insert and a dedup hit alike.

type (bytes: Uint8Array<ArrayBufferLike>, options: HeldFactPutOptions): Promise<FactHash>

bytes

type Uint8Array<ArrayBufferLike>

options

returns Promise<FactHash>

mutates

  • fact_holder — records `options.holder` (when non-null) in the transaction that writes the `fact` row

put_stream

Stream bytes into the store with bounded memory, returning the fact hash + size. Delegates the byte path to stream_fact_to_disk (hash BLAKE3 as it streams, buffer to the embedded threshold, spill to the disk CAS), then inserts the fact row by placement — embedded bytes go to the PG bytes column, disk-spilled bytes record the file: external_url. The cap is enforced mid-stream (FactPayloadTooLargeError); a disk-full mid- stream throws FactStorageFullError.

Refs are exactly options.refs (none when omitted), as on put.

Requires fs (and, for the over-threshold spill, disk_root) to be configured. The streaming twin of put; mirrors the Rust FactStore::put_stream. Records options.holder the way put does.

type (stream: ReadableStream<Uint8Array<ArrayBufferLike>>, max_bytes: number, options: HeldFactPutOptions): Promise<...>

stream

type ReadableStream<Uint8Array<ArrayBufferLike>>

max_bytes

type number

options

returns Promise<PutStreamOutcome>

mutates

  • fact_holder — records `options.holder` (when non-null) in the transaction that writes the `fact` row

get

Retrieve bytes. Embedded reads return PG bytes directly. An external read comes from the disk CAS and nowhere else: the bytes at the row's file:<shard>/<rest> URL are read and verified against the hash.

Returns null, with a warning log, for an external row the store cannot serve — an external_url that is not a file:<shard>/<rest> URL, no disk CAS configured (disk_root + fs), a failed disk read, or bytes that don't match the hash — and, with an error log, for a URL that names another fact's file. Never makes a network request.

type (hash: FactHash): Promise<Uint8Array<ArrayBufferLike> | null>

hash

type FactHash
returns Promise<Uint8Array<ArrayBufferLike> | null>

has

type (hash: FactHash): Promise<boolean>

hash

type FactHash
returns Promise<boolean>

get_meta

type (hash: FactHash): Promise<FactMeta | null>

hash

type FactHash
returns Promise<FactMeta | null>

get_refs

type (hash: FactHash): Promise<FactHash[]>

hash

type FactHash
returns Promise<FactHash[]>

delete

Drop a fact row. fact_ref rows referencing this hash as a source cascade via the FK; fact_ref targeting this hash do not — they remain as dangling pointers, consistent with the federation model where target_hash is intentionally not a FK.

Idempotent: deleting an absent fact returns null. The store does NOT verify the fact is unreferenced — that policy lives one layer up (the orphan-fact admin surface in the consumer; a future GC walker).

The disk file of a disk-backed fact is NOT unlinked — that is the caller's responsibility, using the returned external_url (when non-null): a file:<shard>/<rest> URL names <disk_root>/<shard>/<rest>. The value is the row's free text — parse it with parse_file_fact_url before joining a path.

type (hash: FactHash): Promise<{ size: number; external_url: string | null; } | null>

hash

type FactHash
returns Promise<{ size: number; external_url: string | null; } | null>

{size, external_url} for the deleted row, or null if no row matched the hash.

PgFactStoreDeps
#

db/fact_store.ts view source

PgFactStoreDeps import type {PgFactStoreDeps} from '@fuzdev/fuz_app/db/fact_store.js';

Construction-time deps for PgFactStore.

embedded_threshold (bytes) is the inline-vs-external cutoff: payloads at or under it store embedded in the fact row, larger ones route to the disk CAS. Defaults to FACT_EMBEDDED_THRESHOLD_DEFAULT (1 MiB). Consumers tune it per workload — e.g. a much lower bound (~16 KiB) keeps only small JSON inline and routes image originals + thumbnails to disk.

disk_root is the facts directory backing the <shard>/<rest> disk CAS; fs supplies the filesystem capabilities (a RuntimeDeps satisfies it). When both are set, oversize put + put_stream write to disk and get reads disk-backed facts from it. When either is unset the store writes embedded rows only and get returns null for an external row: an oversize put throws; put_stream throws for a body of any size when fs is unset, and with fs but no disk_root throws FactPayloadTooLargeError for a body that passes the threshold. log is optional — its call sites are the warnings on the paths where get returns null for an external row.

deps

type QueryDeps

embedded_threshold?

type number

disk_root?

type string

fs?

type FactDiskStorageDeps

log?

type Logger

PGLET_COERCION
#

db/db_pglet.ts view source

CoercionOptions import {PGLET_COERCION} from '@fuzdev/fuz_app/db/db_pglet.js';

The value coercion fuz_app creates every pglet instance with.

The adapter defaults int8 to bigint; fuz_app reads int8 as a JS number on every driver (register_pg_type_parsers for pg, PGlite's own default), so AuditLogEvent.seq and friends validate identically across drivers.

pick_auth_headers
#

testing/integration_helpers.ts view source

(spec: RouteSpec, keeper: KeeperHeaderProvider, authed_account: TestAccount, admin_account: TestAccount): Record<...> import {pick_auth_headers} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Pick request headers matching a route spec's auth requirement (pick_auth_principal, then to_principal_headers):

  • none — origin headers only
  • authenticated — the authed account's session cookie
  • role: admin — the admin account's session cookie
  • role: <other> — the keeper provider's session
  • keeper — the keeper provider's daemon token

spec

keeper

authed_account

admin_account

returns

Record<string, string>

pick_auth_principal
#

testing/integration_helpers.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): AuthPrincipal import {pick_auth_principal} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Pick the principal a generated request calls as, from its declared auth. Shared by the REST and RPC round-trips (pick_auth_headers, pick_rpc_auth), so both call each auth shape as the same principal.

Every principal but 'public' carries a live credential of the kind the auth names, so the backend must authenticate it; a role mismatch (a keeper session on a role the keeper lacks) still refuses as 403, after authentication.

auth

returns

AuthPrincipal

pick_rpc_auth
#

testing/rpc_round_trip.ts view source

(method: AppSurfaceRpcMethod, keeper: KeeperHeaderProvider, authed_account: TestAccount, admin_account: TestAccount): RpcRoundTripAuth import {pick_rpc_auth} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

Pick the principal matching an RPC method's auth requirement (pick_auth_principal, shared with the REST round-trip), and the headers carrying its credential. Accepts any KeeperHeaderProvider — both TestApp (in-process) and TestFixture (cross-backend) satisfy the shape structurally.

method

keeper

authed_account

admin_account

returns

RpcRoundTripAuth

PingActionInput
#

actions/peer_ping.ts view source

value + type

{ nonce?: number | undefined; timeout_ms?: number | undefined; } import {PingActionInput} from '@fuzdev/fuz_app/actions/peer_ping.js';

Input to the client→server peer/ping invocation. Both fields optional (mirrors the Rust PingActionInput): nonce defaults to a server-issued value, timeout_ms to DEFAULT_PEER_REQUEST_TIMEOUT (clamped shorten-only).

nonce?

type number

timeout_ms?

type number

PingRequestParams
#

actions/peer_ping.ts view source

value + type

{ nonce: number; } import {PingRequestParams} from '@fuzdev/fuz_app/actions/peer_ping.js';

Params of the server→client peer/ping request frame. Twin of the Rust PingRequest.

nonce

type number

PingResponse
#

actions/peer_ping.ts view source

value + type

{ nonce: number; protocol_version: number; } import {PingResponse} from '@fuzdev/fuz_app/actions/peer_ping.js';

The client's reply shape — also the action's output (the handler returns the validated echo). Twin of the Rust PingResponse.

nonce

type number

protocol_version

type number

Popover
#

ui/popover.svelte.ts view source

import {Popover} from '@fuzdev/fuz_app/ui/popover.svelte.js';

Class that manages state and provides actions for popovers.

visible

Whether the popover is currently visible.

type boolean

$state.raw

position

Position of the popover relative to its trigger.

type Position

$state.raw

align

Alignment along the position edge.

type Alignment

$state.raw

offset

Distance from the position.

type string

$state.raw

disable_outside_click

Whether to disable closing when clicking outside.

type boolean

$state.raw

popover_class

Custom class for the popover.

type string

$state.raw

constructor

type new (params?: PopoverParameters | undefined): Popover

params?

optional

update

Updates the popover configuration. Swaps popover_class on the live content element when supplied and re-syncs the outside-click handler if disable_outside_click changed.

type (params: PopoverParameters): void

params

returns void

mutates

  • this

show

Shows the popover, registering the outside-click handler and updating aria-expanded on the trigger. No-ops when already visible.

type (): void

returns void

mutates

  • this

hide

Hides the popover, removing the outside-click handler. No-ops when already hidden.

type (): void

returns void

mutates

  • this

toggle

Toggles the popover visibility, or sets it to the given value.

type (visible?: boolean): void

visible?

desired visibility (defaults to the inverse of current)

type boolean
optional default !this.visible
returns void

mutates

  • this

container

Attachment for the container element.

type Attachment<HTMLElement>

trigger

Attachment factory for the trigger element that shows/hides the popover.

type (params?: PopoverParameters | undefined) => Attachment<HTMLElement>

content

Attachment factory for the popover content element.

type (params?: PopoverContentParameters | undefined) => Attachment<HTMLElement>

PopoverButton
#

ui/PopoverButton.svelte view source

accepts children

import PopoverButton from '@fuzdev/fuz_app/ui/PopoverButton.svelte';

position?

optional default 'bottom'

align?

optional default 'center'

disable_outside_click?

type boolean
optional default false

popover_class?

type string
optional

popover_attrs?

type HTMLAttributes<HTMLDivElement>
optional

popover_content

type Snippet<[popover: Popover]>
snippet parameters
popover Popover

popover_container_attrs?

type HTMLAttributes<HTMLDivElement>
optional

button?

type Snippet<[popover: Popover]>
optional
snippet parameters
popover Popover

children?

type Snippet<[popover: Popover]>
optional
snippet parameters
popover Popover

external types

OmitStrict<SvelteHTMLElements['button'], 'children'>

PopoverContentParameters
#

PopoverParameters
#

ui/popover.svelte.ts view source

PopoverParameters import type {PopoverParameters} from '@fuzdev/fuz_app/ui/popover.svelte.js';

Parameters for configuring the popover.

position?

Position of the popover relative to its trigger.

type Position

align?

Alignment along the position edge.

type Alignment

offset?

Distance from the position.

type string

disable_outside_click?

Whether to disable closing when clicking outside.

type boolean

popover_class?

Custom class for the popover content.

type string

onshow?

Optional callback when popover is shown.

type (): void

returns void

onhide?

Optional callback when popover is hidden.

type (): void

returns void

Position
#

POSTGRES_URL_PROTOCOLS
#

PRE_ADMISSION_QUEUE_BYTES_FACTOR
#

actions/ws_socket_session.ts view source

4 import {PRE_ADMISSION_QUEUE_BYTES_FACTOR} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

The byte budget of one socket's pre-admission queue, as a multiple of the endpoint's max_message_bytes: the frames queued while the socket is still opening may total at most this many maximum-size messages. The frame that would pass it closes the socket with WS_CLOSE_POLICY_VIOLATION.

The count cap alone (MAX_PRE_ADMISSION_FRAMES) would let each pending socket hold that many full-size messages, and pending sockets are not capped per account — a registration that is never admitted must not be able to pin that much memory. Small requests, which is what a client flushes on reopen, meet the count cap long before this one.

A client whose queued requests total more than this budget and flushes them all on reopen overflows it: the socket closes, the flushed requests fail, and the client reconnects.

prefix_route_specs
#

http/route_spec.ts view source

(prefix: string, specs: RouteSpec[]): RouteSpec[] import {prefix_route_specs} from '@fuzdev/fuz_app/http/route_spec.js';

Prepend a prefix to all route spec paths.

prefix

the path prefix (e.g. /api/account)

type string

specs

type RouteSpec[]

returns

RouteSpec[]

a new array — the input specs are not mutated

prepare_cargo_binary
#

testing/cross_backend/cargo_binary.ts view source

(options: CargoBinaryOptions): string import {prepare_cargo_binary} from '@fuzdev/fuz_app/testing/cross_backend/cargo_binary.js';

Build a Cargo binary before its backend spawns, unless NO_REBUILD_ENV asks to skip it, and return its path. The build's output is inherited, so a cold build shows its progress, and a failed build throws — the project fails loudly rather than spawning a stale binary. cargo build is incremental, so a build with nothing changed is quick.

Synchronous: vitest awaits each project's globalSetup in turn, so one invocation selecting several projects builds once, then finds the build current.

options

the workspace, package, binary and profile

returns

string

the binary's absolute path (resolve_cargo_binary_path)

throws

  • Error - when the workspace is missing, the build fails, or `cargo metadata` fails

PreparedWebsocket
#

testing/cross_backend/testing_server_core.ts view source

PreparedWebsocket import type {PreparedWebsocket} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Result of an adapter's WS preparation step.

create_upgrade_websocket is the factory a build passes to create_app_server as AppServerOptions.create_upgrade_websocket. attach_to_server runs after serve() returns a ServeHandle — Node uses it for injectWebSocket(server), and it does nothing when the factory was never called (no ws_endpoints); Deno and Bun leave it undefined.

create_upgrade_websocket

type (app: Hono<BlankEnv, BlankSchema, "/">): UpgradeWebSocket<unknown, any, WSEvents<unknown>>

app

type Hono<BlankEnv, BlankSchema, "/">
returns UpgradeWebSocket<unknown, any, WSEvents<unknown>>

attach_to_server?

type (handle: ServeHandle): void

handle

returns void

PrepareWebsocketOptions
#

testing/cross_backend/testing_server_core.ts view source

PrepareWebsocketOptions import type {PrepareWebsocketOptions} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Options for TestingServerAdapter.prepare_websocket.

max_message_bytes?

Largest inbound WebSocket message, in bytes — pass the largest WsEndpointSpec.max_message_bytes of the mounted endpoints; a smaller value refuses messages the endpoint would accept. Node and Bun take twice it as their frame cap (ws's maxPayload, Bun's maxPayloadLength): past its cap ws closes with no reason and Bun drops the socket without a close frame, so the headroom leaves a message just over the limit to the per-message check register_action_ws makes, while a grossly oversized one is still refused before it is buffered whole. Deno ignores it — its upgrade takes no frame cap — leaving that per-message check alone. Every runtime closes a message just over the limit with WS_CLOSE_MESSAGE_TOO_BIG. The default sits far below ws's own 100 MiB and Bun's 16 MiB, so a build raising an endpoint's cap must pass it here too.

type number

default DEFAULT_WS_MAX_MESSAGE_BYTES

PrimaryKeyInfo
#

http/db_routes.ts view source

PrimaryKeyInfo import type {PrimaryKeyInfo} from '@fuzdev/fuz_app/http/db_routes.js';

Primary key constraint info.

column_name

type string

probe_backend
#

ui/backend_probe.svelte.ts view source

(options?: BackendProbeOptions | undefined): Promise<BackendProbeResult> import {probe_backend} from '@fuzdev/fuz_app/ui/backend_probe.svelte.js';

Probe whether the backend is serving: its health route must answer 2xx with a body that parses as HealthResponse. A timeout while the reply or its body is read reads as down — naming timeout_ms only when the default fetch timed out, since an injected fetch_health owns its own signal. A failure once the caller's signal aborted reports BACKEND_PROBE_ABORTED, not a timeout or a down backend. A body that isn't JSON is BACKEND_PROBE_NOT_HEALTH; any other failure reading it reports the error.

options?

the health fetch, the default fetch's timeout, and a signal to cancel it

optional

returns

Promise<BackendProbeResult>

{ok: true} when the backend answered, else the reason it didn't

process_session_cookie
#

ProcessDeps
#

runtime/deps.ts view source

ProcessDeps import type {ProcessDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Process lifecycle.

exit

Exit the process with a code.

type (code: number): never

code

type number
returns never

ProcessSessionResult
#

auth/session_cookie.ts view source

ProcessSessionResult<TIdentity> import type {ProcessSessionResult} from '@fuzdev/fuz_app/auth/session_cookie.js';

Result of processing a session cookie.

generics

ProcessSessionResult<TIdentity>
TIdentity

valid

Whether the session is valid.

type boolean

action

Action the adapter should take.

type "none" | "clear"

identity?

The decoded identity if the cookie was valid.

type TIdentity

expires_at?

The cookie's signed expiry, epoch seconds, if the cookie was valid.

type number

PROTOCOL_ACTION_METHODS
#

actions/action_codegen.ts view source

readonly ["heartbeat", "cancel", "peer/ping"] import {PROTOCOL_ACTION_METHODS} from '@fuzdev/fuz_app/actions/action_codegen.js';

Method names of fuz_app's protocol actions — heartbeat (auth-aware client liveness probe) and cancel (request-scoped abort signal). Consumers spread this list when filtering backend request_response methods so the dispatcher-owned protocol actions don't show up in BackendRequestResponseMethod / handler maps. Pairs with protocol_actions / protocol_action_specs from actions/protocol.ts (the runtime bundles).

protocol_action_specs
#

actions/protocol.ts view source

readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } ...

type readonly ActionSpecUnion[]

import {protocol_action_specs} from '@fuzdev/fuz_app/actions/protocol.js';

Canonical protocol specs for ActionRegistry construction on the frontend. Spread before consumer-owned specs so dispatcher-owned methods are present in the lookup map even though codegen excludes them from the generated action_specs array:

new ActionRegistry([...protocol_action_specs, ...action_specs])

Derived from protocol_actions so a future protocol action lands in one place — the two arrays cannot drift.

protocol_actions
#

actions/protocol.ts view source

readonly Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ....

type readonly Action<ActionSpecUnion>[]

import {protocol_actions} from '@fuzdev/fuz_app/actions/protocol.js';

Canonical protocol {spec, handler} tuples for the server's register_action_ws actions array. Spread before consumer-owned actions so disconnect detection and per-request cancel work uniformly:

register_action_ws({actions: [...protocol_actions, ...consumer_actions], ...})

ProtocolActionMethod
#

actions/action_codegen.ts view source

"heartbeat" | "cancel" | "peer/ping"

type "heartbeat" | "cancel" | "peer/ping"

import type {ProtocolActionMethod} from '@fuzdev/fuz_app/actions/action_codegen.js';

Methods that ship from fuz_app, kept out of consumer-owned method enums + handler maps.

provide_admin_rpc_contexts
#

ui/admin_rpc_adapters.ts view source

(adapters: AdminRpcAdapters, options?: ProvideAdminRpcContextsOptions | undefined): void import {provide_admin_rpc_contexts} from '@fuzdev/fuz_app/ui/admin_rpc_adapters.js';

Wire all four admin RPC contexts in a single call.

Call once at the admin shell layout (e.g. src/routes/admin/+layout.svelte) with adapters built from create_admin_rpc_adapters. Every Admin*.svelte component that reads a context below this point sees the adapters.

Each context accessor reads adapters.{domain} on every invocation, so mutating an adapter field on the same object propagates. Replacing the whole adapter set requires calling provide_admin_rpc_contexts again during init — in practice this is one-shot at layout mount.

Pass options.format_scope to render role_grant/offer scope_id values as human labels across AdminAccounts, AdminRoleGrantHistory, RoleGrantOfferInbox, RoleGrantOfferForm, and RoleGrantOfferHistory. Components that accept a format_scope prop honor the prop first; the context is the fallback.

adapters

options?

optional

returns

void

ProvideAdminRpcContextsOptions
#

ui/admin_rpc_adapters.ts view source

ProvideAdminRpcContextsOptions import type {ProvideAdminRpcContextsOptions} from '@fuzdev/fuz_app/ui/admin_rpc_adapters.js';

Optional knobs alongside the adapters when wiring admin contexts.

format_scope?

Render {scope_id, role} as a human label across role-grant-display components. Omit (or return null) to fall back to the truncated uuid.

type (args: { scope_id: string | null; role: string; }): string | null

args

type { scope_id: string | null; role: string; }
returns string | null

ProxyOptions
#

http/proxy.ts view source

ProxyOptions import type {ProxyOptions} from '@fuzdev/fuz_app/http/proxy.js';

Configuration for trusted proxy resolution.

trusted_proxies

Trusted proxy IPs or CIDR ranges (e.g. '127.0.0.1', '10.0.0.0/8', '::1').

type string[]

get_connection_ip

Extract the raw TCP connection IP from the Hono context.

type (c: Context<any, any, {}>): string | undefined

c

type Context<any, any, {}>
returns string | undefined

log?

Optional logger for proxy resolution diagnostics.

type Logger

PutFactInput
#

db/fact_queries.ts view source

PutFactInput import type {PutFactInput} from '@fuzdev/fuz_app/db/fact_queries.js';

Input for query_put_fact — one fact row. Twin of the Rust PutFactInput.

hash

type FactHash

bytes

type Uint8Array<ArrayBufferLike> | null

external_url

type string | null

content_type

type string | null

size

type number

qualify_columns
#

db/sql_columns.ts view source

(columns: readonly string[], alias: string, expr?: ColumnExpr | undefined): string import {qualify_columns} from '@fuzdev/fuz_app/db/sql_columns.js';

Render a *_COLUMNS const as a SQL select list with every column qualified by a table alias, for reads that alias the table (JOINs, the c-aliased cell_list scan).

The alias is interpolated into SQL, so it must be a plain identifier — every in-repo call passes a literal, but a consumer threading a computed alias through gets a thrown error instead of a malformed (or injectable) projection.

An expr override costs one rule: an ORDER BY in a projected SELECT must qualify its column. An override is aliased back to its own name (… AS created_at), and Postgres resolves a bare ORDER BY created_at against the *output* names first, so it would sort on the to_char(…) text — collapsing sub-second order into ties and losing the btree index (a Sort node over the expression instead of an Index Scan). Writing ORDER BY <alias>.created_at (or <table>.created_at) names the input column and both go away. The Rust twin needs no such rule: it decodes positionally, so fuz_db::qualify_columns emits no alias and its bare ORDER BY created_at already means the column.

columns

the column names

type readonly string[]

alias

the table alias or name to prefix each column with

type string

expr?

optional per-column expression override; an overridden column is aliased back to its own name

optional

returns

string

alias.a, alias.b, alias.c

throws

  • Error - when `alias` is not a valid SQL identifier

query_accept_offer
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, input: AcceptOfferInput): Promise<AcceptOfferResult> import {query_accept_offer} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Accept an offer atomically: mark accepted, insert the role_grant, stamp resulting_role_grant_id, supersede sibling pending offers for the same (to_account, role, scope), and emit role_grant_offer_accept + role_grant_create + one role_grant_offer_supersede per sibling. Must run inside a transaction — the caller's route spec should declare transaction: true (or wrap explicitly).

Idempotent on replay and on race: a call that observes the offer already accepted returns the existing role_grant (created: false) rather than creating a duplicate or throwing — while that grant is still held. A grant revoked since the accept, or gone, makes the offer terminal.

Error map:

Sibling supersede is what closes the "accept a pre-revoke sibling offer to bypass a revoke" path: once A is accepted, B/C/... can no longer be accepted even if the resulting role_grant is later revoked.

deps

input

returns

Promise<AcceptOfferResult>

throws

  • RoleGrantOfferNotFoundError - if the offer is missing or belongs to another recipient
  • RoleGrantOfferAlreadyTerminalError - if the offer is declined, retracted, or superseded, or accepted with its grant revoked, gone, or bound to another actor
  • RoleGrantOfferExpiredError - if the offer is pending but past `expires_at`
  • RoleGrantOfferActorMismatchError - if the offer is actor-targeted to a different actor
  • RoleGrantOfferBuiltinRefusedError - if a builtin-role offer must not mint (see `BuiltinOfferRefusal`)
  • Error - if the accepting `actor_id` does not belong to `to_account_id`, or invariant assertions fail

mutates

  • role_grant_offer row — stamps `accepted_at` and `resulting_role_grant_id`
  • role_grant table — inserts the resulting role_grant (idempotent on race)
  • role_grant_offer siblings — stamps `superseded_at` on every other pending offer for the tuple
  • audit_log table — emits `role_grant_offer_accept` + `role_grant_create` + one `role_grant_offer_supersede` per sibling

query_account_by_email
#

auth/account_queries.ts view source

(deps: QueryDeps, email: string): Promise<Account | undefined> import {query_account_by_email} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an account by email (case-insensitive).

deps

email

type string

returns

Promise<Account | undefined>

query_account_by_id
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<Account | undefined> import {query_account_by_id} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an active account by id (deleted_at IS NULL).

This is the auth-resolution workhorse (build_request_context / build_account_context) and the admin-target lookup, so it deliberately excludes soft-deleted accounts: a tombstoned account must not authenticate (delete = soft, purge = hard). Purge, which must operate on soft-deleted rows too, uses query_purge_account directly.

deps

id

type string

returns

Promise<Account | undefined>

query_account_by_username
#

auth/account_queries.ts view source

(deps: QueryDeps, username: string): Promise<Account | undefined> import {query_account_by_username} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an account by username (case-insensitive).

deps

username

type string

returns

Promise<Account | undefined>

query_account_by_username_or_email
#

auth/account_queries.ts view source

(deps: QueryDeps, input: string): Promise<Account | undefined> import {query_account_by_username_or_email} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an account by username or email.

If the input contains @, tries email lookup first then username. Otherwise tries username first then email. This supports a single login field that accepts either format.

Excludes soft-deleted accounts (deleted_at IS NULL) — this is the login lookup, and a tombstoned account must not authenticate. The underlying query_account_by_username / query_account_by_email stay unfiltered because the invite-collision checks need to see soft-deleted accounts (usernames/emails stay reserved after a soft-delete).

deps

query dependencies

input

username or email address

type string

returns

Promise<Account | undefined>

the matching active account, or undefined

query_account_has_active_global_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, account_id: string, role: string): Promise<boolean> import {query_account_has_active_global_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Like query_account_has_global_role, but only counts the grant when the account and the granting actor are both active (deleted_at IS NULL). Used by the last-admin branch of the removability guard: a soft-deleted admin can't log in and is excluded from query_count_active_accounts_with_global_role_for_update, so the guard must use the same active predicate when testing whether the *target* is an admin — otherwise removing an already-tombstoned admin is falsely blocked as cannot_delete_last_admin. Soft-delete does not revoke role_grant rows (it's reversible), so the actor join filters `deleted_at IS NULL` too: a grant held only by a tombstoned actor must not keep the account reading as an admin (the same tombstone-exclusion the acting-actor resolution applies). The keeper branch deliberately uses the unconditional query_account_has_global_role (a keeper is never removable regardless of tombstone state).

deps

query dependencies

account_id

the account to check

type string

role

the role to check for (e.g. ROLE_ADMIN)

type string

returns

Promise<boolean>

true if the account is active and any of its actors holds an active global role grant

query_account_has_any
#

auth/account_queries.ts view source

(deps: QueryDeps): Promise<boolean> import {query_account_has_any} from '@fuzdev/fuz_app/auth/account_queries.js';

Check if any account exists.

deps

returns

Promise<boolean>

query_account_has_global_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, account_id: string, role: string): Promise<boolean> import {query_account_has_global_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Account-grain check: does any actor on account_id hold an active global role_grant for role?

Keyed on the account rather than a single actor — for surfaces with auth: actor: 'none' that don't load auth.role_grants and can't use the in-memory has_scoped_role predicate. Joins role_grant → actor; matches only global role_grants (scope_id IS NULL) since the use case is "is the caller's account broadly admin", not scope-aware.

Deliberately unconditional — no deleted_at filter on the account or the granting actor. This backs the keeper removability guard, where the keeper must read as present *even if* its account or keeper-actor is already tombstoned (deleting it would brick keeper/daemon auth with no in-band recovery). That is the opposite posture from resolve_keeper_account_id, which *resolves* the daemon identity and so excludes a tombstoned keeper actor.

Fast under the existing idx_role_grant_actor index — the inner actor_id IN (...) subquery is index-scan, and the outer EXISTS stops at the first match.

deps

query dependencies

account_id

the account to check

type string

role

the role to check for (e.g. ROLE_ADMIN)

type string

returns

Promise<boolean>

true if any actor on the account has an active global role_grant for role

query_account_soft_delete
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string, deleted_by: string | null): Promise<AccountIdentitySnapshot | undefined> import {query_account_soft_delete} from '@fuzdev/fuz_app/auth/account_queries.js';

Soft-delete an account — the reversible tombstone (delete = soft).

Stamps deleted_at + deleted_by (the initiator's actor) on the active row — paired like role_grant's revoked_at / revoked_by, and deliberately leaving updated_at / updated_by untouched (deletion isn't a content edit). Returns the identity snapshot for the account_delete audit event, or undefined when no active row matched (missing or already soft-deleted). Auth resolution (query_account_by_id) already excludes soft-deleted accounts; the caller revokes sessions/tokens.

deps

id

type string

deleted_by

type string | null

returns

Promise<AccountIdentitySnapshot | undefined>

mutates

  • account row — sets `deleted_at` + `deleted_by` on one active row

query_account_undelete
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<AccountIdentitySnapshot | undefined> import {query_account_undelete} from '@fuzdev/fuz_app/auth/account_queries.js';

Reactivate a soft-deleted account — clears the deleted_at / deleted_by tombstone (the inverse of query_account_soft_delete).

Operates only on a currently soft-deleted row (deleted_at IS NOT NULL) and returns the identity snapshot for the account_undelete audit event, or undefined when no soft-deleted row matched (missing or already active). Reactivation does not restore the revoked sessions/tokens — the account is live again but its principals re-auth fresh. updated_at / updated_by stay untouched.

deps

id

type string

returns

Promise<AccountIdentitySnapshot | undefined>

mutates

  • account row — clears `deleted_at` + `deleted_by` on one soft-deleted row

query_active_actors_by_account
#

auth/account_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<Actor[]> import {query_active_actors_by_account} from '@fuzdev/fuz_app/auth/account_queries.js';

List active (non-tombstoned) actors on an account, ordered by created_at.

Filters deleted_at IS NULL so a soft-deleted actor can never be resolved as the acting actor (and carry its role_grants into the role gate). Used by resolve_acting_actor to resolve the acting actor for a request: 1 actor picks transparently, multiple require an explicit acting field on the request payload. The admin/snapshot handlers that legitimately need tombstoned rows stay on the unfiltered query_actors_by_account.

deps

account_id

type string

returns

Promise<Actor[]>

query_actor_by_id
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<Actor | undefined> import {query_actor_by_id} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an actor by id.

deps

id

type string

returns

Promise<Actor | undefined>

query_actor_search
#

query_actor_soft_delete
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string, deleted_by: string | null): Promise<boolean> import {query_actor_soft_delete} from '@fuzdev/fuz_app/auth/account_queries.js';

Soft-delete one actor (sets deleted_at + deleted_by). Returns true when an active row flipped, false when none matched. Emitted per actor alongside the account-level soft-delete.

deps

id

type string

deleted_by

type string | null

returns

Promise<boolean>

mutates

  • actor row — sets `deleted_at` + `deleted_by` on one active row

query_actor_undelete
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<boolean> import {query_actor_undelete} from '@fuzdev/fuz_app/auth/account_queries.js';

Reactivate one soft-deleted actor (clears deleted_at / deleted_by). Returns true when a soft-deleted row flipped back, false when none matched. Emitted per actor alongside the account-level reactivation.

deps

id

type string

returns

Promise<boolean>

mutates

  • actor row — clears `deleted_at` + `deleted_by` on one soft-deleted row

query_actors_by_account
#

auth/account_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<Actor[]> import {query_actors_by_account} from '@fuzdev/fuz_app/auth/account_queries.js';

List every actor on an account, ordered by created_at, including soft-deleted (tombstoned) rows.

Used by the admin/snapshot handlers (account_delete / account_purge / account_undelete) that must enumerate every actor that ever existed on the account to snapshot names into per-actor audit events. For the acting-actor resolution path — which must never resolve a tombstoned actor — use query_active_actors_by_account. For lookups by id, use query_actor_by_id instead.

deps

account_id

type string

returns

Promise<Actor[]>

query_actors_by_ids
#

auth/actor_lookup_queries.ts view source

(deps: QueryDeps, ids: readonly (string & $brand<"Uuid">)[]): Promise<ActorLookupRow[]> import {query_actors_by_ids} from '@fuzdev/fuz_app/auth/actor_lookup_queries.js';

Resolve a batch of actor ids to (id, username, display_name). Empty input fast-paths to []. Hard-deleted and soft-deleted actors — and the actors of a soft-deleted account — drop out of the result silently, indistinguishable from never-existed.

deps

ids

type readonly Uuid[]

returns

Promise<ActorLookupRow[]>

query_admin_account_list
#

auth/account_queries.ts view source

(deps: QueryDeps, options?: AdminAccountListOptions | undefined): Promise<{ account: { id: string & $brand<"Uuid">; username: string; ... 5 more ...; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }[]> import {query_admin_account_list} from '@fuzdev/fuz_app/auth/account_queries.js';

List accounts with their actors, active role_grants, and pending inbound role_grant offers for admin display.

Pages the accounts query (one round-trip), then fans out three parallel lookups scoped to the page's account_ids (one round-trip). The role_grants and offers queries use a subquery on actor.account_id so the page bound pushes through to the DB without round-tripping actor.ids back to the application. Pending offers surface the "offer pending — awaiting acceptance" UX; message is intentionally excluded (cross-admin visibility of grantor notes would expand beyond what the audit log discloses).

Every list is ordered as the Rust spine orders it, on data keys rather than the random id alone: accounts by created_at, username, each entry's role_grants by created_at, role, scope_id, its pending offers by expires_at, role, scope_id (id last, for a tie no data key breaks). The order is deterministic on each engine, so rows written in one transaction (one created_at) list stably. Two backends holding the same rows list them alike only under the same text collation: a bytewise compare (pglet) and a locale one (Postgres en_US.UTF-8, which passes over _ and - at first) can order the text keys differently.

deps

query dependencies

options?

optional {limit, offset}. Default limit is ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT; pass limit: null to disable.

optional

returns

Promise<AdminAccountEntryJson[]>

admin account entries sorted by creation date (oldest first)

query_api_token_enforce_limit
#

auth/api_token_queries.ts view source

(deps: QueryDeps, account_id: string, max_tokens: number): Promise<string[]> import {query_api_token_enforce_limit} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Enforce a per-account token limit by evicting the oldest tokens.

Race safety: this function must run inside a transaction alongside the INSERT that created the new token. The caller (the account_token_create RPC handler) runs under the dispatcher's transaction path because the spec declares side_effects: true, making one creator's INSERT + enforce_limit pair atomic. That does not serialize concurrent creators: under Read Committed, two transactions can't see each other's uncommitted token row, so each evicts against a stale count and both can commit above max_tokens. Closing this needs one serialization point per (account_id, credential_kind) before count/evict/insert — see the matching note on query_session_enforce_limit.

Ordering carries the same id DESC stability tie-breaker, with the same caveat, as query_session_enforce_limit — deterministic survivors under a created_at tie, not a guarantee that the row just inserted survives.

Deleting a token row does not end the connections it opened, so the returned ids are what the caller closes once the transaction commits (queue_connection_close with a token target) — account_token_create does.

deps

query dependencies (must be transaction-scoped)

account_id

the account to enforce the limit for

type string

max_tokens

maximum number of tokens to keep

type number

returns

Promise<string[]>

the evicted tokens' ids, empty when nothing was evicted

mutates

  • api_token table — deletes the oldest rows past the cap

query_api_token_list_for_account
#

auth/api_token_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<Omit<ApiToken, "token_hash">[]> import {query_api_token_list_for_account} from '@fuzdev/fuz_app/auth/api_token_queries.js';

List all tokens for an account, newest first — API_TOKEN_CLIENT_COLUMNS, so token_hash never rides the listing.

deps

account_id

type string

returns

Promise<Omit<ApiToken, "token_hash">[]>

query_api_token_live_account
#

auth/api_token_queries.ts view source

(deps: QueryDeps, id: string): Promise<string | undefined> import {query_api_token_live_account} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Look up the owning account of a live API token — one that still exists and has not expired — by its public id.

The by-id counterpart of query_validate_api_token, for re-checking a token that already authenticated (revalidate_resolved_auth): no hash, no scope decode, and no touch of last_used_at / last_used_ip — a re-check is not a use. "Live" is the same predicate in both (API_TOKEN_IS_LIVE). The scope document is not re-read: the resolved credential carries the scope it was admitted with, and nothing rewrites the column after the mint.

deps

query dependencies

id

the public token id (e.g. tok_abc123)

type string

returns

Promise<string | undefined>

the owning account id, or undefined when the token is gone or expired

query_app_settings_load
#

auth/app_settings_queries.ts view source

(deps: QueryDeps): Promise<AppSettings> import {query_app_settings_load} from '@fuzdev/fuz_app/auth/app_settings_queries.js';

Load the current app settings.

deps

query dependencies

returns

Promise<AppSettings>

the app settings row

throws

  • Error - if the singleton `app_settings` row is missing (migration drift — should not occur in practice)

query_app_settings_load_with_username
#

auth/app_settings_queries.ts view source

(deps: QueryDeps): Promise<{ open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }> import {query_app_settings_load_with_username} from '@fuzdev/fuz_app/auth/app_settings_queries.js';

Load the current app settings with resolved updater username.

deps

query dependencies

returns

Promise<AppSettingsWithUsernameJson>

the app settings with updated_by_username

throws

  • Error - if the singleton `app_settings` row is missing

query_app_settings_update
#

auth/app_settings_queries.ts view source

(deps: QueryDeps, open_signup: boolean, actor_id: string): Promise<AppSettings> import {query_app_settings_update} from '@fuzdev/fuz_app/auth/app_settings_queries.js';

Update app settings and return the updated row.

deps

query dependencies

open_signup

new value for the open_signup toggle

type boolean

actor_id

the actor making the change

type string

returns

Promise<AppSettings>

the updated app settings row

throws

  • Error - if the singleton `app_settings` row is missing

mutates

  • app_settings row — sets `open_signup`, `updated_at`, and `updated_by`

query_audit_log
#

auth/audit_log_queries.ts view source

<T extends string>(deps: QueryDeps, input: AuditLogInput<T>, config?: AuditLogConfig): Promise<AuditLogEvent> import {query_audit_log} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Insert an audit log entry.

Returns the full row (AUDIT_LOG_COLUMNS) so callers receive DB-assigned fields (id, seq, created_at). Validates metadata against config.metadata_schemas; unknown event_type and metadata mismatches log + bump their counters but write the row anyway. Consumers extend the recognized set via create_audit_log_config({extra_events}).

In-transaction call site for query helpers that must atomically write the row alongside other mutations (e.g. query_accept_offer). Request handlers reach for AppDeps.audit.emit instead, which picks the transaction or the pool by outcome.

deps

query dependencies

input

the audit event to record

config?

audit-log config. Defaults to builtin_audit_log_config.

optional default builtin_audit_log_config

returns

Promise<AuditLogEvent>

the inserted audit log row

generics

query_audit_log<T extends string>
T
constraint string

mutates

  • audit_log table — inserts the new row
  • drift counters — bumps `audit_unknown_event_type_failures` and/or `audit_metadata_validation_failures` on mismatch

query_audit_log_cleanup_before
#

auth/audit_log_queries.ts view source

(deps: QueryDeps, before: Date): Promise<number> import {query_audit_log_cleanup_before} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Delete audit log entries older than the given date.

deps

query dependencies

before

delete entries created before this date

type Date

returns

Promise<number>

the number of entries deleted

mutates

  • audit_log table — deletes every row with `created_at < before`

query_audit_log_list
#

auth/audit_log_queries.ts view source

(deps: QueryDeps, options?: AuditLogListOptions | undefined): Promise<AuditLogEvent[]> import {query_audit_log_list} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

List audit log entries, newest first.

deps

query dependencies

options?

filters and pagination

optional

returns

Promise<AuditLogEvent[]>

matching audit log entries

query_audit_log_list_by_cell
#

db/cell_audit_queries.ts view source

(deps: QueryDeps, cell_id: string & $brand<"Uuid">, options: CellAuditListOptions): Promise<AuditLogEvent[]> import {query_audit_log_list_by_cell} from '@fuzdev/fuz_app/db/cell_audit_queries.js';

Fetch audit rows mentioning cell_id on any cell-domain metadata key. Ordered newest-first by seq for cursor pagination through before.

deps

cell_id

type Uuid

options

returns

Promise<AuditLogEvent[]>

query_audit_log_list_role_grant_history
#

auth/audit_log_queries.ts view source

(deps: QueryDeps, limit?: number, offset?: number): Promise<{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<...>) | null; ... 7 more ...; target_username: string | null; }[]> import {query_audit_log_list_role_grant_history} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

List role_grant grant/revoke events with resolved usernames.

deps

query dependencies

limit?

maximum entries to return

type number
optional default AUDIT_LOG_DEFAULT_LIMIT

offset?

number of entries to skip

type number
optional default 0

returns

Promise<RoleGrantHistoryEventJson[]>

role_grant history events with username and target_username

query_audit_log_list_with_usernames
#

auth/audit_log_queries.ts view source

(deps: QueryDeps, options?: AuditLogListOptions | undefined): Promise<{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; ... 8 more ...; target_username: string | null; }[]> import {query_audit_log_list_with_usernames} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

List audit log entries with resolved usernames, newest first.

deps

query dependencies

options?

filters and pagination

optional

returns

Promise<AuditLogEventWithUsernamesJson[]>

matching audit log entries with username and target_username

query_cell_create
#

db/cell_queries.ts view source

(deps: QueryDeps, input: CellCreateQueryInput): Promise<CellRow> import {query_cell_create} from '@fuzdev/fuz_app/db/cell_queries.js';

Insert a cell row, deriving refs from data.

updated_by is left NULL on insert — same convention as updated_at (NULL until first update). The "last modifier" stamp is meaningful only after a real edit; copying the creator's id into updated_by at create time would make a no-op update by a different actor look authored by the creator.

deps

query deps

input

data, optional visibility, path, and ownership

returns

Promise<CellRow>

the inserted row

mutates

  • cell — inserts one row

query_cell_delete
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, options?: { deleted_by?: (string & $brand<"Uuid">) | null | undefined; } | undefined): Promise<boolean> import {query_cell_delete} from '@fuzdev/fuz_app/db/cell_queries.js';

Soft-delete a cell. Sets deleted_at = NOW(), updated_at = NOW(), and updated_by = options.deleted_by (or NULL). No-op when the row is already deleted.

deps

query deps

id

cell id

type Uuid

options?

deleted_by records who triggered the delete

type { deleted_by?: (string & $brand<"Uuid">) | null | undefined; }
optional

returns

Promise<boolean>

true when a row was soft-deleted, false when no active row matched

mutates

  • cell — sets `deleted_at` on one row

query_cell_field_delete
#

db/cell_field_queries.ts view source

(deps: QueryDeps, source_id: string & $brand<"Uuid">, name: string): Promise<CellFieldRow | null> import {query_cell_field_delete} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Delete a field row by primary key. Returns the deleted row so callers can audit the prior target_id without a pre-fetch.

deps

source_id

type Uuid

name

type string

returns

Promise<CellFieldRow | null>

the deleted row, or null when no row matched (idempotent delete: a 200 response is correct even when nothing was deleted)

mutates

  • cell_field — deletes one row

query_cell_field_list_for_source
#

db/cell_field_queries.ts view source

(deps: QueryDeps, viewer: CellViewer, source_id: string & $brand<"Uuid">, options?: { limit?: number | undefined; name_after?: string | undefined; } | undefined): Promise<...> import {query_cell_field_list_for_source} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Forward fields list (source.fields[]).

Up to limit fields whose target is live and one viewer may view (can_view_cell_sql, applied before the limit); name_after pages on. The source-view gate is the caller's responsibility.

deps

query deps

viewer

who is reading; CELL_VIEWER_UNRESTRICTED filters nothing

source_id

source cell id

type Uuid

options?

limit caps the row count (omitted = unbounded); name_after resumes after a name (keyset pagination)

type { limit?: number | undefined; name_after?: string | undefined; }
optional

returns

Promise<CellFieldRow[]>

matching rows in lex name order

query_cell_field_list_for_target
#

db/cell_field_queries.ts view source

(deps: QueryDeps, viewer: CellViewer, target_id: string & $brand<"Uuid">, options?: { limit?: number | undefined; } | undefined): Promise<CellFieldRow[]> import {query_cell_field_list_for_target} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Reverse fields list (target.upfields[]).

Up to limit fields whose source is live and one viewer may view — filtered before the limit (can_view_cell_sql), so sources the caller can't see never crowd out ones it can. The target-view gate is the caller's responsibility.

deps

query deps

viewer

who is reading; CELL_VIEWER_UNRESTRICTED filters nothing

target_id

target cell id

type Uuid

options?

limit caps the row count (omitted = unbounded)

type { limit?: number | undefined; }
optional

returns

Promise<CellFieldRow[]>

matching rows, oldest field first (by the field row's created_at, then its key)

query_cell_field_set
#

db/cell_field_queries.ts view source

(deps: QueryDeps, input: CellFieldSetQueryInput): Promise<CellFieldRow> import {query_cell_field_set} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Insert or update a field row.

UPSERT on (source_id, name) — re-setting the same name updates target_id and bumps created_at (timestamp reflects last write). Idempotent at the row level: caller can re-issue with the same input without checking existence first.

deps

query deps

input

source, name, target

returns

Promise<CellFieldRow>

the inserted-or-updated row

mutates

  • cell_field — inserts or updates one row

query_cell_get
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, options?: { include_deleted?: boolean | undefined; } | undefined): Promise<CellRow | null> import {query_cell_get} from '@fuzdev/fuz_app/db/cell_queries.js';

Fetch a cell by id. Excludes soft-deleted rows by default.

deps

query deps

id

cell id

type Uuid

options?

include_deleted: true returns tombstones

type { include_deleted?: boolean | undefined; }
optional

returns

Promise<CellRow | null>

the row or null when not found (or soft-deleted and not requested)

query_cell_get_by_path
#

db/cell_queries.ts view source

(deps: QueryDeps, path: string): Promise<CellRow | null> import {query_cell_get_by_path} from '@fuzdev/fuz_app/db/cell_queries.js';

Fetch a cell by path. Excludes soft-deleted rows; the global partial unique index on path WHERE deleted_at IS NULL guarantees at most one result.

deps

query deps

path

the named lookup alias (e.g. /map/main)

type string

returns

Promise<CellRow | null>

the row or null when not found

query_cell_grant_create
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, input: CellGrantCreateQueryInput): Promise<CellGrantRow> import {query_cell_grant_create} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Insert a grant, or update the existing row's level + granted_by when one already exists for the same (cell_id, principal) pair.

Idempotent re-share: caller doesn't need to check existence first. The UPSERT path runs even when the existing row's level matches — handlers reading the row's prior state for audit ("create vs. update") must do so before this call.

deps

query deps

input

cell, level, principal, grantor

returns

Promise<CellGrantRow>

the inserted-or-updated row

mutates

  • cell_grant — inserts or updates one row

query_cell_grant_delete
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, grant_id: string & $brand<"Uuid">, expected_level?: "viewer" | "editor" | undefined): Promise<CellGrantRow | null> import {query_cell_grant_delete} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Delete a grant by id, returning the deleted row.

With expected_level, only a grant still at that level is deleted — a caller whose decision read the level fences the delete to the row it decided on, so a level change landing in between deletes nothing.

Returning the row lets the caller audit the principal + level after the delete and (for self-revoke) recompute still_admitted against the remaining grants on the cell without a second fetch.

deps

query deps

grant_id

grant id

type Uuid

expected_level?

when given, the level the grant must still hold

optional

returns

Promise<CellGrantRow | null>

the deleted row or null when no row matched

mutates

  • cell_grant — deletes one row

query_cell_grant_get
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, grant_id: string & $brand<"Uuid">): Promise<CellGrantRow | null> import {query_cell_grant_get} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Fetch a grant by id.

deps

query deps

grant_id

grant id

type Uuid

returns

Promise<CellGrantRow | null>

the row or null when not found

query_cell_grant_list_for_cell
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, cell_id: string & $brand<"Uuid">): Promise<CellGrantRow[]> import {query_cell_grant_list_for_cell} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

List all grants on a cell, oldest first.

Used by cell_grant_list (RPC) and by handlers that need grants alongside the cell row for the authorize predicate.

deps

query deps

cell_id

cell id

type Uuid

returns

Promise<CellGrantRow[]>

matching rows

query_cell_grant_list_for_cells
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, cell_ids: readonly (string & $brand<"Uuid">)[]): Promise<CellGrantRow[]> import {query_cell_grant_list_for_cells} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

List all grants across a set of cells, ordered by cell then creation. Used by the strict relation-read filter to test can_view_cell per target in memory — the caller groups the flat result by cell_id. Returns every grant on each cell (not caller-filtered), because can_view_cell needs the full grant list to decide admission.

deps

query deps

cell_ids

cells to fetch grants for (duplicates are harmless)

type readonly Uuid[]

returns

Promise<CellGrantRow[]>

matching grant rows (group by cell_id caller-side)

query_cell_grants_for_caller_in_cells
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, cell_ids: readonly (string & $brand<"Uuid">)[], viewer: CellViewer): Promise<CellGrantRow[]> import {query_cell_grants_for_caller_in_cells} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Load grants that admit the caller (by actor or role-scoped role_grants) across multiple cells. Used to enrich cell_list responses with context about what granted access. Returns grants for the given cells that match the caller's identity or role_grant set.

deps

query deps

cell_ids

cells to fetch grants for

type readonly Uuid[]

viewer

the caller, as cell_list bound its visibility predicate; its admin flag is not consulted

returns

Promise<CellGrantRow[]>

matching grants (may include grants the caller doesn't match; caller's list handler must filter when returning to the API)

throws

  • Error - when the viewer's role-grant arrays aren't parallel

query_cell_item_delete
#

db/cell_item_queries.ts view source

(deps: QueryDeps, parent_id: string & $brand<"Uuid">, position: string): Promise<CellItemRow | null> import {query_cell_item_delete} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Delete one item row by (parent_id, position). Returns the deleted row so callers can audit child_id after the delete without a pre-fetch.

deps

parent_id

type Uuid

position

type string

returns

Promise<CellItemRow | null>

the deleted row, or null when nothing matched

mutates

  • cell_item — deletes one row

query_cell_item_insert
#

db/cell_item_queries.ts view source

(deps: QueryDeps, input: CellItemInsertQueryInput): Promise<CellItemRow> import {query_cell_item_insert} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Insert one item row at the caller-supplied position.

Throws on (parent_id, position) collision (Postgres 23505); handler callers detect via is_pg_unique_violation and surface as cell_item_position_taken. Helper-side jitter (fractional_index) makes the collision rate negligible at realistic UX concurrency, so the throw is the cold-path safety net, not the hot path.

deps

input

returns

Promise<CellItemRow>

mutates

  • cell_item — inserts one row

query_cell_item_list_for_child
#

db/cell_item_queries.ts view source

(deps: QueryDeps, viewer: CellViewer, child_id: string & $brand<"Uuid">, options?: { limit?: number | undefined; } | undefined): Promise<CellItemRow[]> import {query_cell_item_list_for_child} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Reverse items list (child.lists[]).

Up to limit items whose parent is live and one viewer may view — filtered before the limit (can_view_cell_sql), so parents the caller can't see never crowd out ones it can. The child-view gate is the caller's responsibility.

deps

query deps

viewer

who is reading; CELL_VIEWER_UNRESTRICTED filters nothing

child_id

child cell id

type Uuid

options?

limit caps the row count (omitted = unbounded)

type { limit?: number | undefined; }
optional

returns

Promise<CellItemRow[]>

matching rows, oldest item first (by the item row's created_at, then its key)

query_cell_item_list_for_parent
#

db/cell_item_queries.ts view source

(deps: QueryDeps, viewer: CellViewer, parent_id: string & $brand<"Uuid">, options?: { limit?: number | undefined; position_after?: string | undefined; } | undefined): Promise<...> import {query_cell_item_list_for_parent} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Forward items list (parent.items[]), ordered by lex position.

Up to limit items whose child is live and one viewer may view (can_view_cell_sql, applied before the limit); position_after pages on. The parent-view gate is the caller's responsibility.

deps

query deps

viewer

who is reading; CELL_VIEWER_UNRESTRICTED filters nothing

parent_id

parent cell id

type Uuid

options?

limit caps the row count (omitted = unbounded); position_after resumes after a position (keyset pagination)

type { limit?: number | undefined; position_after?: string | undefined; }
optional

returns

Promise<CellItemRow[]>

matching rows in lex position order

query_cell_item_move
#

db/cell_item_queries.ts view source

(deps: QueryDeps, parent_id: string & $brand<"Uuid">, position_old: string, position_new: string): Promise<CellItemRow | null> import {query_cell_item_move} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Move an item row from position_old to position_new (same parent).

Implemented as an UPDATE on the PK; throws 23505 on collision with an existing row at position_new so handlers can surface cell_item_position_taken. The caller-supplied position_new is what fractional-indexing produced for the new slot — collisions are rare but the error path keeps the client truthful.

deps

parent_id

type Uuid

position_old

type string

position_new

type string

returns

Promise<CellItemRow | null>

the updated row, or null when the source row was missing (raced with a deleter)

mutates

  • cell_item — updates one row's `position`

query_cell_list
#

db/cell_queries.ts view source

(deps: QueryDeps, params: CellListParams): Promise<CellRow[]> import {query_cell_list} from '@fuzdev/fuz_app/db/cell_queries.js';

Filterable list query for the generic cell_list RPC.

Takes a flat filter shape (single optional clause per dimension; the cell_list API explicitly does NOT support OR'd alternatives within a dimension — keep it simple) plus an optional viewer-aware visibility predicate.

The visibility predicate mirrors can_view_cell || can_steward_cell in SQL form:

(viewer.is_admin OR (viewer.actor_id IS NOT NULL AND created_by = viewer.actor_id) OR (<moderation admits an audience> AND ( cell.visibility = 'public' OR (viewer.actor_id IS NOT NULL AND <grant admits caller>))) OR (viewer.actor_id IS NOT NULL AND <pending or rejected contribution> AND <viewer owns its live governing root>))

The moderation rules are cell_moderation_admits_audience_sql and cell_moderation_is_stewardable_sql (db/cell_moderation.ts). The last arm is the steward arm (auth/cell_steward.ts) — a root owner reads the moderation queue under their root; a global admin is already admitted.

The grants branch closes parity with can_view_cell: a SQL EXISTS over cell_grant, parameterized by the caller's actor_id and the parallel (role[], scope_id[]) projection of auth.role_grants. The caller's role_grants are materialized once via a caller_role_grants CTE so the role-grant unnest isn't re-scanned per outer row. Empty role_grant arrays are fine: the CTE yields zero rows, the inner EXISTS returns false, and the actor-grant branch still fires for actor-shaped grants.

shared_with_caller_only: true (shared_with: 'me' at the wire layer) takes a different SQL shape: instead of layering an extra conjunction on the cell-driven scan, it semi-joins through cell_grant, letting the planner drive from the (typically tiny) admitted-grant set via idx_cell_grant_actor / idx_cell_grant_role_scope rather than scanning every cell row. For a sharee with N grants over a table of M cells, the cost drops from O(M) to O(N + matched-cells). Owner-is-implicit (a cell's owner never appears as a grant principal) means the grants branch is itself owner-excluding, but the explicit created_by IS DISTINCT FROM caller guards against any future deviation. The shared_with branch does NOT bypass for admin: an admin asking "what's shared with me" wants their own grant footprint, not every cell. A grant admits there only while the cell's moderation admits an audience, as in can_view_cell, and the steward arm doesn't apply — a grant footprint is not a moderation queue.

Soft-deleted rows are excluded by default; opt-in via include_deleted.

deps

query deps

params

filter + visibility + ordering + pagination

returns

Promise<CellRow[]>

matching rows, ordered per order_by / order_direction

query_cell_list_by_kind
#

db/cell_queries.ts view source

(deps: QueryDeps, kind: string, options?: Pick<CellListOptions, "limit" | "offset"> | undefined): Promise<CellRow[]> import {query_cell_list_by_kind} from '@fuzdev/fuz_app/db/cell_queries.js';

List active cells with the given kind, newest first. Uses the idx_cell_kind index (cell.kind = ?).

deps

query deps

kind

cell.kind value to match (e.g. 'collection', 'entry')

type string

options?

pagination

type Pick<CellListOptions, "limit" | "offset">
optional

returns

Promise<CellRow[]>

matching active rows

query_cell_load_many
#

db/cell_queries.ts view source

(deps: QueryDeps, ids: readonly (string & $brand<"Uuid">)[]): Promise<CellRow[]> import {query_cell_load_many} from '@fuzdev/fuz_app/db/cell_queries.js';

Bulk-load active cell rows by id, no visibility filter applied — the full rows, for a caller that copies or returns them (the deep-clone walk). A caller that needs only the view check reads query_cell_view_load_many. Soft-deleted rows are excluded so relations to tombstones never surface.

deps

query deps

ids

cell ids to load (duplicates are harmless)

type readonly Uuid[]

returns

Promise<CellRow[]>

active rows in arbitrary order (caller indexes by id)

query_cell_set_moderation
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, moderation: string, options: { set_visibility_public: boolean; updated_by?: (string & $brand<"Uuid">) | null | undefined; }): Promise<...> import {query_cell_set_moderation} from '@fuzdev/fuz_app/db/cell_queries.js';

Transition a contribution's moderation marker (the cell_moderate verb's write). The WHERE carries the allowed from-states: an approval moves only a 'pending' row, and a rejection a 'pending' or 'approved' one (the moderator's takedown). A rejected or unmoderated cell never moves, and nothing already approved is approved again — without the from-state an approval would force any cell its caller can reach public. On set_visibility_public (an approval) it also flips visibility to 'public' so the approved contribution goes live; a rejection leaves visibility untouched — a pending post stays as its author set it, and a taken-down one keeps its visibility, hidden by the marker alone. updated_at / updated_by are stamped.

moderation is deliberately not writable through query_cell_update (it's absent from CellUpdatePatch) — gating the transition on a dedicated query (with the manage-tier authority check in the handler) is what stops an author self-approving their own pending cell.

deps

query deps

id

cell id

type Uuid

moderation

the terminal marker to write ('approved' | 'rejected')

type string

options

set_visibility_public flips visibility on approval; updated_by records the moderator

type { set_visibility_public: boolean; updated_by?: (string & $brand<"Uuid">) | null | undefined; }

returns

Promise<CellRow | null>

the updated row, or null when no active row in an allowed from-state matched (a raced delete or decision, or a refused transition)

mutates

  • cell — sets `moderation` (+ maybe `visibility`) on one row

query_cell_set_policy
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, policy: { [x: string]: unknown; } | null, options: { updated_by?: (string & $brand<"Uuid">) | null | undefined; }): Promise<...> import {query_cell_set_policy} from '@fuzdev/fuz_app/db/cell_queries.js';

Replace a root's contribution policy (the cell_policy_set verb's write); null clears it.

The WHERE carries parent_id IS NULL, so only a root ever takes a policy — the table CHECK's rule restated where the write is. updated_at / updated_by are stamped.

The replaced policy is read by the statement that writes, from a FOR UPDATE subquery: a concurrent set waits on that row lock and then reads the committed value, so the audit's previous_policy is the value this write replaced, never one a racing set already overwrote.

policy is deliberately not writable through query_cell_update (it's absent from CellUpdatePatch): data writes gate on edit authority, which an editor grant carries, and the policy decides who may contribute under the root, so it changes only through this query behind the handler's manage-tier check.

deps

query deps

id

the root's id

type Uuid

policy

the new policy, or null to clear it

type CellPolicy | null

options

updated_by records the manager

type { updated_by?: (string & $brand<"Uuid">) | null | undefined; }

returns

Promise<CellSetPolicyResult | null>

the updated row and the policy it replaced, or null when no active root matched (a raced delete)

mutates

  • cell — sets `policy` on one root

query_cell_update
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, patch: CellUpdatePatch): Promise<CellRow | null> import {query_cell_update} from '@fuzdev/fuz_app/db/cell_queries.js';

Update a cell. Fields left undefined in the patch keep their existing value; explicit null writes NULL. refs is re-derived from data whenever the patch updates data. updated_at is bumped to NOW() on every successful update.

deps

query deps

id

cell id

type Uuid

patch

subset of mutable fields

returns

Promise<CellRow | null>

the updated row, or null when no row matched (already deleted or never existed)

mutates

  • cell — updates one row

query_cell_view_load_many
#

db/cell_queries.ts view source

(deps: QueryDeps, ids: readonly (string & $brand<"Uuid">)[]): Promise<CellViewRow[]> import {query_cell_view_load_many} from '@fuzdev/fuz_app/db/cell_queries.js';

Bulk-load the active cells among ids for a view check — the CELL_VIEW_COLUMNS alone, never the payload. Backs the strict relation-read filter (auth/cell_relation_visibility.ts's filter_visible_target_ids), which runs can_view_cell per row in memory rather than in SQL. Soft-deleted rows and ids with no row are absent.

deps

query deps

ids

cell ids to load (duplicates are harmless)

type readonly Uuid[]

returns

Promise<CellViewRow[]>

active rows in arbitrary order (caller indexes by id)

query_count_active_accounts_with_global_role_for_update
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, role: string, exclude_role_grant_id?: (string & $brand<"Uuid">) | null): Promise<number> import {query_count_active_accounts_with_global_role_for_update} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Count active accounts (deleted_at IS NULL) holding an active global role_grant for role, counting only grants on active actors (act.deleted_at IS NULL) — and lock what it counted. Used by the last-admin guards on account_delete / account_purge and on role_grant_revoke of an admin grant: soft-delete revokes neither a tombstoned account's nor a tombstoned actor's admin grant, so an unfiltered count would include unusable admins — this joins account *and* filters the actor, so an account whose only admin grant sits on a tombstoned actor no longer counts (otherwise the guard could fail-open and let the last usable admin be removed).

Locks every counted role_grant, actor, and account row (FOR UPDATE) until the caller's transaction ends, so two guards racing to remove the last two admins serialize: the second waits for the first to commit, re-reads the rows it changed, and counts one. Run it inside the transaction that does the removal. Twin of the Rust query_count_active_accounts_with_global_role_for_update.

exclude_role_grant_id leaves one grant out of the count, so a revoke asks whether any active admin account would remain without that grant — an account holding the grant on two actors keeps counting through the other. The excluded grant is still locked, so two revokes of each other's grant serialize rather than deadlock.

deps

query dependencies

role

the role to count (e.g. ROLE_ADMIN)

type string

exclude_role_grant_id?

a grant to leave out of the count (the one being revoked)

type Uuid | null
optional default null

returns

Promise<number>

the number of distinct active accounts with an active global role grant

mutates

  • role_grant / actor / account rows — row-locks the counted rows for the transaction

query_create_account
#

auth/account_queries.ts view source

(deps: QueryDeps, input: CreateAccountInput): Promise<Account> import {query_create_account} from '@fuzdev/fuz_app/auth/account_queries.js';

Create a new account.

deps

query dependencies

input

the account fields

returns

Promise<Account>

the created account

mutates

  • account table — inserts the new row

query_create_account_with_actor
#

auth/account_queries.ts view source

(deps: QueryDeps, input: CreateAccountInput): Promise<{ account: Account; actor: Actor; }> import {query_create_account_with_actor} from '@fuzdev/fuz_app/auth/account_queries.js';

Create an account and its actor in a single operation.

For v1, every account gets exactly one actor with the same name as the username.

deps

query dependencies

input

the account fields

returns

Promise<{ account: Account; actor: Actor; }>

the created account and actor

mutates

  • account and actor tables — inserts one row in each

query_create_actor
#

auth/account_queries.ts view source

(deps: QueryDeps, account_id: string, name: string): Promise<Actor> import {query_create_actor} from '@fuzdev/fuz_app/auth/account_queries.js';

Create a new actor for an account.

deps

query dependencies

account_id

the owning account

type string

name

display name (defaults to account username)

type string

returns

Promise<Actor>

the created actor

mutates

  • actor table — inserts the new row

query_create_api_token
#

auth/api_token_queries.ts view source

(deps: QueryDeps, id: string, account_id: string, name: string, token_hash: string, scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }, expires_at?: Date | ... 1 more ... | undefined): Promise<...> import {query_create_api_token} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Store a new API token (the hash, not the raw token).

deps

query dependencies

id

the public token id (e.g. tok_abc123)

type string

account_id

the owning account

type string

name

human-readable name

type string

token_hash

blake3 hash of the raw token

type string

scope

the token's authority narrowing, stored as jsonb

expires_at?

optional expiration

type Date | null
optional

returns

Promise<ApiToken>

the stored token record

mutates

  • api_token table — inserts the new row keyed by `id`

query_create_invite
#

auth/invite_queries.ts view source

(deps: QueryDeps, input: CreateInviteInput): Promise<Invite> import {query_create_invite} from '@fuzdev/fuz_app/auth/invite_queries.js';

Create a new invite.

deps

query dependencies

input

the invite fields

returns

Promise<Invite>

the created invite

mutates

  • invite table — inserts the new row

query_create_role_grant
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, input: CreateRoleGrantInput): Promise<RoleGrant> import {query_create_role_grant} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Grant a role_grant to an actor. Idempotent — if an active (unrevoked and unexpired) role_grant already exists for this actor, role, and scope, returns it instead of creating a duplicate. An expired but unrevoked row in the slot is first revoked (revoked_reason = 'expired', revoked_by null — nobody chose to revoke it) so the fresh grant can take its place; returning the expired row would report a grant that confers nothing. The revoke and the insert are two statements, atomic when the caller runs them in a transaction (the action and offer-accept paths do).

The ON CONFLICT target and the fallback SELECT both collapse NULL scopes via the same sentinel + index-side 'GLOBAL' token used by the partial unique index (role_grant_actor_role_scope_active_unique). The IS NOT DISTINCT FROM form on the fallback is deliberate — plain = would miss the NULL-scope case where the conflict fired.

scope_kind is paired-null with scope_id per the role_grant_scope_kind_paired CHECK; mismatched pairs raise at the DB layer rather than producing silent rows.

deps

query dependencies

input

the role_grant fields

returns

Promise<RoleGrant>

the created or existing active role_grant

mutates

  • role_grant table — revokes an expired unrevoked row at `(actor_id, role, scope_kind, scope_id)`, then inserts a row when no active role_grant matches

query_create_session
#

auth/session_queries.ts view source

(deps: QueryDeps, token_hash: string, account_id: string, expires_at: Date): Promise<void> import {query_create_session} from '@fuzdev/fuz_app/auth/session_queries.js';

Create a new auth session.

deps

query dependencies

token_hash

blake3 hash of the session token (use hash_session_token)

type string

account_id

the account this session belongs to

type string

expires_at

when the session expires

type Date

returns

Promise<void>

mutates

  • auth_session table — inserts a row keyed by `token_hash`

query_db_status
#

db/status.ts view source

(db: Db, namespaces?: MigrationNamespace[] | undefined): Promise<DbStatus> import {query_db_status} from '@fuzdev/fuz_app/db/status.js';

Query database status including connectivity, tables, and migration state.

Designed for CLI db:status commands. Does not modify the database.

db

the database instance

type Db

namespaces?

migration namespaces to check status for

optional

returns

Promise<DbStatus>

a snapshot of database status; connected: false with error set when the initial connectivity probe fails

throws

  • Error - propagated from the driver if a query fails after the connectivity probe (e.g. a table is dropped mid-scan)

query_delete_fact
#

db/fact_queries.ts view source

(deps: QueryDeps, hash: FactHash): Promise<{ size: number; external_url: string | null; } | null> import {query_delete_fact} from '@fuzdev/fuz_app/db/fact_queries.js';

Drop a fact row. Cascades fact_ref rows via the ON DELETE CASCADE FK on source_hash. Returns the deleted row's (size, external_url) so the caller can unlink the disk file (if any) and tally freed bytes, or null when no row matched (idempotent: deleting an absent fact is not an error).

NOTE: this is a low-level primitive — callers MUST verify the fact is truly orphan (no referencing cell) before calling. The orphan check lives in query_orphan_facts_* below; the lifecycle wrapper in PgFactStore.delete handles the disk-file unlink.

deps

hash

type FactHash

returns

Promise<{ size: number; external_url: string | null; } | null>

query_fact_holder_table_exists
#

db/fact_holder_queries.ts view source

(deps: QueryDeps): Promise<boolean> import {query_fact_holder_table_exists} from '@fuzdev/fuz_app/db/fact_holder_queries.js';

Whether the fact_holder table exists — false on a database that mounts the cell verbs without the fact chain.

Probed before query_fact_holder_unheld, since naming a missing table would abort the caller's transaction. Twin of the Rust admission's to_regclass('fact_holder') probe, read from information_schema (which every supported driver answers).

deps

returns

Promise<boolean>

query_fact_holder_unheld
#

db/fact_holder_queries.ts view source

(deps: QueryDeps, actor_id: string & $brand<"Uuid">, hashes: readonly string[]): Promise<string[]> import {query_fact_holder_unheld} from '@fuzdev/fuz_app/db/fact_holder_queries.js';

The subset of hashes that actor_id does not hold, in input order.

Answered from fact_holder alone: a hash with no stored fact and a stored fact the actor never put read the same. Hashes are compared as text. The caller has checked the table exists.

deps

query deps

actor_id

the acting actor

type Uuid

hashes

the candidate hashes

type readonly string[]

returns

Promise<string[]>

the hashes with no (actor_id, hash) row

query_get_fact
#

db/fact_queries.ts view source

(deps: QueryDeps, hash: FactHash): Promise<FactRow | null> import {query_get_fact} from '@fuzdev/fuz_app/db/fact_queries.js';

Fetch a fact's full row (including embedded bytes). Use this from FactStore.get; cheaper accessors live below.

deps

hash

type FactHash

returns

Promise<FactRow | null>

query_get_fact_meta
#

db/fact_queries.ts view source

(deps: QueryDeps, hash: FactHash): Promise<FactMetaRow | null> import {query_get_fact_meta} from '@fuzdev/fuz_app/db/fact_queries.js';

Fetch metadata only — skips the (potentially large) bytes column.

deps

hash

type FactHash

returns

Promise<FactMetaRow | null>

query_get_fact_refs
#

db/fact_queries.ts view source

(deps: QueryDeps, source_hash: FactHash): Promise<FactHash[]> import {query_get_fact_refs} from '@fuzdev/fuz_app/db/fact_queries.js';

List declared targets for a source fact. Order is unspecified; callers that need stable ordering should sort.

deps

source_hash

type FactHash

returns

Promise<FactHash[]>

query_has_fact
#

db/fact_queries.ts view source

(deps: QueryDeps, hash: FactHash): Promise<boolean> import {query_has_fact} from '@fuzdev/fuz_app/db/fact_queries.js';

Cheap existence check. Backed by the fact PK index.

deps

hash

type FactHash

returns

Promise<boolean>

query_invite_claim_unscoped
#

auth/invite_queries.ts view source

(deps: QueryDeps, invite_id: string, account_id: string): Promise<boolean> import {query_invite_claim_unscoped} from '@fuzdev/fuz_app/auth/invite_queries.js';

Claim an invite by setting the claimed_by and claimed_at fields.

The _unscoped suffix is the safety signal — the SQL only checks the row state (claimed_at IS NULL), not whether the claiming account's email or username matches the invite. Callers must scope the lookup upstream through query_invite_find_unclaimed_match_for_update, which also makes find + claim atomic. Skipping the find step lets a caller claim any unclaimed invite by id.

Mirrors the query_session_revoke_by_hash_unscoped precedent — there is no scoped sibling because the scoping is provided by a separate find query, not by an alternate variant of this query.

deps

query dependencies

invite_id

the invite to claim

type string

account_id

the account claiming the invite

type string

returns

Promise<boolean>

true if the invite was claimed, false if already claimed or not found

mutates

  • invite row — sets `claimed_by` and `claimed_at` when still unclaimed

query_invite_delete_unclaimed
#

auth/invite_queries.ts view source

(deps: QueryDeps, id: string): Promise<boolean> import {query_invite_delete_unclaimed} from '@fuzdev/fuz_app/auth/invite_queries.js';

Delete an unclaimed invite.

deps

query dependencies

id

the invite id

type string

returns

Promise<boolean>

true if deleted, false if not found or already claimed

mutates

  • invite table — deletes the row when still unclaimed

query_invite_find_unclaimed_match_for_update
#

auth/invite_queries.ts view source

(deps: QueryDeps, email: string | null, username: string): Promise<Invite | undefined> import {query_invite_find_unclaimed_match_for_update} from '@fuzdev/fuz_app/auth/invite_queries.js';

Find an unclaimed invite matching email and/or username, taking a row-level write lock on the matched row.

Three scoping modes:

  • Email-only invite (email set, username NULL) → matches only if signup provides matching email.
  • Username-only invite (username set, email NULL) → matches only if signup provides matching username.
  • Both-field invite (both set) → requires BOTH email and username to match.

Must run inside the same transaction as query_invite_claim_unscoped: FOR UPDATE makes find + claim atomic, so a concurrent signup that matched the same invite blocks on the lock until this transaction commits/rolls back. After commit, the loser's find_for_update returns no row (the winner flipped claimed_at) and falls through to ERROR_NO_MATCHING_INVITE — no race window between find and claim.

deps

query dependencies — deps.db MUST be a transaction

email

email to match (or null if signup provides none)

type string | null

username

username to match

type string

returns

Promise<Invite | undefined>

the matching invite (locked), or undefined

query_invite_list_all_with_usernames
#

auth/invite_queries.ts view source

(deps: QueryDeps): Promise<{ id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; ... 4 more ...; claimed_by_username: string | null; }[]> import {query_invite_list_all_with_usernames} from '@fuzdev/fuz_app/auth/invite_queries.js';

List all invites with resolved creator/claimer usernames, newest first.

deps

query dependencies

returns

Promise<InviteWithUsernamesJson[]>

invites with created_by_username and claimed_by_username

query_migration_tracker
#

testing/schema_introspect.ts view source

(db: Db): Promise<{ entries: { namespace: string; name: string; sequence: number; }[]; }> import {query_migration_tracker} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Read every schema_version row into a deterministic MigrationTracker.

The migration-identity twin of query_schema_snapshot: that captures the resulting schema (and excludes this tracker); this captures the tracker rows themselves, so the cross-impl gate can assert the two spines record byte-identical migration identity.

db

type Db

returns

Promise<MigrationTracker>

query_orphan_facts_list
#

db/fact_queries.ts view source

(deps: QueryDeps, older_than: Date | null, sample_limit: number): Promise<OrphanFactsListResult> import {query_orphan_facts_list} from '@fuzdev/fuz_app/db/fact_queries.js';

Compute the "orphan facts" set: rows in fact where no active (non-tombstone) cell.refs array contains the hash.

The cell join is deliberately app-coupled — fact lives in the fuz_facts namespace and cell.refs lives in fuz_cell, but the orphan predicate only makes sense in apps that route content through cells. When a non-cell fact consumer ever appears (signed memo outputs? external fact mirrors?) the predicate moves to a generic fact_consumers registry; today the cell layer is the only consumer.

The older_than filter is the grace cutoff: a fact passes it when both its created_at and every fact_holder row's are older (a re-put refreshes the holder row). Pass null to skip the filter (used by the list-summary preview); the delete handler always passes a non-null cutoff (default 0, meaning "any orphan").

deps

query deps

older_than

filter to facts created, and last put by any holder, before this Date (or null to skip)

type Date | null

sample_limit

row cap for the returned sample

type number

returns

Promise<OrphanFactsListResult>

query_orphan_facts_select_for_delete
#

db/fact_queries.ts view source

(deps: QueryDeps, older_than: Date): Promise<{ hash: FactHash; size: number; external_url: string | null; }[]> import {query_orphan_facts_select_for_delete} from '@fuzdev/fuz_app/db/fact_queries.js';

Select the orphan-fact hashes for deletion. Returns the rows directly (no row-count limit) — callers iterate to unlink disk files. The older_than cutoff is required (non-null) here: bulk delete should always be operator-scoped to a time window. A "delete all" sweep passes a far-future cutoff, not null. It is the same grace cutoff as query_orphan_facts_list: a fact put — or re-put, deduplicated — by an actor within the window is kept, whatever its own created_at.

deps

older_than

type Date

returns

Promise<{ hash: FactHash; size: number; external_url: string | null; }[]>

query_public_columns
#

db/schema_ready.ts view source

(db: Db, options?: { table?: string | undefined; } | undefined): Promise<Record<string, string[]>> import {query_public_columns} from '@fuzdev/fuz_app/db/schema_ready.js';

Introspect every column in the public schema, grouped by relation. Shared by the runtime /ready check and the fixture-generating helper so both observe the exact same shape. information_schema.columns spans tables and views; for the drift check that's harmless (a never-bootstrapped schema has neither, and extra relations are ignored — see check_schema_drift).

Unlike query_schema_snapshot (which excludes the schema_version migration tracker as framework bookkeeping), this keeps schema_version — a never-migrated DB then correctly fails readiness instead of passing on an empty expectation.

db

the database to introspect

type Db

options?

table narrows the scan to one relation (the drift guard's one-table question); omitted, every public relation is returned

type { table?: string | undefined; }
optional

returns

Promise<Record<string, string[]>>

relation name → sorted column names

query_purge_account
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<AccountIdentitySnapshot | undefined> import {query_purge_account} from '@fuzdev/fuz_app/auth/account_queries.js';

Hard-purge an account — irreversible cascading removal (purge = hard).

Physically deletes the row, cascading to actors, role_grants, sessions, and tokens. Operates on active OR already-soft-deleted rows. Returns the identity snapshot for the account_purge audit event, or undefined when no row matched. The audit_log identity columns carry no FK, so the purged id survives on historical rows for forensic correlation back to the purge event.

Keeper-gated, loud, irreversible — restrict to the keeper credential and confirm explicitly at the call site. The purge name flags the danger.

deps

id

type string

returns

Promise<AccountIdentitySnapshot | undefined>

mutates

  • account table and downstream FK rows — DELETE cascades through actors/role_grants/sessions/tokens

query_put_fact
#

db/fact_queries.ts view source

(deps: QueryDeps, input: PutFactInput): Promise<boolean> import {query_put_fact} from '@fuzdev/fuz_app/db/fact_queries.js';

Idempotently insert a fact row.

bytes xor external_url per the fact_storage_present CHECK constraint; the caller is responsible for satisfying it (the queries layer does not second-guess). Returns true when a new row was inserted, false when a row already existed (caller can use this to decide whether to also write fact_ref).

deps

input

returns

Promise<boolean>

query_put_fact_holder
#

db/fact_holder_queries.ts view source

(deps: QueryDeps, actor_id: string & $brand<"Uuid">, hash: FactHash): Promise<void> import {query_put_fact_holder} from '@fuzdev/fuz_app/db/fact_holder_queries.js';

Record actor_id as a holder of hash, or refresh the row's created_at when it already holds it. The fact row must already exist (FK).

created_at is the holder's most recent put, which the orphan sweep reads as the fact's grace window: a re-put keeps an old orphan out of any sweep whose window covers the put.

deps

query deps

actor_id

the actor that brought the bytes

type Uuid

hash

the fact's hash

type FactHash

returns

Promise<void>

mutates

  • fact_holder — inserts the `(actor_id, hash)` row, or sets an existing row's `created_at` to now

query_put_fact_refs
#

db/fact_queries.ts view source

(deps: QueryDeps, source_hash: FactHash, target_hashes: FactHash[]): Promise<void> import {query_put_fact_refs} from '@fuzdev/fuz_app/db/fact_queries.js';

Idempotently insert declared refs for a fact. No-ops on `(source_hash, target_hash)` collisions and skips the round trip entirely when target_hashes is empty.

deps

source_hash

type FactHash

target_hashes

type FactHash[]

returns

Promise<void>

query_revoke_all_api_tokens_for_account
#

auth/api_token_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<number> import {query_revoke_all_api_tokens_for_account} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Revoke all tokens for an account.

deps

query dependencies

account_id

the account whose tokens to revoke

type string

returns

Promise<number>

the number of tokens revoked

mutates

  • api_token table — deletes every row for `account_id`

query_revoke_api_token_for_account
#

auth/api_token_queries.ts view source

(deps: QueryDeps, id: string, account_id: string): Promise<boolean> import {query_revoke_api_token_for_account} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Revoke a token only if it belongs to the specified account.

Prevents cross-account token revocation.

deps

query dependencies

id

the public token id

type string

account_id

the account that must own the token

type string

returns

Promise<boolean>

true if a token was revoked, false if not found or wrong account

mutates

  • api_token table — deletes the row when account ownership matches

query_revoke_role_grant
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, role_grant_id: string & $brand<"Uuid">, actor_id: string & $brand<"Uuid">, revoked_by: (string & $brand<"Uuid">) | null, reason?: string | ... 1 more ... | undefined): Promise<...> import {query_revoke_role_grant} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Revoke a role_grant by id, constrained to a specific actor.

Requires actor_id to prevent cross-account revocation (IDOR guard). Returns null if the role_grant is not found, already revoked, or belongs to a different actor.

Supersedes any pending offers for the revoked role_grant's (to_account, role, scope) in the same transaction. Prevents the "accept a pre-revoke offer to bypass the revoke" path — any stale offer becomes terminal at revoke time. A fresh post-revoke grant requires the grantor to call query_role_grant_offer_create again.

deps

query dependencies

role_grant_id

the role_grant to revoke

type Uuid

actor_id

the actor that must own the role_grant

type Uuid

revoked_by

the actor who revoked it (for audit trail)

type Uuid | null

reason?

optional free-form reason, stamped on role_grant.revoked_reason and surfaced to the revokee notification.

type string | null
optional

returns

Promise<RevokeRoleGrantResult | null>

mutates

  • role_grant row — sets `revoked_at`, `revoked_by`, and `revoked_reason`
  • role_grant_offer rows — stamps `superseded_at` on every pending sibling for the same `(account, role, scope)`

query_role_grant_by_id
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">): Promise<RoleGrant | undefined> import {query_role_grant_by_id} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Find a role_grant by id, regardless of revoked / expired state.

Unscoped by design — the one caller is query_accept_offer's race-loser path, which resolves an already-accepted offer's resulting_role_grant_id (a row the offer's own IDOR guard has already vouched for). Authority checks stay at the call site.

deps

id

type Uuid

returns

Promise<RoleGrant | undefined>

query_role_grant_find_account_id_for_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, role: string): Promise<string | null> import {query_role_grant_find_account_id_for_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Find the account ID of an account that holds an active global role_grant for a given role.

Global-only (scope_id IS NULL), like every builtin read: a scoped row of the role confers nothing, so it must not resolve the keeper identity.

Joins role_grant → actor → account, counting only an active account + active actor (deleted_at IS NULL on both). This *resolves* an identity (the daemon-token keeper via resolve_keeper_account_id), so a tombstoned actor must not grant the role — the opposite posture from the removability guard query_account_has_global_role, which stays unconditional. Returns the first match by ascending account.id (deterministic under multiple holders), or null if none.

deps

query dependencies

role

the role to search for

type string

returns

Promise<string | null>

the account ID, or null

query_role_grant_find_active_for_actor
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, actor_id: string): Promise<RoleGrant[]> import {query_role_grant_find_active_for_actor} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Find all active (non-revoked, non-expired) role_grants for an actor.

deps

actor_id

type string

returns

Promise<RoleGrant[]>

query_role_grant_find_active_role_for_actor
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, role_grant_id: string, actor_id: string): Promise<{ role: string; scope_id: (string & $brand<"Uuid">) | null; account_id: string & $brand<"Uuid">; } | null> import {query_role_grant_find_active_role_for_actor} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Look up the role and scope of an active role_grant (constrained to a specific actor) plus the actor's account_id.

Used by admin routes to inspect the role_grant's role before acting (e.g., enforcing the admin-grant-path gate and the global-admin guards on revoke). The actor constraint mirrors query_revoke_role_grant so IDOR protection is consistent: a caller can only see role_grants belonging to the target actor.

The JOIN to actor collapses what used to be a second query_actor_by_id round-trip in the revoke handler into one read, which closes the small TOCTOU window where the actor row could be deleted between the IDOR check and the actor lookup. The account_id is needed by the audit envelope's target_account_id field and the SSE/WS socket-close fan-out targeting.

Returns null if the role_grant is not found, already revoked, or belongs to a different actor.

deps

query dependencies

role_grant_id

the role_grant id to look up

type string

actor_id

the actor that must own the role_grant

type string

returns

Promise<{ role: string; scope_id: (string & $brand<"Uuid">) | null; account_id: string & $brand<"Uuid">; } | null>

{role, scope_id, account_id} on a match, or null

query_role_grant_offer_create
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, input: CreateRoleGrantOfferInput): Promise<RoleGrantOffer> import {query_role_grant_offer_create} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Create a new role_grant offer, or refresh an existing pending offer for the same (to_account_id, role, scope_id, from_actor_id) tuple.

Re-offer semantics: a second call by the same grantor with the same (to_account, role, scope) while pending upserts the existing row, refreshing message and expires_at (and to_actor_id — supplying a different to_actor_id on re-offer narrows the existing row to the named actor; supplying null widens it back to account-grain) and clearing expire_audited_at, so a re-offer of an expired, already-audited row gets its own role_grant_offer_expire when the refreshed expiry passes. A different grantor offering the same (to_account, role, scope) creates a distinct row — multiple pending grantors coexist. After a terminal state, a re-offer is a fresh INSERT.

Self-offer rejection: throws RoleGrantOfferSelfTargetError if the offering actor belongs to the recipient account.

Actor-targeted offers: when to_actor_id is supplied, query_accept_offer rejects any actor other than the named one. Closes the audit hole where offer-shape events would otherwise leave target_actor_id null even when the recipient binding is known at offer time. The actor↔account binding is verified here in one SELECT.

deps

input

returns

Promise<RoleGrantOffer>

throws

  • RoleGrantOfferSelfTargetError - if the offering actor belongs to `to_account_id`
  • RoleGrantOfferActorAccountMismatchError - if `to_actor_id` is set but does not belong to `to_account_id`

mutates

  • role_grant_offer table — inserts a new offer or upserts the matching pending row

query_role_grant_offer_decline
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, offer_id: string, to_account_id: string, reason: string | null): Promise<DeclinedOffer | null> import {query_role_grant_offer_decline} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Mark an offer declined.

Guarded by to_account_id (IDOR). Returns null if the offer does not exist or belongs to a different account. Throws RoleGrantOfferAlreadyTerminalError if the offer exists for the caller but is already in a terminal state.

Returns the declined offer with the grantor's from_account_id joined in via CTE — the decline audit envelope populates both target_actor_id (the grantor actor) and target_account_id (the grantor account), satisfying the "both populated → same account" invariant the audit-log column comments describe.

deps

offer_id

type string

to_account_id

type string

reason

type string | null

returns

Promise<DeclinedOffer | null>

throws

  • RoleGrantOfferAlreadyTerminalError - if the offer is already accepted, declined, retracted, or superseded

mutates

  • role_grant_offer row — sets `declined_at` and `decline_reason`

query_role_grant_offer_history_for_account
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, account_id: string, limit?: number, offset?: number): Promise<RoleGrantOffer[]> import {query_role_grant_offer_history_for_account} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

List every offer involving an account (either direction), newest first.

Includes terminal offers — used by the grantor-side admin / history view.

deps

account_id

type string

limit?

type number
optional default 100

offset?

type number
optional default 0

returns

Promise<RoleGrantOffer[]>

query_role_grant_offer_list
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, to_account_id: string): Promise<RoleGrantOffer[]> import {query_role_grant_offer_list} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

List pending, non-expired offers for an account, soonest expiry first.

Expired offers are filtered server-side (expires_at > NOW()) so the inbox never surfaces a row that can no longer be accepted. The periodic sweep (query_role_grant_offer_sweep_expired) audits each expiry once.

deps

to_account_id

type string

returns

Promise<RoleGrantOffer[]>

query_role_grant_offer_retract
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, offer_id: string, from_actor_id: string): Promise<RoleGrantOffer | null> import {query_role_grant_offer_retract} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Mark an offer retracted by the grantor.

Guarded by from_actor_id (IDOR). Returns null if the offer does not exist or was issued by a different actor. Throws RoleGrantOfferAlreadyTerminalError if the offer exists for this grantor but is already in a terminal state.

deps

offer_id

type string

from_actor_id

type string

returns

Promise<RoleGrantOffer | null>

throws

  • RoleGrantOfferAlreadyTerminalError - if the offer is already accepted, declined, retracted, or superseded

mutates

  • role_grant_offer row — sets `retracted_at`

query_role_grant_offer_supersede_outgoing
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, from_actor_ids: readonly (string & $brand<"Uuid">)[]): Promise<SupersededOffer[]> import {query_role_grant_offer_supersede_outgoing} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Supersede every pending offer made by one of from_actor_ids — the grantor-side cascade.

An offer carries no record of the authority it was made under, so one outliving that authority would still confer its role on accept. Revoking a grant (role_grant_revoke, the revokee's actor) and removing an account (account_delete / account_purge, every actor of the account) end the grantor's outgoing pending offers in the same transaction. Each row carries the grantor's from_account_id, as every supersede cascade's does; the caller emits a role_grant_offer_supersede audit event per entry, and the revoke caller also sends the supersede notification. Twin of the Rust query_role_grant_offer_supersede_outgoing.

deps

query dependencies

from_actor_ids

the grantors whose pending offers end

type readonly Uuid[]

returns

Promise<SupersededOffer[]>

the superseded offers, each with its grantor's account

mutates

  • role_grant_offer rows — stamps `superseded_at` on every pending offer from `from_actor_ids`

query_role_grant_offer_sweep_expired
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps): Promise<RoleGrantOffer[]> import {query_role_grant_offer_sweep_expired} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Claim the pending offers whose expires_at has passed and whose expiry has not been audited yet, stamping expire_audited_at on each, and return them soonest-expiry first.

The stamp is the sweep's idempotency: a claimed row never matches again, so each expiry is audited exactly once. It must share a transaction with the caller's role_grant_offer_expire audit inserts — committed together, or rolled back together so the next sweep retries. Concurrent sweeps claim disjoint rows: a row another sweep has stamped but not committed blocks on its row lock, then fails the re-checked expire_audited_at IS NULL. cleanup_expired_role_grant_offers is the caller.

deps

returns

Promise<RoleGrantOffer[]>

mutates

  • role_grant_offer rows — stamps `expire_audited_at` on each claimed row

query_role_grant_revoke_for_scope
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, scope_id: string & $brand<"Uuid">, revoked_by: (string & $brand<"Uuid">) | null, reason?: string | null | undefined): Promise<RevokeForScopeResult> import {query_role_grant_revoke_for_scope} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Revoke every active role_grant bound to a scope and supersede every pending offer at the scope, in one cascade.

Use this from a consumer's parent-scope delete handler (e.g., classroom deletion) — role_grant.scope_id and role_grant_offer.scope_id are polymorphic with no FK constraint by design, so a parent row deletion would otherwise orphan role_grants and offers. The cascade is role-agnostic: anything attached to the destroyed scope is cleaned up.

Both updates run as separate statements inside the caller's transaction, the role_grant revoke first. The two halves are independent — orphan pending offers can exist at a scope with no active role_grants, so the supersede half always runs even when no role_grant was revoked.

deps

query dependencies

scope_id

the scope whose role_grants and offers to terminate

type Uuid

revoked_by

the actor performing the cascade (audit trail)

type Uuid | null

reason?

optional free-form reason, stamped on role_grant.revoked_reason.

type string | null
optional

returns

Promise<RevokeForScopeResult>

the revoked role_grants (with account_id for fan-out) and superseded offers (with from_account_id for fan-out)

mutates

  • role_grant table — sets `revoked_at`/`revoked_by`/`revoked_reason` on every active row at `scope_id`
  • role_grant_offer table — stamps `superseded_at` on every pending row at `scope_id`

query_schema_snapshot
#

testing/schema_introspect.ts view source

(db: Db, options?: QuerySchemaSnapshotOptions): Promise<{ tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { ...; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }> import {query_schema_snapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Introspect a live database into a deterministic SchemaSnapshot.

Reads information_schema and pg_catalog to capture tables, columns, indexes, constraints, and sequences.

The schema_version migration tracker never appears in the tables field — it's framework bookkeeping created by the migration runner, identical across consumers, and would only add noise.

db

the database to introspect

type Db

options?

schema, table excludes, and the pglet opt-in

optional default {}

returns

Promise<SchemaSnapshot>

the snapshot, every collection sorted

throws

  • Error - on pglet when the snapshot holds an index and `accept_pglet_index_definitions` is not set — its index definitions omit modifiers, so a comparison built on them is wrong in both directions

query_session_cleanup_expired
#

auth/session_queries.ts view source

(deps: QueryDeps): Promise<string[]> import {query_session_cleanup_expired} from '@fuzdev/fuz_app/auth/session_queries.js';

Delete every expired session, returning the ids (token hashes) of the rows it deleted.

The ids are the session_hash of the session close target (ConnectionCloser.close_connections). A row past expires_at already fails query_session_get_valid, but deleting it does not end the connections it opened: a WebSocket keeps the authority it resolved at upgrade and an SSE stream never rechecks. The row was also the only per-session handle on those connections — account_session_revoke closes only for a row it deleted — so the caller closes each returned session's connections once the delete has committed. cleanup_expired_sessions (auth/cleanup.ts) is that caller; reach for it rather than this query.

One statement, so the returned ids are exactly the rows this call removed: a concurrent pass gets a disjoint set. Run it in a transaction, as the caller above does, so the ids are in hand before the delete commits — on a pool-level Db a connection lost on the response leaves the rows deleted and their ids unread. Served by idx_auth_session_expires. Twin of the Rust query_session_cleanup_expired.

deps

query dependencies (transaction-scoped, see above)

returns

Promise<string[]>

the deleted sessions' ids (their token hashes), empty when nothing had expired

mutates

  • auth_session table — deletes every row past `expires_at`

query_session_enforce_limit
#

auth/session_queries.ts view source

(deps: QueryDeps, account_id: string, max_sessions: number): Promise<string[]> import {query_session_enforce_limit} from '@fuzdev/fuz_app/auth/session_queries.js';

Enforce a per-account session limit by evicting the oldest sessions.

Keeps the newest max_sessions sessions and deletes the rest.

Race safety: this function must run inside a transaction alongside the INSERT that created the new session. All callers satisfy this requirement:

  • POST /login uses the default transaction: true (framework-managed transaction wrapping in apply_route_specs)
  • The account_token_create RPC handler runs under the dispatcher's transaction path because its spec declares side_effects: true
  • POST /bootstrap and POST /signup manage their own transactions and pass the transaction-scoped deps to create_session_and_set_cookie

The transaction makes one creator's INSERT + enforce_limit pair atomic, but it does not serialize concurrent creators. Under Read Committed, two transactions can't see each other's uncommitted session row, so each computes its OFFSET eviction against a stale count, each preserves its own row, and both commit above max_sessions. A transaction is necessary here but not sufficient. Closing this needs one serialization point per (account_id, credential_kind) — a locked parent row or a transaction-scoped advisory lock — taken before count/evict/insert.

Ordering is created_at DESC, id DESC. The id leg is a stability tie-breaker, not a recency one: created_at defaults to NOW(), the *transaction* timestamp, so two sessions born in one transaction — or in two transactions that started in the same microsecond — tie, and an untied OFFSET may keep a different set on two evaluations of the same rows. id is the blake3 token hash, so the tie-break is arbitrary but deterministic; it makes the survivors reproducible, it does not make the row just inserted a guaranteed survivor. Only the serialization point above does that.

Expired-but-unreaped rows count toward the cap (the predicate is account_id alone). Matches the Rust twin query_session_enforce_limit.

Deleting a session row does not end the connections it opened: a WebSocket keeps the authority it resolved at upgrade and an SSE stream never rechecks. The returned ids are the token hashes those connections are registered under, so the caller closes them once the transaction commits (queue_connection_close) — POST /login does.

deps

query dependencies (must be transaction-scoped)

account_id

the account to enforce the limit for

type string

max_sessions

maximum number of sessions to keep

type number

returns

Promise<string[]>

the evicted sessions' ids (their token hashes), empty when nothing was evicted

mutates

  • auth_session table — deletes the oldest rows past the cap

query_session_get_valid
#

auth/session_queries.ts view source

(deps: QueryDeps, token_hash: string): Promise<ValidAuthSession | undefined> import {query_session_get_valid} from '@fuzdev/fuz_app/auth/session_queries.js';

Get a session if it exists, is not expired, and has not been revoked, with how long it has left.

The remaining lifetime is read in the same statement that checks the expiry, on the database clock, so it agrees with the check whatever the server's clock says (auth/credential_deadline.ts).

deps

query dependencies

token_hash

blake3 hash of the session token

type string

returns

Promise<ValidAuthSession | undefined>

query_session_list_all_active
#

auth/session_queries.ts view source

(deps: QueryDeps, limit?: number): Promise<(AuthSession & { username: string; })[]> import {query_session_list_all_active} from '@fuzdev/fuz_app/auth/session_queries.js';

List all active sessions across all accounts with usernames.

Ordered by created_at DESC (newest session first), matching the per-account listing.

deps

query dependencies

limit?

maximum entries to return

type number
optional default 200

returns

Promise<(AuthSession & { username: string; })[]>

active sessions joined with account usernames, newest first

query_session_list_for_account
#

auth/session_queries.ts view source

(deps: QueryDeps, account_id: string, limit?: number): Promise<AuthSession[]> import {query_session_list_for_account} from '@fuzdev/fuz_app/auth/session_queries.js';

List sessions for an account, newest first.

deps

account_id

type string

limit?

type number
optional default 50

returns

Promise<AuthSession[]>

query_session_revoke_all_for_account
#

auth/session_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<number> import {query_session_revoke_all_for_account} from '@fuzdev/fuz_app/auth/session_queries.js';

Revoke all sessions for an account.

deps

account_id

type string

returns

Promise<number>

the number of sessions revoked

mutates

  • auth_session table — deletes every row for `account_id`

query_session_revoke_by_hash_unscoped
#

auth/session_queries.ts view source

(deps: QueryDeps, token_hash: string): Promise<void> import {query_session_revoke_by_hash_unscoped} from '@fuzdev/fuz_app/auth/session_queries.js';

Revoke (delete) a session by its token hash, with no account scoping.

The _unscoped suffix is the safety signal — there is no account_id constraint, so callers must guarantee the hash came from a trusted source (the authenticated session cookie path is the only safe production caller — see auth/account_routes.ts /logout). For user-facing revocation of a specific session by ID, use query_session_revoke_for_account (IDOR-guarded).

deps

token_hash

type string

returns

Promise<void>

mutates

  • auth_session table — deletes the row keyed by `token_hash`

query_session_revoke_for_account
#

auth/session_queries.ts view source

(deps: QueryDeps, token_hash: string, account_id: string): Promise<boolean> import {query_session_revoke_for_account} from '@fuzdev/fuz_app/auth/session_queries.js';

Revoke a session only if it belongs to the specified account.

Prevents cross-account session revocation.

deps

query dependencies

token_hash

blake3 hash of the session token

type string

account_id

the account that must own the session

type string

returns

Promise<boolean>

true if a session was revoked, false if not found or wrong account

mutates

  • auth_session table — deletes the row when account ownership matches

query_update_account_password
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string, password_hash: string, updated_by: string | null, expected_hash: string): Promise<boolean> import {query_update_account_password} from '@fuzdev/fuz_app/auth/account_queries.js';

Update the password hash for an account, conditional on the current stored hash matching expected_hash — the verify-write atomic guard.

The condition closes the race where two concurrent password changes both verify against the pre-update hash (loaded by the authorization phase outside the route's transaction) and would otherwise both UPDATE, silently clobbering whichever lands first. With the conditional WHERE, the second UPDATE matches zero rows; the route reads the boolean return and surfaces 401 instead of pretending success.

Pass the same hash the verify ran against — typically ctx.account.password_hash from the request context.

deps

id

type string

password_hash

type string

updated_by

type string | null

expected_hash

type string

returns

Promise<boolean>

true if the row was updated, false if expected_hash no longer matched (concurrent change won — caller should treat as a stale-credential failure).

mutates

  • account row — updates `password_hash`, `updated_at`, and `updated_by` only when the stored hash equals `expected_hash`

query_validate_api_token
#

auth/api_token_queries.ts view source

(deps: ApiTokenQueryDeps, raw_token: string, ip: string | undefined, pending_effects: Promise<void>[] | undefined): Promise<ValidatedApiToken | undefined> import {query_validate_api_token} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Validate a raw API token and return the token record with its scope parsed.

Hashes the token with blake3, looks up the hash, and checks expiration, reading how long the token has left on the database clock in the same statement. A row whose stored scope is unreadable is refused like an unknown hash. Updates last_used_at and last_used_ip on success (fire-and-forget — errors logged, never thrown).

deps

query dependencies with logger

raw_token

the raw API token from the Authorization header

type string

ip

the client IP address (for audit)

type string | undefined

pending_effects

optional array to register the usage-tracking effect for later awaiting

type Promise<void>[] | undefined

returns

Promise<ValidatedApiToken | undefined>

the token record with its parsed scope if valid, or undefined

mutates

  • api_token row — fire-and-forget UPDATE of `last_used_at` / `last_used_ip` on a valid token
  • pending_effects — pushes the in-flight tracking promise when provided

QueryDeps
#

db/query_deps.ts view source

QueryDeps import type {QueryDeps} from '@fuzdev/fuz_app/db/query_deps.js';

Base dependency for all query functions.

db

type Db

QuerySchemaSnapshotOptions
#

testing/schema_introspect.ts view source

QuerySchemaSnapshotOptions import type {QuerySchemaSnapshotOptions} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Filter options for query_schema_snapshot.

schema?

Schema name to introspect — defaults to 'public'. Single-schema only; cross-schema introspection isn't a current need.

type string

readonly

exclude_tables?

Tables to exclude from the snapshot. The schema_version migration tracker is always excluded — it's framework bookkeeping created by the migration runner, identical across impls, and not part of any consumer's domain schema.

type readonly string[]

readonly

accept_pglet_index_definitions?

Take the snapshot on pglet even though it holds indexes. Default false: pglet's pg_indexes.indexdef omits index modifiers (sort direction, NULLS placement, operator classes, NULLS NOT DISTINCT), so the snapshot's index definitions cannot be compared with another engine's, and do not tell two pglet schemas apart when they differ only in a modifier. Pass true only where no index definition is compared — reading tables, columns, constraints, sequences or enums off the snapshot. No effect on PostgreSQL or PGlite.

type boolean

readonly

QueryShapeCrossTestOptions
#

testing/cross_backend/query_shape.ts view source

RpcPathCapabilityGatedCrossSuiteOptions import type {QueryShapeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/query_shape.js';

Options for the query-shape parity suite. The standard capability-gating RPC-dispatched cross-suite shape (setup_test / capabilities / rpc_path); aliases the shared RpcPathCapabilityGatedCrossSuiteOptions rather than minting a duplicate.

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability declarations — each suite gates on its own flag.

type BackendCapabilities

readonly

QueryTestCase
#

testing/adversarial_input_cases.ts view source

QueryTestCase import type {QueryTestCase} from '@fuzdev/fuz_app/testing/adversarial_input_cases.js';

One adversarial query for a route's query schema.

label

type string

query

type Record<string, string>

queue_connection_close
#

actions/connection_closer.ts view source

(ctx: EmitAfterCommitContext, closer: ConnectionCloser, target: ConnectionCloseTarget): void import {queue_connection_close} from '@fuzdev/fuz_app/actions/connection_closer.js';

Queue a connection close on ctx's post-commit queue, so it runs only after the request's transaction commits and never when the handler throws.

The one way a revocation handler closes connections — see the module doc for why an in-transaction revocation must not close inline.

In a handler with no wrapping transaction a throw still drops the queued close while an autocommit write stays; such a handler closes inline straight after its own commit instead, with close_connections_after_commit, which catches and logs a failing close as the post-commit flush does.

ctx

the route or action context of the request doing the revoking

closer

the closer to run, deps.connection_closer in a handler

target

the connections to close

returns

void

mutates

  • ctx.post_commit_effects — appends the close

rate_limit_exceeded_response
#

rate_limiter.ts view source

(c: Context<any, any, {}>, retry_after: number): Response import {rate_limit_exceeded_response} from '@fuzdev/fuz_app/rate_limiter.js';

Build a 429 rate-limit-exceeded JSON response with Retry-After header.

c

Hono context

type Context<any, any, {}>

retry_after

seconds until the client should retry

type number

returns

Response

a 429 Response

rate_limit_ip_key
#

http/ip_canonical.ts view source

(ip: string): string import {rate_limit_ip_key} from '@fuzdev/fuz_app/http/ip_canonical.js';

The bucket key an IP-keyed rate limiter uses for client IP ip.

An IPv6 address keys its /64: the low 64 bits masked off, rendered in RFC 5952 canonical form with a /64 suffix (2001:db8:1:2::/64; the all-zero prefix is ::/64). A zone suffix (%eth0) is dropped first. An IPv4 address — an IPv4-mapped IPv6 one included — keys its canonical form, with no suffix. Anything that doesn't parse ('unknown' among them) is returned unchanged. Twin of the Rust spine's fuz_http::rate_limit_ip_key.

For limiter keys only: audit rows and every other record of the client keep the full address.

ip

type string

returns

string

examples

rate_limit_ip_key('2001:db8:1:2::1') // → '2001:db8:1:2::/64' rate_limit_ip_key('2001:db8:1:2:8000::') // → '2001:db8:1:2::/64' rate_limit_ip_key('fe80::1%eth0') // → 'fe80::/64' rate_limit_ip_key('203.0.113.7') // → '203.0.113.7' rate_limit_ip_key('unknown') // → 'unknown'

RATE_LIMITERS_DISABLED_BANNER
#

rate_limiter.ts view source

"!!! RATE LIMITERS DISABLED — test-only mode. A production server must never print this." import {RATE_LIMITERS_DISABLED_BANNER} from '@fuzdev/fuz_app/rate_limiter.js';

The stderr line a process prints the first time 'disabled_for_testing' leaves a limiter null — the same text as the Rust spine's.

RateLimiter
#

rate_limiter.ts view source

import {RateLimiter} from '@fuzdev/fuz_app/rate_limiter.js';

In-memory sliding window rate limiter.

Stores an array of timestamps per key, plus a count of attempts reserved (try_reserve) and not yet settled. On check/record, timestamps outside the window are pruned. retry_after reports seconds until the oldest active timestamp expires.

The backing store is an LruMap when options.max_keys is a positive number (default DEFAULT_RATE_LIMITER_MAX_KEYS) and a plain Map when max_keys is null. The LruMap path bounds memory under key-enumeration attack at the cost of a slight per-op overhead and the LRU trade-off described on RateLimiterOptions.max_keys.

Parameters that accept RateLimiter | null (e.g. login_ip_rate_limiter, login_account_rate_limiter) disable rate limiting when null is passed — no checks are performed and all requests are allowed through. create_app_server surfaces each disabled limiter as a warning diagnostic.

options

type RateLimiterOptions

readonly

constructor

type new (options: RateLimiterOptions): RateLimiter

options

the limiter's bounds

throws

  • RangeError - when `max_attempts`, `window_ms`, or `cleanup_interval_ms` is not a safe integer ≥ 0, `cleanup_interval_ms` exceeds `PERIODIC_INTERVAL_MS_MAX`, or `max_keys` is neither `null` nor a positive safe integer

[Symbol.for('nodejs.util.inspect.custom')]

Custom inspect output that exposes only options and size, never the tracked keys. The key set is sensitive (usernames / IP addresses) and unbounded, so it must not leak into logs via util.inspect / console.log. The #attempts field is already #private, but this keeps the boundary explicit against inspect(…, {showHidden: true}) and future field-visibility changes.

type (): { options: RateLimiterOptions; size: number; }

returns { options: RateLimiterOptions; size: number; }

check

Check whether key is allowed without recording an attempt, counting recorded attempts and reserved ones still in flight.

Prunes timestamps that fell outside the window as a side effect (and removes the key entirely when none remain and none are in flight), so the backing map stays bounded even under read-only traffic.

A check followed by an awaited operation and then a record lets a concurrent burst past the cap; reserve with try_reserve instead.

type (key: string, now?: number): RateLimitResult

key

rate limit key (e.g. IP address)

type string

now?

current timestamp in ms (defaults to Date.now())

type number
optional default Date.now()

mutates

  • internal map — prunes expired timestamps for `key`

record

Record a failed attempt for key and return the updated result.

type (key: string, now?: number): RateLimitResult

key

rate limit key (e.g. IP address)

type string

now?

current timestamp in ms (defaults to Date.now())

type number
optional default Date.now()

mutates

  • internal map — appends `now` to the timestamp list for `key` (after pruning expired entries)

try_reserve

Admit an attempt for key and count it in flight, in one synchronous step.

The attempt counts against the cap — for this request and every concurrent one — until the returned reservation is settled: recorded as an attempt, or released. So no burst of concurrent requests gets more than max_attempts past the limiter between them. Release in a finally: nothing settles a reservation for you, so one left unsettled holds its slot for the life of the process.

type (key: string, now?: number): RateLimitReserveResult

key

rate limit key (e.g. IP address)

type string

now?

current timestamp in ms (defaults to Date.now())

type number
optional default Date.now()

the reservation, or {ok: false, retry_after} when recorded plus in-flight attempts already reach the cap (nothing is reserved then)

mutates

  • internal map — increments `key`'s in-flight count, creating its entry when absent

reserve

try_reserve, waiting up to max_wait_ms for a slot when the key is at its cap only because of attempts still in flight.

Each settle (and record and reset) on the key wakes its waiters to re-decide — that key's only, so a settle costs nothing for waiters parked elsewhere: a freed slot is reserved; recorded attempts reaching the cap refuse at once with their real retry_after; reaching max_wait_ms (or signal aborting) refuses with IN_FLIGHT_RETRY_AFTER_SECONDS. At most max_attempts reservations wait on one key — one more is refused at once — so a flood can't park requests without bound. A waiter that leaves, by any path, leaves the key's waiter count as it found it. A waiter holds no place in a queue: each wake, every waiter re-decides, so one can lose every race for a freed slot until max_wait_ms refuses it.

type (key: string, options?: RateLimitReserveOptions): Promise<RateLimitReserveResult>

key

rate limit key (e.g. IP address)

type string

options?

the wait bound and an abort signal

optional default {}
returns Promise<RateLimitReserveResult>

the reservation, or {ok: false, retry_after} as described (nothing is reserved then)

mutates

  • internal map — increments `key`'s in-flight count on admission; counts the call in `key`'s waiters while it waits

reset

Clear all attempts for key.

Account-grain keys only. Called after a successful auth to forgive a user's own typos, which is safe because the key *is* the account being attacked — clearing it requires that account's credential and can only widen the budget against an account the caller already holds.

Never call this on an IP-aggregate key. That bucket is the distributed-spray backstop, and success on one account must not zero the budget shared by every other account reachable from the same IP; otherwise an attacker interleaves their own logins (or, under open signup, throwaway account creations needing no credential at all) with guesses against arbitrary victims and sprays indefinitely from one address. The only success that records on an IP bucket is an open signup (charged like a failure); no success clears one, so with no reset each is a monotone-within-window counter that only time clears.

Because they are monotone, the IP buckets are one instance per auth surface (login_ip_rate_limiter, signup_ip_rate_limiter, bootstrap_ip_rate_limiter) rather than one shared across all of them. A single instance meant a failure on any surface spent the budget that bounds guessing on every other one, and one caller's exhaustion denied four routes at once — a cost that used to be masked by the reset this doc forbids.

Attempts still in flight on key (reserved by concurrent requests) are kept: a reset forgives what was recorded, not what is still running.

type (key: string): void

key

type string
returns void

mutates

  • internal map — removes the entry for `key`, or clears its timestamps while attempts are in flight

cleanup

Remove entries whose timestamps are all outside the window and that have no attempt in flight.

type (now?: number): void

now?

current timestamp in ms (defaults to Date.now())

type number
optional default Date.now()
returns void

mutates

  • internal map — prunes expired timestamps and deletes empty keys

dispose

Stop the cleanup timer. Safe to call multiple times.

type (): void

returns void

mutates

  • timer — clears the cleanup `setInterval` and nulls the handle

size

Number of tracked keys.

type number

getter

waiter_keys

Keys holding a parked-waiter slot right now (see reserve): each slot is removed with its key's last waiter. For tests pinning that a waiter leaves nothing behind; the twin of the Rust spine's test-only waiter_keys.

type number

getter

internal

RateLimiterMode
#

rate_limiter.ts view source

RateLimiterMode

type "enforced" | "disabled_for_testing"

import type {RateLimiterMode} from '@fuzdev/fuz_app/rate_limiter.js';

Whether a server builds the rate limiters it is not handed — AppServerOptions.rate_limiters.

  • 'enforced' — every limiter left out of the options is a default instance. The only mode a production server uses, and the default.
  • 'disabled_for_testing' — every limiter left out is null, so nothing is throttled. For in-process test apps and test binaries: they drive many round-trips from one address, and the IP buckets are monotone within their window (a success never refunds them, see RateLimiter.reset), so a suite's failures pile up until every later call is refused.

A limiter passed explicitly — an instance or null — wins over the mode either way, so a rate-limit test enables just the limiter it pins. The disabled mode is loud: create_app_server adds a surface warning, which log_startup_summary logs at assembly, and the first limiter it leaves null prints RATE_LIMITERS_DISABLED_BANNER to stderr once per module instance.

Twin of the Rust spine's fuz_auth::RateLimiterMode (Enforced / DisabledForTesting).

RateLimiterOptions
#

rate_limiter.ts view source

RateLimiterOptions import type {RateLimiterOptions} from '@fuzdev/fuz_app/rate_limiter.js';

Configuration for a rate limiter instance.

max_attempts

Maximum allowed attempts within the window, a safe integer ≥ 0. 0 refuses every attempt — check, try_reserve, and reserve answer at once with the window's length as retry_after, tracking nothing.

type number

window_ms

Sliding window duration in milliseconds, a safe integer ≥ 0.

type number

cleanup_interval_ms

Interval for pruning stale entries, an integer from 0 to PERIODIC_INTERVAL_MS_MAX (0 disables the timer) — the largest delay a timer honours; past it setInterval fires every millisecond.

type number

max_keys?

Maximum tracked keys. When exceeded, the least-recently-used key is evicted — bounds memory under key-enumeration attacks. Default: DEFAULT_RATE_LIMITER_MAX_KEYS (100_000). Pass null to disable the cap (falls back to an unbounded Map — only recommended when the key set is known to be closed, e.g. a per-account limiter keyed to a bounded-size account table).

LRU trade-off: every check / record call marks the key as most-recently-used, so keys under active attack stay fresh and won't be evicted. A slow-burn attacker spread across many low-volume keys can, however, drop out of the table and reset their budget — set max_keys high enough to fit the expected legitimate key set and this stays theoretical.

A number must be a positive safe integer.

type number | null

RateLimitError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "rate_limit_exceeded"; retry_after: number; } import {RateLimitError} from '@fuzdev/fuz_app/http/error_schemas.js';

Rate limit error — returned when a rate limiter rejects the request.

error

type "rate_limit_exceeded"

retry_after

type number

[key: string]

type unknown

RateLimitingTestOptions
#

testing/rate_limiting.ts view source

RateLimitingTestOptions import type {RateLimitingTestOptions} from '@fuzdev/fuz_app/testing/rate_limiting.js';

Configuration for describe_rate_limiting_tests.

session_options

Session config for cookie-based auth.

type SessionOptions<string>

create_route_specs

Route spec factory — same one used in production.

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

app_options?

Optional overrides for AppServerOptions.

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

db_factories?

Database factories to run tests against. Default: the default factory (create_default_db_factory) only.

type DbFactory[]

max_attempts?

Maximum attempts before rate limiting kicks in. Default: 2 (tight limit for fast tests).

type number

rpc_endpoints?

RPC endpoint specs mounted on the test app. Optional — both suites below exercise REST auth routes, so nothing here needs an RPC path.

Accepts either an array (eager) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> — the factory form is required when action handlers must close over the per-test ctx.deps.

type RpcEndpointsSuiteOption

RateLimitKey
#

http/error_schemas.ts view source

value + type

"both" | "account" | "ip"

type "both" | "account" | "ip"

import {RateLimitKey} from '@fuzdev/fuz_app/http/error_schemas.js';

Rate limit key type — declares what a route or RPC action's rate limiter is keyed on.

  • 'ip' — per-IP rate limiting (login, bootstrap, password change, signup)
  • 'account' — per-account rate limiting. On REST auth routes the key is the submitted identifier (login). On RPC actions (post-auth) the key is the resolved actor id (request_context.actor.id) — separate namespace.
  • 'both' — both keys.

RateLimitReservation
#

rate_limiter.ts view source

RateLimitReservation import type {RateLimitReservation} from '@fuzdev/fuz_app/rate_limiter.js';

An attempt admitted by RateLimiter.try_reserve or RateLimiter.reserve, counted against the key's cap until settled.

Settle it exactly once: record keeps it as an attempt (a failure, on the failure-counting surfaces), release gives it back. Both are idempotent and the first call wins, so a handler calls record where the outcome is known and release in a finally. When the key's entry was evicted or reset meanwhile, record still records (into the key's current entry) and release is a no-op. A reservation made against no limiter (try_reserve_optional / reserve_optional with null) is inert.

record

Settle as a recorded attempt.

type (now?: number | undefined): void

now?

current timestamp in ms (defaults to Date.now())

type number
optional
returns void

release

Settle without recording an attempt.

type (): void

returns void

RateLimitReserveOptions
#

rate_limiter.ts view source

RateLimitReserveOptions import type {RateLimitReserveOptions} from '@fuzdev/fuz_app/rate_limiter.js';

Options for RateLimiter.reserve.

max_wait_ms?

How long to wait for a slot. Default RESERVATION_WAIT_MAX_MS.

type number

signal?

Stops the wait (a refusal, retry_after: IN_FLIGHT_RETRY_AFTER_SECONDS) — pass the request's signal.

type AbortSignal

RateLimitReserveResult
#

rate_limiter.ts view source

RateLimitReserveResult

type { ok: true; reservation: RateLimitReservation; } | { ok: false; retry_after: number; }

import type {RateLimitReserveResult} from '@fuzdev/fuz_app/rate_limiter.js';

Result of RateLimiter.try_reserve and RateLimiter.reserve.

RateLimitResult
#

rate_limiter.ts view source

RateLimitResult import type {RateLimitResult} from '@fuzdev/fuz_app/rate_limiter.js';

Result of a rate limit check or record operation.

allowed

Whether the request is allowed.

type boolean

remaining

Remaining attempts before blocking, counting attempts still in flight.

type number

retry_after

Seconds until the oldest active attempt expires (0 if allowed).

type number

raw_http_count_responses
#

testing/transports/raw_http.ts view source

(raw: string): number import {raw_http_count_responses} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Count HTTP response status lines in a raw byte stream. Deliberately unanchored (no ^/m): a second pipelined response is concatenated straight after the first response's body, which carries no trailing newline, so a line-anchored match would miss it — and missing a smuggled second response is a silent false negative. It counts exactly the status lines as long as no response header or body contains the literal HTTP/1.x NNN.

raw

type string

returns

number

raw_http_exchange
#

testing/transports/raw_http.ts view source

(base_url: string, request_bytes: string, options: RawHttpExchangeOptions): Promise<RawHttpExchange> import {raw_http_exchange} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Open a raw TCP socket to base_url, write request_bytes once, and collect what the server sends back until it closes the connection, a whole response has arrived (until: 'response'), or read_timeout_ms elapses.

Write errors are swallowed: a server that refuses a request and closes mid-upload surfaces as EPIPE / ECONNRESET on the unfinished write, and what a caller asserts on is what was read back. A write error also ends the socket, so bytes the server sent just before it can go unread — a caller that needs to read a refusal reliably sends nothing the server will leave unread.

base_url

the server's base URL; only its host and port are used

type string

request_bytes

the bytes to send, latin1-encoded

type string

options

returns

Promise<RawHttpExchange>

raw_http_first_status
#

testing/transports/raw_http.ts view source

(raw: string): number | null import {raw_http_first_status} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

The status code of the first HTTP response in a raw byte stream, or null if none.

raw

type string

returns

number | null

raw_http_header_lines
#

testing/transports/raw_http.ts view source

(headers: Record<string, string>): string import {raw_http_header_lines} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Render request headers as raw HTTP/1.1 header lines, each ending in CRLF.

headers

header names to values, sent in iteration order

type Record<string, string>

returns

string

the lines, concatenated

raw_http_parse_json
#

testing/transports/raw_http.ts view source

(body: string): unknown import {raw_http_parse_json} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Parse a response body as JSON.

body

the body text

type string

returns

unknown

the parsed value, or undefined when the body is not JSON

raw_http_parse_response
#

testing/transports/raw_http.ts view source

(raw: string): RawHttpResponse | null import {raw_http_parse_response} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Parse the first response in a raw byte stream, once all of it has arrived.

The body is delimited by Transfer-Encoding: chunked or Content-Length. A response carrying neither is delimited by the connection closing, which the bytes alone don't show, so its body is whatever followed the head.

raw

the bytes read so far, latin1-decoded

type string

returns

RawHttpResponse | null

the response, or null when the head or the framed body is incomplete

RAW_HTTP_READ_TIMEOUT_MS
#

testing/transports/raw_http.ts view source

2000 import {RAW_HTTP_READ_TIMEOUT_MS} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

The read window a raw exchange gets by default in the cross suites. The server holds every byte it will get when the wait starts, so a backend that answers on what it has answers at once, and one that waits for more runs the whole window.

raw_http_send_head
#

testing/transports/raw_http.ts view source

(base_url: string, request: RawHttpHeadRequest): Promise<RawHttpResponse> import {raw_http_send_head} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Send a request head declaring content_length body bytes and none of them, then read the one response the server must answer with — the probe for a request the server refuses on its declared length (an over-cap body's 413).

Sending the body instead races the refusal: a server that answers from the head and closes with the upload unread can fail the client's write (EPIPE) and lose the response it did send, while one that keeps the connection until the declared body arrives never closes at all. So nothing the server will leave unread is sent, and the read stops at the first whole response rather than at the close.

Withholding the body also pins *where* the refusal is made. A server that reads the body before refusing waits for bytes that never arrive and fails the probe on its read timeout; one that carries no cap on the route does the same, or answers whatever the head alone decides. That makes this the wrong tool for a request whose verdict needs body bytes — an under-cap body, an empty one, a chunked upload.

base_url

the server's base URL; only its host and port are used

type string

request

the method, path, headers and declared length to send

returns

Promise<RawHttpResponse>

the parsed response; its body is the raw text

throws

  • Error - when `request.headers` names `Host` or `Content-Length`, or when no whole response arrives — the connection was refused, the server closed it before any byte or partway through a response, or it waited for the body it should have refused without

raw_ws_close_code
#

testing/transports/ws_raw_client.ts view source

(frame: RawWsFrame): number | null import {raw_ws_close_code} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

The close code a close frame carries, or null when it carries none.

frame

returns

number | null

RAW_WS_OPCODE_CLOSE
#

RAW_WS_OPCODE_TEXT
#

RAW_WS_UPGRADE_HEADERS
#

testing/transports/ws_raw_client.ts view source

Readonly<Record<string, string>>

type Readonly<Record<string, string>>

import {RAW_WS_UPGRADE_HEADERS} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

The headers that make a request a WebSocket upgrade, for a probe of what a server answers an upgrade request with — a refusal at its origin, auth or role gate — sent through any HTTP client. The key is RFC 6455's sample nonce: any 16-byte base64 value has the shape the handshake requires, and a refused upgrade never reaches the key's check.

RawHttpExchange
#

testing/transports/raw_http.ts view source

RawHttpExchange import type {RawHttpExchange} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

What a raw exchange read back, and how it ended.

received

Every byte the server sent, latin1-decoded.

type string

readonly

closed_by_server

true when the connection ended before the exchange stopped reading — the server closed it, or it never opened (a refused or unresolvable address reads as closed, with nothing received).

type boolean

readonly

RawHttpExchangeOptions
#

testing/transports/raw_http.ts view source

RawHttpExchangeOptions import type {RawHttpExchangeOptions} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Options for raw_http_exchange.

read_timeout_ms

How long to read before giving up on the server.

type number

readonly

until?

When the exchange stops reading. 'close' reads until the server closes the connection; 'response' stops as soon as one whole response has arrived (raw_http_parse_response), for a server that answers and then keeps the connection open. Either way the read timeout bounds it.

'response' stops at the first response the bytes frame, so it is for a final response carrying Content-Length or chunked framing: a 1xx interim response ends the read, and so does the head of a response delimited by the connection closing, before its body.

type "close" | "response"

readonly
default 'close'

RawHttpHeadRequest
#

testing/transports/raw_http.ts view source

RawHttpHeadRequest import type {RawHttpHeadRequest} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

One head-only request for raw_http_send_head: where it goes and what it declares.

method?

The request method.

type string

readonly
default 'POST'

path

Request path, e.g. /api/rpc.

type string

readonly

headers

Request headers beyond Host and Content-Length, which the helper writes — naming either here, in any casing, throws. Nothing else is defaulted — no Origin, no credential — so a request carries exactly what the caller names.

type Readonly<Record<string, string>>

readonly

content_length

The Content-Length to declare; no body byte is sent.

type number

readonly

read_timeout_ms?

How long to wait for the response.

type number

readonly
default RAW_HTTP_READ_TIMEOUT_MS

RawHttpResponse
#

testing/transports/raw_http.ts view source

RawHttpResponse import type {RawHttpResponse} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

One parsed HTTP/1.1 response.

status

type number

readonly

headers

Response headers, names lowercased; a repeated name keeps its last value.

type Readonly<Record<string, string>>

readonly

body

The response body, with any chunked framing removed.

type string

readonly

RawWsClient
#

testing/transports/ws_raw_client.ts view source

RawWsClient import type {RawWsClient} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

A WebSocket client that sends frames and never answers a close.

frames

Every frame the server sent, in order.

type RawWsFrame[]

send_text

Send one masked text frame.

type (text: string): void

text

type string
returns void

wait_for

Resolves with the first frame, already received or not, matching predicate.

type (predicate: (frame: RawWsFrame) => boolean, timeout_ms?: number | undefined): Promise<RawWsFrame>

predicate

type (frame: RawWsFrame) => boolean

timeout_ms?

type number
optional
returns Promise<RawWsFrame>

destroy

Drop the TCP connection, with no close handshake.

type (): void

returns void

RawWsConnectOptions
#

testing/transports/ws_raw_client.ts view source

RawWsConnectOptions import type {RawWsConnectOptions} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

Options for connect_raw_ws.

port

type number

hostname?

type string

default '127.0.0.1'

path

The WebSocket endpoint path (e.g. /api/ws).

type string

headers?

Extra upgrade request headers — Cookie, Origin.

type Record<string, string>

pipelined?

Text frames to write in the same TCP write as the upgrade request, so they are in the server's hands before the handshake is answered — and so before the connection can have been admitted.

type readonly string[]

RawWsFrame
#

read_bootstrap_token
#

auth/bootstrap_account.ts view source

(deps: Pick<BootstrapAccountDeps, "read_secure_file" | "log">, token_path: string): Promise<Uint8Array<ArrayBufferLike>> import {read_bootstrap_token} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Read the bootstrap token the way both the availability check and the request-time compare need it: the hardened secure read, less one trailing LF, refused when shorter than BOOTSTRAP_TOKEN_MIN_BYTES. A short token logs an error naming the file before the throw — it is a misconfiguration the operator must fix, never a quiet "unavailable". Twin of the Rust bootstrap_token_from_file.

deps

the secure read and a logger for the short-token error

type Pick<BootstrapAccountDeps, "read_secure_file" | "log">

token_path

the bootstrap token file

type string

returns

Promise<Uint8Array<ArrayBufferLike>>

the token bytes the provided token is compared against

throws

  • Error - when the secure read refuses the file or the token is too short

read_daemon_info
#

testing/daemon_info.ts view source

(runtime: Pick<EnvDeps, "env_get"> & Pick<FsReadDeps, "stat" | "read_text_file"> & LogDeps, name: string): Promise<{ version: number; pid: number; port: number; started: string; app_version: string; } | null> import {read_daemon_info} from '@fuzdev/fuz_app/testing/daemon_info.js';

Read and validate daemon info from the PID file.

runtime

runtime with file read and env capabilities

type Pick<EnvDeps, "env_get"> & Pick<FsReadDeps, "stat" | "read_text_file"> & LogDeps

name

application name

type string

returns

Promise<DaemonInfo | null>

parsed daemon info, or null if missing or invalid

read_env_var
#

dev/setup.ts view source

(deps: Pick<FsReadDeps, "read_text_file">, env_path: string, name: string): Promise<string | undefined> import {read_env_var} from '@fuzdev/fuz_app/dev/setup.js';

Read a single env var from a dotenv-style file.

Delegates to load_env_file so the value is tokenized exactly like every other dotenv read — surrounding quotes, inline comments, and escapes are handled, and a leading export is tolerated. Returns undefined when the file is absent or the variable is unset; a read error other than the file not existing (permission denied, I/O failure) propagates rather than being masked as "not found".

deps

file read capability

type Pick<FsReadDeps, "read_text_file">

env_path

path to the .env file

type string

name

the variable name to read

type string

returns

Promise<string | undefined>

the value, or undefined if the file or variable doesn't exist

throws

  • Error - if reading fails for any reason other than `ENOENT` / `NotFound`

read_fact_bytes_from_disk
#

db/fact_disk_storage.ts view source

(deps: Pick<FactDiskStorageDeps, "read_file">, facts_dir: string, url: string): Promise<Uint8Array<ArrayBufferLike>> import {read_fact_bytes_from_disk} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Read a disk-backed fact's bytes from the <facts_dir>/<shard>/<rest> layout the writers above produce, addressed by the row's file:<shard>/<rest> external_url. The only read of external bytes PgFactStore.get performs. Does NOT verify hash content — PgFactStore.get calls fact_hash_verify(hash, bytes) on the result and returns null on mismatch.

Defense at the read seam is the FILE_FACT_URL_PATTERN regex (via parse_file_fact_url) — .. segments, foreign schemes, and non-hex chars fail before any disk access.

deps

type Pick<FactDiskStorageDeps, "read_file">

facts_dir

type string

url

type string

returns

Promise<Uint8Array<ArrayBufferLike>>

throws

  • Error - when `url` is not a `file:<shard>/<rest>` URL, or when the read fails

read_secure_bounded
#

runtime/secure_file.ts view source

(path: string, read_chunk: (target: Uint8Array<ArrayBufferLike>) => Promise<number | null>): Promise<Uint8Array<ArrayBufferLike>> import {read_secure_bounded} from '@fuzdev/fuz_app/runtime/secure_file.js';

Drain read_chunk into a cap-bounded buffer.

Reads up to cap + 1 bytes so a file growing between stat and read is still caught (the stat-time size check alone races). read_chunk fills the given target from the file's current position and returns the bytes read (null/0 = EOF) — the seam that lets the Node and Deno handle APIs share one loop.

path

type string

read_chunk

type (target: Uint8Array<ArrayBufferLike>) => Promise<number | null>

returns

Promise<Uint8Array<ArrayBufferLike>>

throws

  • Error - when more than `MAX_SECURE_FILE_SIZE` bytes arrive

ReadTextFromOffsetResult
#

runtime/deps.ts view source

ReadTextFromOffsetResult import type {ReadTextFromOffsetResult} from '@fuzdev/fuz_app/runtime/deps.js';

Result of reading text from a byte offset.

content

Decoded text content read from the offset.

type string

bytes_read

Number of bytes actually read.

type number

file_size

Total file size at the time of the read (for truncation detection).

type number

READY_ERROR
#

db/schema_ready.ts view source

{ readonly schema_drift: "schema_drift"; readonly db_unreachable: "db_unreachable"; } import {READY_ERROR} from '@fuzdev/fuz_app/db/schema_ready.js';

Error codes a readiness check returns at 503 (conforms to {error: string}).

ReadyCrossTestOptions
#

ReadyRouteOptions
#

http/common_routes.ts view source

ReadyRouteOptions import type {ReadyRouteOptions} from '@fuzdev/fuz_app/http/common_routes.js';

Options for the readiness probe route.

expected

The committed expected column map — typically load_expected_schema(url). DI'd because the spine can't resolve a path relative to the consumer's fixture.

type ExpectedSchema

log?

Logger for server-side drift diagnostics (the public body stays minimal).

type Logger

RealtimeCloser
#

actions/connection_closer.ts view source

RealtimeCloser import type {RealtimeCloser} from '@fuzdev/fuz_app/actions/connection_closer.js';

One ConnectionCloser over every live-connection registry of a backend.

Each close fans out to every member and returns the sum of what each closed. A revocation handler holds this rather than one registry, so its close reaches the WebSocket registries and the SSE registries alike, and AppServer.close runs its close_all_connections to end every live connection of the backend at shutdown.

A member that throws does not stop the fan-out: every member is still called, and the first error is thrown once they all have been — so the caller (the post-commit flush, an audit listener) logs it.

inheritance

add

Add a transport's closer, returning a remover for this addition.

Membership is by reference: a closer added twice — two endpoints sharing one transport — is one member, and every close reaches it once. Each add is counted, and its remover releases only that count: the closer stays a member until every addition is removed, so one owner releasing a shared transport leaves it reachable for the others. Calling a remover again is a no-op.

Members must be the same instances connections are registered on — a closer over a fresh registry closes nothing.

type (closer: ConnectionCloser): () => void

closer

the transport's closer

returns () => void

a remover for this addition, idempotent

member_count

Count of distinct members — introspection for tests and diagnostics.

type (): number

returns number

REASON_PEER_CONNECTION_GONE
#

actions/peer_ping.ts view source

"peer_connection_gone" import {REASON_PEER_CONNECTION_GONE} from '@fuzdev/fuz_app/actions/peer_ping.js';

The socket closed before the peer replied.

REASON_PEER_NO_TRANSPORT
#

actions/peer_ping.ts view source

"peer_no_transport" import {REASON_PEER_NO_TRANSPORT} from '@fuzdev/fuz_app/actions/peer_ping.js';

No return socket — the action ran over a transport that can't initiate a server→client request (HTTP RPC).

REASON_PEER_PING_INVALID_REPLY
#

REASON_PEER_PING_NONCE_MISMATCH
#

actions/peer_ping.ts view source

"peer_ping_nonce_mismatch" import {REASON_PEER_PING_NONCE_MISMATCH} from '@fuzdev/fuz_app/actions/peer_ping.js';

The peer replied with a PingResponse whose nonce didn't echo the issued one.

REASON_PEER_TIMEOUT
#

actions/peer_ping.ts view source

"peer_timeout" import {REASON_PEER_TIMEOUT} from '@fuzdev/fuz_app/actions/peer_ping.js';

The peer did not reply within the deadline.

REASON_PEER_TOO_MANY_IN_FLIGHT
#

actions/peer_ping.ts view source

"peer_too_many_in_flight" import {REASON_PEER_TOO_MANY_IN_FLIGHT} from '@fuzdev/fuz_app/actions/peer_ping.js';

The per-connection in-flight server→client request cap was hit.

recheck_admission
#

auth/resolved_auth.ts view source

(deps: QueryDeps, input: RecheckAdmissionInput): Promise<AdmissionRecheck> import {recheck_admission} from '@fuzdev/fuz_app/auth/resolved_auth.js';

Re-read everything a long-lived connection's gates read, once the connection is registered pending — the admission step a WebSocket upgrade and the audit stream share (twin of the Rust spine's admission re-check).

In order, stopping at the first refusal:

  1. The credential (revalidate_resolved_auth) → 'credential_revoked'.
  2. The acting actor the gates resolved, when there is one: it must still be an active actor of the account → 'actor_inactive'. A soft-deleted actor keeps its account's credentials and its own grant rows, so neither other re-read would refuse it.
  3. The role, when required_roles is non-empty: the acting actor's grants read afresh (refresh_role_grants), a global grant of any listed role admitting → 'role_revoked'. A scoped grant never admits at a role gate.

Every read must see what committed before it started, so deps.db is the pool-level Db (see revalidate_resolved_auth).

deps

query dependencies; the pool-level Db

input

the credential, the gates' request context, and the required roles

returns

Promise<AdmissionRecheck>

'admitted', or which check refused

throws

  • Error - propagated from the database. The caller must fail closed — an unchecked connection is not an admitted one.

RecheckAdmissionInput
#

auth/resolved_auth.ts view source

RecheckAdmissionInput import type {RecheckAdmissionInput} from '@fuzdev/fuz_app/auth/resolved_auth.js';

The connection being admitted, for recheck_admission.

resolved

The credential the request authenticated with (get_resolved_auth).

type ResolvedAuth

request_context

The request context the upgrade's gates ran against — its acting actor is re-read.

type RequestContext

required_roles

The roles the connection's gate required, any-of; [] for an ungated connection. A global grant of any of them admits, as the gate did.

type readonly string[]

reconstruct_bootstrapped_handle
#

testing/cross_backend/setup.ts view source

(serialized: SerializableBootstrappedBackendHandle): ReconstructedBootstrappedBackendHandle import {reconstruct_bootstrapped_handle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Rebuild a usable handle from the serialized subset. Synthesizes a fresh FetchTransport primed with the keeper's Set-Cookie values so _testing_reset and other keeper-authenticated calls work. The returned shape omits child and teardown — lifecycle stays with globalSetup; tests that try to teardown themselves wouldn't have a serializable reference anyway.

serialized

returns

ReconstructedBootstrappedBackendHandle

ReconstructedBootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

ReconstructedBootstrappedBackendHandle import type {ReconstructedBootstrappedBackendHandle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

BootstrappedBackendHandle minus the live child / teardown references that only make sense in the globalSetup process. The cross-process provide/inject path strips them on serialization, and the per-test helpers (mint_account, fire_testing_reset, default_cross_process_setup) never read either field — so the test-worker view of the handle has this shape. Also the return type of reconstruct_bootstrapped_handle.

daemon_token

Deterministic daemon token captured from config.bootstrap.daemon_token_path after the binary booted. default_cross_process_setup builds keeper-daemon-token headers from this for _testing_reset calls.

type string

readonly

keeper_actor

Keeper actor JSON captured from POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; }

readonly

keeper_transport

Transport carrying the keeper session cookie + cookie jar.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

keeper_daemon_transport

Daemon-token transport for the keeper — same cookie jar as keeper_transport but with origin: null so it sends no Origin header. The daemon-token middleware discards the credential in a browser context (any Origin / Referer), so the _testing_* daemon-token calls (_testing_reset / _testing_schema_snapshot / _testing_action_manifest / _testing_mint_session) must route through this Origin-free transport. CSRF/cookie-session calls (invite_create, role_grant_offer_create) stay on keeper_transport, which keeps its default Origin.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

keeper_account

Keeper account JSON captured from POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

keeper_cookies

Raw keeper Set-Cookie values — thread into ws_transport for keeper-authenticated WS upgrades.

type readonly string[]

readonly

config

The config used to spawn this backend. Carried for diagnostic + downstream access.

type BackendConfig

readonly

RecordedClose
#

RecordingAuditEmitter
#

testing/audit_drift_guard.ts view source

RecordingAuditEmitter import type {RecordingAuditEmitter} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Pair returned by create_recording_audit_emitter — the AuditEmitter to inject as deps.audit, plus the shared calls array that records every captured emission. Both fields are live — callers read calls after exercising the handler to assert on the audit metadata shape.

emitter

type AuditEmitter

calls

type AuditLogInput<"login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete">[]

RecordingCloser
#

redact_database_url
#

testing/cross_backend/postgres_database.ts view source

(database_url: string): string import {redact_database_url} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

A database URL fit to print: the password, when there is one, replaced — in the userinfo and in the password query parameter pg and libpq also take. The whole URL rather than its host, which is empty for a socket URL that names its server in the query string.

database_url

a postgres:// URL

type string

returns

string

the URL with any password replaced by REDACTED

refresh_role_grants
#

auth/request_context.ts view source

(ctx: RequestContext, deps: QueryDeps): Promise<RequestContext> import {refresh_role_grants} from '@fuzdev/fuz_app/auth/request_context.js';

Reload active role_grants from the database, returning a new request context.

Useful for long-lived WebSocket connections where role_grants may change (grant or revoke) during the connection lifetime. Call periodically or after receiving a revocation signal.

Returns a new RequestContext with updated role_grants — the original context is not mutated, making concurrent calls safe. Throws when ctx.actor is null; account-grain contexts have no role_grants to refresh.

ctx

the request context to refresh

deps

query dependencies

returns

Promise<RequestContext>

a new RequestContext with fresh role_grants

throws

  • Error - when called on an account-grain context (`actor: null`)

register_action_ws
#

actions/register_action_ws.ts view source

(options: RegisterActionWsOptions): RegisterActionWsResult import {register_action_ws} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Mount a JSON-RPC WebSocket endpoint — the upgrade route with its origin, auth, token-scope, acting-actor, and role guards (module doc, "The upgrade route") — that dispatches via the shared perform_action core.

Wire behavior:

  • Batch JSON-RPC is rejected (single-message only).
  • Notifications (method + no id) are silently dropped per JSON-RPC spec. Exception: cancel notifications abort the matching pending request's ctx.signal before bubbling out.
  • A request whose id names a request still running on the socket is answered invalid_request with data.reason ERROR_DUPLICATE_REQUEST_ID and not dispatched.
  • At most MAX_INBOUND_DISPATCHES_PER_CONNECTION requests run at once per socket; one more is answered queue_overflow and not dispatched, and the socket stays open.
  • Per-message dispatch goes through perform_action: pre-authorization auth (401) → authorization phase → post-authorization auth (403) → rate limit (429) → input validation (400) → handler (with transaction wrap iff spec.side_effects: true) → DEV output validation.
  • Authorization phase runs per message — role_grant changes during a connection lifetime are picked up on the next message without any in-place refresh. Authentication invalidation closes the socket: the revocation handlers through connection_closer, and the revocation guard (create_revocation_guard) on the audit row.
  • Admission re-reads the credential once, after the handshake: a socket whose session or token was revoked while it was upgrading closes with WS_CLOSE_SESSION_REVOKED, one that lost a role required_roles lists with WS_CLOSE_PERMISSION_REVOKED, and one whose re-check failed with WS_CLOSE_INTERNAL_ERROR. Frames sent before admission wait for it.
  • A revoked global grant of a role required_roles lists closes the account's sockets on this endpoint with WS_CLOSE_PERMISSION_REVOKED — the revoking handler through connection_closer, the revocation guard again on the audit row; nothing more is sent on them.
  • A socket the server has closed dispatches nothing more, whether or not its client answers the close.
  • Every frame sent — responses, error frames, notifications — goes through the registry's outbound queue. A connection with more waiting than its outbound_queue_bytes budget closes with WS_CLOSE_OUTBOUND_OVERFLOW, and the response of the request whose handler overflowed it is not sent.

options

returns

RegisterActionWsResult

the registry (supplied or freshly created) — retain it to broadcast

throws

  • Error - when `max_connections_per_account` or `outbound_queue_bytes` is passed alongside `registry`, when the cap is neither `null` nor a positive integer, when the budget is not a positive integer, or when `actions` fails to compile (`compile_action_registry`)

mutates

  • options.app — registers the `GET path` upgrade route, its guards ahead of `upgradeWebSocket`
  • options.registry — per socket, registers, admits, and removes a connection via `register_pending` / `admit` / `remove_connection`
  • options.connection_closer — adds the endpoint's registry, once the mount has succeeded

register_jsonrpc_error_code
#

http/jsonrpc_errors.ts view source

(code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">), http_status: number): void import {register_jsonrpc_error_code} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Declare a consumer JSON-RPC error code and the HTTP status it answers, so jsonrpc_error_code_to_http_status maps it instead of falling back to 500.

Call once at module load, beside the code's declaration — the module's import is then a side effect, so list it in the package's sideEffects. Registering the same code again with the same status is a no-op, so a module evaluated twice (a duplicated dependency, a test reload) is fine. A registered code joins neither HTTP_STATUS_TO_JSONRPC_ERROR_CODE (a bare status carries no consumer meaning, e.g. a proxy's envelope-less 502) nor JSONRPC_ERROR_CODE_TO_NAME (its name lives in the consumer's own taxonomy), matching the Rust spine's ConsumerErrorCode, which carries a code and a status only.

code

a new code in the -32099 to -32000 server-error range

http_status

the 4xx or 5xx status the code answers

type number

returns

void

throws

  • Error - when `code` is not an integer in -32099 to -32000, is a code `JSONRPC_ERROR_CODES` defines, or is already registered with another status, or when `http_status` is not an integer in 400 to 599

examples

export const JSONRPC_ERROR_CODES = { ...BASE_JSONRPC_ERROR_CODES, ai_provider_error: -32020 as JsonrpcErrorCode } as const; register_jsonrpc_error_code(JSONRPC_ERROR_CODES.ai_provider_error, 502);

register_pg_type_parsers
#

db/db_pg.ts view source

(): Promise<void> import {register_pg_type_parsers} from '@fuzdev/fuz_app/db/db_pg.js';

Register the shared pg type-parser overrides on the module-global pg.types.

Dynamically imports the pg runtime (so pglite-only consumers, who never call this, don't need pg installed) and coerces int8 (BIGINT, OID 20) to a JS number. pg defaults to returning int8 as a string to avoid 2^53 precision loss; our int8 columns today (audit_log.seq, fact.size) stay well under that bound, and reading as a number keeps the wire shape uniform with PGlite — which returns int8 as a number — so AuditLogEvent.seq and friends validate identically across both drivers.

Both create_db (production) and the test pg factory (testing/db.ts) register through this single site so test and prod read the same shape; a divergence here is exactly the test/prod write-semantics gap the parser exists to close.

CAVEAT: setTypeParser mutates pg.types globally — every pg.Pool in the process inherits the coercion, including pools the consumer constructs against unrelated databases. Any future int8 column that could legitimately exceed 2^53 (byte offsets, counters) will silently round; if one lands, localize via a per-pool types override instead of widening this global.

returns

Promise<void>

mutates

  • pg.types — registers the int8 parser on the global pg type registry

register_spine_case_exclusions
#

testing/cross_backend/spine_suites.ts view source

internal

(exclude_cases: Readonly<Partial<Record<"cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | ... 33 more ... | "ws_round_trip", Readonly<...>>>>, ranges: readonly SuiteTaskRange[]): void import {register_spine_case_exclusions} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

Skip the exclude_cases tests, and fail on an entry no collected test has. describe_spine_cross_suites calls it after registering the suites, in the same suite.

One beforeEach on the calling suite: it finds the test's top-level task among the ranges the suites registered, and calls ctx.skip(reason) when that suite's entries name the test's path — so the suite files stay as they are, and the test shows as skipped with its reason. A closing test holds every entry to a test its suite collected (check_spine_case_exclusions; the paths are read at collection, so a filtered run still checks them). A skipped test's own beforeEach hooks don't run, but its afterEach hooks and its suite's beforeAll still do.

exclude_cases

the entries, by suite

type Readonly<Partial<Record<"cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | "body_size" | "body_size_smuggling" | "cell_grant_role" | "cell_grant_steward" | "cell_moderate" | "cell_ref_admission" | "cookie_attributes" | "credential_deadline" | "credential_header_robustness" | "identity_parity" | "origin_conformance" | "origin_scope" | "peer_ping_ws" | "query_shape" | "role_grant_offer_enumeration" | "role_grant_offer_lifecycle" | "role_grant_offer_notification_ws" | "role_grant_participation" | "role_grant_role_grammar" | "rpc_adversarial" | "session_cap" | "session_cookie_codec" | "simple_request" | "sse_round_trip" | "standard" | "testing_backdoor" | "token_lifetime" | "token_scope_surface" | "transport_composition" | "ws_connection_cap" | "ws_envelope" | "ws_inbound_dispatch" | "ws_outbound_overflow" | "ws_round_trip", Readonly<Record<string, string>>>>>

ranges

each run suite's top-level tasks in the calling suite

type readonly SuiteTaskRange[]

returns

void

RegisterActionWsOptions
#

actions/register_action_ws.ts view source

RegisterActionWsOptions import type {RegisterActionWsOptions} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Options for register_action_ws.

path

Mount path (e.g., /api/ws).

type string

app

The Hono app to mount on.

type Hono<BlankEnv, BlankSchema, "/">

upgradeWebSocket

Hono's upgradeWebSocket helper from the runtime adapter.

type UpgradeWebSocket<unknown, any, WSEvents<unknown>>

allowed_origins

Origin allowlist regexes for the upgrade — passed straight to verify_request_source, the route's first guard. create_app_server passes the server's allowed_origins unless the WsEndpointSpec narrows them.

type readonly RegExp[]

actions

The actions registered on this endpoint — each carries a spec (drives method lookup, per-action auth, input/output validation) and an optional handler (omit for client-only specs like inbound notifications). Spread protocol_actions from actions/protocol.ts here to complete the disconnect-detection + per-request cancel pairing with the frontend client.

type readonly Action<ActionSpecUnion>[]

db

Pool-level DB. The dispatcher wraps in db.transaction for side_effects: true actions, the same way HTTP RPC does. Per-message authorization phase reads through this pool.

Audit writes run through AppDeps.audit.emit from the action factory's closure (a success row in the dispatch transaction, a failure row on the pool) — the dispatcher never holds an audit-side pool reference; the bound emitter owns the pool.

type Db

required_roles?

Roles permitted to upgrade — any-of disjunction, global grants only, mounted as the route's require_role gate (403). Omit (or pass []) for any authenticated account. The per-action auth in each spec still applies at dispatch time — this is a coarse upgrade-time gate on who may hold a socket, and so on who receives the endpoint's server pushes.

It holds for the socket's life: recorded on every registration (ConnectionIdentity.required_roles), so a revoked global grant of a listed role closes the socket (a role close target, any listed role — see its over-close), and re-read at admission against the acting actor's grants, so a revocation that landed between the gate and the registration refuses the upgrade with WS_CLOSE_PERMISSION_REVOKED.

type readonly string[]

registry?

Existing registry to register connections with. When omitted, a fresh one is created and returned in the result. Pass your own to keep a handle for send_to / broadcast before the mount.

type ConnectionRegistry

connection_closer

The backend's closer — deps.connection_closer. This endpoint's registry is added to it, so every revocation handler's close reaches the sockets opened here. Required so it can't be forgotten: left out of the closer, no revocation closes a registry's sockets — neither the handlers' closes nor the revocation guard, which closes through the same closer. Pass null only when no revocation has to reach this registry (a harness driving the dispatcher directly), or when the registry was added by hand.

The registry is added once the mount succeeds, so a mount that throws adds nothing. The addition is permanent — its remover is not returned. A caller that must release the membership (as create_app_server does on close) passes null and adds the registry itself, holding the remover RealtimeCloser.add returns.

type RealtimeCloser | null

max_connections_per_account?

Per-account connection cap for the registry this call creates — see ConnectionRegistryOptions.max_connections_per_account (evict-oldest, closing with WS_CLOSE_CONNECTION_LIMIT; null disables). Rejected alongside registry: a supplied registry carries its own cap, set where it was constructed.

type number | null

default DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT

outbound_queue_bytes?

Byte budget of the frames waiting to be written to one connection, for the registry this call creates — see ConnectionRegistryOptions.outbound_queue_bytes (a frame that does not fit closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW). Not derived from max_message_bytes. Rejected alongside registry: a supplied registry carries its own budget, set where it was constructed.

type number

default DEFAULT_WS_OUTBOUND_QUEUE_BYTES

receive_timeout?

Receive silence (ms) past which the server closes a socket with WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT. Any incoming message resets the count — chatty clients never trip it — and the first window after admission is exempt (cold-start grace). null keeps silent sockets open: only do this if the upstream stack (TCP keepalive, a proxy's idle timeout) already owns disconnect detection. Must be positive and at most WS_RECEIVE_TIMEOUT_MAX — registration throws otherwise. Twin of the Rust spine's ActionWsOptions.receive_timeout.

type number | null

default DEFAULT_WS_RECEIVE_TIMEOUT

max_message_bytes?

Cap on one inbound message, in bytes (UTF-8 for text frames). A larger message closes the socket with WS_CLOSE_MESSAGE_TOO_BIG before it's parsed — there's no per-message error reply, matching the Rust spine. The cap is checked after the runtime adapter has buffered the message, so it bounds dispatch work, not memory; bound the adapter's own frame limit too (e.g. ws's maxPayload) where memory matters, set above this cap — twice it, as the cross-backend test adapters do. A message past the frame limit is refused by the adapter without WS_CLOSE_MESSAGE_TOO_BIG_REASON (ws closes with the code and an empty reason, Bun drops the socket), so a frame limit equal to this cap loses the reason a message just over it gets. A consumer that raises it passes the same value to the client's FrontendWebsocketTransportOptions.max_message_bytes.

type number

default DEFAULT_WS_MAX_MESSAGE_BYTES

log?

Optional logger; defaults to [ws] namespace.

type Logger

on_socket_open?

Called once per socket, after the connection is admitted (actions/ws_socket_session.ts, "Admission: the revocation race"). Awaited before any inbound frame is dispatched — every frame the client sends meanwhile waits in arrival order, replies to server-initiated requests included: a hook that awaits registry.request_connection on its own connection gets no reply until it returns, so start such a request without awaiting it. Throwing logs an error and closes the socket with an internal_error frame and WS_CLOSE_INTERNAL_ERROR — a failing bootstrap should not leave a partially-initialized socket alive.

type (ctx: SocketOpenContext): void | Promise<void>

ctx

returns void | Promise<void>

on_socket_close?

Called once per admitted socket on close, *after* the registry has removed the connection — a slow hook never holds a dead entry that broadcasts still target and the per-account cap still counts. Receives connection_id and identity captured at open time, the same whether the close came from the client, a revocation, or a connection-cap eviction. Errors are logged and swallowed.

type (ctx: SocketCloseContext): void | Promise<void>

ctx

returns void | Promise<void>

action_ip_rate_limiter

Per-IP rate limiter consulted for actions whose spec declares rate_limit: 'ip' or 'both', keyed on the client IP read at upgrade time. Required, with no default: pass null to turn the IP check off, so an unthrottled socket is a choice the call site states rather than an option it forgot. Share one instance with the HTTP RPC dispatcher so one budget covers both transports per action — create_app_server does, for the endpoints it mounts from ws_endpoints.

type RateLimiter | null

action_account_rate_limiter

Per-account rate limiter consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on request_context.account.id. Required, with no default: pass null to turn the account check off. Share one instance with the HTTP RPC dispatcher.

type RateLimiter | null

on_effect_error?

Called when an eager pending effect of a frame's action rejects, after the rejection is logged — the WS half of create_app_server's on_effect_error, which threads it here for the endpoints it mounts. The context carries transport: 'ws', the action method, and path.

type (error: unknown, context: EffectErrorContext): void

error

type unknown

context

returns void

RegisterActionWsResult
#

RemoteNotificationActionSpec
#

actions/action_spec.ts view source

value + type

{ method: string; initiator: "frontend" | "backend" | "both"; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; description: string; kind: "remote_notification"; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } import {RemoteNotificationActionSpec} from '@fuzdev/fuz_app/actions/action_spec.js';

method

type string

initiator

type "frontend" | "backend" | "both"

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

description

type string

kind

type "remote_notification"

auth

type null

side_effects

type true

output

type ZodVoid

async

type true

streams?

Names the notification method this action emits as request-scoped progress. Forward-compatible handshake — transport-agnostic, does not imply a specific delivery mechanism. Registry-time validation (e.g., ensuring the named method is a remote_notification spec) is a consumer-side concern.

type string

error_reasons?

Error reason codes this action may surface via error.data.reason on failure. Declarative metadata mirroring the streams precedent — codegen, UI form-state matching, and docs read it off the spec instead of scanning handler implementations. Reuses the same as const string constants the handler throws (e.g. ERROR_ROLE_GRANT_OFFER_*) so call sites can import either side. Optional — actions that surface only standard transport errors leave it unset.

type readonly string[]

rate_limit?

Rate limit key the RPC dispatcher consults for this action. Optional — actions without it skip the rate-limit hook entirely.

  • 'ip' — keyed on the resolved client IP (get_client_ip(c)).
  • 'account' — keyed on the post-auth account id (request_context.account.id). Registration-time error if paired with auth.account !== 'required' (no account to key on).
  • 'both' — both checks run; either can block.

Throttle-requests semantics — every invocation records, regardless of outcome (different from REST login's throttle-failures, which resets on success). Suits admin mutation oracles (invite_create account- existence probe) where the *successful* invocation is the threat.

Today only RequestResponseActionSpec is consulted by the RPC dispatcher. The field is on the base for shape symmetry with streams and error_reasons; remote_notification / local_call dispatchers don't read it.

type "both" | "account" | "ip"

REQUEST_CONTEXT_KEY
#

auth/request_context.ts view source

"request_context" import {REQUEST_CONTEXT_KEY} from '@fuzdev/fuz_app/auth/request_context.js';

Hono context variable name for the request context.

RequestActorContext
#

auth/request_context.ts view source

RequestActorContext import type {RequestActorContext} from '@fuzdev/fuz_app/auth/request_context.js';

Request context narrowed to a resolved acting actor.

Used by handlers bound through rpc_action against an actor-implying spec (auth.actor === 'required') — the binder's conditional return type tightens ctx.auth to this shape because the dispatcher's authorization phase always resolves an actor before the handler runs. The biconditional actor !== 'none' ⟺ input declares acting?: ActingActor is enforced at registry time.

inheritance

actor

type Actor

RequestClient
#

actions/peer_request.ts view source

RequestClient import type {RequestClient} from '@fuzdev/fuz_app/actions/peer_request.js';

Initiate a JSON-RPC request to the connected client and await its reply — the server→client direction of ActionPeer. Threaded onto ActionContext.request_client for WebSocket handlers (absent on HTTP RPC, where there is no return socket). Returns a PeerRequestOutcome; never throws.

(call)

type (method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<PeerRequestOutcome>

method

type string

params

type JsonrpcRequestParams | undefined

options?

optional
returns Promise<PeerRequestOutcome>

RequestContext
#

auth/request_context.ts view source

RequestContext import type {RequestContext} from '@fuzdev/fuz_app/auth/request_context.js';

The resolved identity context for an authenticated request.

actor is null on account-grain routes (no acting field on input, no role / keeper auth) — those handlers don't trigger actor resolution. role_grants is empty in that case. Role grant checks (has_scoped_role, has_any_scoped_role) are null-tolerant on RequestContext | null; they additionally treat actor: null as "no role_grants" so callers don't have to narrow.

Multi-actor invariant: when populated, actor.account_id === account.id. build_request_context enforces this; the dispatcher's authorization phase rejects with actor_not_on_account before reaching the handler.

account

type Account

actor

type Actor | null

role_grants

type RoleGrant[]

RequestContextMiddlewareOptions
#

auth/request_context.ts view source

RequestContextMiddlewareOptions import type {RequestContextMiddlewareOptions} from '@fuzdev/fuz_app/auth/request_context.js';

starts_chain?

Whether this leg starts the credential chain, clearing RESOLVED_AUTH_KEY on entry so a layer mounted before the legs cannot pre-set an identity that every leg would skip past. false only when an earlier credential leg runs first and clears it itself — create_auth_middleware_specs passes false when it composes the daemon-token leg.

type boolean

default true

log?

Debug-logs a start that clears a credential set before it — a hand-built chain that puts this leg behind an earlier credential leg but leaves starts_chain at its default.

type Logger

RequestResponseActionSpec
#

actions/action_spec.ts view source

value + type

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } import {RequestResponseActionSpec} from '@fuzdev/fuz_app/actions/action_spec.js';

method

type string

initiator

type "frontend" | "backend" | "both"

side_effects

type boolean

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

output

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

description

type string

kind

type "request_response"

auth

type RouteAuth

async

type true

streams?

Names the notification method this action emits as request-scoped progress. Forward-compatible handshake — transport-agnostic, does not imply a specific delivery mechanism. Registry-time validation (e.g., ensuring the named method is a remote_notification spec) is a consumer-side concern.

type string

error_reasons?

Error reason codes this action may surface via error.data.reason on failure. Declarative metadata mirroring the streams precedent — codegen, UI form-state matching, and docs read it off the spec instead of scanning handler implementations. Reuses the same as const string constants the handler throws (e.g. ERROR_ROLE_GRANT_OFFER_*) so call sites can import either side. Optional — actions that surface only standard transport errors leave it unset.

type readonly string[]

rate_limit?

Rate limit key the RPC dispatcher consults for this action. Optional — actions without it skip the rate-limit hook entirely.

  • 'ip' — keyed on the resolved client IP (get_client_ip(c)).
  • 'account' — keyed on the post-auth account id (request_context.account.id). Registration-time error if paired with auth.account !== 'required' (no account to key on).
  • 'both' — both checks run; either can block.

Throttle-requests semantics — every invocation records, regardless of outcome (different from REST login's throttle-failures, which resets on success). Suits admin mutation oracles (invite_create account- existence probe) where the *successful* invocation is the threat.

Today only RequestResponseActionSpec is consulted by the RPC dispatcher. The field is on the base for shape symmetry with streams and error_reasons; remote_notification / local_call dispatchers don't read it.

type "both" | "account" | "ip"

require_audit_sse
#

server/app_server.ts view source

(source: { audit_sse: AuditLogSse | null; }): AuditLogSse import {require_audit_sse} from '@fuzdev/fuz_app/server/app_server.js';

Assert that audit_sse was wired by create_app_server and return it as a non-null AuditLogSse. Throws a labelled error when the audit_log_sse option was not passed to create_app_server.

Use in route factories that depend on factory-managed audit SSE:

create_route_specs: (ctx) => create_audit_log_route_specs({ stream: require_audit_sse(ctx), }),

Preferred over ctx.audit_sse! — ! lies to the type system and produces a downstream cannot-read-property crash if a consumer wires the route without enabling the option.

source

type { audit_sse: AuditLogSse | null; }

returns

AuditLogSse

require_auth
#

auth/request_context.ts view source

(c: Context<any, string, {}>, next: Next): Promise<void | Response> import {require_auth} from '@fuzdev/fuz_app/auth/request_context.js';

Middleware that requires authentication.

Returns 401 if the auth middleware resolved no credential.

c

type Context<any, string, {}>

next

type Next

returns

Promise<void | Response>

require_credential_types
#

auth/request_context.ts view source

(credential_types: readonly string[]): MiddlewareHandler import {require_credential_types} from '@fuzdev/fuz_app/auth/request_context.js';

Create middleware that requires the request's credential_type to be one of the given values.

Returns 401 if unauthenticated, 403 with ERROR_CREDENTIAL_TYPE_REQUIRED + required_credential_types echoing the spec's allowlist when the wire-side credential isn't in it.

Reads only the credential the auth middleware resolved (get_resolved_auth) — never the resolved RequestContext. That is why fuz_auth_guard_resolver mounts it in the pre_authorization phase: a wrong channel is refused before the route resolves an actor for it, so the denial costs no DB work and discloses no account state. Body shape is symmetric with the role gate (ERROR_INSUFFICIENT_PERMISSIONS + required_roles) and matches what the RPC dispatcher's post-auth gate emits for the same condition. Today's only credential gate is keeper (['daemon_token']); future gates (agent_token, group_actor_token) reuse this literal and label themselves through the array.

credential_types

allowed credential types (any-of)

type readonly string[]

returns

MiddlewareHandler

require_editable_cell
#

auth/cell_action_helpers.ts view source

(deps: QueryDeps, auth: RequestActorContext, cell_id: string & $brand<"Uuid">): Promise<CellRow> import {require_editable_cell} from '@fuzdev/fuz_app/auth/cell_action_helpers.js';

Load a cell and gate the caller on can_edit_cell, 404-masking a miss and a denial alike.

The write tier: cell_update / cell_delete, the source a field write writes, and the parent every item mutation writes.

deps

query deps

auth

the caller

cell_id

the cell to load

type Uuid

returns

Promise<CellRow>

the loaded row

throws

  • the - `cell_not_found` 404 on a miss or a denial

require_manageable_cell
#

auth/cell_action_helpers.ts view source

(deps: QueryDeps, auth: RequestActorContext, cell_id: string & $brand<"Uuid">): Promise<CellRow> import {require_manageable_cell} from '@fuzdev/fuz_app/auth/cell_action_helpers.js';

Load a cell and gate the caller on can_manage_cell, 404-masking a miss and a denial alike.

The manage tier: cell_audit_list, cell_grant_create, cell_grant_list. Manage is admin-or-owner and not a grant level, so no grant is read. A viewer or an editor gets the same mask a stranger does: an audit timeline and a share list say who touched the cell, which is more sensitive than the content.

deps

query deps

auth

the caller

cell_id

the cell to load

type Uuid

returns

Promise<CellRow>

the loaded row

throws

  • the - `cell_not_found` 404 on a miss or a denial

require_new_refs_held
#

auth/cell_ref_admission.ts view source

(deps: QueryDeps, auth: RequestActorContext, base: readonly string[] | null, data: Json): Promise<void> import {require_new_refs_held} from '@fuzdev/fuz_app/auth/cell_ref_admission.js';

Refuse a write whose data introduces a fact ref the caller does not hold.

new_refs = derive_cell_refs(data) \ base, where base is what the write already carries — null on cell_create, the cell's current refs on cell_update, the source row's refs on each row cell_clone writes — so keeping refs someone else introduced, or cloning a viewable cell's refs, is admitted (the bytes were already readable through that cell). A global admin is not gated; a scoped admin is. The check runs on the write's transaction, only when new_refs is non-empty — a table probe, then one holder lookup for every new ref at once — and answers from holder rows only: an unheld hash reads the same whether or not a fact is stored under it.

A database without the fact chain (a consumer that mounts the cell verbs but no fact store) has no holders, so every new ref is refused there too — checked first, since naming a missing table would abort the transaction.

Runs as the last gate before the row is written: after the 404 masks, the per-kind validator, and the create authorizer.

deps

query deps on the write's transaction

auth

the acting caller

base

the refs the write already carries, null for none

type readonly string[] | null

data

the data the write stores

type Json

returns

Promise<void>

throws

  • ThrownJsonrpcError - 403 `forbidden` with reason `cell_ref_not_held` when a new ref is unheld

require_request_context
#

auth/request_context.ts view source

(c: Context<any, any, {}>): RequestContext import {require_request_context} from '@fuzdev/fuz_app/auth/request_context.js';

Get the request context, throwing if unauthenticated.

Use in route handlers where the dispatcher's authorization phase guarantees a context exists (i.e., routes with auth: {type: 'authenticated'} or stricter). Prefer this over get_request_context(c)! for explicit error handling.

c

the Hono context

type Context<any, any, {}>

returns

RequestContext

the request context (never null)

throws

  • Error - if no request context is set (dispatcher misconfiguration)

require_role
#

auth/request_context.ts view source

(roles: readonly string[]): MiddlewareHandler import {require_role} from '@fuzdev/fuz_app/auth/request_context.js';

Create middleware that requires the actor to hold any of the given roles globally (scope_id IS NULL).

Returns 401 if unauthenticated, 403 if none of the roles are present. Reads REQUEST_CONTEXT_KEY because role-gated routes always run the dispatcher's authorization phase before this guard (the phase sets the actor-bound RequestContext).

Uses has_any_scoped_role(ctx, roles, null) so the gate matches global / unscoped role_grants only. A scoped role_grant ({role: 'admin', scope_id: <some uuid>}) does not unlock route-spec gates that are inherently global. The same scope-aware check is run in actions/perform_action.ts, the core both the HTTP RPC and WS dispatchers call, so the transports agree.

Multi-role disjunction (any-of) lets auth.roles: ['admin', 'steward'] specs translate to one middleware that admits either role. Single-role routes pass [role_name]; the array shape is uniform.

roles

the roles to admit (any-of)

type readonly string[]

returns

MiddlewareHandler

require_rpc_endpoint_path
#

testing/rpc_helpers.ts view source

(rpc_endpoints: readonly RpcEndpointSpec[]): string import {require_rpc_endpoint_path} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Resolve a single RPC endpoint path — the common case where a consumer mounts exactly one create_rpc_endpoint.

Used at suite setup time to hard-fail integration suites (admin / audit / SSE / rate-limiting) when the consumer omitted rpc_endpoints rather than letting tests fail mid-run with confusing errors.

Callers that need multi-endpoint support should iterate rpc_endpoints directly.

rpc_endpoints

type readonly RpcEndpointSpec[]

returns

string

throws

  • Error - if `rpc_endpoints` is empty (hard-fail; see the suite options docs) or has more than one entry (ambiguous).

require_token_scope
#

auth/request_context.ts view source

(capability: string): MiddlewareHandler import {require_token_scope} from '@fuzdev/fuz_app/auth/request_context.js';

Create middleware refusing a route to a credential whose scope does not admit capability.

The route-spec form, mounted by fuz_auth_guard_resolver from auth.required_scope. Lands in the pre_authorization phase, so a narrowed token is told about its scope rather than about the role it also lacks — and learns nothing about the route's input shape on the way.

Parses at construction, so a malformed capability is a registration-time throw rather than a guard that mounts and reports nonsense.

Only the surface:<name> arm is accepted (is_route_scope_capability): an rpc:<method> capability is the dispatcher's per-method gate, which a route never reaches.

capability

the capability string this route demands, e.g. surface:audit_stream

type string

returns

MiddlewareHandler

middleware that 403s a narrowed token

throws

  • Error - if `capability` is not a well-formed `surface:<name>` string

require_viewable_cell
#

auth/cell_action_helpers.ts view source

(deps: QueryDeps, auth: RequestContext | null, cell_id: string & $brand<"Uuid">): Promise<CellRow> import {require_viewable_cell} from '@fuzdev/fuz_app/auth/cell_action_helpers.js';

Load a cell and gate the caller on can_view_cell, 404-masking a miss and a denial alike.

The read tier: a create's parent, a clone's source, the far endpoint a write links to (so a link is no existence oracle), and the endpoints of the public relation lists, where auth may be null.

deps

query deps

auth

the caller, or null when unauthenticated

type RequestContext | null

cell_id

the cell to load

type Uuid

returns

Promise<CellRow>

the loaded row

throws

  • the - `cell_not_found` 404 on a miss or a denial

RESERVATION_WAIT_MAX_MS
#

rate_limiter.ts view source

5000 import {RESERVATION_WAIT_MAX_MS} from '@fuzdev/fuz_app/rate_limiter.js';

How long a password-surface reservation (RateLimiter.reserve) waits for a slot when the key is at its cap only because of attempts still in flight, before answering 429. Mirrors the Rust spine's RESERVATION_WAIT_MAX.

Counting attempts while they are in flight caps *successful* concurrent attempts from one key too — a class behind one NAT logging in together. Those attempts settle within a request's lifetime, so a short wait turns most of those refusals into a late success, while a key at its cap on *recorded* attempts is still refused at once.

reserve_optional
#

rate_limiter.ts view source

(limiter: RateLimiter | null, key: string | null, options?: RateLimitReserveOptions | undefined): Promise<RateLimitReserveResult> import {reserve_optional} from '@fuzdev/fuz_app/rate_limiter.js';

RateLimiter.reserve against a limiter that may be absent — an inert reservation when limiter or key is null. Twin of the Rust spine's reserve_optional.

limiter

the limiter, or null when disabled

type RateLimiter | null

key

rate limit key, or null to skip

type string | null

options?

the wait bound and an abort signal

optional

returns

Promise<RateLimitReserveResult>

reserved_migration_namespaces
#

db/migrate.ts view source

readonly string[] import {reserved_migration_namespaces} from '@fuzdev/fuz_app/db/migrate.js';

Migration namespace names reserved by the spine: auth (fuz_auth), cells (fuz_cell), and facts (fuz_facts) — the Rust spine's RESERVED_NAMESPACES. Only a namespace built by create_spine_migration_namespace runs under one: run_migrations and baseline refuse any other MigrationNamespace bearing one of these names, so a consumer can't mint tracker rows that collide with a spine chain's. Typed as ReadonlyArray<string> (not a literal tuple) so .includes() accepts any namespace string without a cast.

reset_audit_metadata_validation_failures
#

auth/audit_log_queries.ts view source

(): void import {reset_audit_metadata_validation_failures} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Reset the counter — for tests only.

returns

void

reset_audit_unknown_event_type_failures
#

auth/audit_log_queries.ts view source

(): void import {reset_audit_unknown_event_type_failures} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Reset the counter — for tests only.

returns

void

reset_audit_write_failures
#

auth/audit_emitter.ts view source

(): void import {reset_audit_write_failures} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Reset the counter — for tests only.

returns

void

reset_bootstrap_token
#

dev/setup.ts view source

(deps: FsReadDeps & FsWriteDeps & FsRemoveDeps & EnvDeps, app_name: string, options?: SetupBootstrapTokenOptions | undefined): Promise<...> import {reset_bootstrap_token} from '@fuzdev/fuz_app/dev/setup.js';

Remove an existing bootstrap token and create a new one.

deps

file, env, and remove capabilities

app_name

application name

type string

options?

state_dir override, logger

optional

returns

Promise<SetupTokenResult>

result from creating the new token

mutates

  • filesystem — removes the existing token file (if any) then writes a fresh one

reset_database
#

dev/setup.ts view source

(deps: CommandDeps & FsReadDeps & FsRemoveDeps, database_url: string, options?: ResetDatabaseOptions | undefined): Promise<...> import {reset_database} from '@fuzdev/fuz_app/dev/setup.js';

Reset a database to a clean slate.

For PostgreSQL: drops and recreates the database. For pglite: removes the data directory if pglite_data_dir is provided. For empty/missing URLs: skips.

deps

command and file capabilities

database_url

the DATABASE_URL value

type string

options?

pglite_data_dir, logger

optional

returns

Promise<ResetDbResult>

result describing what happened

mutates

  • external database — drops and recreates the PostgreSQL database, or removes the PGlite data directory
  • filesystem — removes `options.pglite_data_dir` recursively for PGlite URLs

reset_mock_runtime
#

runtime/mock.ts view source

(runtime: MockRuntime): void import {reset_mock_runtime} from '@fuzdev/fuz_app/runtime/mock.js';

Reset a mock runtime to initial state.

runtime

returns

void

mutates

  • runtime — clears all mock state (env, fs, dirs, exit/command/stdout/fetch call records, mock results, stdin buffer)

reset_public_schema
#

testing/db.ts view source

(db: Db): Promise<void> import {reset_public_schema} from '@fuzdev/fuz_app/testing/db.js';

Reset a test database to a clean slate by dropping and recreating the public schema.

DROP SCHEMA public CASCADE; CREATE SCHEMA public in plain SQL, so it resets every driver: the in-memory factories call it on each create(), and it is recommended at the start of init_schema callbacks for create_pg_factory, whose persistent databases accumulate stale DDL across fuz_app versions. Drift-proof — it wipes every table, type, and sequence regardless of namespace, so it needs no upkeep when the schema gains a table. The database instance remains usable after reset.

db

type Db

returns

Promise<void>

mutates

  • db — drops the `public` schema and recreates it; all rows in all tables are gone after this returns.

ResetDatabaseOptions
#

dev/setup.ts view source

ResetDatabaseOptions import type {ResetDatabaseOptions} from '@fuzdev/fuz_app/dev/setup.js';

Options for reset_database.

pglite_data_dir?

Directory to remove for file-based pglite.

type string

log?

type SetupLogger

ResetDbResult
#

dev/setup.ts view source

ResetDbResult import type {ResetDbResult} from '@fuzdev/fuz_app/dev/setup.js';

Result of reset_database.

reset

Whether the database was actually reset.

type boolean

skipped

Whether the operation was skipped (e.g. pglite with no data dir).

type boolean

db_type

What type of database was detected.

type "postgres" | "pglite" | "none"

resolve_acting_actor
#

auth/request_context.ts view source

(deps: QueryDeps, account_id: string, acting_actor_id: string | undefined): Promise<ResolveActingActorResult> import {resolve_acting_actor} from '@fuzdev/fuz_app/auth/request_context.js';

Resolve the acting actor for an authenticated request.

Called from the route-spec / RPC dispatcher's authorization phase with the authenticated account id and the validated acting value (from the request payload). Applies the uniform resolution rules:

  • acting_actor_id omitted + 1 actor → use it.
  • acting_actor_id omitted + 0 actors → no_actors (defensive — signup / bootstrap always create an actor in the same tx, so this is a server error).
  • acting_actor_id omitted + multiple actors → actor_required with the available list so the client can prompt; never pick silently.
  • acting_actor_id present + matches an actor on the account → use it.
  • acting_actor_id present + does not match → actor_not_on_account. The available list is intentionally not echoed in this branch (treat as opaque rejection).

acting_actor_id is compared lowercased, the form actor ids are stored and read back in, so a caller passing an id that skipped the Uuid schema still matches its actor whatever its case.

deps

query dependencies

account_id

the authenticated account

type string

acting_actor_id

the requested acting actor id, or undefined

type string | undefined

returns

Promise<ResolveActingActorResult>

resolve_action_log
#

resolve_cargo_binary_path
#

testing/cross_backend/cargo_binary.ts view source

(options: CargoBinaryOptions): string import {resolve_cargo_binary_path} from '@fuzdev/fuz_app/testing/cross_backend/cargo_binary.js';

The path of a Cargo-built binary, without building it — what a config's start_command names. Runs cargo metadata once per process per workspace, so it needs cargo on PATH.

options

the workspace, package, binary and profile

returns

string

the binary's absolute path

throws

  • Error - when the workspace is missing or `cargo metadata` fails

resolve_client_ip
#

http/proxy.ts view source

(forwarded_for: string, proxies: ParsedProxy[]): string | undefined import {resolve_client_ip} from '@fuzdev/fuz_app/http/proxy.js';

Resolve the real client IP from an X-Forwarded-For header value.

Walks right-to-left, skipping trusted proxy entries. The first untrusted entry is the client IP. The walk stops at the first entry that fails strict IP validation (validate_ip_strict) and returns undefined, so the middleware falls back to the connection IP: an entry is only as trustworthy as the hop that wrote it, and everything left of a malformed one was written before any trusted hop vouched for it. If every entry is trusted, returns the leftmost one (likely-misconfigured all-trusted case). Empty entries are dropped before the walk. All entries are normalized before matching and in the returned value.

Stopping, not skipping, is the rate-limit-key rule. Returning a malformed entry would let an attacker rotate arbitrary strings (incl. 'attacker:controlled', which Hono's lax distinctRemoteAddr misclassifies as IPv6) into fresh per-IP rate-limit buckets; skipping it and walking on would hand the key to the entry the client wrote whenever a trusted hop appends a non-bare-IP token (ip:port, [v6]:port, unknown, unix:). Tradeoff: behind such a proxy every client collapses to the proxy's connection IP in rate limiting (one bucket for everyone behind it). Standard proxies (nginx's $remote_addr, cloud LBs) append bare IPs.

forwarded_for

the X-Forwarded-For header value

type string

proxies

parsed trusted proxy entries

returns

string | undefined

the normalized client IP, or undefined if the header is empty or the walk met a malformed entry

resolve_conformance_principal
#

testing/cross_backend/conformance_table.ts view source

(fixture: TestFixture, as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session", principals?: ConformancePrincipalConfig | undefined, resolved_rpc_endpoints?: RpcEndpointSpec[]): Promise<...> import {resolve_conformance_principal} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Map a ConformancePrincipal onto the transport + headers it authenticates with, reading exclusively from the per-test TestFixture.

The five always-available principals resolve from fixture accessors; role_holder / wrong_role read a seeded extra_accounts entry named via options.principals (throws a clear setup error when unconfigured); scoped_token mints its narrowed credential over the production path. Also used outside the table by suites that send a principal's requests themselves (describe_rpc_adversarial_cross_tests).

fixture

the per-test fixture

as

the principal

principals?

usernames for role_holder / wrong_role

optional

resolved_rpc_endpoints?

the endpoints scoped_token mints through

optional default []

returns

Promise<ResolvedConformancePrincipal>

the transport and headers the principal sends with

resolve_conformance_rpc_endpoints
#

testing/cross_backend/conformance_table.ts view source

(options: ConformanceRpcEndpointsOptions): RpcEndpointSpec[] import {resolve_conformance_rpc_endpoints} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Resolve the endpoints a conformance case's RPC method is looked up in: the declared rpc_endpoints, then live_rpc_endpoints. find_rpc_action takes the first match, so a declared method resolves from the declared surface even when the live mount repeats it.

options

the declared and live endpoints, and the session options

returns

RpcEndpointSpec[]

the endpoints in lookup order

resolve_fixture_path
#

testing/assertions.ts view source

(filename: string, import_meta_url: string): string import {resolve_fixture_path} from '@fuzdev/fuz_app/testing/assertions.js';

Resolve an absolute path relative to the caller's module.

filename

type string

import_meta_url

the caller's import.meta.url

type string

returns

string

resolve_heartbeat_receive_timeout
#

actions/socket.svelte.ts view source

(interval: number, receive_timeout?: number): number import {resolve_heartbeat_receive_timeout} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Resolve the effective heartbeat receive timeout for interval: the given receive_timeout (or DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT), raised to at least HEARTBEAT_RECEIVE_TIMEOUT_MIN_FACTOR × interval.

interval

heartbeat idle interval in ms

type number

receive_timeout?

requested receive timeout in ms

type number
optional default DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT

returns

number

the receive timeout in ms the client enforces

resolve_invalid_reason
#

schema_meta.ts view source

(input_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, path: readonly PropertyKey[]): string | undefined import {resolve_invalid_reason} from '@fuzdev/fuz_app/schema_meta.js';

The error.data.reason an input validation failure at path carries: the invalid_reason of the deepest field along path that declares one.

Walks object fields only, each through its wrappers: a segment that is not a key of an object schema (an array index, a union member's key, an unknown key) ends the walk, so a fault inside a declared field still resolves to that field's reason. An empty path — a whole-input fault such as an unrecognized key on the input object — resolves to none, as does a schema that is not an object.

input_schema

an action's input schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

path

the failing issue's path (ZodError.issues[0].path)

type readonly PropertyKey[]

returns

string | undefined

the declared reason, or undefined

resolve_issues_invalid_reason
#

schema_meta.ts view source

(input_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, issues: readonly $ZodIssue[]): string | undefined import {resolve_issues_invalid_reason} from '@fuzdev/fuz_app/schema_meta.js';

The error.data.reason a failed input parse answers: the declared reason of the first issue's field (resolve_invalid_reason), or none when any issue sits on the input itself. Such an issue is an unknown key on the input object (or a non-object input), which the Rust twin refuses before reading any field, so a stale client sending an extra key gets the same answer from both spines whatever else its body gets wrong.

input_schema

an action's input schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

issues

the failed parse's issues, in Zod's order

type readonly $ZodIssue[]

returns

string | undefined

the declared reason, or undefined

resolve_keeper_account_id
#

auth/daemon_token_middleware.ts view source

(deps: QueryDeps): Promise<string | null> import {resolve_keeper_account_id} from '@fuzdev/fuz_app/auth/daemon_token_middleware.js';

Resolve the keeper account ID by querying for the account with an active keeper role_grant.

There is exactly one keeper account (the bootstrap account). Runs once at server startup — the result is cached in DaemonTokenState.keeper_account_id. The acting actor is resolved per-request by the dispatcher's authorization phase (which runs resolve_acting_actor against this account id), so multi-actor keeper accounts surface actor_required if a daemon caller doesn't pass an explicit acting.

deps

query dependencies

returns

Promise<string | null>

the keeper account ID, or null if no keeper exists yet (pre-bootstrap)

resolve_rate_limiters
#

server/app_server.ts view source

internal

(options: Pick<AppServerOptions, "login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter" | "rate_limiters">): ResolvedRateLimiters import {resolve_rate_limiters} from '@fuzdev/fuz_app/server/app_server.js';

Resolve each rate-limiter option: an explicit option (instance or null) wins; an omitted one is a default instance under 'enforced' and null under 'disabled_for_testing', which also announces the mode on stderr (announce_rate_limiters_disabled). One instance per surface — see AppServerOptions.login_ip_rate_limiter for why these are not a single shared bucket.

options

the rate-limiter fields and rate_limiters mode of AppServerOptions

type Pick<AppServerOptions, AppServerRateLimiterKey | "rate_limiters">

returns

ResolvedRateLimiters

resolve_rpc_endpoints_for_setup
#

testing/rpc_helpers.ts view source

(rpc_endpoints: RpcEndpointsSuiteOption, session_options: SessionOptions<string>): RpcEndpointSpec[] import {resolve_rpc_endpoints_for_setup} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Resolve a suite's rpc_endpoints option to an array for setup-time inspection (path lookup, action presence checks).

For the factory form this invokes the factory twice with stub AppServerContexts and asserts that both invocations produce the same (path, method-list) shape — catching factories that close over mutable state or otherwise diverge across calls. The first array is returned; the second is discarded after the comparison. create_app_server invokes the factory again per-test with its real ctx, and those are the handlers that actually serve requests.

Safe as long as the factory is pure with respect to the endpoint path and the action spec.method list — the canonical helpers (create_standard_rpc_actions, create_admin_actions, create_account_actions, etc.) are. Factories that return a different path based on ctx will produce a setup/runtime mismatch; the path-purity assert below surfaces that as a clear gro check error rather than a silent test/runtime drift.

rpc_endpoints

session_options

type SessionOptions<string>

returns

RpcEndpointSpec[]

throws

  • Error - if the factory's two stub-ctx invocations produce different `(path, method-list)` shapes — surfaces non-pure factories at setup time.

resolve_scope_label
#

ui/format_scope.ts view source

<G extends string | null>(scope_id: string | null, role: string, format_scope: FormatScope, global_label: G): string | G import {resolve_scope_label} from '@fuzdev/fuz_app/ui/format_scope.js';

Resolve a scope label across the context → truncated-uuid fallback chain.

global_label is returned for scope_id === null. Callers pass null to render no chip (admin tables — global is the implicit default) or 'global' for explicit labels (offer surfaces). The return type propagates null only when global_label is null.

scope_id

type string | null

role

type string

format_scope

global_label

type G

returns

string | G

generics

resolve_scope_label<G extends string | null>
G
constraint string | null

resolve_standard_error_schema_tightness
#

testing/attack_surface.ts view source

(consumer: ErrorSchemaTightnessOptions | null | undefined): ErrorSchemaTightnessOptions | null import {resolve_standard_error_schema_tightness} from '@fuzdev/fuz_app/testing/attack_surface.js';

Merge a consumer's error_schema_tightness option with default_error_schema_tightness so allowlist and ignore_statuses are additive rather than replacing.

  • undefined → return the default as-is.
  • null → return null (opt out of the assertion).
  • object → spread the default, then consumer overrides for scalar fields (min_specificity), then concat stock-then-consumer for the list fields (allowlist, ignore_statuses) so consumer entries extend rather than replace.

Exported for direct use when a consumer calls assert_error_schema_tightness outside the standard suite but still wants the additive merge.

consumer

type ErrorSchemaTightnessOptions | null | undefined

returns

ErrorSchemaTightnessOptions | null

resolve_valid_path
#

testing/schema_generators.ts view source

(path: string, params_schema?: ZodObject<$ZodLooseShape, $strip> | undefined): string import {resolve_valid_path} from '@fuzdev/fuz_app/testing/schema_generators.js';

Resolve a route path with valid-ish param values so params validation passes. Used when testing input on routes that also have params.

Without a params schema (or for a param the schema doesn't name) each :name becomes test_<name> — fine for an unconstrained segment. With one, test_<name> is still preferred wherever the field schema accepts it, because the param name in the URL is what makes a failure message readable. Only a segment that refuses it falls through: a uuid param takes the nil UUID (the fast path — by far the most common shape), and anything else is synthesized by generate_valid_string, so a format-constrained segment (a blake3:… fact hash, a tok_… id) resolves to a value its schema accepts instead of a test_<name> that 400s before the route's real gates run.

The synthesized value is percent-encoded, and a value containing / is refused (it would silently add a path segment and route elsewhere) — both fall back to test_<name>.

path

type string

params_schema?

type ZodObject<$ZodLooseShape, $strip>
optional

returns

string

resolve_ws_endpoints
#

actions/ws_endpoint_spec.ts view source

(ws_endpoints: readonly WsEndpointSpec[] | undefined, options: ResolveWsEndpointsOptions): ResolvedWsEndpointSpec[] | undefined import {resolve_ws_endpoints} from '@fuzdev/fuz_app/actions/ws_endpoint_spec.js';

Fill in each WS endpoint spec's omitted allowed_origins (the server's) and actions (the sole RPC endpoint's, through to_ws_actions), so every consumer of the resolved list — the mount, the surface — reads the effective values.

ws_endpoints

the resolved ws_endpoints option

type readonly WsEndpointSpec[] | undefined

options

the server's allowlist and RPC endpoints, the defaults

returns

ResolvedWsEndpointSpec[] | undefined

the specs, each with allowed_origins and actions set

throws

  • Error - when a spec omits `actions` and `rpc_endpoints` does not hold exactly one endpoint — there is no single action set to serve

ResolveActingActorResult
#

auth/request_context.ts view source

ResolveActingActorResult

type { ok: true; actor_id: string; } | { ok: false; reason: "no_actors"; } | { ok: false; reason: "actor_required"; available: { id: string; name: string; }[]; } | { ok: false; reason: "actor_not_on_account"; }

import type {ResolveActingActorResult} from '@fuzdev/fuz_app/auth/request_context.js';

Result of resolve_acting_actor — either an actor id or a structured error the caller maps to an HTTP response.

resolved_auth_api_token_id
#

auth/resolved_auth.ts view source

(auth: ResolvedAuth | null): string | null import {resolved_auth_api_token_id} from '@fuzdev/fuz_app/auth/resolved_auth.js';

The token id of an API token credential, null for any other (or none).

auth

the resolved credential, or null for an anonymous caller

type ResolvedAuth | null

returns

string | null

the api_token.id, or null

RESOLVED_AUTH_KEY
#

hono_context.ts view source

"resolved_auth" import {RESOLVED_AUTH_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the request's resolved credential (ResolvedAuth) — the one value every auth middleware leg writes, read through get_resolved_auth.

The daemon-token, session, and bearer legs run in that order, and the first that authenticates sets it; the legs after it see it set and skip. Unset (read as null) for an unauthenticated request.

resolved_auth_scope
#

auth/resolved_auth.ts view source

(auth: ResolvedAuth | null): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } | null import {resolved_auth_scope} from '@fuzdev/fuz_app/auth/resolved_auth.js';

The credential's authority narrowing: an API token's own scope, full for a session or the daemon token (a browser session *is* full account authority — narrowing it is what role grants are for — and the daemon token is singular, filesystem-proved, and keeper-bound). null only for the anonymous caller, who holds no credential to narrow. Every token-scope gate reads this. The twin of the Rust spine's ResolvedAuth::scope.

auth

the resolved credential, or null for an anonymous caller

type ResolvedAuth | null

returns

TokenScope | null

the scope, or null for an anonymous caller

resolved_auth_session_hash
#

auth/resolved_auth.ts view source

(auth: ResolvedAuth | null): string | null import {resolved_auth_session_hash} from '@fuzdev/fuz_app/auth/resolved_auth.js';

The session hash of a session credential, null for any other (or none).

auth

the resolved credential, or null for an anonymous caller

type ResolvedAuth | null

returns

string | null

the auth_session.id, or null

ResolvedApiTokenAuth
#

auth/resolved_auth.ts view source

ResolvedApiTokenAuth import type {ResolvedApiTokenAuth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

An Authorization: Bearer API token — the only credential a scope narrows.

inheritance

extends: ResolvedAuthBase

credential_type

type "api_token"

readonly

api_token_id

The api_token.id.

type string

readonly

scope

The authority the token was minted with, read off its row.

type TokenScope

readonly

ResolvedAuth
#

auth/resolved_auth.ts view source

ResolvedAuth

type ResolvedSessionAuth | ResolvedApiTokenAuth | ResolvedDaemonTokenAuth

import type {ResolvedAuth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

An authenticated request's credential, account-grain — no actor.

One arm per credential type, each carrying only what belongs to it: the session hash on a session, the token id and scope on an API token, so a session with a narrowed scope or a bearer with a session hash cannot be built. The twin of the Rust spine's ResolvedAuth, whose ResolvedCredential enum holds the same correlation.

ResolvedConformancePrincipal
#

testing/cross_backend/conformance_table.ts view source

ResolvedConformancePrincipal import type {ResolvedConformancePrincipal} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

A resolved principal: the transport + headers a case fires through.

transport

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

headers

type Record<string, string>

readonly

suppress_default_origin?

Suppress the default Origin header on the request. Required for non-browser credential probes (bearer / daemon) — bearer_auth discards the token when Origin / Referer is present.

type boolean

readonly

actor_id?

The actor of the account the credential names — what the principal acting binding writes. Absent for principals that present no account.

type string

readonly

ResolvedDaemonTokenAuth
#

auth/resolved_auth.ts view source

ResolvedDaemonTokenAuth import type {ResolvedDaemonTokenAuth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

The keeper-bound daemon token — full authority, no row, no expiry.

inheritance

extends: ResolvedAuthBase

credential_type

type "daemon_token"

readonly

credential_deadline

type null

readonly

ResolvedRateLimiters
#

server/app_server.ts view source

internal

ResolvedRateLimiters import type {ResolvedRateLimiters} from '@fuzdev/fuz_app/server/app_server.js';

The rate limiters create_app_server resolved from its options.

mode

The rate_limiters mode, defaulted to 'enforced'.

type RateLimiterMode

limiters

Each limiter by option name — null when off.

type Record<AppServerRateLimiterKey, RateLimiter | null>

built

The limiters built here for omitted options, in build order — the server's to dispose. Never one passed in, which its caller owns.

type RateLimiter[]

ResolvedSessionAuth
#

auth/resolved_auth.ts view source

ResolvedSessionAuth import type {ResolvedSessionAuth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

A session cookie — full account authority by construction.

inheritance

extends: ResolvedAuthBase

credential_type

type "session"

readonly

session_hash

Blake3 hash of the session token — the auth_session.id.

type string

readonly

ResolvedWsEndpointSpec
#

actions/ws_endpoint_spec.ts view source

ResolvedWsEndpointSpec

type WsEndpointSpec & { allowed_origins: readonly RegExp[]; actions: readonly Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }>[]; }

import type {ResolvedWsEndpointSpec} from '@fuzdev/fuz_app/actions/ws_endpoint_spec.js';

A WsEndpointSpec with allowed_origins and actions resolved — its own, or the server's.

path

Hono mount path (e.g. /api/ws).

type string

allowed_origins

Origin allowlist regexes for the upgrade — passed straight to verify_request_source. Defaults to the server's AppServerOptions.allowed_origins, the list the HTTP routes check. Set it only to narrow one endpoint: the auth middleware's origin check runs on the upgrade too, so a request reaches the endpoint only when its origin passes both lists. An empty list throws at assembly.

type readonly RegExp[]

actions

The actions registered on this endpoint. Spread protocol_actions from actions/protocol.ts first to complete the disconnect-detection + per-request cancel pairing with the frontend client.

Omit to serve the actions of the server's sole rpc_endpoints entry — to_ws_actions of its list, the protocol actions first — so one action set (one set of handler closures) runs on both transports. Assembly throws when it is omitted and the server resolves no RPC endpoint or more than one.

type readonly Action<ActionSpecUnion>[]

required_roles?

Roles permitted to upgrade — any-of disjunction, global grants only. Omit (or pass []) to skip the upgrade-time role gate; per-action auth on each spec still applies at dispatch time via perform_action. Pass [ROLE_ADMIN] for an admin-only WS endpoint. Held for the socket's life — see register_action_ws's required_roles.

type readonly string[]

registry?

Existing registry to register connections with. Auto-created when omitted. Either way the mounted registry is reachable on AppServer.ws_endpoints[path] for broadcast / fan-out.

type ConnectionRegistry

max_connections_per_account?

Per-account connection cap for the auto-created registry. See RegisterActionWsOptions.max_connections_per_account — rejected alongside registry, which carries its own cap.

type number | null

default DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT

outbound_queue_bytes?

Byte budget of the frames waiting to be written to one connection, for the auto-created registry. See RegisterActionWsOptions.outbound_queue_bytes — rejected alongside registry, which carries its own budget.

type number

default DEFAULT_WS_OUTBOUND_QUEUE_BYTES

receive_timeout?

Receive silence (ms) past which the server closes a socket with WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT; null keeps silent sockets open. See RegisterActionWsOptions.receive_timeout.

type number | null

default DEFAULT_WS_RECEIVE_TIMEOUT

max_message_bytes?

Cap on one inbound message, in bytes. See RegisterActionWsOptions.max_message_bytes.

type number

default DEFAULT_WS_MAX_MESSAGE_BYTES

on_socket_open?

Called once per socket after the connection is admitted — registered and its credential re-checked — and before the first message dispatches. See RegisterActionWsOptions.on_socket_open.

type (ctx: SocketOpenContext): void | Promise<void>

ctx

returns void | Promise<void>

on_socket_close?

Called once per admitted socket on close, after registry.remove_connection. See RegisterActionWsOptions.on_socket_close.

type (ctx: SocketCloseContext): void | Promise<void>

ctx

returns void | Promise<void>

ResolveWsEndpointsOptions
#

actions/ws_endpoint_spec.ts view source

ResolveWsEndpointsOptions import type {ResolveWsEndpointsOptions} from '@fuzdev/fuz_app/actions/ws_endpoint_spec.js';

What resolve_ws_endpoints fills a spec's omitted fields from.

allowed_origins

The server's allowlist, the default allowed_origins.

type readonly RegExp[]

rpc_endpoints

The server's resolved RPC endpoints — a spec that omits actions serves the sole entry's, through to_ws_actions.

type readonly RpcEndpointSpec[] | undefined

rest_auth_route_suffixes
#

testing/integration_helpers.ts view source

readonly ["/login", "/logout", "/password", "/verify", "/signup", "/bootstrap"] import {rest_auth_route_suffixes} from '@fuzdev/fuz_app/testing/integration_helpers.js';

REST auth route suffixes on the account/bootstrap surface — the only routes still REST. find_auth_route rejects any other suffix at runtime; session/token CRUD, admin operations, and role_grant flows live on the RPC surface and should be reached via rpc_call.

RestAuthRouteSuffix
#

testing/integration_helpers.ts view source

"/login" | "/logout" | "/password" | "/verify" | "/signup" | "/bootstrap"

type "/login" | "/logout" | "/password" | "/verify" | "/signup" | "/bootstrap"

import type {RestAuthRouteSuffix} from '@fuzdev/fuz_app/testing/integration_helpers.js';

revalidate_resolved_auth
#

auth/resolved_auth.ts view source

(deps: QueryDeps, resolved: ResolvedAuth): Promise<boolean> import {revalidate_resolved_auth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

Re-check that a resolved credential still authenticates: true when it does, false when it has been revoked, has expired, or its account is gone.

For a connection that outlives its request — a WebSocket, an SSE stream — and is authorized once, at open. Such a connection is closed on revocation by identity, but only once it is *registered*; between the credential read and the registration a revocation closes nothing. So the opener registers first (un-admitted — ConnectionRegistry.register_pending / SseRegistry.subscribe_pending), then calls this, then admits. A revocation that committed before this read is seen by it; one whose close ran after the registration found the pending entry.

That needs each statement here to see everything committed before it started, so deps.db must be the pool-level Db (autocommit) or a READ COMMITTED transaction — never a REPEATABLE READ / SERIALIZABLE transaction opened before the registration, whose snapshot would predate it.

What is re-read, per credential — each through the same predicate the resolve path used, so the two cannot disagree about what "valid" means (the session and account legs call the middleware's own queries; the token leg shares its WHERE fragment):

  • Session — the auth_session row still exists, is unexpired, and belongs to the resolved account.
  • API token — the api_token row still exists, is unexpired, and belongs to the resolved account (by id; the raw token is not kept).
  • Daemon token — nothing of its own. The token has no row, and no revocation event: rotation closes no connection, so there is no close an opening connection could have missed. Its keeper account is still re-read.
  • Every credential — the account row still exists and is not soft-deleted.

It re-checks the credential, not what a revocation chose to close: a logout closes every connection of the account but deletes only its own session, so another session's connection opening concurrently passes this check and is refused only if that account-wide close found its pending registration — the same two outcomes an already-open connection has.

No side effects. Unlike the bearer middleware this never touches api_token.last_used_at / last_used_ip — re-checking a credential is not using it — and it writes nothing else.

The twin of the Rust spine's revalidate_resolved_auth.

deps

query dependencies; the pool-level Db, not a snapshot transaction

resolved

the credential the request authenticated with

returns

Promise<boolean>

whether the credential still authenticates

throws

  • Error - propagated from the database. The caller must fail closed — an unchecked credential is not a valid one.

RevocationScope
#

auth/audit_log_schema.ts view source

RevocationScope

type "none" | "session" | "token" | "account" | "role"

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

What a successful audit row invalidates — the axis the revocation listener (create_revocation_guard) dispatches on.

  • none — the row invalidates no live connection.
  • session — one session: the connections whose session-token hash equals the row's metadata.session_id.
  • token — one API token: the connections whose api_token.id equals the row's metadata.token_id.
  • account — every connection of target_account_id ?? account_id.
  • role — the target account's connections gated on the row's metadata.role, when the revoked grant was global (to_global_role_revocation).

The scope says what the row invalidated; the listener acts on every scope alike, for every transport. On role that matters for the WebSocket too: per-message dispatch re-authorizes each request against its own action, but a socket admitted by a role gate also receives every server push its endpoint sends, which no dispatch ever re-checks — so a socket that lost the gating role is closed, like a stream.

Twin of the Rust spine's RevocationScope.

RevokeForScopeResult
#

auth/role_grant_queries.ts view source

RevokeForScopeResult import type {RevokeForScopeResult} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Result of query_role_grant_revoke_for_scope — every role_grant revoked plus every pending offer superseded by the scope-wide cascade.

revoked

One entry per role_grant revoked by this call. Carries both the revokee's actor_id (the role_grant's grantee — drives target_actor_id audit envelopes) and account_id (the actor's account — drives target_account_id for SSE/WS socket-close fan-out). Empty array means no active role_grant was bound to the scope. scope_kind is surfaced for forensic completeness; the cascade itself keys on scope_id regardless of kind.

type { role_grant_id: string & $brand<"Uuid">; role: string; scope_kind: string | null; scope_id: string & $brand<"Uuid">; actor_id: string & $brand<"Uuid">; account_id: string & $brand<...>; }[]

superseded_offers

Every pending offer at the scope — tuple-matched and orphan, undifferentiated — superseded in the same cascade. Each entry carries its grantor's from_account_id for role_grant_offer_supersede notification fan-out.

The caller is responsible for emitting role_grant_offer_supersede audit events with reason: 'scope_destroyed' and cause_id: <destroyed scope row id> per entry — the cause of every supersede here is the scope deletion, not any individual role_grant revoke (the revokes are themselves consequences of the scope going away).

type SupersededOffer[]

RevokeRoleGrantResult
#

auth/role_grant_queries.ts view source

RevokeRoleGrantResult import type {RevokeRoleGrantResult} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Result of query_revoke_role_grant — the revoked role_grant plus any pending offers superseded by the revoke.

id

type Uuid

role

type string

scope_kind

type string | null

scope_id

type Uuid | null

superseded_offers

Pending offers for the revoked role_grant's (account, role, scope) that were marked superseded as a side effect. Each entry carries its grantor's from_account_id so callers can fan out role_grant_offer_supersede notifications without a second round-trip. The caller is responsible for emitting a role_grant_offer_supersede audit event per entry (with reason: 'role_grant_revoked' and cause_id: <revoked role_grant id>).

type SupersededOffer[]

RevokingActionFactoryDeps
#

auth/deps.ts view source

RevokingActionFactoryDeps import type {RevokingActionFactoryDeps} from '@fuzdev/fuz_app/auth/deps.js';

Capabilities for the action factories whose handlers end credentials or revoke roles — create_account_actions, create_admin_actions, create_role_grant_offer_actions, create_self_service_role_actions, and the create_standard_rpc_actions bundle. ActionFactoryDeps plus the closer their revocations close live connections through. AppDeps and RouteFactoryDeps satisfy it structurally.

inheritance

connection_closer

Closes the live connections of a revoked credential, after the revocation commits. Required rather than optional: a revocation that closes nothing leaves an open WebSocket or SSE stream running on a dead credential. A backend with no live-connection surface passes noop_connection_closer.

type ConnectionCloser

ROLE_ADMIN
#

auth/role_schema.ts view source

"admin" import {ROLE_ADMIN} from '@fuzdev/fuz_app/auth/role_schema.js';

App-level administrative role. Granted via the admin path.

role_grant_active_sql
#

auth/role_grant_sql.ts view source

(alias: string): string import {role_grant_active_sql} from '@fuzdev/fuz_app/auth/role_grant_sql.js';

The SQL predicate for an active role grant: not revoked and not expired.

The grant row's own liveness only. Whether its holder is live — the actor and its account not soft-deleted, which doesn't revoke grants — is a join the caller adds where the read needs it.

alias

the table alias qualifying the columns, or '' for unqualified columns

type string

returns

string

the predicate, with no surrounding parentheses (it is a top-level AND chain)

role_grant_assign_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ to_account_id: $ZodBranded<ZodUUID, "Uuid", "out">; ... 4 more ...; acting: ZodOptional<...>; }, $strict>; ... 4 more ...; rate_limit: "account... import {role_grant_assign_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

rate_limit: 'account' bounds admin-side burn of role_grant_assign — the action is admin-gated and audit-trailed, but the per-account cap matches role_grant_revoke so a single admin script can't churn grants in a loop.

ROLE_GRANT_COLUMNS
#

auth/role_grant_queries.ts view source

readonly ["id", "actor_id", "role", "scope_kind", "scope_id", "created_at", "expires_at", "revoked_at", "revoked_by", "revoked_reason", "granted_by", "source_offer_id"] import {ROLE_GRANT_COLUMNS} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

The full role_grant column set, named explicitly so a row read fails loud on schema drift (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class). Column order follows RoleGrant — the frozen fuz_auth v0 body plus the appended scope_kind / scope_id / revoked_reason / source_offer_id columns. Keep in sync with RoleGrant and the role_grant migration chain in auth/auth_ddl.ts.

ROLE_GRANT_INDEXES
#

role_grant_offer_accept_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ offer_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; ... 4 more ...; rate_limit: "account"; } import {role_grant_offer_accept_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

rate_limit: 'account', matching role_grant_offer_create: every call locks the offer row and reads its grant, and a replay of an accepted offer answers without minting anything, so the account bucket is what bounds a loop of them.

credential_types: ['session'] — see docs/security.md §Credential-channel gating. The offer verb that moves the *caller's own* authority: accepting a pending admin offer over a leaked API token is a privilege pivot in the same family the credential-lifecycle mutations are gated to prevent (the token is account-wide and non-expiring, and the resulting role_grant outlives revoking it). Consent inherently belongs to a human at a browser, and no automation accepts an offer on its own behalf. _create carries the same gate (the grantor's side of the consent); _decline / _retract stay ungated deliberately — they destroy a pending offer and confer nothing.

ROLE_GRANT_OFFER_ACCEPTED_NOTIFICATION_METHOD
#

role_grant_offer_accepted_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; }, $strict>; output: ZodVoid; a... import {role_grant_offer_accepted_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

role_grant_offer_and_accept
#

testing/role_grant_helpers.ts view source

(args: RoleGrantOfferAndAcceptArgs): Promise<{ offer_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; }> import {role_grant_offer_and_accept} from '@fuzdev/fuz_app/testing/role_grant_helpers.js';

Drive the full consent flow (grantor offer → recipient accept) over the production RPC surface and return the materialized role_grant id.

grantor and recipient carry both the account id (for to_account_id derivation) and the create_session_headers factory (for cookie-threaded auth) — closing that loop on a single object per party rules out caller-side header/account mismatch.

args

returns

Promise<{ offer_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; }>

ROLE_GRANT_OFFER_COLUMNS
#

auth/role_grant_offer_ddl.ts view source

readonly ["id", "from_actor_id", "to_account_id", "to_actor_id", "role", "scope_kind", "scope_id", "message", "created_at", "expires_at", "accepted_at", "declined_at", "decline_reason", "retracted_at", "superseded_at", "resulting_role_grant_id", "expire_audited_at"] import {ROLE_GRANT_OFFER_COLUMNS} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

The full role_grant_offer column set, named explicitly so a row read fails loud on schema drift (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class). Column order follows RoleGrantOffer and ROLE_GRANT_OFFER_SCHEMA below, with expire_audited_at last — the appended role_grant_offer_expire_audited_at migration adds it after the frozen CREATE TABLE.

Lives here rather than in auth/role_grant_offer_queries.ts because two query modules project this table — see the placement rule in db/sql_columns.ts.

role_grant_offer_create_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ to_account_id: $ZodBranded<ZodUUID, "Uuid", "out">; ... 5 more ...; acting: ZodOptional<...>; }, $strict>; ... 4 more ...; rate_limi... import {role_grant_offer_create_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

rate_limit: 'account' throttles offer-spam at the authenticated grantor and bounds the account-existence oracle on to_account_id — the same shape as invite_create_action_spec upstream addresses, where a hostile authed caller iterates recipients to probe ERROR_ACCOUNT_NOT_FOUND (and the actor-binding via ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH) as an enumeration vector. Failure-outcome audit rows preserve the forensic trail; the rate cap closes the budget.

credential_types: ['session'] — see docs/security.md §Credential-channel gating. Offering spends the caller's authority to confer a role. Over a leaked API token that is a privilege pivot onto another account: the leak offers a role to an account its holder controls, that account accepts over its own session, and the resulting role_grant outlives revoking the token. Under a consumer authorize that delegates offering (a scoped holder offering roles in its scope), every such holder's token would be a role-minting credential. An offer is the grantor's consent act, as accepting is the recipient's; scripted conferral is role_grant_assign's, admin-gated and narrowable by a token scope.

role_grant_offer_decline_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ offer_id: $ZodBranded<ZodUUID, "Uuid", "out">; reason: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description... import {role_grant_offer_decline_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

ROLE_GRANT_OFFER_DECLINED_NOTIFICATION_METHOD
#

role_grant_offer_declined_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; }, $strict>; output: ZodVoid; a... import {role_grant_offer_declined_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

ROLE_GRANT_OFFER_DEFAULT_TTL_MS
#

auth/role_grant_offer_schema.ts view source

number import {ROLE_GRANT_OFFER_DEFAULT_TTL_MS} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Default TTL for a newly created offer — 30 days. Matches GitHub org-invite expiry.

role_grant_offer_expr
#

auth/role_grant_offer_ddl.ts view source

(alias: string) => ColumnExpr import {role_grant_offer_expr} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

The role_grant_offer timestamp override, by row qualifier ('' bare, o for the history JOIN, u for the updated CTE's outer select). Lives beside the const for the same reason the const does — both query modules that project this table need it.

A cascade's inner RETURNING deliberately projects the *raw* columns: the outer select over the updated CTE applies this, and to_char over an already-formatted text column would fail.

role_grant_offer_history_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodDefault<ZodObject<{ account_id: ZodOptional<ZodNullable<$ZodBranded<ZodUUID, "Uuid", "out">>>; limit: ZodOptional<...>; offset: ZodOptional<...>; acting: ZodOptional<..... import {role_grant_offer_history_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

ROLE_GRANT_OFFER_HISTORY_LIMIT_MAX
#

ROLE_GRANT_OFFER_INBOX_INDEX
#

auth/role_grant_offer_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS role_grant_offer_inbox\n ON role_grant_offer (to_account_id, expires_at)\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n AND retracted_at IS NULL\n AND superseded_at IS NULL" import {ROLE_GRANT_OFFER_INBOX_INDEX} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

Inbox lookup — pending offers for an account, ordered by soonest expiry.

role_grant_offer_list_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodDefault<ZodObject<{ account_id: ZodOptional<ZodNullable<$ZodBranded<ZodUUID, "Uuid", "out">>>; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; async: tr... import {role_grant_offer_list_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX
#

auth/role_grant_offer_schema.ts view source

500 import {ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Maximum length of the optional message attached to an offer, and of a decline reason, in UTF-16 code units.

role_grant_offer_notification_specs
#

ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX
#

auth/role_grant_offer_ddl.ts view source

"\nCREATE UNIQUE INDEX IF NOT EXISTS role_grant_offer_pending_unique\n ON role_grant_offer (\n to_account_id,\n role,\n COALESCE(scope_kind, 'GLOBAL'),\n COALESCE(scope_id, '00000000-0000-0000-0000-000000000000'::uuid),\n from_actor_id\n )\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n ... import {ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

At most one pending offer per (to_account, role, scope_kind, scope, from_actor).

Including from_actor_id in the tuple lets multiple grantors coexist — teacher A and teacher B can each have a pending classroom_student offer for the same student and scope. A same-grantor re-offer upserts the existing pending row. COALESCE collapses NULL scopes into the sentinel values so Postgres's NULL-in-unique-index quirk does not allow duplicate global pending offers; the scope_kind / scope_id pair is always either both null (global) or both non-null (scoped) per the role_grant_offer_scope_kind_paired CHECK, so the two COALESCE expressions always agree. The ON CONFLICT target in query_role_grant_offer_create must match this expression literally.

ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD
#

role_grant_offer_received_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; }, $strict>; output: ZodVoid; a... import {role_grant_offer_received_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

role_grant_offer_retract_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ offer_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; error_reasons: (... import {role_grant_offer_retract_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

ROLE_GRANT_OFFER_RETRACTED_NOTIFICATION_METHOD
#

role_grant_offer_retracted_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; }, $strict>; output: ZodVoid; a... import {role_grant_offer_retracted_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

ROLE_GRANT_OFFER_SCHEMA
#

auth/role_grant_offer_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS role_grant_offer (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n from_actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n to_account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n to_actor_id UUID NULL REFERENCES actor(id) ON DELETE CASCADE,\n role T... import {ROLE_GRANT_OFFER_SCHEMA} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN
#

auth/role_grant_offer_ddl.ts view source

"GLOBAL" import {ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

Index-side token for the global case in the partial unique index. Uppercase so it cannot collide with consumer-declared ScopeKindName values (which are lowercase by regex). Never appears as a column value — column-level scope_kind = NULL and scope_id = NULL together encode the global case.

ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID
#

auth/role_grant_offer_ddl.ts view source

"00000000-0000-0000-0000-000000000000" import {ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

Sentinel UUID used inside the partial unique indexes to collapse scope_id IS NULL into a comparable value.

ROLE_GRANT_OFFER_SUPERSEDE_NOTIFICATION_METHOD
#

role_grant_offer_supersede_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; reason: ZodEnum<...>; cause_id:... import {role_grant_offer_supersede_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT
#

auth/role_grant_offer_ddl.ts view source

string import {ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

Outer SELECT over an updated CTE of role_grant_offer rows, joining the grantor's account_id on as from_account_id — the SupersededOffer / DeclinedOffer shape every offer-superseding cascade returns (the revoke cascades in auth/role_grant_queries.ts, decline + accept-supersede in auth/role_grant_offer_queries.ts). The CTE must be named updated and RETURNING the full offer row.

role_grant_offers_state_context
#

ui/role_grant_offers_state.svelte.ts view source

{ get: (error_message?: string | undefined) => RoleGrantOffersState; get_maybe: () => RoleGrantOffersState | undefined; set: (value: RoleGrantOffersState) => RoleGrantOffersState; } import {role_grant_offers_state_context} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

Svelte context for RoleGrantOffersState. Use role_grant_offers_state_context.set(state) in the provider and role_grant_offers_state_context.get() to access.

role_grant_revoke_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ actor_id: $ZodBranded<ZodUUID, "Uuid", "out">; role_grant_id: $ZodBranded<...>; reason: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>... import {role_grant_revoke_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

rate_limit: 'account' bounds admin-side burn of role_grant_revoke — the action is admin-gated and audit-trailed, but the per-account cap keeps a single admin script from churning role_grants in a loop and obscuring audit context for unrelated activity.

ROLE_GRANT_REVOKE_NOTIFICATION_METHOD
#

role_grant_revoke_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ role_grant_id: $ZodBranded<ZodUUID, "Uuid", "out">; role: ZodString; scope_id: ZodNullable<...>; reason: ZodNullable<...>; }, $strict>; output: ZodVoid; async: true; description: string; } import {role_grant_revoke_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

ROLE_GRANT_REVOKED_REASON_LENGTH_MAX
#

auth/account_schema.ts view source

500 import {ROLE_GRANT_REVOKED_REASON_LENGTH_MAX} from '@fuzdev/fuz_app/auth/account_schema.js';

Maximum length of the optional free-form revoked_reason attached to a revoked role_grant. Bounds the value at the schema layer so both the admin input (when the route surfaces a reason field) and the revokee-facing role_grant_revoke WS notification validate against the same ceiling. Counted in UTF-16 code units.

ROLE_GRANT_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS role_grant (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ,\n revoked_at TIMESTAMPTZ,\n revoked_by UUID REFERENCES a... import {ROLE_GRANT_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

role_has_grant_path
#

auth/role_schema.ts view source

(role_specs: ReadonlyMap<string, RoleSpec>, role: string, grant_path: string): boolean import {role_has_grant_path} from '@fuzdev/fuz_app/auth/role_schema.js';

Predicate over a RoleSpec map: does the named role include the given grant path? Returns false for unknown roles. Used by admin_actions.create_admin_actions (path = 'admin') and self_service_role_actions.create_self_service_role_actions (path = 'self_service') to derive their default eligibility filters.

role_specs

type ReadonlyMap<string, RoleSpec>

role

type string

grant_path

type string

returns

boolean

ROLE_KEEPER
#

auth/role_schema.ts view source

"keeper" import {ROLE_KEEPER} from '@fuzdev/fuz_app/auth/role_schema.js';

System-level role. Requires daemon token (filesystem proof). Controls the keep.

ROLE_NAME_LENGTH_MAX
#

auth/role_schema.ts view source

64 import {ROLE_NAME_LENGTH_MAX} from '@fuzdev/fuz_app/auth/role_schema.js';

The longest role name RoleName admits, in characters. A role string is caller-chosen text until it passes the grammar, and the grant verbs record the role they refuse in a failure audit row, so the grammar bounds its length too. Same value as the Rust spine's ROLE_NAME_LENGTH_MAX.

RoleGrant
#

auth/account_schema.ts view source

RoleGrant import type {RoleGrant} from '@fuzdev/fuz_app/auth/account_schema.js';

Role grant — time-bounded, revocable grant of a role to an actor.

id

type Uuid

actor_id

type Uuid

role

type string

scope_kind

Machine-readable kind tag for the polymorphic scope_id. Paired-null with scope_id per the role_grant_scope_kind_paired CHECK: both null (global) or both non-null (scoped). Consumer-declared via create_scope_kind_schema(...); v1 keeps validation registry-membership only, with no INSERT-time (role, scope_kind) enforcement.

type string | null

scope_id

Resource scope this grant applies to (e.g. a classroom id). null for global role_grants.

type Uuid | null

created_at

type string

expires_at

type string | null

revoked_at

type string | null

revoked_by

type Uuid | null

revoked_reason

Optional free-form reason attached on revoke (rides on the role_grant_revoke WS notification to the revokee).

type string | null

granted_by

type Uuid | null

source_offer_id

Offer that produced this role_grant (set by query_accept_offer). null for direct grants.

type Uuid | null

RoleGrantAssignInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } import {RoleGrantAssignInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_assign — the immediate admin-only conferral path, the consent-free sibling of role_grant_offer_create. An admin assigns a role_grant straight onto the target actor; there is no offer for a grantee to accept. No message (no offer to carry it on). scope_kind pairs with scope_id exactly as on role_grant_offer_create — both or neither — and the grant row carries the pair.

to_account_id

type string & $brand<"Uuid">

role

type string

to_actor_id?

type (string & $brand<"Uuid">) | null

scope_kind?

type string | null

scope_id?

type (string & $brand<"Uuid">) | null

acting?

type string & $brand<"Uuid">

RoleGrantAssignOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ ok: true; role_grant_id: string & $brand<"Uuid">; } import {RoleGrantAssignOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_assign.

ok

type true

role_grant_id

type string & $brand<"Uuid">

RoleGrantFixtureOptions
#

testing/cross_backend/role_grant_cross_helpers.ts view source

RoleGrantFixtureOptions import type {RoleGrantFixtureOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_cross_helpers.js';

Options for the role-grant fixtures.

role?

The role to offer. Default ROLE_ADMIN, the one builtin admin-grantable role.

type string

readonly

rpc_path?

The JSON-RPC endpoint path. Default SPINE_RPC_PATH.

type string

readonly

RoleGrantHistoryEventJson
#

auth/audit_log_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 6 more ...; target_username: string | null; } import {RoleGrantHistoryEventJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for role_grant history events with resolved usernames.

id

type string & $brand<"Uuid">

seq

type number

event_type

type string

outcome

type "success" | "failure"

actor_id

type (string & $brand<"Uuid">) | null

account_id

type (string & $brand<"Uuid">) | null

target_account_id

type (string & $brand<"Uuid">) | null

target_actor_id

type (string & $brand<"Uuid">) | null

ip

type string | null

created_at

type string

metadata

type Record<string, unknown> | null

username

type string | null

target_username

type string | null

RoleGrantOffer
#

auth/role_grant_offer_schema.ts view source

RoleGrantOffer import type {RoleGrantOffer} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Role grant offer row as returned by the database.

id

type Uuid

from_actor_id

type Uuid

to_account_id

type Uuid

to_actor_id

Optional actor-grain target on the recipient account. When set, accept is gated to this specific actor — query_accept_offer rejects any other actor with role_grant_offer_actor_mismatch even when they belong to to_account_id. When null the offer is account-grain and any actor on to_account_id may accept (the v1 default).

Drives the audit envelope's target_actor_id on offer-shape events (role_grant_offer_create / _expire / _retract / _supersede) — when set, the actor-grain forensic field carries the named actor; when null the offer-shape events leave it null by design.

type Uuid | null

role

type string

scope_kind

Machine-readable kind tag for the polymorphic scope_id. Paired-null with scope_id per the role_grant_offer_scope_kind_paired CHECK: both null (global) or both non-null (scoped). Consumer-declared via create_scope_kind_schema(...); v1 keeps validation registry-membership only, with no INSERT-time (role, scope_kind) enforcement.

type string | null

scope_id

type Uuid | null

message

type string | null

created_at

type string

expires_at

type string

accepted_at

type string | null

declined_at

type string | null

decline_reason

type string | null

retracted_at

type string | null

superseded_at

Set when the offer was obsoleted by an external event — a sibling offer was accepted (yielding the role_grant this offer's role+scope maps to) or the resulting role_grant for this (to_account, role, scope) was revoked. Closes the "accept a pre-revoke offer to bypass the revoke" path.

type string | null

resulting_role_grant_id

type Uuid | null

expire_audited_at

When the expiry sweep audited this offer's role_grant_offer_expire — the claim stamp query_role_grant_offer_sweep_expired sets in the same transaction as the audit row, so each expiry is audited once. Not a lifecycle state (an expired offer is still pending by the terminal columns) and server-only: to_role_grant_offer_json drops it, and the strict RoleGrantOfferJson would reject it. A re-offer clears it.

type string | null

RoleGrantOfferAcceptedParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferAcceptedParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_accepted — recipient accepted the offer.

offer

type RoleGrantOfferJson

RoleGrantOfferAcceptInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferAcceptInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_accept.

offer_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

RoleGrantOfferAcceptOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ role_grant_id: string & $brand<"Uuid">; offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; ... 12 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; superseded_offer_ids: (string & $brand<...>)[]; } import {RoleGrantOfferAcceptOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_accept.

role_grant_id

type string & $brand<"Uuid">

offer

type RoleGrantOfferJson

superseded_offer_ids

type (string & $brand<"Uuid">)[]

RoleGrantOfferActionOptions
#

auth/role_grant_offer_actions.ts view source

RoleGrantOfferActionOptions import type {RoleGrantOfferActionOptions} from '@fuzdev/fuz_app/auth/role_grant_offer_actions.js';

roles?

Role schema result from create_role_schema(). Defaults to builtin roles only. Drives the grantability gate: a role is offerable / revocable through this surface only when its RoleSpec.grant_paths includes 'admin' (the GRANT_PATH_ADMIN constant).

type RoleSchemaResult

default_ttl_ms?

TTL applied to newly-created offers. Defaults to ROLE_GRANT_OFFER_DEFAULT_TTL_MS.

type number

authorize?

Custom authorization for role_grant_offer_create and role_grant_assign. The default requires the caller to hold a global admin grant; the role's RoleSpec.grant_paths must include 'admin' either way. Consumers with richer policies (scope-aware, chained roles) override this. A builtin role never reaches the callback unless the caller holds a global admin and the offer is unscoped — the spine decides builtin-role offers itself, so a callback needs no builtin handling of its own.

type (auth: RequestContext, input: { to_account_id: string; role: string; scope_kind: string | null; scope_id: string | null; }, deps: { log: Logger; }, ctx: ActionContext): boolean | Promise<...>

auth

input

type { to_account_id: string; role: string; scope_kind: string | null; scope_id: string | null; }

deps

type { log: Logger; }

ctx

returns boolean | Promise<boolean>

RoleGrantOfferActorAccountMismatchError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferActorAccountMismatchError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when query_role_grant_offer_create is called with a to_actor_id that does not exist or does not belong to to_account_id. Surfaces the actor↔account binding mismatch at the boundary instead of letting the FK silently disagree with the recipient field.

inheritance

extends: Error

constructor

type new (): RoleGrantOfferActorAccountMismatchError

RoleGrantOfferActorMismatchError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferActorMismatchError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when an actor-targeted offer is being accepted by an actor other than offer.to_actor_id. Distinct from RoleGrantOfferNotFoundError (the IDOR mask): once an offer has been resolved to the recipient account, a wrong-actor accept on a same-account actor is a contract violation, not a privacy boundary — surface a specific error so the client UI can distinguish "this offer isn't for you" from "no such offer".

inheritance

extends: Error

constructor

type new (offer_id: string): RoleGrantOfferActorMismatchError

offer_id

type string

RoleGrantOfferAlreadyTerminalError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferAlreadyTerminalError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown by offer-lifecycle queries when the offer is in a non-pending state (accepted / declined / retracted / superseded) and therefore not actionable. Distinct from RoleGrantOfferExpiredError — expiry has its own user-facing story ("ask the grantor to re-send") so it travels separately.

inheritance

extends: Error

constructor

type new (offer_id: string): RoleGrantOfferAlreadyTerminalError

offer_id

type string

RoleGrantOfferAndAcceptArgs
#

RoleGrantOfferBuiltinRefusedError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferBuiltinRefusedError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown by query_accept_offer when the offer confers a builtin role (admin / keeper) that this accept must not mint — refusal names the rule. Carries the locked (still pending, untouched) offer so the caller can audit the refusal with the offer's role and scope.

inheritance

extends: Error

offer

type RoleGrantOffer

readonly

refusal

type BuiltinOfferRefusal

readonly

constructor

type new (offer: RoleGrantOffer, refusal: BuiltinOfferRefusal): RoleGrantOfferBuiltinRefusedError

offer

refusal

RoleGrantOfferCreateAuthorize
#

auth/role_grant_offer_actions.ts view source

RoleGrantOfferCreateAuthorize import type {RoleGrantOfferCreateAuthorize} from '@fuzdev/fuz_app/auth/role_grant_offer_actions.js';

Authorization callback for role_grant_offer_create and role_grant_assign. Returns true to allow, false to reject (handler converts to forbidden with role_grant_offer_not_authorized).

Provided with the fully-resolved request context and the parsed input (pre-TTL, pre-normalization). Consumers override the default to implement policies like "teacher may offer classroom_student only in classrooms they teach". role_grant_assign — the global-admin path that confers a grant with no offer — runs it too, so a policy over which grants may exist (a role that must be scoped, a scope that must be real) holds on both paths. Twin of the Rust OfferCreateAuthorize.

ctx is the request's: both verbs have side effects, so ctx.db is the request transaction, and a callback needing the database (a scope-existence check, say) reads through it. Read-only — the callback decides, it never writes; a write would commit or roll back with the request, and nothing enforces this but the callback.

(call)

type (auth: RequestContext, input: { to_account_id: string; role: string; scope_kind: string | null; scope_id: string | null; }, deps: { log: Logger; }, ctx: ActionContext): boolean | Promise<...>

auth

input

type { to_account_id: string; role: string; scope_kind: string | null; scope_id: string | null; }

deps

type { log: Logger; }

ctx

returns boolean | Promise<boolean>

RoleGrantOfferCreateInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; message?: string | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } import {RoleGrantOfferCreateInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_create.

to_actor_id (optional) narrows the offer to a specific actor on the recipient account. When supplied, role_grant_offer_accept will only admit the named actor — wrong-actor accepts reject with role_grant_offer_actor_mismatch. The audit envelope's target_actor_id is stamped from this column on the create / supersede / expire / retract events. Omit (or pass null) for the account-grain default — any actor on to_account_id may accept.

to_account_id

type string & $brand<"Uuid">

role

type string

to_actor_id?

type (string & $brand<"Uuid">) | null

scope_kind?

type string | null

scope_id?

type (string & $brand<"Uuid">) | null

message?

type string | null

acting?

type string & $brand<"Uuid">

RoleGrantOfferCreateOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferCreateOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_create.

offer

type RoleGrantOfferJson

RoleGrantOfferCreateParams
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOfferCreateParams

type (Omit<RoleGrantOfferCreateInput, "scope_kind" | "scope_id"> & { scope_kind?: null | undefined; scope_id?: null | undefined; }) | (Omit<RoleGrantOfferCreateInput, "scope_kind" | "scope_id"> & { scope_kind: string; scope_id: string & $brand<"Uuid">; })

import type {RoleGrantOfferCreateParams} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

Params for submit_create — RoleGrantOfferCreateInput with its scope as the paired RoleGrantOfferScope, so a lone scope_id is a type error.

RoleGrantOfferDeclinedParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferDeclinedParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_declined. The decline reason (if any) rides along inside offer.decline_reason — the DB stamps it on the offer row during decline, so a sibling reason field would just duplicate it.

offer

type RoleGrantOfferJson

RoleGrantOfferDeclineInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferDeclineInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_decline.

offer_id

type string & $brand<"Uuid">

reason?

type string | null

acting?

type string & $brand<"Uuid">

RoleGrantOfferDeclineOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferDeclineOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_decline — the declined offer, as accept answers its own.

offer

type RoleGrantOfferJson

RoleGrantOfferEnumerationCrossTestOptions
#

testing/cross_backend/role_grant_offer_enumeration.ts view source

RoleGrantOfferEnumerationCrossTestOptions import type {RoleGrantOfferEnumerationCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_enumeration.js';

Options for the offer-enumeration parity suite.

setup_test

Per-test fixture producer. Must be configured with extra_actors (≥1) so the keeper is multi-actor and the sibling-actor mismatch arm is reachable — the entrypoint passes default_cross_process_setup(handle, {extra_actors: [...]}).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

rpc_path?

RPC endpoint path. Default /api/rpc.

type string

readonly

RoleGrantOfferExpiredError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferExpiredError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when an offer's expires_at has passed. The accept path enforces this independently of the sweep — a stale offer past its expiry must not be accepted, even in the race window between expiry and the sweep stamping the audit event.

inheritance

extends: Error

constructor

type new (offer_id: string): RoleGrantOfferExpiredError

offer_id

type string

RoleGrantOfferForm
#

ui/RoleGrantOfferForm.svelte view source

import RoleGrantOfferForm from '@fuzdev/fuz_app/ui/RoleGrantOfferForm.svelte';

scope_kind?

type string | null
optional

scope_id?

type (string & $brand<"Uuid">) | null
optional

to_account_id

type Uuid

to_actor_id?

Narrow the offer to a specific actor on to_account_id. Omit (or null, the default) for the account-grain default — any actor on the recipient account may accept.

type Uuid | null
optional default null

roles

Roles the caller may offer — caller filters upstream (default: admin-grant-path).

type string[]

on_created?

type (offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }) => void
optional
snippet parameters

format_role?

type (role: string) => string
optional default (role: string) => role
snippet parameters
role string

RoleGrantOfferHistory
#

ui/RoleGrantOfferHistory.svelte view source

import RoleGrantOfferHistory from '@fuzdev/fuz_app/ui/RoleGrantOfferHistory.svelte';

current_actor_id

Used to label a row as sent vs received. When null, direction shows as -.

type string | null

format_actor?

type (from_actor_id: string) => string
optional default truncate_uuid
snippet parameters
from_actor_id string

format_scope?

Display label for an offer's scope. Bypasses format_scope_context when supplied — return null to fall back to a truncated uuid (or 'global' for null scope_id). Omit to use the context value directly.

optional
snippet parameters
args { scope_id: string | null; role: string; }

format_role?

type (role: string) => string
optional default (role: string) => role
snippet parameters
role string

RoleGrantOfferHistoryInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferHistoryInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_history. Returns every offer involving the account in either direction (recipient or grantor), including terminal rows, newest first. account_id is admin-only.

account_id?

type (string & $brand<"Uuid">) | null

limit?

type number | null

offset?

type number | null

acting?

type string & $brand<"Uuid">

RoleGrantOfferHistoryOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offers: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }[]; } import {RoleGrantOfferHistoryOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_history.

offers

type RoleGrantOfferJson[]

RoleGrantOfferInbox
#

ui/RoleGrantOfferInbox.svelte view source

import RoleGrantOfferInbox from '@fuzdev/fuz_app/ui/RoleGrantOfferInbox.svelte';

format_actor?

Display label for from_actor_id. Defaults to a truncated uuid.

type (from_actor_id: string) => string
optional default truncate_uuid
snippet parameters
from_actor_id string

format_scope?

Display label for an offer's scope. Bypasses format_scope_context when supplied — return null to fall back to a truncated uuid (or 'global' for null scope_id). Omit to use the context value directly.

optional
snippet parameters
args { scope_id: string | null; role: string; }

format_role?

Display label for a role constant. Defaults to identity.

type (role: string) => string
optional default (role: string) => role
snippet parameters
role string

RoleGrantOfferJson
#

auth/role_grant_offer_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; } import {RoleGrantOfferJson} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Zod schema for client-safe role_grant offer data.

id

type string & $brand<"Uuid">

from_actor_id

type string & $brand<"Uuid">

to_account_id

type string & $brand<"Uuid">

to_actor_id

type (string & $brand<"Uuid">) | null

role

type string

scope_kind

type string | null

scope_id

type (string & $brand<"Uuid">) | null

message

type string | null

created_at

type string

expires_at

type string

accepted_at

type string | null

declined_at

type string | null

decline_reason

type string | null

retracted_at

type string | null

superseded_at

type string | null

resulting_role_grant_id

type (string & $brand<"Uuid">) | null

RoleGrantOfferLifecycleCrossTestOptions
#

testing/cross_backend/role_grant_offer_lifecycle.ts view source

RoleGrantOfferLifecycleCrossTestOptions import type {RoleGrantOfferLifecycleCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_lifecycle.js';

inheritance

role?

The role offered — one the backend's registry holds with the admin grant path. Default SPINE_PARTICIPANT_ROLE, the spine's own; a backend with only the builtin roles passes ROLE_ADMIN.

type string

readonly

RoleGrantOfferListInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ account_id?: (string & $brand<"Uuid">) | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferListInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_list. account_id is admin-only (inspect another account's inbox).

account_id?

type (string & $brand<"Uuid">) | null

acting?

type string & $brand<"Uuid">

RoleGrantOfferListOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offers: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }[]; } import {RoleGrantOfferListOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_list.

offers

type RoleGrantOfferJson[]

RoleGrantOfferNotFoundError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferNotFoundError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when an offer cannot be located for the caller. Covers both "offer does not exist" and "offer belongs to a different recipient" (IDOR guard) — the standard 404-over-403 pattern that avoids disclosing whether an offer id exists.

inheritance

extends: Error

constructor

type new (offer_id: string): RoleGrantOfferNotFoundError

offer_id

type string

RoleGrantOfferNotification
#

RoleGrantOfferNotificationWsTestOptions
#

testing/cross_backend/role_grant_offer_notification_ws.ts view source

RoleGrantOfferNotificationWsTestOptions import type {RoleGrantOfferNotificationWsTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_notification_ws.js';

inheritance

setup_test

Per-test fixture producer (default_cross_process_setup(handle, ...)).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags; every case gates on capabilities.ws.

type BackendCapabilities

readonly

ws_required_roles?

The roles ws_path gates its upgrade on (its required_roles), or omitted / [] for an ungated endpoint. Declare them for a backend whose socket is role-gated: revoking ROLE_ADMIN then closes the revokee's socket with WS_CLOSE_PERMISSION_REVOKED, and the revoke case asserts that close instead of the notification frame.

type readonly string[]

readonly

RoleGrantOfferReceivedParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferReceivedParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_received — offer delivered to its recipient.

offer

type RoleGrantOfferJson

RoleGrantOfferRetractedParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferRetractedParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_retracted — grantor-side retraction.

offer

type RoleGrantOfferJson

RoleGrantOfferRetractInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferRetractInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_retract.

offer_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

RoleGrantOfferRetractOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferRetractOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_retract — the retracted offer, as accept answers its own.

offer

type RoleGrantOfferJson

RoleGrantOfferScope
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOfferScope

type { scope_kind?: null | undefined; scope_id?: null | undefined; } | { scope_kind: string; scope_id: string & $brand<"Uuid">; }

import type {RoleGrantOfferScope} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

The scope of an offer: global (both absent / null) or scoped (both set). scope_kind and scope_id are paired on the wire — a lone scope_id is an invalid_params refusal — so the pair is one union rather than two optional fields.

RoleGrantOfferSelfTargetError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferSelfTargetError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when a grantor attempts to offer a role_grant to their own account.

Enforced via a single SELECT on the grantor's actor.account_id (rather than via a CHECK constraint or a denormalized column). Resolving from the grantor side keeps the check multi-actor-correct: under multi-actor the recipient account may host many actors, but the grantor → account binding remains 1:1 by definition of actor.

inheritance

extends: Error

constructor

type new (): RoleGrantOfferSelfTargetError

RoleGrantOffersRpc
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOffersRpc import type {RoleGrantOffersRpc} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

Narrow RPC surface consumed by RoleGrantOffersState. Consumers adapt their typed client (e.g. a create_rpc_client Proxy) to this shape — the state class stays decoupled from the client's Result return type so tests can inject plain-function stubs. Each method takes its action's input as one params object and returns its output, both typed from the action specs, so the typed throwing client's methods bridge with no casts.

list

type (params?: { account_id?: (string & $brand<"Uuid">) | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ offers: { id: string & $brand<...>; ... 14 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }[]; }>

params?

optional
returns Promise<RoleGrantOfferListOutput>

history

type (params?: { account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<...>

params?

optional

create

type (params: { to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; message?: string | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; }): Promise<...>

params

accept

type (params: { offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ role_grant_id: string & $brand<"Uuid">; offer: { ...; }; superseded_offer_ids: (string & $brand<...>)[]; }>

params

decline

type (params: { offer_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ offer: { id: string & $brand<"Uuid">; ... 14 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }>

params

retract

type (params: { offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<...>; ... 13 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }>

params

RoleGrantOffersState
#

ui/role_grant_offers_state.svelte.ts view source

import {RoleGrantOffersState} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

list_history

type AsyncSlot<void, string>

readonly

create

type AsyncSlot<RoleGrantOfferJson, string>

readonly

accept

Per-offer accept, keyed by offer id.

type KeyedAsyncSlot<string, void, string>

readonly

decline

Per-offer decline, keyed by offer id.

type KeyedAsyncSlot<string, void, string>

readonly

retract

Per-offer retract, keyed by offer id.

type KeyedAsyncSlot<string, void, string>

readonly

incoming

Pending offers for the current account, soonest-expiring first.

type RoleGrantOfferJson[]

readonly $derived.by

outgoing

Pending offers from the current actor, newest-created first.

type RoleGrantOfferJson[]

readonly $derived.by

history

Every offer known to this state, newest-created first. Feeds the history view.

type RoleGrantOfferJson[]

readonly $derived.by

incoming_count

type number

readonly $derived

constructor

type new (options: RoleGrantOffersStateOptions): RoleGrantOffersState

options

fetch

Seed the cache with the recipient-side pending inbox.

type (): Promise<void>

returns Promise<void>

fetch_history

Seed both-directions history (includes terminal rows).

type (options?: { limit?: number | undefined; offset?: number | undefined; } | undefined): Promise<void>

options?

type { limit?: number | undefined; offset?: number | undefined; }
optional
returns Promise<void>

submit_create

Issue a new offer; merges the returned offer into the cache on success.

to_actor_id (optional) narrows the offer to a specific actor on to_account_id; omit / null for the account-grain default (any actor on the recipient account may accept). A scoped offer passes scope_kind and scope_id together; an unpaired scope (reachable from untyped callers) fails with GRANT_SCOPE_PAIRED_MESSAGE without an RPC.

type (params: RoleGrantOfferCreateParams): Promise<{ id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<...>; ... 12 more ...; resulting_role_grant_id: (string & $brand<...>) | null; } | undefined>

params

returns Promise<RoleGrantOfferJson | undefined>

submit_accept

Accept an offer; stamps it terminal in the cache and drops any siblings the server superseded.

type (offer_id: string & $brand<"Uuid">): Promise<void>

offer_id

type Uuid
returns Promise<void>

submit_decline

Decline an offer; stamps the declined offer the server returns into the cache.

type (offer_id: string & $brand<"Uuid">, reason?: string | null | undefined): Promise<void>

offer_id

type Uuid

reason?

type string | null
optional
returns Promise<void>

submit_retract

Retract an offer; stamps the retracted offer the server returns into the cache.

type (offer_id: string & $brand<"Uuid">): Promise<void>

offer_id

type Uuid
returns Promise<void>

subscribe

Wire a notification subscription. The handler dispatches each matching notification into apply_notification; the returned disposer unwires.

type (subscribe_fn: RoleGrantOfferSubscribe): () => void

subscribe_fn

returns () => void

apply_notification

Reduce a single WS notification into the cache. Exposed so consumers wiring their WS receiver directly (without subscribe) and tests can drive the reducer without allocating a subscription.

type (notification: RoleGrantOfferNotification): void

notification

returns void

mutates

  • this

reset

Clear the cache and reset every slot.

type (): void

returns void

RoleGrantOffersStateOptions
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOffersStateOptions import type {RoleGrantOffersStateOptions} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

rpc

type RoleGrantOffersRpc

account_id

Reactive accessor for the current account id; returns null when logged out.

type (): string | null

returns string | null

actor_id

Reactive accessor for the current actor id — required to classify offers as outgoing. Returns null when unknown.

type (): string | null

returns string | null

RoleGrantOfferSubscribe
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOfferSubscribe import type {RoleGrantOfferSubscribe} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

Subscription primitive — consumer wires their WS receiver; returns a disposer.

(call)

type (handler: (notification: RoleGrantOfferNotification) => void): () => void

handler

type (notification: RoleGrantOfferNotification) => void
returns () => void

RoleGrantOfferSupersedeParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; reason: "sibling_accepted" | ... 1 more ... | "scope_destroyed"; cause_id:... import {RoleGrantOfferSupersedeParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_supersede. Fires to the grantor's sockets when their pending offer is obsoleted — either by a sibling accept (reason: 'sibling_accepted'), by revoke of the resulting role_grant or of one of the grantor's own (reason: 'role_grant_revoked'), or by deletion of the parent scope row the offer was bound to (reason: 'scope_destroyed'). cause_id points at the accepted offer id, the revoked role_grant id, or the destroyed scope row id respectively.

offer

type RoleGrantOfferJson

reason

type "sibling_accepted" | "role_grant_revoked" | "scope_destroyed"

cause_id

type string & $brand<"Uuid">

RoleGrantParticipationCrossTestOptions
#

testing/cross_backend/role_grant_participation.ts view source

RoleGrantParticipationCrossTestOptions import type {RoleGrantParticipationCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_participation.js';

Options for the role-gated-participation success-path parity suite.

setup_test

Per-test fixture producer (in-process or cross-process).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

rpc_path?

RPC endpoint path. Default /api/rpc.

type string

readonly

role?

The role conferred — one the backend's registry holds with the admin grant path. Default SPINE_PARTICIPANT_ROLE, the spine's own; a backend with only the builtin roles passes ROLE_ADMIN.

type string

readonly

scope_kind?

A scope kind role applies to, for the scoped-assign case; null skips that case. Default 'space' (the spine participant's), or null for a builtin role, which is global-only (a scoped builtin is refused role_grant_builtin_scoped).

type string | null

readonly

RoleGrantRevokeInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ actor_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantRevokeInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_revoke. Admin-only mutation that revokes an active role_grant on a target actor. actor_id is the natural key — role_grants are actor-scoped, and the admin UI reads row.actor.id straight from the listing. Deriving actor_id from account_id would collapse under multi-actor accounts.

actor_id

type string & $brand<"Uuid">

role_grant_id

type string & $brand<"Uuid">

reason?

type string | null

acting?

type string & $brand<"Uuid">

RoleGrantRevokeOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ ok: true; revoked: true; } import {RoleGrantRevokeOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_revoke.

ok

type true

revoked

type true

RoleGrantRevokeParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ role_grant_id: string & $brand<"Uuid">; role: string; scope_id: (string & $brand<"Uuid">) | null; reason: string | null; } import {RoleGrantRevokeParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_revoke. Delivered to the revokee's sockets when one of their active role_grants is revoked. Flat wire shape — revoked_by is admin-UI-visible but deliberately omitted here (the revokee doesn't need to learn the admin's identity). Target account is implicit in the send target.

role_grant_id

type string & $brand<"Uuid">

role

type string

scope_id

type (string & $brand<"Uuid">) | null

reason

type string | null

RoleGrantRoleGrammarCrossTestOptions
#

testing/cross_backend/role_grant_role_grammar.ts view source

RpcPathCrossSuiteOptions import type {RoleGrantRoleGrammarCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_role_grammar.js';

Options for the role-grammar suite (the standard RPC-dispatched shape).

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

RoleGrantSummaryJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; role: string; scope_kind: string | null; scope_id: (string & $brand<"Uuid">) | null; created_at: string; expires_at: string | null; granted_by: (string & $brand<...>) | null; } import {RoleGrantSummaryJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for the role_grant summary returned in admin account listings.

id

type string & $brand<"Uuid">

role

type string

scope_kind

type string | null

scope_id

type (string & $brand<"Uuid">) | null

created_at

type string

expires_at

type string | null

granted_by

type (string & $brand<"Uuid">) | null

RoleName
#

RoleSchemaResult
#

auth/role_schema.ts view source

RoleSchemaResult import type {RoleSchemaResult} from '@fuzdev/fuz_app/auth/role_schema.js';

The result of create_role_schema — a Zod schema and spec map for all roles.

Role

Zod schema that validates role strings. Use at I/O boundaries (grant endpoint, role_grant queries).

type ZodType<string, unknown, $ZodTypeInternals<string, unknown>>

role_specs

Specs for every role (builtins + app-defined). Keyed by role name.

type ReadonlyMap<string, RoleSpec>

RoleSpec
#

auth/role_schema.ts view source

RoleSpec import type {RoleSpec} from '@fuzdev/fuz_app/auth/role_schema.js';

Configuration for a role.

Each role declares the credential types its holders are meant to use, the scope kinds it applies to, and the grant paths through which it can be granted. Every cross-axis field is an open-registry string array — required_credential_types against create_credential_type_schema, applicable_scope_kinds against create_scope_kind_schema, grant_paths against create_grant_path_schema. Pass the registry results to create_role_schema and every entry is checked at construction time.

Empty arrays carry meaning:

  • required_credential_types: [] — any authenticated credential type may exercise the role (the default for app-defined roles).
  • applicable_scope_kinds: [] — the role applies at the global scope only (no scope_kind / scope_id set on its role_grants). This is the default for app-defined roles; consumers add scope kinds explicitly.
  • grant_paths: [] — the role has no grant path declared in this registry; it is unreachable through admin / self-service / system flows. Only useful for diagnostic snapshotting.

Builtins (keeper, admin) ship preconfigured in builtin_role_specs_by_name.

name

Unique role name. Must match RoleName — its regex and length cap; collisions with builtins throw.

type string

description?

Admin-UI-facing copy describing the role's intent.

type string

required_credential_types?

Credential types whose holders are meant to exercise this role. Informative only: each entry is checked at construction time against the credential_types registry passed to create_role_schema, but no middleware or dispatcher reads it per request — a route or action gates credentials with its own auth.credential_types (the keeper verbs declare ['daemon_token'] there). Empty array = any authenticated credential type.

type readonly string[]

applicable_scope_kinds?

Scope kinds at which this role's role_grants may be granted. Each entry is checked at construction time against the scope_kinds registry passed to create_role_schema. Empty array = global only. v1 keeps this informative-only (no INSERT-time enforcement).

type readonly string[]

grant_paths?

Grant paths through which this role can be granted. Each entry is checked at construction time against the grant_paths registry passed to create_role_schema. Drives downstream defaults:

  • admin_actions.grantable_roles ⊇ {role : 'admin' ∈ grant_paths}
  • self_service_role_actions default eligibility ⊇ {role : 'self_service' ∈ grant_paths}

Empty array = role is not granted via any registered path (only exists for diagnostic / future use).

type readonly string[]

round_trip_uses_fresh_account
#

testing/integration_helpers.ts view source

(principal: AuthPrincipal, side_effects: boolean): boolean import {round_trip_uses_fresh_account} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Whether a round-trip sends a request as a fresh account rather than the shared authed one: a side-effecting request picked for the 'authed' principal. Generated input omits optional fields, and an omitted target on a self-service action is the caller — account_delete deletes it, account_session_revoke_all, a logout or a password change end its session — so any side-effecting call may destroy the credential it was sent with, and on a shared account every later call would refuse as unauthenticated. Keyed on the request's own side-effect signal (an action's side_effects, a route's non-GET method) rather than a list of known destroyers, so a consumer's self-targeting action is covered without registering it. The other principals stay shared: the keeper and its daemon token are one per fixture, and an admin-gated request with generated nil ids targets no existing account. A credential they lose still fails the round-trip on its 401 check (is_unauthenticated_refusal).

principal

the principal pick_auth_principal chose

side_effects

whether the request can change state

type boolean

returns

boolean

RoundTripTestOptions
#

testing/round_trip.ts view source

RoundTripTestOptions import type {RoundTripTestOptions} from '@fuzdev/fuz_app/testing/round_trip.js';

setup_test

Per-test fixture-producing function. describe_round_trip_validation invokes this once in beforeAll (per-describe cadence — see module docstring) to share a single bootstrapped keeper + accounts across every route case.

type (): Promise<TestFixture>

returns Promise<TestFixture>

surface_source

App surface (with route specs) for route iteration. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

skip_routes?

Routes to skip, in 'METHOD /path' format.

type string[]

input_overrides?

Override generated bodies for specific routes ('METHOD /path' → body).

type Map<string, Record<string, unknown>>

success_fixtures?

Success-case fixtures for routes whose populated success body the generic nil-id input can't reach — referential REST routes whose path params / body must point at existing rows. Maps 'METHOD /path' to an async factory that receives the per-test fixture (so it can seed the referenced state) and returns {url?, body?}: an explicit resolved url (when the factory built it from the ids it just seeded) and/or a request body. Omit url to fall back to the generated valid path.

Distinct from input_overrides (body-only, accepts a valid error envelope): a success_fixtures entry asserts a 2xx response and validates it against the route's output schema — the success-shape parity check the nil-id round-trip can't perform.

type Map<string, (fixture: TestFixture) => Promise<{ url?: string | undefined; body?: Record<string, unknown> | undefined; }>>

RouteAuth
#

http/auth_shape.ts view source

value + type

{ account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; } import {RouteAuth} from '@fuzdev/fuz_app/http/auth_shape.js';

The canonical auth shape used by both ActionSpec.auth and RouteSpec.auth.

Cross-axis registry invariants enforced via .superRefine:

  1. Roles imply actor. roles?.length ⟹ actor === 'required'. Role checks read the actor's role_grants, so a role-gated spec without a resolved actor would have nothing to check.
  2. No accountless actors yet. account === 'none' && actor !== 'none' is invalid in v1. The credential resolver always binds account before actor today; agent-token / group-actor credentials will lift this.
  3. Unrestricted is leaf. account === 'none' && actor === 'none' ⟹ no roles, no credential_types (nothing left to gate). The same rule covers required_scope, but enforcing it needs the capability vocabulary from auth/, so it lives with the other required_scope check in fuz_auth_guard_resolver — which is also the only site every route spec traverses, since the route pipeline never parses this schema.

Invariant 2 — the `actor !== 'none' ⟺ input or query declares acting?: ActingActor` biconditional — needs introspection of the spec's input/query schemas, so it is checked at registration time, not on this schema. See assert_route_auth_acting_biconditional below.

account

type "optional" | "none" | "required"

actor

type "optional" | "none" | "required"

roles?

type readonly string[]

credential_types?

type readonly string[]

required_scope?

The capability a narrowed api token's scope must admit to reach this route, in the <section>:<id> vocabulary a denial reports back as required_scope:

  • surface:<name> — a non-RPC surface, refused to every narrowed token whatever its method list says (token scoping's rule that a narrowed token is RPC-only). surface:audit_stream, surface:fact_bare, surface:account_verify, and surface:app_surface are the spine's route-spec ones; a consumer names its own without registering it.

The rpc:<method> arm of that vocabulary is the dispatcher's per-method gate, which a route never reaches, so a route may not declare it.

Declaring it makes the guard resolver mount the refusal ahead of the role gate, instead of the route hand-rolling the check in its handler (which the auth phase has already role-gated by then, so a narrowed token whose account lacks the role would be told about the role rather than about the scope).

Opt-in rather than blanket: no narrowed token reaches login (which reads no credential) or logout and the password change (session-only), the RPC endpoint runs its own per-method check inside the dispatcher, the reads_credential routes that answer anonymous callers (account status, the cell-scoped fact read) can't take the field and answer a narrowed token as anonymous in their handler (get_public_account_id), and the server-status route admits a narrowed token by design — and all are route specs too.

Typed as a plain string here to keep http/ from importing auth/; the resolver checks it at registration and throws on anything but a well-formed surface:<name>.

type string

RouteContext
#

http/route_spec.ts view source

RouteContext import type {RouteContext} from '@fuzdev/fuz_app/http/route_spec.js';

Per-request deps provided by the framework to route handlers.

Audit writes run through AppDeps.audit.emit(ctx, input) (see auth/audit_emitter.ts). It writes a success row in the route's transaction when one is open and a failure row on the pool it closes over, so a refused attempt's row survives the rollback.

Routes whose body manages its own transaction (signup, bootstrap) declare transaction: false on the spec, which makes route.db the pool — they reach for it directly.

db

Transaction-scoped when RouteSpec.transaction is true (the default for non-GET); pool-level otherwise.

type Db

pending_effects

Eager fire-and-forget queue — push the in-flight Promise<void> for pool writes already running (audit emits, api-token usage tracking). The flush middleware drains via flush_pending_effects after the handler returns.

type Promise<void>[]

post_commit_effects

Deferred post-commit thunks — do not push directly; reach for emit_after_commit(ctx, fn) from http/pending_effects.ts. The flush middleware invokes each thunk after the handler (and any wrapping db.transaction) returns, closing the microtask-ordering window that an eager Promise.resolve().then(fn) leaves open inside the transaction.

type (() => void | Promise<void>)[]

RouteErrorSchemas
#

http/error_schemas.ts view source

Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

type Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

import type {RouteErrorSchemas} from '@fuzdev/fuz_app/http/error_schemas.js';

Error schema map — maps HTTP status codes to Zod schemas.

Used on RouteSpec.errors and internally by derive_error_schemas.

[key: number]

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | undefined

RouteFactoryDeps
#

auth/deps.ts view source

RouteFactoryDeps import type {RouteFactoryDeps} from '@fuzdev/fuz_app/auth/deps.js';

Capabilities for route spec factories.

AppDeps without db — route handlers receive database connections via RouteContext, so factories don't capture a pool-level Db.

password

Password hashing operations. Use argon2_password_deps in production.

type PasswordHashDeps

read_secure_file

Hardened secret-file read — used for the bootstrap token (the file that mints the keeper account). Production wiring passes the runtime's read_secure_file (FsSecureReadDeps), which rejects symlinks, non-regular files, group/other-accessible modes, and oversized files; both the boot-time availability probe and the request-time read go through this one capability so the probe can never be laxer than the read it gates.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

delete_file

Delete a file.

type (path: string): Promise<void>

path

type string
returns Promise<void>

keyring

HMAC-SHA256 cookie signing keyring.

type Keyring

log

Structured logger instance.

type Logger

audit

Bound audit emitter. Closes over the pool, its registered listeners, and the optional AuditLogConfig. Built once at backend assembly via create_audit_emitter. emit writes a success row in the request transaction when one is open and every other row on the captured pool (auth/audit_emitter.ts, "Where a row is written"). There is no pool slot on the handler context.

type AuditEmitter

connection_closer

Closes live connections — WebSocket and SSE — when a credential is revoked. Every revocation handler queues its close on this (queue_connection_close), so it must reach every registry of the backend: create_app_backend creates it empty, and create_app_server adds each WebSocket registry it mounts and its audit stream registry. A registry mounted by hand is added through the connection_closer option of register_action_ws / create_audit_log_sse.

type RealtimeCloser

fact_store?

Optional content-addressed byte store. Present only on backends that serve binary content (facts) — minimal consumers leave it unset. The consumer constructs a PgFactStore (db/fact_store.ts) over its facts directory (disk_root + fs) at its own backend assembly and assigns it here; create_app_backend stays facts-agnostic. Typed HeldFactStore, not the @fuzdev/fuz_util FactStore: its puts name a holder (HeldFactPutOptions.holder), the possession record cell ref admission reads.

type HeldFactStore

RouteHandler
#

http/route_spec.ts view source

RouteHandler import type {RouteHandler} from '@fuzdev/fuz_app/http/route_spec.js';

Route handler function — receives the Hono context and a RouteContext with per-request deps (db, pending_effects).

TypeScript allows fewer params, so handlers that don't need route can use (c) => ... without changes.

(call)

type (c: Context<any, any, {}>, route: RouteContext): Response | Promise<Response>

c

type Context<any, any, {}>

route

returns Response | Promise<Response>

RouteMethod
#

http/route_spec.ts view source

RouteMethod

type "GET" | "POST" | "PUT" | "DELETE" | "PATCH"

import type {RouteMethod} from '@fuzdev/fuz_app/http/route_spec.js';

HTTP methods supported by route specs.

RouteSpec
#

http/route_spec.ts view source

RouteSpec import type {RouteSpec} from '@fuzdev/fuz_app/http/route_spec.js';

A single route definition — the unit of the surface map.

input and output schemas align with SAES ActionSpec naming. Use z.null() for routes with no request body (GET, DELETE without body).

method

type RouteMethod

path

type string

auth

type RouteAuth

handler

type (c: Context<any, any, {}>, route: RouteContext): Response | Promise<Response>

c

type Context<any, any, {}>

route

returns Response | Promise<Response>

description

type string

params?

URL path parameter schema. Use z.strictObject() with string fields matching :param segments.

REST-only — actions dispatch through a single JSON-RPC endpoint and encode everything in input, so params doesn't appear on ActionSpec.

type ZodObject<$ZodLooseShape, $strip>

query?

URL query parameter schema. Use z.strictObject() with string fields.

type ZodObject<$ZodLooseShape, $strip>

input

Request body schema. Use z.null() for routes with no body.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

output

Success response body schema.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

raw_body?

Marks a route whose request and/or response carries raw bytes or a streaming protocol rather than JSON — git smart-HTTP, file-store binary uploads/downloads, raw internal callbacks. Disambiguates the overloaded input: z.null(), which otherwise can't distinguish "no body" (GET /health) from "raw bytes" (a binary upload).

A raw_body POST takes any Content-Type a browser can't send cross-origin without a preflight; every other POST route requires application/json, bodyless or not (415 unsupported_content_type otherwise — see http/simple_request.ts). The schema-driven round-trip test suite auto-skips raw_body routes (it can neither synthesize a meaningful body nor assert a JSON output shape), so consumers don't hand-maintain a skip_routes entry per binary route. Also surfaces in AppSurfaceRoute so generated docs render "raw" instead of a misleading null body.

type boolean

mints_credential?

Marks a route that issues a credential — a session cookie — while declaring no auth itself: login, signup, bootstrap. A forged cross-site request to one is login CSRF, so it must take the origin check, and assembly refuses it outside the auth middleware's scope, where no origin carve-out can reach (assert_credential_routes_in_auth_scope). A route that reads a credential (auth.account not 'none') is held there without the mark.

type boolean

reads_credential?

Marks a route that reads the resolved credential in its handler while declaring no auth gate — it answers an anonymous caller itself instead of taking the dispatcher's 401 (account status, the cell-scoped fact read, the JSON-RPC endpoint whose dispatcher gates each action). Outside the auth middleware's scope no session or bearer parsing runs and it would see every caller as anonymous, so assembly refuses it there (assert_credential_routes_in_auth_scope), as it does a route whose auth.account is not 'none'.

The handler owns the token-scope rule a gate would otherwise apply: a scope-narrowed API token reaches no non-RPC surface that reads account data, and auth.required_scope is refused on a route that admits anonymous callers. Read the credential through get_public_account_id (auth/request_context.ts), not get_resolved_auth, so a narrowed token is answered as the anonymous caller. The RPC endpoint is the exception — its dispatcher gates each method against the scope.

type boolean

rate_limit?

Rate limit key type — declares what this route's rate limiter is keyed on.

When set, 429 (RateLimitError) is auto-derived in derive_error_schemas. The actual RateLimiter instance is still wired imperatively in the handler — this field is metadata for surface introspection and policy invariants.

type RateLimitKey

errors?

Handler-specific error response schemas keyed by HTTP status code.

Middleware errors (auth 401/403, validation 400, rate limit 429) are auto-derived from auth, input, and rate_limit. Declare handler-specific errors here (e.g., 404 for not-found, 409 for conflicts).

Explicit entries override auto-derived ones for the same status code.

type Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

transaction?

Whether to wrap the handler in a database transaction.

When omitted, defaults are derived from the HTTP method:

  • GET → false (read-only, no transaction)
  • All others (POST, PUT, DELETE, PATCH) → true

Set explicitly to override the default (e.g., false for a POST that manages its own transaction like signup).

type boolean

rpc_action
#

actions/action_types.ts view source

<TSpec extends RequestResponseActionSpec>(spec: TSpec, handler: HandlerForSpec<TSpec>): RpcAction import {rpc_action} from '@fuzdev/fuz_app/actions/action_types.js';

Pair a spec with a handler while preserving per-method input/output types and selecting the narrowest ctx.auth shape the spec literal admits.

Constructing {spec, handler} literals widens handler to ActionHandler<any, any>, so spec/handler drift (renamed Zod schema, output field removal, input shape change) slips past the typechecker. rpc_action(spec, handler) binds the handler signature to (input: z.infer<spec.input>, ctx) => z.infer<spec.output> via the generic spec parameter — drift surfaces at the call site.

The ctx.auth narrowing follows the spec's auth.account / auth.actor literals (see HandlerForSpec): an actor-implying spec gets ctx.auth: RequestActorContext; an account-grain spec gets ctx.auth: RequestContext; everything else stays `ctx.auth: RequestContext | null`. Handlers can rely on the dispatcher's runtime guarantee without a manual narrowing call.

Fits fuz_app's factory-closure pattern (handlers close over grantable_roles, app_settings ref, notification_sender, etc.). zzz uses a different shape — a codegen-keyed Record<Method, Handler> map typed via generated ActionInputs/ActionOutputs — which works when handlers are pure (no closure state) and specs are codegen-enumerated. fuz_app's admin + role-grant-offer actions have neither, so per-pair typing at the registration site is the right fit.

Spec-literal preservation is load-bearing: declare specs with satisfies RequestResponseActionSpec (canonical) so auth.actor keeps its 'required' / 'none' literal type. A spec typed directly as RequestResponseActionSpec widens the axes to AuthAxisState and the handler defaults to the loosest tier — sound, but loses the ergonomic narrowing.

spec

type TSpec

handler

type HandlerForSpec<TSpec>

returns

RpcAction

generics

rpc_action<TSpec extends RequestResponseActionSpec>
TSpec

examples

// actor-implying spec → ctx.auth: RequestActorContext rpc_action(role_grant_revoke_action_spec, async (input, ctx) => { const revoker_id = ctx.auth.actor.id; // no narrowing needed }); // account-grain spec → ctx.auth: RequestContext (actor: null) rpc_action(account_verify_action_spec, (_input, ctx) => { return to_session_account(ctx.auth.account); // no narrowing needed });

RPC_AUTH_ANONYMOUS_EQUIVALENCE_GROUP
#

testing/cross_backend/conformance_rpc_auth.ts view source

"generated_rpc_auth_anonymous_401" import {RPC_AUTH_ANONYMOUS_EQUIVALENCE_GROUP} from '@fuzdev/fuz_app/testing/cross_backend/conformance_rpc_auth.js';

The equivalence group every generated anonymous 401 row joins — the unauthenticated refusal must be byte-identical whatever the method.

rpc_call
#

testing/rpc_helpers.ts view source

(args: RpcCallArgs): Promise<RpcCallResult> import {rpc_call} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

One-shot JSON-RPC call over a Hono app.

Merges sensible defaults (host, origin, Content-Type) under caller-provided headers, fires POST (default) or GET, parses the envelope, and returns a discriminated result.

args

returns

Promise<RpcCallResult>

throws

  • Error - if the response body is neither a valid `JsonrpcResponse` nor `JsonrpcErrorResponse` envelope — protocol-level failures the caller should never tolerate. All JSON-RPC errors come back via `{ok: false, error}` so assertions can focus on `error.code` / `error.data.reason`.

rpc_call_for_spec
#

testing/rpc_helpers.ts view source

<TSpec extends RequestResponseActionSpec>(args: RpcCallForSpecArgs<TSpec>): Promise<RpcCallResultForSpec<TSpec>> import {rpc_call_for_spec} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Typed wrapper over rpc_call — binds params to z.infer<spec.input> and the success result to z.infer<spec.output> via the generic.

Success results are validated at runtime against spec.output (same contract as rpc_call_typed); a mismatch throws. Error responses come back on the discriminated {ok: false, error} branch — use this for happy-path + denial-path assertions where the error data.reason shape is still asserted manually. For adversarial input tests that send malformed params, use the untyped rpc_call.

args

returns

Promise<RpcCallResultForSpec>

generics

rpc_call_for_spec<TSpec extends RequestResponseActionSpec>
TSpec

throws

  • Error - if the success `result` does not parse against `spec.output`, or if `rpc_call` itself throws on an envelope violation.

rpc_call_non_browser
#

testing/rpc_helpers.ts view source

(args: Omit<RpcCallArgs, "suppress_default_origin">): Promise<RpcCallResult> import {rpc_call_non_browser} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Same as rpc_call but without the default origin header. Use for bearer-auth probes: bearer_auth discards the token when Origin or Referer is present (browser context), so a bearer probe via rpc_call would short-circuit to 401 before the token is ever validated.

Equivalent to rpc_call({...args, suppress_default_origin: true}).

args

type Omit<RpcCallArgs, "suppress_default_origin">

returns

Promise<RpcCallResult>

rpc_call_raw
#

testing/rpc_helpers.ts view source

(args: RpcCallArgs): Promise<RpcCallRawResult> import {rpc_call_raw} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Send one JSON-RPC request the way rpc_call does — same defaults, verbs and header merging — and return the response unparsed.

For callers that compare whole bodies (the cross-backend differential), where rpc_call's envelope parse would hide anything outside result / error.

args

returns

Promise<RpcCallRawResult>

throws

  • Error - if the response body is not JSON

rpc_call_typed
#

testing/rpc_helpers.ts view source

<T>(args: RpcCallArgs, output_schema: ZodType<T, unknown, $ZodTypeInternals<T, unknown>>): Promise<T> import {rpc_call_typed} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Same as rpc_call but parses the success result through the given output schema and returns typed data. Envelope-level failures or error responses throw — use the untyped rpc_call for tests that need to assert on specific error shapes.

args

output_schema

type ZodType<T, unknown, $ZodTypeInternals<T, unknown>>

returns

Promise<T>

generics

rpc_call_typed<T>
T

throws

  • Error - if the response is a JSON-RPC error, if `rpc_call` throws on an envelope violation, or if the result fails `output_schema.safeParse`.

RPC_ENVELOPE_PROBE_ID
#

testing/rpc_envelope_cases.ts view source

"envelope_probe" import {RPC_ENVELOPE_PROBE_ID} from '@fuzdev/fuz_app/testing/rpc_envelope_cases.js';

The request id every envelope case sends where it sends one.

RPC_GET_CACHE_CONTROL
#

actions/action_rpc.ts view source

"no-store" import {RPC_GET_CACHE_CONTROL} from '@fuzdev/fuz_app/actions/action_rpc.js';

The Cache-Control every response of the RPC endpoint's GET route carries — a read is per caller and per moment, so nothing may store it.

rpc_get_side_effects_reason
#

testing/rpc_envelope_cases.ts view source

(method: string): string import {rpc_get_side_effects_reason} from '@fuzdev/fuz_app/testing/rpc_envelope_cases.js';

The data.reason the dispatchers answer a GET of a side_effects method with.

method

type string

returns

string

RPC_NON_OBJECT_PARAMS
#

testing/rpc_envelope_cases.ts view source

readonly string[] import {RPC_NON_OBJECT_PARAMS} from '@fuzdev/fuz_app/testing/rpc_envelope_cases.js';

The JSON values that are neither an object nor null, one per JSON type — what a GET ?params= can carry that no input schema admits.

RpcAction
#

actions/action_types.ts view source

RpcAction import type {RpcAction} from '@fuzdev/fuz_app/actions/action_types.js';

An RPC action — combines an action spec with its handler.

The spec defines the contract (method, auth, schemas, side effects). The handler implements the behavior.

spec

type RequestResponseActionSpec

handler

type (input: any, ctx: ActionContext): any

input

type any

ctx

returns any

RpcAdversarialCrossTestOptions
#

testing/cross_backend/rpc_adversarial.ts view source

RpcAdversarialCrossTestOptions import type {RpcAdversarialCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/rpc_adversarial.js';

inheritance

surface_source

The declared surface; the endpoint at rpc_path supplies the methods and their input schemas.

type AppSurfaceSpec

readonly

live_rpc_endpoints?

The endpoints the backend live-mounts (resolved — resolve_rpc_endpoints_for_setup turns the factory form into this). The methods the one at rpc_path carries beyond the declared surface run through both groups too. Omitted, only declared methods run.

type readonly RpcEndpointSpec[]

readonly

skip_methods?

Methods to leave out, beyond the protocol actions always skipped.

type readonly string[]

readonly

input_overrides?

Valid params per method (method name → params) to build its params cases from, in place of the generated base — for an input whose valid shape the generator can't reach. Each must pass the method's input schema; one that doesn't throws at collection, naming the method. The cases are sent as a single-actor principal, so an override omits acting. describe_spine_cross_suites passes its rpc_input_overrides here.

type ReadonlyMap<string, Record<string, unknown>>

readonly

xfail_params?

Generated params cases a backend is known to answer otherwise, each run as its own xfail_until test; the method's other cases keep their test. Pass it only for the backend that diverges.

type readonly RpcParamsXfail[]

readonly

RpcAttackSurfaceOptions
#

testing/rpc_attack_surface.ts view source

RpcAttackSurfaceOptions import type {RpcAttackSurfaceOptions} from '@fuzdev/fuz_app/testing/rpc_attack_surface.js';

build

Build the app surface bundle (surface + route specs + middleware specs + rpc_endpoints).

type (): AppSurfaceSpec

roles

All roles in the app (e.g. ['admin', 'keeper']).

type string[]

input_overrides?

Valid params per method (method name → params) to build its adversarial params cases from, in place of the generated base — for an input whose valid shape the generator can't reach. Each must pass the method's input schema; one that doesn't throws at collection, naming the method. The cases are sent as a single-actor principal, so an override omits acting. The cross-process twin is RpcAdversarialCrossTestOptions.input_overrides.

type ReadonlyMap<string, Record<string, unknown>>

RpcAuthConformanceOptions
#

testing/cross_backend/conformance_rpc_auth.ts view source

RpcAuthConformanceOptions import type {RpcAuthConformanceOptions} from '@fuzdev/fuz_app/testing/cross_backend/conformance_rpc_auth.js';

surface_source

The declared surface; its rpc_endpoints supply each method's auth and input schema.

type AppSurfaceSpec

readonly

live_rpc_endpoints?

The endpoints the backend live-mounts, for rows on the methods the declared surface lacks (resolved — resolve_rpc_endpoints_for_setup turns the factory form into this). Omitted, only declared methods get rows.

type readonly RpcEndpointSpec[]

readonly

skip_methods?

Methods to leave out, beyond the protocol actions always skipped.

type readonly string[]

readonly

RpcCallApp
#

testing/rpc_helpers.ts view source

RpcCallApp

type { request: (input: string, init: RequestInit) => Response | Promise<Response>; } | (RpcTestTransport)

import type {RpcCallApp} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

App shape accepted by rpc_call. Either a Hono-like object with a .request(input, init) method (in-process TestApp.app directly) or a bare RpcTestTransport callable (cross-process fixture.transport).

RpcCallArgs
#

testing/rpc_helpers.ts view source

RpcCallArgs import type {RpcCallArgs} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Arguments for rpc_call.

app

Hono-like app or bare RpcTestTransport callable.

type RpcCallApp

path

RPC endpoint path, e.g. '/api/rpc'.

type string

method

JSON-RPC method name.

type string

params?

Params for the call. Omit (or pass undefined) for parameterless (z.void()) methods — the helper drops params from the envelope either way. See create_rpc_post_init for the null-stripping affordance and JSON-RPC 2.0 §4.2's prohibition on params: null.

type unknown

params_json?

Params as raw JSON text, sent verbatim in place of params — for a spelling JSON.stringify loses (5.0, -0). Exclusive with params.

type string

headers?

Extra request headers (session cookie, bearer, etc.). Overrides defaults.

type Record<string, string>

id?

Request id. Defaults to 'test'.

type string | number

verb?

HTTP verb — 'POST' (default) or 'GET' for side_effects: false methods.

type "POST" | "GET"

suppress_default_origin?

Suppress the default origin header. Required for bearer-auth paths: bearer_auth discards the token when Origin or Referer is present (browser context), so probing it via rpc_call needs this flag — or use rpc_call_non_browser, which sets it for you.

type boolean

RpcCallForSpecArgs
#

testing/rpc_helpers.ts view source

RpcCallForSpecArgs<TSpec>

type Omit<RpcCallArgs, "method" | "params"> & { spec: TSpec; params: output<TSpec["input"]>; }

import type {RpcCallForSpecArgs} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Arguments for rpc_call_for_spec. spec replaces the loose method field.

generics

RpcCallForSpecArgs<TSpec extends RequestResponseActionSpec>
TSpec

id?

Request id. Defaults to 'test'.

type string | number

path

RPC endpoint path, e.g. '/api/rpc'.

type string

params_json?

Params as raw JSON text, sent verbatim in place of params — for a spelling JSON.stringify loses (5.0, -0). Exclusive with params.

type string

verb?

HTTP verb — 'POST' (default) or 'GET' for side_effects: false methods.

type "POST" | "GET"

headers?

Extra request headers (session cookie, bearer, etc.). Overrides defaults.

type Record<string, string>

app

Hono-like app or bare RpcTestTransport callable.

type RpcCallApp

suppress_default_origin?

Suppress the default origin header. Required for bearer-auth paths: bearer_auth discards the token when Origin or Referer is present (browser context), so probing it via rpc_call needs this flag — or use rpc_call_non_browser, which sets it for you.

type boolean

spec

Action spec whose method drives the envelope and whose input/output types pin params + result.

type TSpec

params

Params, typed against spec.input.

type output<TSpec["input"]>

RpcCallRawResult
#

testing/rpc_helpers.ts view source

RpcCallRawResult import type {RpcCallRawResult} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

The unparsed response rpc_call_raw returns: the HTTP status, the response-headers snapshot (lowercased keys), and the JSON body exactly as the server sent it — the whole envelope, jsonrpc and id included.

status

type number

headers

type Record<string, string>

body

type unknown

RpcCallResult
#

testing/rpc_helpers.ts view source

RpcCallResult

type { ok: true; status: number; headers: Record<string, string>; result: unknown; } | { ok: false; status: number; headers: Record<string, string>; error: { code: number; message: string; data?: unknown; }; }

import type {RpcCallResult} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Discriminated return from rpc_call. status is the HTTP status; headers is the response-headers snapshot (lowercased keys) so callers can assert header-level properties (e.g. no backend-fingerprinting headers).

RpcCallResultForSpec
#

testing/rpc_helpers.ts view source

RpcCallResultForSpec<TSpec>

type { ok: true; status: number; headers: Record<string, string>; result: output<TSpec["output"]>; } | { ok: false; status: number; headers: Record<string, string>; error: { code: number; message: string; data?: unknown; }; }

import type {RpcCallResultForSpec} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Typed discriminated result returned by rpc_call_for_spec. The success branch's result is inferred from TSpec['output']. The error branch stays untyped because JSON-RPC error.data shapes vary per error and are asserted per call site.

generics

RpcCallResultForSpec<TSpec extends RequestResponseActionSpec>
TSpec

RpcClientCallOptions
#

actions/rpc_client.ts view source

RpcClientCallOptions import type {RpcClientCallOptions} from '@fuzdev/fuz_app/actions/rpc_client.js';

Per-call options accepted by every typed Proxy method. Same shape as ActionDispatcherSendOptions — the client threads these through unchanged to the underlying peer. transport_name overrides the per-method transport_for_method selector for this call.

inheritance

RpcEndpointSpec
#

http/surface.ts view source

RpcEndpointSpec import type {RpcEndpointSpec} from '@fuzdev/fuz_app/http/surface.js';

An RPC endpoint definition for surface generation.

path

type string

actions

type RpcAction[]

RpcEndpointsSuiteOption
#

testing/rpc_helpers.ts view source

RpcEndpointsSuiteOption

type RpcEndpointSpec[] | ((ctx: AppServerContext) => RpcEndpointSpec[])

import type {RpcEndpointsSuiteOption} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Union accepted by the suite-level rpc_endpoints option — eager array or a factory that takes an AppServerContext and returns endpoint specs. The factory form is required when action handlers must close over the per-test ctx.deps (e.g. the canonical create_standard_rpc_actions(ctx.deps) pattern). create_app_server resolves either shape natively; test helpers forward the raw value to the top-level rpc_endpoints slot on CreateTestAppOptions for live dispatch.

RpcEnvelopeCase
#

testing/rpc_envelope_cases.ts view source

RpcEnvelopeCase import type {RpcEnvelopeCase} from '@fuzdev/fuz_app/testing/rpc_envelope_cases.js';

One malformed-envelope request and the refusal it owes.

Data, so the in-process group here and the cross-process describe_rpc_adversarial_cross_tests run the same set.

name

type string

readonly

as

Who sends it: the principal method's auth admits, so the envelope check is what refuses, or no credential at all — the ordering cases, where an answer of 401 would mean auth ran before the envelope check.

type "authorized" | "anonymous"

readonly

method

The declared method whose auth picks the principal ('authorized').

type AppSurfaceRpcMethod

readonly

request

A raw POST body, or a GET query string starting with ?.

type { readonly verb: "POST"; readonly body: string; } | { readonly verb: "GET"; readonly query: string; }

readonly

expect

type { readonly status: number; readonly code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); readonly id: string | number | null; readonly reason: string | null; }

readonly

RpcEnvelopeCaseOptions
#

RpcMethodCoverageInput
#

testing/cross_backend/method_coverage.ts view source

RpcMethodCoverageInput import type {RpcMethodCoverageInput} from '@fuzdev/fuz_app/testing/cross_backend/method_coverage.js';

live_methods

Every method the live RPC endpoint mounts (action.spec.method).

type readonly string[]

readonly

declared_methods

The declared-surface method names (from create_*_surface_spec).

type readonly string[]

readonly

manifest

The tagged manifest the live set must reconcile against.

type readonly MethodCoverageEntry[]

readonly

testing_method_prefix?

Backdoor method prefix. Defaults to '_testing_'.

type string

readonly

RpcParamsXfail
#

testing/cross_backend/rpc_adversarial.ts view source

RpcParamsXfail import type {RpcParamsXfail} from '@fuzdev/fuz_app/testing/cross_backend/rpc_adversarial.js';

Generated params cases of one method marked xfail_until, by case label.

method

type string

readonly

labels

InputTestCase.labels of generate_rpc_params_test_cases(method's input).

type readonly string[]

readonly

tracking_id

type string

readonly

reason

type string

readonly

RpcPathCapabilityGatedCrossSuiteOptions
#

testing/cross_backend/setup.ts view source

RpcPathCapabilityGatedCrossSuiteOptions import type {RpcPathCapabilityGatedCrossSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

The RPC-dispatched shape for a capability-gating suite — the intersection of RpcPathCrossSuiteOptions and CapabilityGatedCrossSuiteOptions, and the most common option shape among the imperative cross suites (every cell_* suite, query_shape, token_scope_surface, fact_serving, account_lifecycle, testing_backdoor).

inheritance

RpcPathCrossSuiteOptions
#

testing/cross_backend/setup.ts view source

RpcPathCrossSuiteOptions import type {RpcPathCrossSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

CrossSuiteOptions plus the RPC endpoint path the suite dispatches against — the shape every JSON-RPC-driven imperative suite takes (cell verbs, origin, body-size, actor lookup, account lifecycle, app settings). Suites alias this under a self-documenting per-suite name (e.g. OriginCrossTestOptions = RpcPathCrossSuiteOptions).

inheritance

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

RpcResult
#

testing/cross_backend/cross_rpc.ts view source

RpcResult import type {RpcResult} from '@fuzdev/fuz_app/testing/cross_backend/cross_rpc.js';

Minimal JSON-RPC envelope shape the suites read off responses.

status

The HTTP status the reply carried.

type number

readonly

ok

type boolean

readonly

result?

type unknown

readonly

error?

type { readonly code: number; readonly message: string; readonly data?: unknown; }

readonly

RpcRoundTripAuth
#

testing/rpc_round_trip.ts view source

RpcRoundTripAuth import type {RpcRoundTripAuth} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

A picked principal and the headers that carry its credential.

principal

type AuthPrincipal

headers

type Record<string, string>

RpcRoundTripTestOptions
#

testing/rpc_round_trip.ts view source

RpcRoundTripTestOptions import type {RpcRoundTripTestOptions} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

setup_test

Per-test fixture-producing function (per-describe cadence).

type (): Promise<TestFixture>

returns Promise<TestFixture>

surface_source

App surface (with route + RPC endpoint specs) for RPC endpoint enumeration. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

session_options

Session config — only needed to resolve factory-form rpc_endpoints against a stub AppServerContext at setup time (the actions' input schemas drive params generation; auth/dispatch run against the real backend through fixture.transport).

type SessionOptions<string>

rpc_endpoints

RPC endpoint specs — eager array or factory. The factory must return the same endpoint path + spec.method list regardless of ctx (invoked once at setup with a stub ctx; the real per-test live dispatch goes through whatever the backend was started with).

type RpcEndpointsSuiteOption

skip_methods?

Methods to skip, by name (e.g., 'zap_plan').

type string[]

input_overrides?

Override generated params for specific methods (method name → params).

type Map<string, Record<string, unknown>>

success_fixtures?

Success-case fixtures for methods whose populated success body the generic nil-id input can't reach — referential reads (*_get, *_log) whose required ids must point at existing rows. Maps method name to an async factory that receives the per-test fixture (so it can seed the referenced state — e.g. create a repo via fixture.transport + fixture.create_session_headers()) and returns the params that drive a success response.

Distinct from input_overrides, which only swaps the request params; the response may still be a valid *error* envelope (missing-row not_found), which the generic loop accepts. A success_fixtures entry asserts the response is ok and validates result against the method's output schema — so a backend that drops a field, or errors where the other backend succeeds, fails loud. This is the success-shape parity check the nil-id round-trip structurally cannot perform (it only ever sees error envelopes for referential methods).

Fired as POST. The factory runs against the shared per-describe fixture, so it must not assume a clean slate between entries (seed unique state). By default the request carries the identity the generic loop picks for the method's auth (authed / admin / keeper); a case that returns headers sends those instead — for methods whose row must belong to the caller (revoking one's own token or session, retracting one's own offer). create_spine_rpc_success_fixtures (cross_backend/rpc_success_fixtures.ts) builds the map for the standard spine methods.

type RpcSuccessFixtures

RpcSuccessFixture
#

testing/rpc_round_trip.ts view source

RpcSuccessFixture import type {RpcSuccessFixture} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

One success-fixture factory: builds the row against the per-test fixture, then returns the request.

(call)

type (fixture: TestFixture): Promise<RpcSuccessFixtureCase>

fixture

returns Promise<RpcSuccessFixtureCase>

RpcSuccessFixtureCase
#

testing/rpc_round_trip.ts view source

RpcSuccessFixtureCase import type {RpcSuccessFixtureCase} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

One success-fixture request: the params, and optionally the caller's headers.

params

Params that drive a success response.

type Record<string, unknown>

headers?

Auth headers to send in place of the identity the suite picks from the method's auth — for a row that only its owner may act on.

type Record<string, string>

RpcSuccessFixtures
#

testing/rpc_round_trip.ts view source

RpcSuccessFixtures

type Map<string, RpcSuccessFixture>

import type {RpcSuccessFixtures} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

Success-case fixtures, method name → async factory. See RpcRoundTripTestOptions.success_fixtures.

RpcTestTransport
#

testing/rpc_helpers.ts view source

RpcTestTransport import type {RpcTestTransport} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Minimal transport surface — the duck type Hono.request already satisfies. Extracted so test setups that want an in-process / WS / mock path can plug a different dispatcher without changing call sites.

(call)

type (url: string, init: RequestInit): Promise<Response>

url

type string

init

type RequestInit
returns Promise<Response>

run_auth_cleanup
#

auth/cleanup.ts view source

(deps: AuthCleanupDeps): Promise<AuthCleanupResult> import {run_auth_cleanup} from '@fuzdev/fuz_app/auth/cleanup.js';

Run every auth cleanup sweep — expired sessions, then expired role_grant offers — and return both counts. Twin of the Rust run_auth_cleanup.

The session sweep (cleanup_expired_sessions) commits its delete and closes the swept sessions' connections before the offer sweep starts, so a failed offer sweep takes neither back; a session sweep whose commit failed has closed too, and the offer sweep does not run.

start_auth_cleanup is the scheduler over this; call it directly for a one-off pass or from a scheduler of your own. Errors are re-thrown so the caller can log or alert; use the per-sweep helpers (cleanup_expired_sessions, cleanup_expired_role_grant_offers) for error isolation per sweep.

deps

returns

Promise<AuthCleanupResult>

throws

  • Error - re-thrown from the first sweep that fails (no per-sweep isolation here)

mutates

  • auth_session table — deletes expired sessions
  • role_grant_offer rows — stamps `expire_audited_at` on expired offers
  • audit_log table — emits `role_grant_offer_expire` rows for expired offers

run_authorization_phase
#

auth/request_context.ts view source

(c: Context<any, any, {}>, deps: QueryDeps, auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }, acting: string | undefined): Promise<...> import {run_authorization_phase} from '@fuzdev/fuz_app/auth/request_context.js';

Run the authorization phase on a Hono request: resolve the acting actor from acting against the authenticated account, and set the resulting RequestContext on REQUEST_CONTEXT_KEY for the post-authorization guards (require_role, require_credential_types) and the handler.

The phase every Hono-mounted surface shares — the route-spec handler (create_fuz_authorization_handler) and the WebSocket upgrade (register_action_ws, which reads acting off the query string). The HTTP RPC and WS dispatchers consume apply_authorization_phase per action directly instead.

Skipped for a public auth shape, and under the test-preset escape hatch: harnesses that pre-populate REQUEST_CONTEXT_KEY set TEST_CONTEXT_PRESET_KEY to TEST_CONTEXT_PRESET so the phase trusts the supplied context instead of running DB-backed resolution. Production middleware never sets that flag.

c

the request context, after the pre-authorization guards

type Context<any, any, {}>

deps

query deps for actor resolution

auth

the route's auth shape

acting

the acting selector, already through parse_acting; undefined when absent

type string | undefined

returns

Promise<Response | void>

the refusal to answer (400 actor selection, 500 a vanished account), or nothing when the request may proceed

run_cross_impl_bench
#

run_migrations
#

db/migrate.ts view source

(db: Db, namespaces: MigrationNamespace[]): Promise<MigrationResult[]> import {run_migrations} from '@fuzdev/fuz_app/db/migrate.js';

Run pending migrations for each namespace.

For each namespace: acquires an advisory lock, reads applied rows ordered by sequence, length-checks (binary-older-than-db short-circuits), name- prefix-verifies, then runs the pending tail in a single chain transaction. Each migration's row is INSERTed with sequence = max(sequence) + 1 for the namespace.

Length check before name verify is load-bearing: a binary-older case with a rename in the overlap would otherwise fire name-divergence-at-N first and the operator would chase a phantom source-revert before discovering the binary is the real problem.

Atomicity: any failure rolls back every migration that ran in that invocation. Namespaces are independent: a later namespace's failure does not roll back an earlier namespace that already committed.

Concurrency: per-namespace advisory locks reduce contention in multi-instance deployments but are best-effort on pool drivers (see the module docstring's "Advisory locking" notes). Correctness on concurrent boots falls out of chain-tx atomicity + the (namespace, name) PK — the loser's INSERT triggers PK violation and rollback; subsequent boots see the committed state.

db

the database instance

type Db

namespaces

migration namespaces, processed in the order passed

returns

Promise<MigrationResult[]>

one result per namespace where work happened (already-up-to-date namespaces are omitted)

throws

  • MigrationError - with `kind` of `namespace-reserved` (before touching the database), `binary-older-than-db`, `name-divergence-at-N`, or `migration-failed`

mutates

  • schema_version — inserts one row per applied migration

run_testing_origin_check
#

testing/cross_backend/testing_origin_check.ts view source

(input: { patterns: string; origin: string | null; }): { ok: true; allowed: boolean; } | { ok: false; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | ... 12 more ... | "no_origins"; index?: number | undefined; }[]; } import {run_testing_origin_check} from '@fuzdev/fuz_app/testing/cross_backend/testing_origin_check.js';

Answer one _testing_origin_check input: parse patterns with parse_allowed_origins, then run the request-level check for one Origin carrying origin (null — no Origin — passes). The action handler and the conformance table's unit test both call this, so the table checks the same function the wire serves.

input

the allowlist value and the Origin value

returns

TestingOriginCheckOutput

the answer the Rust twin returns for the same input

RunCommandOptions
#

runtime/deps.ts view source

RunCommandOptions import type {RunCommandOptions} from '@fuzdev/fuz_app/runtime/deps.js';

Options for run_command.

cwd?

Working directory for the child process.

type string

signal?

AbortSignal to terminate the child process.

type AbortSignal

timeout_ms?

Kill the process and return timed_out: true after this many milliseconds.

type number

RunCrossImplBenchOptions
#

testing/cross_backend/bench/run_cross_impl_bench.ts view source

RunCrossImplBenchOptions import type {RunCrossImplBenchOptions} from '@fuzdev/fuz_app/testing/cross_backend/bench/run_cross_impl_bench.js';

handles

Already-bootstrapped backends to benchmark. Each one's keeper_transport is the pre-authed transport scenarios fire against — bootstrap once, then hammer; no per-iteration reset.

type readonly BootstrappedBackendHandle[]

readonly

scenarios

type readonly BenchScenario[]

readonly

config?

Overrides merged over the network-tuned defaults below.

type Partial<BenchmarkConfig>

readonly

RunOptions
#

ui/async_slot.svelte.ts view source

RunOptions import type {RunOptions} from '@fuzdev/fuz_app/ui/async_slot.svelte.js';

signal?

External signal chained into the slot's internal controller. Aborts the in-flight run when fired (alongside automatic supersession by the next run() and manual AsyncSlot.abort calls).

type AbortSignal

RuntimeDeps
#

runtime/deps.ts view source

RuntimeDeps import type {RuntimeDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Full runtime capabilities returned by create_deno_runtime or create_node_runtime.

Extends all *Deps interfaces with additional app-level capabilities. Functions should accept narrow *Deps interfaces, not this full type — this type is for the wiring layer that creates and passes the runtime.

inheritance

env_all

Get all environment variables.

type (): Record<string, string>

returns Record<string, string>

args

CLI arguments passed to the program.

type readonly string[]

readonly

cwd

Get current working directory.

type (): string

returns string

run_command_inherit

Run a command with inherited stdout/stderr (output goes directly to terminal).

type (cmd: string, args: string[]): Promise<number>

cmd

type string

args

type string[]
returns Promise<number>

rust_default_capabilities
#

rust_spine_stub_backend_config
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

(options?: SpineStubBackendConfigOptions): BackendConfig import {rust_spine_stub_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

Build the BackendConfig for testing_spine_stub. Resolves the binary from options.binary_path or FUZ_TESTING_RUST_SPINE_STUB_BIN; throws when neither is set so a missing build surfaces as a clear error rather than a confusing spawn failure. Reconciles the binary's env contract: port via --port (and FUZ_RUST_SPINE_STUB_PORT), daemon-token dir via FUZ_RUST_SPINE_STUB_DIR (anchored to paths.root so the written {dir}/run/daemon_token matches the path spawn_backend reads).

options?

optional default {}

returns

BackendConfig

throws

  • Error - when no binary path is available.

RUST_SPINE_STUB_BIN_ENV
#

RUST_SPINE_STUB_DATABASE_URL_ENV
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

"FUZ_TESTING_RUST_SPINE_STUB_DATABASE_URL" import {RUST_SPINE_STUB_DATABASE_URL_ENV} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

Env var overriding the stub's Postgres URL when database_url is omitted — e.g. a scratch cluster on a spare port, keeping the run off the machine's default server. A runner that creates the stub's database before the spawn must create the one this URL names, on the server it names.

RUST_SPINE_STUB_DEFAULT_DATABASE_URL
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

"postgres://localhost/fuz_app_test_rust_spine_stub" import {RUST_SPINE_STUB_DEFAULT_DATABASE_URL} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

Default Postgres database — real PG (PGlite isn't reachable from tokio-postgres).

RUST_SPINE_STUB_DEFAULT_NAME
#

RUST_SPINE_STUB_DEFAULT_PORT
#

RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

"FUZ_RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH" import {RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

Env var the stub reads for the absolute path of the committed expected_schema.json its /ready gate introspects against. Pointed at the same fixture the TS spine reads (SPINE_EXPECTED_SCHEMA_URL) — column-presence is engine-portable, so one file is the cross-impl contract.

RustSpineConsumerRpcEndpointsOptions
#

testing/cross_backend/rust_spine_consumer_surface.ts view source

RustSpineConsumerRpcEndpointsOptions import type {RustSpineConsumerRpcEndpointsOptions} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_consumer_surface.js';

domain_action_specs

The consumer's own action specs. The request-response specs that aren't protocol actions (heartbeat, peer/ping, …) go on the RPC endpoint, so a full registry like all_action_specs can be passed as it is.

type readonly ActionSpecUnion[]

readonly

extra_rpc_actions?

Actions besides the standard set and the domain specs — opt-in families the consumer mounts (create_actor_lookup_actions, create_self_service_role_actions), built against the stub context.

type (ctx: AppServerContext): RpcAction[]

readonly

ctx

returns RpcAction[]

roles?

The consumer's role schema, for the standard admin and role-grant-offer actions.

type RoleSchemaResult

readonly

exclude_methods?

Domain methods kept off the declared surface — ones the spec-derived suites can't drive (an input whose .refine() the generic body generator can't satisfy). They stay mounted on the backend; this only leaves them out of what the suites enumerate.

type readonly string[]

readonly

rpc_path?

The RPC endpoint's path. Default SPINE_RPC_PATH.

type string

readonly

RustSpineConsumerSurfaceSpecOptions
#

testing/cross_backend/rust_spine_consumer_surface.ts view source

RustSpineConsumerSurfaceSpecOptions import type {RustSpineConsumerSurfaceSpecOptions} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_consumer_surface.js';

inheritance

session_options

The backend's session config — its cookie name above all.

type SessionOptions<string>

readonly

host_gate?

Whether the server turns on the spine's opt-in host gate (fuz_http::StackOptions::host_gate), declared as a host_validation layer at the head of the middleware list.

type boolean

readonly
default false

safe_parse_dropping_unknown_keys
#

http/schema_helpers.ts view source

<T extends z.ZodType>(schema: T, value: unknown): ZodSafeParseResult<output<T>> import {safe_parse_dropping_unknown_keys} from '@fuzdev/fuz_app/http/schema_helpers.js';

schema.safeParse(value), except that keys the schema doesn't declare are dropped rather than refused — the forward-compatible read of a payload a remote authored.

Output schemas are strict so the server's DEV output validation catches a handler leaking fields it shouldn't. The receiving side, though, is often older than the remote it talks to — a deploy lands before open tabs reload — and a remote that adds a field is making a compatible change. So when every issue is an unrecognized key, those keys are stripped from a copy of value and it's parsed again; any other issue (a wrong type, a missing field, null where a value is required) still fails. value itself is never mutated. This matches serde's default on the Rust spine, which ignores unknown fields. Keys nested in a z.union branch surface as a union issue and aren't stripped; a z.discriminatedUnion reports its matched branch's issues directly, so those are.

Used for a response's result (ActionEvent) and for a peer's reply to a server-initiated request (peer/ping).

schema

the declared shape

type T

value

the remote-authored payload

type unknown

returns

ZodSafeParseResult

the parse result, with undeclared keys dropped on success

generics

safe_parse_dropping_unknown_keys<T extends z.ZodType>
T
constraint z.ZodType

schedule_credential_deadline
#

auth/credential_deadline.ts view source

(deadline: number | null, on_expire: () => void): () => void import {schedule_credential_deadline} from '@fuzdev/fuz_app/auth/credential_deadline.js';

Call on_expire once deadline has passed, waiting in timers of at most CREDENTIAL_DEADLINE_TIMER_CAP_MS, each re-reading the clock against the absolute deadline — so a session's 30 days (past setTimeout's ceiling) does not fire at once. Each wake-up also checks the same deadline on the wall clock, so a host suspend, which stops the monotonic clock, delays the close by at most one timer after the resume (module doc).

A null deadline schedules nothing. The returned function cancels the wait and is safe to call more than once, before or after it fires.

deadline

the monotonic deadline (ResolvedAuth.credential_deadline)

type number | null

on_expire

run once, when the deadline passes

type () => void

returns

() => void

a cancel function

schema_has_sensitive_field
#

schema_meta.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {schema_has_sensitive_field} from '@fuzdev/fuz_app/schema_meta.js';

Whether an input schema carries a sensitive value at any depth — 'secret' or 'personal' on the schema itself or anywhere inside it: through its wrappers (as schema_sensitivity), and into object fields, array and set elements, tuple items, record and map keys and values, template literal parts, union members and intersection sides.

What keeps a read's input off the URL: FrontendHttpTransport sends a read (side_effects: false) as a GET with the params in the query string, which proxies and server logs record, unless its input carries a sensitive value — that call goes as a POST (the transport walks each spec's input once).

schema

an action input schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

true when any value the schema admits may hold a sensitive field

schema_invalid_reason
#

schema_meta.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): string | undefined import {schema_invalid_reason} from '@fuzdev/fuz_app/schema_meta.js';

The invalid_reason a schema declares, read through its wrappers, the outermost declaration winning.

schema

a field schema, possibly wrapped

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

string | undefined

the declared reason, or undefined when no layer declares one

schema_meta_value
#

schema_meta.ts view source

<TKey extends keyof SchemaFieldMeta>(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, key: TKey): SchemaFieldMeta[TKey] | undefined import {schema_meta_value} from '@fuzdev/fuz_app/schema_meta.js';

A SchemaFieldMeta key's value as a schema declares it, read through its optional, nullable, default, prefault, transform and pipe layers — Zod keeps .meta() on the instance it was set on, so a wrapper added after it carries none — the outermost declaration winning.

schema

a field schema, possibly wrapped

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

key

the metadata key to read

type TKey

returns

SchemaFieldMeta[TKey] | undefined

the declared value, or undefined when no layer declares one

generics

schema_meta_value<TKey extends keyof SchemaFieldMeta>
TKey
constraint keyof SchemaFieldMeta

schema_sensitivity
#

schema_meta.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): Sensitivity | undefined import {schema_sensitivity} from '@fuzdev/fuz_app/schema_meta.js';

The sensitivity a schema declares anywhere among its wrapping layers, so a secret stays masked however the field is wrapped. Fail-safe where schema_meta_value reads one chain: the walk follows the innerType of every wrapper that has one (optional, nullable, default, prefault, nonoptional, catch, readonly, promise), both sides of a pipe (so .transform(), z.preprocess() and .pipe(secret) all reach the secret), a z.lazy() getter, every member of a union (the value is one of them), both sides of an intersection (it is both), and a template literal's schema parts (the value contains them), visiting each schema once, and answers the strongest level any layer reached declares — 'secret' over 'personal'. It stops at structural schemas — an object's fields and an array's element carry their own.

schema

a field schema, possibly wrapped

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

Sensitivity | undefined

the strongest level any reachable layer declares, else undefined

schema_to_surface
#

http/schema_helpers.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): unknown import {schema_to_surface} from '@fuzdev/fuz_app/http/schema_helpers.js';

Convert a Zod schema to a JSON-serializable representation for the surface.

Returns null for null schemas, JSON Schema for object schemas.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

unknown

SchemaDiff
#

testing/schema_parity.ts view source

SchemaDiff

type { readonly kind: "table_only_in"; readonly where: "a" | "b"; readonly table: string; } | { readonly kind: "column_only_in"; readonly where: "a" | "b"; readonly table: string; readonly column: string; } | { readonly kind: "column_field_differs"; readonly table: string; readonly column: string; readonly field: "data_type" | "udt_name" | "is_nullable" | "column_default" | "is_identity"; readonly a: unknown; readonly b: unknown; } | { readonly kind: "index_only_in"; readonly where: "a" | "b"; readonly table: string; readonly index: string; } | { readonly kind: "index_definition_differs"; readonly table: string; readonly index: string; readonly a: string; readonly b: string; } | { readonly kind: "constraint_only_in"; readonly where: "a" | "b"; readonly table: string; readonly constraint: string; } | { readonly kind: "constraint_differs"; readonly table: string; readonly constraint: string; readonly a: { type: string; definition: string; }; readonly b: { type: string; definition: string; }; } | { readonly kind: "sequence_only_in"; readonly where: "a" | "b"; readonly sequence: string; } | { readonly kind: "sequence_data_type_differs"; readonly sequence: string; readonly a: string; readonly b: string; } | { readonly kind: "enum_only_in"; readonly where: "a" | "b"; readonly enum_name: string; } | { readonly kind: "enum_labels_differ"; readonly enum_name: string; readonly a: readonly string[]; readonly b: readonly string[]; }

import type {SchemaDiff} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structured drift entry. where is the named source impl ('a' or 'b').

SchemaDiffLabels
#

testing/schema_parity.ts view source

SchemaDiffLabels import type {SchemaDiffLabels} from '@fuzdev/fuz_app/testing/schema_parity.js';

Labels used in formatted output — defaults to 'a' and 'b'.

a?

type string

readonly

b?

type string

readonly

SchemaDriftResult
#

db/schema_ready.ts view source

SchemaDriftResult import type {SchemaDriftResult} from '@fuzdev/fuz_app/db/schema_ready.js';

Outcome of a schema-drift check.

ok

type boolean

missing_tables

Expected tables absent from the live DB.

type string[]

missing_columns

Per-table columns the expected schema declares that the live DB lacks.

type MissingColumns[]

SchemaFieldMeta
#

schema_meta.ts view source

SchemaFieldMeta import type {SchemaFieldMeta} from '@fuzdev/fuz_app/schema_meta.js';

Zod .meta() shape for fuz_app schema metadata conventions.

description?

type string

sensitivity?

Sensitivity level (Sensitivity): 'secret' masks the value, 'personal' only keeps it out of URLs. Read through every wrapping layer (schema_sensitivity), so .meta({sensitivity: 'secret'}).optional() masks too.

type Sensitivity

empty_input_handler_guarded?

Set on an action's input schema that accepts {} by design while its handler refuses {} with a declared reason — an input taking one of several optional selectors, where the handler names the missing or conflicting one (a schema-level refine would answer a reason-less invalid_params first). A call with no input is never valid for such an action, so the typed surface keeps its input required (action_input_is_optional), and the input-shape invariant ("an input accepting {} accepts undefined too") exempts it. Read from the input schema itself, so a wrapper (.optional(), .extend()) drops it.

type boolean

invalid_reason?

The error.data.reason an action input refuses this field with. On a field of an action's input schema, every invalid_params whose first Zod issue lands on the field — missing, wrong type, null, out of range, malformed, or a fault anywhere inside it — carries this reason, where an undeclared field's refusal carries none. The message stays the generic invalid params. A body with faults on several fields answers the first issue's reason in Zod's order, which a twin checking in another order need not match, so the reason is defined for a single-fault body only — except that an issue on the input itself (an unknown key) anywhere among the issues means no reason, as the Rust twin refuses unknown keys first. Declared reasons are named <field>_invalid.

The declaration is the one source of the reason: the dispatcher reads it (perform_action), the adversarial suites expect it, and the Rust twin owes the same reason on every refusal of the field. Read through the field's optional, nullable, default, prefault, transform and pipe layers (not .readonly(), .catch() or z.lazy()), so it may sit on any of them; a declaration on a nested field overrides its parent's for a fault inside it, and an object-level refine whose issue names the field takes its reason too. See resolve_issues_invalid_reason.

type string

SchemaSnapshot
#

testing/schema_introspect.ts view source

value + type

{ tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; } import {SchemaSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Normalized database schema snapshot for parity comparison — the single source of truth for the snapshot shape across the introspection query (query_schema_snapshot), the diff comparator (testing/schema_parity.ts), and the cross-impl RPC action's wire validator (testing/cross_backend/testing_reset_actions.ts).

All fields are deterministically ordered on capture so structural equality via JSON.stringify or per-key comparison yields stable results.

tables

Tables keyed by name.

type Record<string, TableSnapshot>

sequences

Sequences keyed by name.

type Record<string, SequenceSnapshot>

enums

Enum types (CREATE TYPE ... AS ENUM) keyed by name; labels in declared order.

type Record<string, EnumTypeSnapshot>

SCOPE_KIND_LENGTH_MAX
#

auth/scope_kind_schema.ts view source

64 import {SCOPE_KIND_LENGTH_MAX} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

Maximum length of a scope_kind, in UTF-16 code units (what String.length measures; enforced by bounded_string). Bounds the tag where it enters — the grant inputs (role_grant_offer_create, role_grant_assign) copy it into the row and its audit metadata — and a registered name, so no registry declares a kind the grant inputs would refuse. Same value as ROLE_NAME_LENGTH_MAX, whose grammar the name mirrors, and as the Rust spine's SCOPE_KIND_LENGTH_MAX.

SCOPE_KIND_NAME_REGEX
#

auth/scope_kind_schema.ts view source

RegExp import {SCOPE_KIND_NAME_REGEX} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

Letter (lowercase a-z) start and end (or single letter), with letters and underscores in between. Mirrors RoleName. Rejects empty strings, leading or trailing underscores, uppercase, digits, and the index-side 'GLOBAL' token.

SCOPED_TOKEN_ADMITTED_METHOD
#

testing/cross_backend/conformance_case.ts view source

"account_verify" import {SCOPED_TOKEN_ADMITTED_METHOD} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

The single RPC method the scoped_token principal's narrowed token admits.

Part of the principal's contract, so the runner (which mints the token) and the cases (which assert the denials) agree on one value. account_verify is the minimal credential_types: any read on the spine: it clears the credential gate a bearer must pass first, and holds no role gate that could shadow the scope denial — so a case naming any *other* method isolates the scope gate and nothing else.

ScopeKindMeta
#

auth/scope_kind_schema.ts view source

ScopeKindMeta import type {ScopeKindMeta} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

Per-scope-kind metadata. description is admin-UI-facing copy (mirrors RoleSpec.description). Open shape so v2 can extend without a breaking change.

description?

type string

ScopeKindName
#

ScopeKindSchemaResult
#

auth/scope_kind_schema.ts view source

ScopeKindSchemaResult import type {ScopeKindSchemaResult} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

The result of create_scope_kind_schema — a Zod schema and metadata map.

ScopeKind

Zod schema that validates scope-kind name strings against the registered set. Use at I/O boundaries (admin UIs, codegen) and as the construction-time check inside create_role_schema for every RoleSpec.applicable_scope_kinds entry.

type ZodType<string, unknown, $ZodTypeInternals<string, unknown>>

scope_kinds

Map of every registered scope-kind to its metadata. Keyed by name. Read at startup by admin / codegen surfaces.

type ReadonlyMap<string, ScopeKindMeta>

seed_dev_account
#

dev/setup.ts view source

(deps: SeedDevAccountDeps, input: SeedDevAccountInput, options?: { log?: SetupLogger | undefined; } | undefined): Promise<...> import {seed_dev_account} from '@fuzdev/fuz_app/dev/setup.js';

Seed a development test account, bypassing username/password policy.

Idempotent by username — if an account with the given username already exists, reuses it and only reconciles the requested role grants. Never updates an existing password (rerun would silently rotate it).

Intended for scripts/dev_setup.ts — do not call in production.

deps

input

options?

type { log?: SetupLogger | undefined; }
optional

returns

Promise<SeedDevAccountResult>

throws

  • Error - if an existing account is found without an associated actor row

mutates

  • database — inserts an account/actor pair when missing and grants any requested role role_grants

SeedDevAccountDeps
#

dev/setup.ts view source

SeedDevAccountDeps import type {SeedDevAccountDeps} from '@fuzdev/fuz_app/dev/setup.js';

Dependencies for seed_dev_account.

inheritance

extends: QueryDeps

hash_password

Password hasher (e.g., argon2_password_deps.hash_password).

type (password: string): Promise<string>

password

type string
returns Promise<string>

SeedDevAccountInput
#

dev/setup.ts view source

SeedDevAccountInput import type {SeedDevAccountInput} from '@fuzdev/fuz_app/dev/setup.js';

username

Account username. Policy is bypassed — any non-empty string is accepted.

type string

password

Account password. Policy is bypassed — any non-empty string is accepted.

type string

roles?

Roles to grant via role_grant (idempotent).

type readonly string[]

SeedDevAccountResult
#

dev/setup.ts view source

SeedDevAccountResult import type {SeedDevAccountResult} from '@fuzdev/fuz_app/dev/setup.js';

Result of seed_dev_account.

account_id

type string

actor_id

type string

created

True if a new account was created; false if one already existed.

type boolean

select_auth_app
#

testing/auth_apps.ts view source

(apps: AuthTestApps, auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): Hono<...> import {select_auth_app} from '@fuzdev/fuz_app/testing/auth_apps.js';

Select the Hono test app with correct auth for a route.

The credential channel is decided first, because the channel gate is pre-authorization: an app whose credential the route refuses never reaches the role gate, so a role-correct app on the wrong channel answers credential_type_required, and a "correct auth passes guard" case reads it as a failure of the role gate that never ran.

A gate admitting 'session' falls through to the by_role / authed apps, which carry that channel. Any other gate leaves the by_credential_type app as the only candidate — those hold the keeper role, which is why a route pairing a non-session channel with some *other* role has no app and says so.

apps

auth

returns

Hono<BlankEnv, BlankSchema, "/">

throws

  • Error - if `auth.roles` names a role not present in `apps.by_role` — surfaces a missing entry in the `roles` array passed to `create_auth_test_apps`.
  • Error - if the route pairs a non-session channel with a role the channel apps don't hold. Silently handing back a session app would turn every case on the route into a misattributed 403.

self_service_role_set_action_spec
#

auth/self_service_role_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ role: ZodString; enabled: ZodBoolean; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: strin... import {self_service_role_set_action_spec} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

rate_limit: 'account' bounds audit-row churn. The toggle is idempotent (changed: false re-grants/re-revokes), but every call still writes a role_grant_create or role_grant_revoke audit row with self_service: true. Without the cap, a caller could flap the role in a loop to inflate the audit log and obscure other activity.

credential_types: ['session'] — see docs/security.md §Credential-channel gating. Self-toggling a role moves the caller's own authority, the same privilege-pivot shape as role_grant_offer_accept. The verb exists to serve a UI affordance, so the bearer channel buys nothing and a leaked account-wide token self-granting an eligible role is what the gate refuses.

SelfServiceRoleActionsOptions
#

auth/self_service_role_actions.ts view source

SelfServiceRoleActionsOptions import type {SelfServiceRoleActionsOptions} from '@fuzdev/fuz_app/auth/self_service_role_actions.js';

eligible_roles?

Optional override allowlist of role strings eligible for self-service. When omitted, eligibility is derived from roles.role_specs (or builtin_role_specs_by_name when roles is also omitted) by selecting every role whose RoleSpec.grant_paths includes 'self_service'. Pass an empty array to lock the surface down (every call comes back as forbidden with reason role_not_self_service_eligible).

When supplied alongside roles, every entry is checked against roles.role_specs at factory time so typos throw at startup.

type readonly string[]

roles?

Optional role schema. Drives default eligibility derivation from RoleSpec.grant_paths and validates the eligible_roles override (when supplied) against the registered role set.

type RoleSchemaResult

SelfServiceRoleSetInput
#

auth/self_service_role_action_specs.ts view source

value + type

{ role: string; enabled: boolean; acting?: (string & $brand<"Uuid">) | undefined; } import {SelfServiceRoleSetInput} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

Input for self_service_role_set.

role

type string

enabled

type boolean

acting?

type string & $brand<"Uuid">

SelfServiceRoleSetOutput
#

auth/self_service_role_action_specs.ts view source

value + type

{ ok: true; enabled: boolean; changed: boolean; } import {SelfServiceRoleSetOutput} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

Output for self_service_role_set. enabled echoes the post-call state (always equals the input enabled on success). changed is true only when the call mutated — re-grants / re-revokes return false.

ok

type true

enabled

type boolean

changed

type boolean

send_conformance_case
#

testing/cross_backend/conformance_table.ts view source

(c: { name: string; request: { method: string; as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"; params?: unknown; params_json?: string | undefined; acting?: "principal" | ... 2 more ... | undefined; verb?: "GET" | ... 1 more ... | undefined; }; expect: { ...; }; note?: string | undefined; xfail?: { ...; } | undefined; }, options: SendConformanceCaseOptions): Promise<...> import {send_conformance_case} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Resolve a case's principal on fixture and send its request, asserting nothing about the response.

The send half of the table runner, shared with the cross-backend differential (describe_differential_conformance_tests), which sends one case to two backends and compares what came back.

c

the case

options

the fixture, surface and endpoints to send through

returns

Promise<ConformanceCaseResponse>

the resolved target and the raw response

throws

  • Error - when the method is on neither the RPC surface nor the REST auth routes

SendConformanceCaseOptions
#

testing/cross_backend/conformance_table.ts view source

SendConformanceCaseOptions import type {SendConformanceCaseOptions} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

What send_conformance_case needs besides the case.

fixture

The per-test fixture the principal resolves from.

type TestFixture

readonly

surface_source

Surface spec — supplies the RouteSpecs for the REST branch.

type AppSurfaceSpec

readonly

principals?

Usernames for the role_holder / wrong_role principals.

type ConformancePrincipalConfig

readonly

resolved_rpc_endpoints

The endpoints RPC methods (and the scoped_token mint) resolve against — resolve_conformance_rpc_endpoints of the declared and live endpoints.

type RpcEndpointSpec[]

readonly

ws?

Send the request as a frame on a socket at this endpoint instead of over HTTP.

type WsEndpointTarget

readonly

request_headers?

Headers added to the HTTP request beneath the principal's, which win on a name clash. Not sent on the WS leg (a frame carries no headers).

type Readonly<Record<string, string>>

readonly

sensitive_field_blocklist
#

testing/integration_helpers.ts view source

readonly string[] import {sensitive_field_blocklist} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Field names that must never appear in any HTTP response body.

Sensitivity
#

sensitivity.ts view source

Sensitivity

type "secret" | "personal"

import type {Sensitivity} from '@fuzdev/fuz_app/sensitivity.js';

Sensitivity level for a schema field.

  • 'secret' — value is masked in logs and UI (e.g. passwords, API keys, signing keys) and kept out of URLs
  • 'personal' — personal data (email fragments, names, search terms about people): kept out of URLs — a read whose input carries it is sent as a POST, not a GET (schema_has_sensitive_field) — but not masked in logs or UI

SequenceSnapshot
#

testing/schema_introspect.ts view source

value + type

{ data_type: string; } import {SequenceSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Sequence metadata — data_type is bigint (BIGSERIAL) or integer (SERIAL).

data_type

type string

SerializableBootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

SerializableBootstrappedBackendHandle import type {SerializableBootstrappedBackendHandle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Serializable subset of BootstrappedBackendHandle suitable for vitest's project.provide() — vitest 4 hard-rejects non-serializable values, so the live child: ChildProcess + teardown: () => Promise<void> + keeper_transport: FetchTransport (closure) must stay in the globalSetup process. The handful of fields tests actually read (config, daemon_token, keeper_account, keeper_actor, keeper_cookies) round-trip through structured clone fine.

globalSetup calls serialize_bootstrapped_handle before project.provide; test files call reconstruct_bootstrapped_handle on the injected value to rebuild a usable handle (without child / teardown — lifecycle stays with globalSetup).

config

type BackendConfig

readonly

daemon_token

type string

readonly

keeper_account

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

keeper_actor

type { readonly id: string & $brand<"Uuid">; }

readonly

keeper_cookies

type readonly string[]

readonly

serialize_action_result
#

actions/perform_action.ts view source

(id: string | number, result: PerformActionResult, method: string, log: Logger): SerializedActionResult import {serialize_action_result} from '@fuzdev/fuz_app/actions/perform_action.js';

Serialize a PerformActionResult to its JSON-RPC envelope text — the one step HTTP RPC and WS share after perform_action returns.

A result that cannot be serialized (a bigint, a cycle) answers internal_error rather than throwing. By then the action has committed, so a throw would cost its effects: over HTTP the route wrapper reads it as a rollback and discards post_commit_effects (a revocation's connection closes among them); over WS the adapter does not await the frame handler, so the rejection is unhandled and the caller gets no reply to a request that did run.

id

the request id the envelope echoes

result

what perform_action returned

method

the action method, for the log line

type string

log

where a serialization failure is logged

type Logger

returns

SerializedActionResult

the envelope text and the HTTP status to send it with

serialize_bootstrapped_handle
#

testing/cross_backend/setup.ts view source

(handle: BootstrappedBackendHandle): SerializableBootstrappedBackendHandle import {serialize_bootstrapped_handle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Strip the non-serializable members so the result can be passed to vitest's project.provide. Call in globalSetup before provide.

handle

returns

SerializableBootstrappedBackendHandle

serialize_token_scope
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }): string import {serialize_token_scope} from '@fuzdev/fuz_app/auth/token_scope.js';

Serialize for storage, enforcing the size cap.

Throws on an over-cap document — the mint path maps that to a 400, matching the Rust side's TokenScopeError::TooLarge.

scope

returns

string

SerializedActionResult
#

actions/perform_action.ts view source

SerializedActionResult import type {SerializedActionResult} from '@fuzdev/fuz_app/actions/perform_action.js';

A PerformActionResult serialized for the wire: the JSON body and its HTTP status.

body

type string

status

200 for a result, the error's status otherwise. WS ignores it.

type number

ServeHandle
#

testing/cross_backend/testing_server_core.ts view source

ServeHandle import type {ServeHandle} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Adapter-built handle to a bound HTTP server.

shutdown stops accepting new connections at once and settles when the in-flight ones have drained — the core starts it, closes the app (which ends live WebSockets), then awaits it. native is an adapter-specific server reference — used by Node's @hono/node-ws injectWebSocket(server) post-serve hook.

shutdown

type (): Promise<void>

returns Promise<void>

native?

Adapter-specific server ref for post-serve hooks. Type-erased at the seam.

type unknown

ServerEnvValidationError
#

server/env.ts view source

ServerEnvValidationError import type {ServerEnvValidationError} from '@fuzdev/fuz_app/server/env.js';

Error from validate_server_env — keyring or origin validation failed.

field

type "SECRET_FUZ_COOKIE_KEYS" | "FUZ_ALLOWED_ORIGINS"

errors

type string[]

ServerStatusOptions
#

http/common_routes.ts view source

ServerStatusOptions import type {ServerStatusOptions} from '@fuzdev/fuz_app/http/common_routes.js';

Options for the authenticated server status route.

version

Application version string.

type string

get_uptime_ms

Returns milliseconds since server start.

type (): number

returns number

ServeStaticFactory
#

server/static.ts view source

ServeStaticFactory import type {ServeStaticFactory} from '@fuzdev/fuz_app/server/static.js';

Factory function that creates a static file serving middleware.

Matches the signature of serveStatic from @hono/deno and @hono/node-server/serve-static.

(call)

type (options: ServeStaticOptions): MiddlewareHandler

options

returns MiddlewareHandler

ServeStaticOptions
#

server/static.ts view source

ServeStaticOptions import type {ServeStaticOptions} from '@fuzdev/fuz_app/server/static.js';

Options for serve_static factory functions (matches Hono's serveStatic signature).

root

type string

rewriteRequestPath?

type (path: string): string

path

type string
returns string

mimes?

type Record<string, string>

SESSION_AGE_MAX
#

auth/session_cookie.ts view source

number import {SESSION_AGE_MAX} from '@fuzdev/fuz_app/auth/session_cookie.js';

Cookie max age in seconds (30 days — aligned with AUTH_SESSION_LIFETIME_MS).

This is an absolute session lifetime: the cookie is signed once at mint (expires_at = mint + max_age) and never re-signed, matching the DB row's fixed expires_at. There is deliberately no sliding renewal on either leg — see docs/security.md §Session Security.

SESSION_COOKIE_EXPIRES_AT_KEY
#

session_cookie_options
#

SESSION_TOKEN_KEY
#

hono_context.ts view source

"auth_session_id" import {SESSION_TOKEN_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the session token — set by the session middleware (create_session_middleware) to the token a verified, unexpired session cookie carries, null otherwise, and read by the session leg of the auth middleware (create_request_context_middleware), which hashes it to find the auth_session row. Fixed rather than configurable: the session middleware and the session leg must agree on it, and no other spine key may share it.

SessionAccount
#

auth/account_schema.ts view source

SessionAccount import type {SessionAccount} from '@fuzdev/fuz_app/auth/account_schema.js';

Account without sensitive fields, scoped to the authenticated user's own session.

id

type Uuid

username

type string

email

type string | null

email_verified

type boolean

created_at

type string

SessionAccountJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; } import {SessionAccountJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for SessionAccount — account without sensitive fields.

id

type string & $brand<"Uuid">

username

type string

email

type string | null

email_verified

type boolean

created_at

type string

SessionCapCrossTestOptions
#

testing/cross_backend/session_cap.ts view source

SessionCapCrossTestOptions import type {SessionCapCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/session_cap.js';

Options for the session-cap parity suite.

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

max_sessions?

The cap both spines enforce. Defaults to DEFAULT_MAX_SESSIONS — the TS route default and the Rust const. A consumer that overrides max_sessions on the TS side passes its value here.

type number

readonly

login_path?

REST login route path. Default /api/account/login.

type string

readonly

status_path?

REST account-status route path. Default /api/account/status.

type string

readonly

rpc_path?

JSON-RPC endpoint path, for the session-list case. Default SPINE_RPC_PATH.

type string

readonly

ws?

Where the backend's WebSocket endpoint is, for the case that an evicted session's socket is closed. Pass it when the backend has a WS transport (capabilities.ws); omitted, that case is skipped.

type WsEndpointTarget

readonly

ws_account_actions?

Whether the ws endpoint mounts the self-service account actions (capabilities.ws_account_actions), so the session-list case also lists over a socket. Default false.

type boolean

readonly

ws_account_roles?

The roles the account whose socket the ws case watches is created with, for a backend whose WS upgrade requires a role (an admin-gated /api/ws refuses a roleless account's upgrade). Each must be admin-grantable — the fixture offers it. Default none.

type readonly string[]

readonly

SessionCookieCodecCrossTestOptions
#

testing/cross_backend/session_cookie_codec.ts view source

SessionCookieCodecCrossTestOptions import type {SessionCookieCodecCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/session_cookie_codec.js';

Options for the session cookie codec parity suite.

setup_test

Per-test fixture producer (cross-process).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

cookie_name

The spine's session cookie name (handle.config.cookie_name).

type string

readonly

cookie_keys

The backend's SECRET_FUZ_COOKIE_KEYS value (handle.config.env.SECRET_FUZ_COOKIE_KEYS) — the suite signs with its primary key.

type string

readonly

rpc_path?

JSON-RPC endpoint path. Default SPINE_RPC_PATH.

type string

readonly

SessionCookieOptions
#

auth/session_cookie.ts view source

SessionCookieOptions import type {SessionCookieOptions} from '@fuzdev/fuz_app/auth/session_cookie.js';

Cookie options for session cookies.

path

type string

httpOnly

type boolean

secure

type boolean

sameSite

type "strict" | "lax" | "none"

maxAge

type number

SessionId
#

auth/account_schema.ts view source

value + type

string & $brand<"SessionId">

type string & $brand<"SessionId">

import {SessionId} from '@fuzdev/fuz_app/auth/account_schema.js';

A session's storage key — the blake3 hash of its raw token, branded so a bare string can't stand in for one. Blake3Hash (primitive_schemas.ts) is the shape; this is the meaning. Mint only via hash_session_token, or SessionId.parse at an external boundary.

The raw token itself is never typed — it stays a string that exists only long enough to be hashed.

external types

z.infer<typeof SessionId>

SessionListInput
#

SessionListOutput
#

auth/account_action_specs.ts view source

value + type

{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; is_current: boolean; }[]; } import {SessionListOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_session_list.

sessions

type AuthSessionJson[]

SessionOptions
#

auth/session_cookie.ts view source

SessionOptions<TIdentity> import type {SessionOptions} from '@fuzdev/fuz_app/auth/session_cookie.js';

Configuration for a session cookie format.

Apps provide encode/decode to control the identity portion of the cookie payload.

The TIdentity type parameter determines the trust model:

  • string (e.g. a session_id) — the cookie references a server-side session record, enabling per-session revocation and metadata. Use when you need admin controls like "revoke all sessions" or per-session audit trails.
  • number (e.g. an account_id) — the cookie directly encodes the user identity, requiring no server-side session state. Simpler, but individual sessions can only be invalidated by rotating the signing key (which invalidates all sessions).

The cookie helpers here take either. The spine's session middleware and auth chain take SessionOptions<string> — the identity is the session token the session leg hashes to find its auth_session row (create_session_config).

generics

SessionOptions<TIdentity>
TIdentity

examples

// zap: 3-part format (admin:session_id) const zap_config: SessionOptions<string> = { cookie_name: 'zap_session', encode_identity: (session_id) => `admin:${session_id}`, decode_identity: (payload) => { const parts = payload.split(':'); if (parts.length !== 2 || parts[0] !== 'admin') return null; return parts[1] || null; }, }; // visiones: 1-part format (account_id) const visiones_config: SessionOptions<number> = { cookie_name: 'session_id', encode_identity: (id) => String(id), decode_identity: (payload) => { const n = parseInt(payload, 10); return Number.isFinite(n) && n > 0 ? n : null; }, };

cookie_name

type string

max_age?

Cookie lifetime in seconds. Single source of truth for both the embedded expires_at (via create_session_cookie_value) and the cookie's HTTP Max-Age attribute (via set_session_cookie). Defaults to SESSION_AGE_MAX (30 days). The cookie_options slot intentionally cannot carry maxAge so the two values can't drift.

type number

cookie_options?

type Partial<Omit<SessionCookieOptions, "maxAge">>

encode_identity

Encode identity into the cookie payload (before the :expires_at suffix).

type (identity: TIdentity): string

identity

type TIdentity
returns string

decode_identity

Decode identity from cookie payload. Return null if invalid.

type (payload: string): TIdentity | null

payload

type string
returns TIdentity | null

SessionRevokeAllInput
#

auth/account_action_specs.ts view source

value + type

void import {SessionRevokeAllInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_session_revoke_all. No parameters.

SessionRevokeAllOutput
#

auth/account_action_specs.ts view source

value + type

{ ok: true; count: number; } import {SessionRevokeAllOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_session_revoke_all.

ok

type true

count

type number

SessionRevokeInput
#

auth/account_action_specs.ts view source

value + type

{ session_id: string & $brand<"SessionId">; } import {SessionRevokeInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_session_revoke. session_id is the blake3 hash.

session_id

type string & $brand<"SessionId">

SessionRevokeOutput
#

auth/account_action_specs.ts view source

value + type

{ ok: true; revoked: boolean; } import {SessionRevokeOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_session_revoke. revoked is false for IDOR misses.

ok

type true

revoked

type boolean

set_default_db_factory
#

testing/db.ts view source

(create_factory: DbFactoryBuilder | null): DbFactoryBuilder | null import {set_default_db_factory} from '@fuzdev/fuz_app/testing/db.js';

Install the builder create_default_db_factory uses, so suites written against the default run against another driver without being edited — create_pglite_factory to run them on PGlite. Pass null to restore pglet-wasm.

The slot is read when a factory is built, not when it creates a database, so install before the first create_default_db_factory call — a vitest setupFiles entry, which runs ahead of every test file's imports, is the shape fuz_app's own db project uses. A factory built earlier (including the module-level fallback in testing/app_server.ts) keeps the driver it was built with, and uninstalling does not revert it.

create_factory

the builder to install, or null to restore the default

type (DbFactoryBuilder) | null

returns

(DbFactoryBuilder) | null

the builder this replaced (null for pglet-wasm), so a caller can put it back

set_mock_stdin
#

runtime/mock.ts view source

(runtime: MockRuntime, input: string): void import {set_mock_stdin} from '@fuzdev/fuz_app/runtime/mock.js';

Set stdin buffer for simulating user input.

runtime

input

string to provide as stdin input

type string

returns

void

mutates

  • runtime.stdin_buffer

set_session_cookie
#

setup_bootstrap_token
#

dev/setup.ts view source

(deps: FsReadDeps & FsWriteDeps & EnvDeps, app_name: string, options?: SetupBootstrapTokenOptions | undefined): Promise<...> import {setup_bootstrap_token} from '@fuzdev/fuz_app/dev/setup.js';

Create a bootstrap token file if it doesn't exist.

The token is a one-shot secret used to create the first admin account: 32 random bytes as 64 lowercase hex characters plus one LF, the file openssl rand -hex 32 writes. Stored at ~/.{app_name}/secret_bootstrap_token by default.

deps

file and env capabilities

app_name

application name (used for default state directory)

type string

options?

state_dir override, logger

optional

returns

Promise<SetupTokenResult>

result indicating whether a token was created

mutates

  • filesystem — creates the state directory (`0o700`) and writes the token file atomically at `0o600`

setup_env_file
#

dev/setup.ts view source

(deps: FsReadDeps & FsWriteDeps, env_path: string, example_path: string, options?: SetupEnvOptions | undefined): Promise<SetupEnvResult> import {setup_env_file} from '@fuzdev/fuz_app/dev/setup.js';

Create an env file from its example template, auto-generating SECRET_FUZ_COOKIE_KEYS.

If the file already exists, backfills any empty values that have generators. Idempotent — safe to re-run. Values are written through update_env_content, the one env-file writer.

deps

file read and write capabilities

env_path

path for the env file (e.g. .env.development)

type string

example_path

path to the example template

type string

options?

extra replacements, logger

optional

returns

Promise<SetupEnvResult>

result indicating whether the file was created or updated

mutates

  • filesystem — writes `env_path` atomically at mode `0o600` (creating from `example_path` if missing)

SetupBootstrapTokenOptions
#

SetupEnvOptions
#

dev/setup.ts view source

SetupEnvOptions import type {SetupEnvOptions} from '@fuzdev/fuz_app/dev/setup.js';

Options for setup_env_file.

replacements?

Extra env var replacements beyond the default SECRET_FUZ_COOKIE_KEYS.

Keys are env var names, values are async generators. Each fills a key the env file assigns an empty value (KEY=, KEY=""), written through update_env_content (quoted when the value needs it, never interpreted); a key the file doesn't assign is left out.

type Record<string, () => Promise<string>>

log?

type SetupLogger

SetupEnvResult
#

dev/setup.ts view source

SetupEnvResult import type {SetupEnvResult} from '@fuzdev/fuz_app/dev/setup.js';

Result of setup_env_file.

created

Whether a new file was created (vs updating existing).

type boolean

updated

Whether any values were generated/replaced.

type boolean

path

The env file path.

type string

SetupLogger
#

dev/setup.ts view source

SetupLogger import type {SetupLogger} from '@fuzdev/fuz_app/dev/setup.js';

Optional logger for setup helpers.

Functions that accept a logger use it for status messages. When omitted, a default bracket-format logger writes to console.

ok

type (msg: string): void

msg

type string
returns void

skip

type (msg: string): void

msg

type string
returns void

error

type (msg: string): void

msg

type string
returns void

SetupTest
#

testing/cross_backend/setup.ts view source

SetupTest import type {SetupTest} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Fixture-producing function. Invoked once inside every test() body by default; a suite whose cases share no state past the reset may call it once in beforeAll instead. The implementation captures factory inputs (in-process) or a long-running backend handle (cross-process) and creates a fresh per-test bundle on each call.

(call)

type (): Promise<TestFixture>

returns Promise<TestFixture>

SetupTokenResult
#

dev/setup.ts view source

SetupTokenResult import type {SetupTokenResult} from '@fuzdev/fuz_app/dev/setup.js';

created

Whether a new token was created (false if already existed).

type boolean

token_path

The token file path.

type string

should_allow_origin
#

http/origin.ts view source

(origin: string, allowed_patterns: readonly RegExp[]): boolean import {should_allow_origin} from '@fuzdev/fuz_app/http/origin.js';

Tests if an Origin value matches any of the allowed patterns. Pattern matching is case-insensitive (web hosts and schemes are). A value that isn't visible ASCII matches nothing — a browser never sends one.

origin

type string

allowed_patterns

type readonly RegExp[]

returns

boolean

should_validate_output
#

actions/action_event_helpers.ts view source

(kind: "request_response" | "remote_notification" | "local_call", phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): boolean import {should_validate_output} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

kind

phase

returns

boolean

ShouldDeliverFn
#

actions/broadcast_api.ts view source

ShouldDeliverFn import type {ShouldDeliverFn} from '@fuzdev/fuz_app/actions/broadcast_api.js';

Per-connection delivery predicate for subscription ACLs.

Called once per connection for every broadcast send. Returning false skips that connection. Keep it fast — this runs in the broadcast hot path.

input is the already-validated payload (matches the spec's input schema); method is the action method name.

(call)

type (connection: Readonly<ConnectionIdentity>, method: string, input: unknown): boolean

connection

type Readonly<ConnectionIdentity>

method

type string

input

type unknown
returns boolean

SIDEBAR_NARROW_QUERY_DEFAULT
#

sidebar_state_context
#

SidebarState
#

ui/sidebar_state.svelte.ts view source

import {SidebarState} from '@fuzdev/fuz_app/ui/sidebar_state.svelte.js';

narrow_query

The media query for narrow mode, or null when narrow mode is disabled.

type string | null

readonly

sidebar_id

The sidebar element's id, or null to let AppShell generate one.

type string | null

readonly

show_sidebar_wide

Whether the sidebar shows beside the content outside narrow mode — the desktop preference. Narrow-mode interactions never touch it.

type boolean

$state.raw

show_sidebar_narrow

Whether the narrow-mode overlay is open.

type boolean

$state.raw

constructor

Creates the state, subscribing to narrow_query unless it is null.

type new (options?: SidebarStateOptions | undefined): SidebarState

options?

enabled getter, narrow-mode query, and sidebar id

optional

throws

  • Error - in DEV when `narrow_query` has no parentheses — `MediaQuery` would auto-wrap it but CSS `media` would not, so they'd disagree

toggle_sidebar

Sets the current mode's visibility, routed by narrow at call time.

type (value?: boolean): void

value?

the new visibility, defaults to the inverse of show_sidebar

type boolean
optional default !this.show_sidebar
returns void

close_narrow

Closes the narrow-mode overlay, leaving the wide preference untouched. AppShell calls it on navigation, Escape, scrim click, and when the viewport widens out of narrow mode.

type (): void

returns void

activate

Show the sidebar and enable the toggle. The returned disposer hides and disables on cleanup — pair with $effect for scoped activation.

type (): () => void

returns () => void

mutates

  • this — sets `enabled` and `show_sidebar_wide`; the disposer clears `enabled`, `show_sidebar_wide`, and `show_sidebar_narrow`

enabled

type boolean

gettersetter

narrow

Whether the viewport matches narrow_query. Always false during SSR and when narrow_query is null. Read it for input handling and ARIA only — markup that must match the server render should key on show_sidebar_wide / show_sidebar_narrow instead.

type boolean

getter

show_sidebar

Effective visibility for the current mode: false while disabled, otherwise show_sidebar_narrow in narrow mode and show_sidebar_wide outside it. The setter writes the current mode's field.

type boolean

gettersetter

SidebarStateOptions
#

ui/sidebar_state.svelte.ts view source

SidebarStateOptions import type {SidebarStateOptions} from '@fuzdev/fuz_app/ui/sidebar_state.svelte.js';

enabled?

Reactive getter that controls whether the sidebar is enabled. When supplied, overrides the internal enabled state — show_sidebar auto-returns false while the getter returns false.

type (): boolean

returns boolean

narrow_query?

Media query under which the sidebar overlays the content and starts closed. It feeds both AppShell's CSS media attribute and a MediaQuery, so it must be a full query with parentheses ('(max-width: 900px)', not 'max-width: 900px'). null disables narrow mode.

type string | null

default SIDEBAR_NARROW_QUERY_DEFAULT

sidebar_id?

The sidebar element's id. Set it when a toggle outside AppShell needs aria-controls — the relationship closing the overlay also follows to return focus when the opener didn't take it (Safari doesn't focus a clicked button). Omitted, AppShell generates one, which only its own toggle knows. Set, it assumes one shell per state: shells mounted at the same time with this state would render the same id.

type string

SIGNUP_FAIL_FLOOR_DISABLED_BANNER
#

auth/fail_floor.ts view source

"!!! SIGNUP FAILURE TIMING FLOOR DISABLED — test-only mode. A production server must never print this." import {SIGNUP_FAIL_FLOOR_DISABLED_BANNER} from '@fuzdev/fuz_app/auth/fail_floor.js';

The stderr line a process prints when the signup failure floor is off — the Rust spine's text.

SignupForm
#

ui/SignupForm.svelte view source

import SignupForm from '@fuzdev/fuz_app/ui/SignupForm.svelte';

redirect_on_signup?

Path to navigate to after a successful signup.

type string
optional default resolve('/')

SignupInput
#

auth/signup_route_schema.ts view source

value + type

{ username: string; password: string; email?: string | null | undefined; } import {SignupInput} from '@fuzdev/fuz_app/auth/signup_route_schema.js';

Input for POST /signup. email is optional (absent or null = no email) and must match any referenced invite.

username

type string

password

type string

email?

type string | null

SignupOutput
#

auth/signup_route_schema.ts view source

value + type

{ ok: true; account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; } import {SignupOutput} from '@fuzdev/fuz_app/auth/signup_route_schema.js';

Output for POST /signup.

Session cookie is the operative side effect. The returned account and actor mirror BootstrapOutput so cross-process per-test setup can read the per-test identity straight off the signup response.

ok

type true

account

type { id: string & $brand<"Uuid">; username: string; }

actor

type { id: string & $brand<"Uuid">; }

SignupRouteOptions
#

auth/signup_routes.ts view source

SignupRouteOptions import type {SignupRouteOptions} from '@fuzdev/fuz_app/auth/signup_routes.js';

Per-factory configuration for signup route specs.

inheritance

signup_ip_rate_limiter

Rate limiter for signup attempts, keyed by client IP. Pass null to disable. Its own instance, not login's: signup failures bound username *enumeration* (the 403/409 split), not credential guessing, and an open-signup deployment lets any unauthenticated caller spend this budget — which must not cost anyone their login attempts. Charged by every classified failure and by every successful open signup (an invite-gated success spends an invite instead); never refunded on success (see RateLimiter.reset). Mirrors the Rust spine's signup_ip_rate_limiter.

type RateLimiter | null

signup_account_rate_limiter

Rate limiter for signup attempts, keyed by submitted username. Pass null to disable.

type RateLimiter | null

signup_fail_floor_ms?

Minimum wall-clock time (ms) for signup denial responses (403 / 409). Set to 0 or a negative number to disable (e.g., in tests) — announced once on stderr (SIGNUP_FAIL_FLOOR_DISABLED_BANNER). Default DEFAULT_SIGNUP_FAIL_FLOOR_MS. 429 responses are not floored.

type number

signup_fail_jitter_ms?

Uniform jitter window (±ms) layered on the floor. Set to 0 to disable jitter while keeping the floor. Default DEFAULT_SIGNUP_FAIL_JITTER_MS.

type number

SignupRouteShapeOptions
#

auth/signup_route_schema.ts view source

SignupRouteShapeOptions import type {SignupRouteShapeOptions} from '@fuzdev/fuz_app/auth/signup_route_schema.js';

Option inputs that shape the signup route metadata (not its handler).

signup_account_rate_limited

Whether a per-account signup rate limiter is wired — toggles rate_limit.

type boolean

SimpleRequestCrossTestOptions
#

testing/cross_backend/simple_request.ts view source

SimpleRequestCrossTestOptions import type {SimpleRequestCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/simple_request.js';

Options for the simple-request parity suite.

inheritance

login_path?

REST login route path. Default /api/account/login (the spine convention).

type string

readonly

logout_path?

REST logout route path. Default /api/account/logout (the spine convention).

type string

readonly

socket_status_to_async_status
#

actions/socket.svelte.ts view source

(status: SocketStatus, revoked: boolean): AsyncStatus import {socket_status_to_async_status} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Project SocketStatus onto fuz_util's AsyncStatus — the 5-way → 4-way mapping every consumer re-derives to surface connection state to UI (loading indicators, retry banners). Collapses reconnecting into failure (UI shows "lost, retrying") and splits closed by revoked so a terminal session-revocation read as failure while a clean client- initiated close reads as initial (the "not connected, not trying" state). A superseded close reads as initial too — the client is not trying and an explicit connect() reopens it; read FrontendWebsocketClient.superseded to tell the user why, and before connecting on an initial status without the user asking (connecting a superseded client on its own closes a newer socket in turn, which is the loop the close code exists to stop). A WS_CLOSE_PERMISSION_REVOKED close reads as initial the same way, with neither flag set: check FrontendWebsocketClient.last_close_code before connecting on your own, since without the role the upgrade is refused at the gate.

status

revoked

whether the session has been permanently revoked (typically FrontendWebsocketClient.revoked)

type boolean

returns

AsyncStatus

SocketCloseContext
#

actions/ws_socket_session.ts view source

SocketCloseContext import type {SocketCloseContext} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

Context passed to the on_socket_close hook.

Fires after the registry has removed the connection — connection_id and identity are the values captured at open, so consumer cleanup reads them from here, not from the registry. Fires for both client-initiated closes (Hono onClose) and server-initiated closes — a revocation, the per-account connection cap's eviction, and a heartbeat timeout all call ws.close(), which triggers Hono's onClose. Fires only for a socket that was admitted: one refused at admission never ran on_socket_open, so it has no close hook either.

ws

The WebSocket context the close event carried. Not necessarily the object on_socket_open received — an adapter may build one per event — so key per-socket state on connection_id.

type WSContext<unknown>

connection_id

Connection id captured at open time.

type Uuid

identity

Auth identity captured at open time — the registry no longer holds it.

type Readonly<ConnectionIdentity>

SocketErrorHandler
#

actions/socket.svelte.ts view source

SocketErrorHandler import type {SocketErrorHandler} from '@fuzdev/fuz_app/actions/socket.svelte.js';

(call)

type (event: Event): void

event

type Event
returns void

SocketMessageHandler
#

actions/socket.svelte.ts view source

SocketMessageHandler import type {SocketMessageHandler} from '@fuzdev/fuz_app/actions/socket.svelte.js';

(call)

type (event: MessageEvent<any>): void

event

type MessageEvent<any>
returns void

SocketOpenContext
#

actions/ws_socket_session.ts view source

SocketOpenContext import type {SocketOpenContext} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

Context passed to the on_socket_open hook.

Fires once the connection is admitted — registered in the registry and its credential re-checked (so connection_id is valid, and broadcasts reach it) — and before any client message dispatches. A socket refused at admission never reaches it. Consumers use this to bootstrap per-socket domain state — e.g. spawning a per-account unit and pushing an initial state snapshot.

ws

The raw WebSocket context — exposed for edge cases; prefer notify for sends. A frame sent on it directly bypasses the connection's outbound budget and its ordering with queued frames.

type WSContext<unknown>

connection_id

Connection id assigned by ConnectionRegistry.register_pending.

type Uuid

identity

Auth identity registered for this connection. The registry holds its own copy, so this record is not what close targets match on.

type Readonly<ConnectionIdentity>

notify

Send a JSON-RPC notification to just this socket. Mirrors ctx.notify on per-message handler contexts — same socket-scoped semantics.

type (method: string, params: unknown): void

method

type string

params

type unknown
returns void

signal

Fires when this socket ends — the client closed it, or the server did (revocation, cap eviction, heartbeat timeout), at the moment the server closes rather than when the close handshake lands. Threaded through to every handler's ctx.signal.

type AbortSignal

SocketStatus
#

actions/socket.svelte.ts view source

SocketStatus

type "initial" | "connecting" | "connected" | "reconnecting" | "closed"

import type {SocketStatus} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Client-side WebSocket status.

  • initial — never connected; connect() has not been called.
  • connecting — WebSocket readyState === CONNECTING.
  • connected — WebSocket readyState === OPEN.
  • reconnecting — close fired; waiting out backoff before next attempt.
  • closed — socket is not open, and nothing is scheduled to reopen it. Terminal only when revoked is true; otherwise connect() reopens, including after a superseded or permission-revoked close.

soft_delete_test_actor
#

testing/db_entities.ts view source

(db: Db, actor_id: string): Promise<boolean> import {soft_delete_test_actor} from '@fuzdev/fuz_app/testing/db_entities.js';

Soft-delete (tombstone) one actor via the production query_actor_soft_delete — returns true when an active row flipped. The TS twin of the Rust query_actor_soft_delete the guard tests use, for seeding the latent multi-actor tombstone state directly (no per-actor-delete RPC ships yet).

db

type Db

actor_id

type string

returns

Promise<boolean>

SPACE_CELL_KIND
#

spawn_backend
#

testing/cross_backend/spawn_backend.ts view source

(config: BackendConfig): Promise<BackendHandle> import {spawn_backend} from '@fuzdev/fuz_app/testing/cross_backend/spawn_backend.js';

Spawn config.start_command and return a handle once the binary is health-probe-ready and the daemon-token file is readable.

Errors at any stage SIGTERM the child group before rethrowing — the caller never sees a half-started backend.

config

returns

Promise<BackendHandle>

throws

  • Error - when `config.base_url`'s port already accepts connections, before anything is spawned; when the child exits before it is healthy (with its exit code or signal and stderr tail); or when the health probe or the daemon-token read times out

SpecSource
#

actions/action_codegen.ts view source

SpecSource import type {SpecSource} from '@fuzdev/fuz_app/actions/action_codegen.js';

One source in a multi-source consumer's namespace map. ns is the local alias used inside the generated file; module is the import path; specs is the runtime spec array. create_namespace_qualifier consumes a list of these.

ns

type string

module

type string

specs

type readonly ActionSpecUnion[]

SPINE_ACCOUNT_BOOTSTRAP_PATH
#

SPINE_ACCOUNT_LOGIN_PATH
#

SPINE_ACCOUNT_LOGOUT_PATH
#

SPINE_ACCOUNT_PASSWORD_PATH
#

SPINE_ACCOUNT_PATH_PREFIX
#

testing/cross_backend/spine_surface_constants.ts view source

"/api/account" import {SPINE_ACCOUNT_PATH_PREFIX} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Mount prefix of the account REST routes (login, logout, password, status, signup, bootstrap) on both spine binaries. The SPINE_ACCOUNT_*_PATH constants below are the routes under it the suites drive.

SPINE_ACCOUNT_SIGNUP_PATH
#

SPINE_ACCOUNT_STATUS_PATH
#

SPINE_BODY_LIMIT_DEFAULT_BYTES
#

testing/cross_backend/spine_surface_constants.ts view source

number import {SPINE_BODY_LIMIT_DEFAULT_BYTES} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

The request-body cap both spines ship by default, 1 MiB (DEFAULT_BODY_LIMIT_BYTES, the same name in TS and in fuz_http). Kept here as the cross-impl contract value rather than imported from either impl — the boundary the body-size suites pin is the shared default, and a change to either is a deliberate cross-impl decision that would revisit them regardless.

SPINE_CELL_EDITOR_ROLE
#

testing/cross_backend/spine_surface_constants.ts view source

"cell_editor" import {SPINE_CELL_EDITOR_ROLE} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

App role the role-shaped-cell_grant cross suite exercises. Registered with no grant path (grant_paths: []) so it stays a valid registry member without entering the admin / self-service grant flows — holders are seeded directly via extra_accounts. Must match the cell_editor entry in the Rust stub's RoleRegistry (which the cell spec set also reads) — a cross-language test contract.

SPINE_EXPECTED_SCHEMA_URL
#

testing/cross_backend/spine_surface_constants.ts view source

URL import {SPINE_EXPECTED_SCHEMA_URL} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Committed expected-schema fixture for the spine /ready deploy gate — the column map a fresh full spine bootstrap (auth + cell + fact) produces. Resolved relative to this module so the spawned TS binary (which imports this source under its loader) reads it off disk via node:fs. Regenerated + guarded by src/test/cross_backend/spine_expected_schema.db.test.ts.

The Rust testing_spine_stub reads the same committed file (its absolute path passed via env by rust_spine_stub_backend_config) — column-presence is engine-portable, so one fixture is the cross-impl contract.

SPINE_PARTICIPANT_ROLE
#

testing/cross_backend/spine_surface_constants.ts view source

"participant" import {SPINE_PARTICIPANT_ROLE} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Admin-grantable app role the role-gated-participation cross suite exercises. Registered with grant_paths: ['admin'] so it enters the admin grant flow — the cross-backend proof that an app-defined role is conferrable (offer / role_grant_assign) admin-only on both spines. Must match the participant entry in the Rust stub's RoleRegistry (which the cell spec set also reads) — a cross-language test contract. Distinct from SPINE_CELL_EDITOR_ROLE (no grant path): this one is the *grantable* role, that one is the bootstrap-seed-only cell role.

SPINE_ROLE_GATED_WS_PATH
#

testing/cross_backend/spine_surface_constants.ts view source

"/api/gated/ws" import {SPINE_ROLE_GATED_WS_PATH} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Mount path of the spine binaries' second, role-gated WebSocket endpoint — the same actions as the ungated /api/ws, behind SPINE_ROLE_GATED_WS_ROLES. Both spines mount it (the TS binary sharing /api/ws's transport, the Rust stub its connection registry), so one account can hold a gated and an ungated socket side by side. Driven by describe_ws_role_gated_cross_tests, behind capabilities.ws_role_gated. Must match the Rust testing_spine_stub's gated mount — a cross-language test contract.

SPINE_ROLE_GATED_WS_ROLES
#

testing/cross_backend/spine_surface_constants.ts view source

readonly string[] import {SPINE_ROLE_GATED_WS_ROLES} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

The roles SPINE_ROLE_GATED_WS_PATH requires, any-of: admin, the gate a privileged-broadcast socket uses, and participant, a role that can be granted scoped — so the suite can revoke a scoped grant of a listed role and see nothing close. Must match the Rust testing_spine_stub's gated mount.

spine_roles
#

testing/cross_backend/default_spine_surface.ts view source

RoleSchemaResult import {spine_roles} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

The spine's closed role registry: built-ins plus two app roles — SPINE_CELL_EDITOR_ROLE (no grant path; the role-shaped-cell_grant suite's bootstrap-seeded role) and SPINE_PARTICIPANT_ROLE (grant_paths: ['admin']; the role-gated-participation suite's admin-grantable role). Threaded into the cell spec set's role-validity gate and the auth grantability gates; the same membership is in the Rust stub's RoleRegistry (which the cell spec set also reads). The participant entry also gives the admin suite real app-role grant-path coverage (admin_account_list.grantable_roles carries it on both spines).

spine_rpc_endpoints
#

testing/cross_backend/default_spine_surface.ts view source

(ctx: AppServerContext, options?: SpineRpcEndpointsOptions | undefined): RpcEndpointSpec[] import {spine_rpc_endpoints} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

Factory-form RPC endpoints over the per-test ctx.deps. create_app_server (in the binary) owns live dispatch; the surface builder invokes the factory once with a stub ctx for setup-time path/method lookup, so the handler closures are never called across the process boundary.

Test binaries append their own _testing_reset action to this endpoint's actions (see testing/cross_backend/testing_reset_actions.ts); it is intentionally excluded here so it stays off the declared surface (the harness calls it directly over the daemon-token channel).

options.notification_sender, when supplied, reaches the role-grant-offer sub-factory so the spine emits the WS notification family — see SpineRpcEndpointsOptions.

ctx

options?

optional

returns

RpcEndpointSpec[]

SPINE_RPC_PATH
#

spine_session_options
#

testing/cross_backend/default_spine_surface.ts view source

SessionOptions<string>

type SessionOptions<string>

import {spine_session_options} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

Session config — cookie name matches the binary's issued session cookie (fuz_session) so cookie-attribute assertions + jar extraction line up.

SPINE_SSE_PATH
#

testing/cross_backend/spine_surface_constants.ts view source

"/api/admin/audit/stream" import {SPINE_SSE_PATH} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Audit-log SSE stream path — /api/admin prefix + the create_audit_log_route_specs /audit/stream route. Matches the default BackendConfig.sse_path and the cross-process SSE suite's default. Only mounted by the TS spine binary (which wires audit_log_sse); the shared surface stub leaves ctx.audit_sse null so the snapshot stays SSE-free.

SPINE_SUITES_REQUIRED_BACKDOORS
#

testing/cross_backend/spine_suites.ts view source

readonly ("_testing_reset" | "_testing_drain_effects" | "_testing_mint_session" | "_testing_put_fact" | "_testing_flood_notifications" | "_testing_schema_snapshot" | "_testing_migration_tracker" | "_testing_action_manifest" | "_testing_origin_check" | "_testing_shorten_credential_expiry")[]

type readonly TestingBackdoorMethod[]

import {SPINE_SUITES_REQUIRED_BACKDOORS} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

The backdoors describe_spine_cross_suites requires of every backend, since the suites share them: _testing_reset is every setup_test's per-test reset, and _testing_drain_effects the barrier every audit read waits on. A backend whose capabilities.testing_backdoors lacks one fails at collection.

SPINE_WS_PATH
#

SpineCrossSuitesOptions
#

testing/cross_backend/spine_suites.ts view source

SpineCrossSuitesOptions import type {SpineCrossSuitesOptions} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

surface_source

The declared surface — the standard bundle and rpc_adversarial enumerate it.

type AppSurfaceSpec

readonly

session_options

The backend's session config, for the standard bundle.

type SessionOptions<string>

readonly

rpc_endpoints

The backend's RPC endpoints, for the standard bundle.

type RpcEndpointsSuiteOption

readonly

live_rpc_endpoints?

The RPC endpoints the backend live-mounts, for the methods it keeps off the declared surface (cell verbs, opt-in resolvers, _testing_* backdoors); given, rpc_adversarial sends them bad envelopes and params too. A backend's whole live mount is a fine value — a declared method still runs once, from the declared surface.

type RpcEndpointsSuiteOption

readonly

roles?

The backend's role schema; given, the standard bundle runs its admin suite.

type RoleSchemaResult

readonly

setup?

Setup options every suite's setup_test starts from — a signup path the backend mounts elsewhere, reject_unknown_audit_metadata_keys for a backend's own spine suite. A suite's own seeding is added to it (the role, account and actor lists concatenate).

type CrossProcessSetupOptions

readonly

exclude?

Suites this backend doesn't run, each with a non-empty reason. Each shows in the run as one skipped test carrying the reason. Not testing_backdoor, nor a suite that already skips itself for an undeclared backdoor.

type Readonly<Partial<Record<SuiteName, string>>>

readonly

exclude_cases?

Single tests this backend doesn't run, by suite, each by its name path (SuiteCaseExclusions) with a non-empty reason; each shows in the run as skipped with its reason. An entry naming a suite the call doesn't run, testing_backdoor, or a path no collected test of that suite has, fails.

type Readonly<Partial<Record<"cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | "body_size" | "body_size_smuggling" | "cell_grant_role" | "cell_grant_steward" | "cell_moderate" | "cell_ref_admission" | "cookie_attributes" | "credential_deadline" | "credential_header_robustness" | "identity_parity" | "origin_conformance" | "origin_scope" | "peer_ping_ws" | "query_shape" | "role_grant_offer_enumeration" | "role_grant_offer_lifecycle" | "role_grant_offer_notification_ws" | "role_grant_participation" | "role_grant_role_grammar" | "rpc_adversarial" | "session_cap" | "session_cookie_codec" | "simple_request" | "sse_round_trip" | "standard" | "testing_backdoor" | "token_lifetime" | "token_scope_surface" | "transport_composition" | "ws_connection_cap" | "ws_envelope" | "ws_inbound_dispatch" | "ws_outbound_overflow" | "ws_round_trip", Readonly<Record<string, string>>>>>

readonly

overrides?

Per-suite option overrides, a shallow merge over what the handle gives.

type SuiteOverrides

readonly

suite_roles?

The roles the role-shaped suites use, when the backend lacks the spine's own.

type SpineSuiteRoles

readonly

ws_required_roles?

The roles the backend's WS upgrade on ws_path requires (an admin-gated /api/ws: [ROLE_ADMIN]). Given, the suites that open sockets for accounts they create — credential_deadline, session_cap, ws_round_trip — create them holding these (ws_account_roles), and role_grant_offer_notification_ws expects revoking one to close the socket (ws_required_roles). Each must be admin-grantable.

type readonly string[]

readonly

rpc_max_body_bytes?

The backend's request-body cap on rpc_path, in bytes, when it isn't the spine default (SPINE_BODY_LIMIT_DEFAULT_BYTES): the boundary body_size, body_size_smuggling and transport_composition's RPC over-cap row probe.

type number

readonly

ws_max_message_bytes?

The backend's inbound WebSocket message cap on ws_path, in bytes, when it isn't the spine default (DEFAULT_WS_MAX_MESSAGE_BYTES): the boundary ws_round_trip's oversized-message case sends past.

type number

readonly

rpc_success_fixtures?

Forwarded to the standard bundle (StandardCrossProcessTestOptions.rpc_success_fixtures).

type RpcSuccessFixtures

readonly

rpc_skip_methods?

Forwarded to the standard bundle (StandardCrossProcessTestOptions.rpc_skip_methods).

type string[]

readonly

rpc_input_overrides?

Forwarded to the standard bundle (StandardCrossProcessTestOptions.rpc_input_overrides) and to rpc_adversarial (RpcAdversarialCrossTestOptions.input_overrides), where each override seeds the method's params cases. Those cases are sent as a single-actor principal, so an override omits acting. A key naming a method on neither the declared surface nor live_rpc_endpoints throws at collection.

type Map<string, Record<string, unknown>>

readonly

SpineRpcEndpointsOptions
#

testing/cross_backend/default_spine_surface.ts view source

SpineRpcEndpointsOptions import type {SpineRpcEndpointsOptions} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

Options for spine_rpc_endpoints.

notification_sender?

WS notification sender threaded into the role-grant-offer sub-factory for server-initiated fan-out (role_grant_offer_received / _accepted / _declined / _retracted / _supersede, flat role_grant_revoke).

Shared-instance trap. Pass the SAME ConnectionRegistry instance the WS endpoint registers connections against — the registry *is* the fan-out target, so a separate instance would fan out to an empty registry and reach nobody (silently). The TS spine binary constructs one ConnectionRegistry and threads it both here and into register_action_ws.

Omitted (the default) for the shared create_spine_surface_spec path — surface generation doesn't depend on it, and it must stay absent there so the declared snapshot is unaffected.

type NotificationSender | null

readonly

SpineRpcSuccessFixturesOptions
#

SpineStubBackendConfigOptions
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

SpineStubBackendConfigOptions import type {SpineStubBackendConfigOptions} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

port?

Listening port. Default RUST_SPINE_STUB_DEFAULT_PORT.

type number

readonly

database_url?

Postgres connection URL. Default $FUZ_TESTING_RUST_SPINE_STUB_DATABASE_URL, then RUST_SPINE_STUB_DEFAULT_DATABASE_URL.

type string

readonly

name?

Backend name — labels the spawn in errors and logs, and names its directory under os.tmpdir() (default_test_backend_paths, namespaced by the running repo), which holds the bootstrap token, the daemon token and the fact store. Default RUST_SPINE_STUB_DEFAULT_NAME. Give each stub spawned alongside another its own name, so those files don't collide.

type string

readonly

binary_path?

Prebuilt binary path. Overrides the FUZ_TESTING_RUST_SPINE_STUB_BIN env var. When neither is set the preset throws.

type string

readonly

enable_login_rate_limit?

Enable the per-IP + per-account login rate limiters on the stub (FUZ_LOGIN_RATE_LIMIT_ENABLED=true). Off by default — the standard cross suites fire many loopback logins a live limiter would 429. Set true only for the dedicated login-security cross project (global_setup_login_security.ts). Pair with trusted_proxies so the limiter keys on the resolved X-Forwarded-For client IP. Mirrors TsSpineBackendConfigOptions.enable_login_rate_limit.

type boolean

readonly

trusted_proxies?

Comma-separated trusted-proxy allowlist passed as FUZ_TRUSTED_PROXIES (e.g. '127.0.0.1,::1'). Unset, the stub trusts the loopback set (127.0.0.1, ::1), as the TS spine binary does unconditionally, so a loopback caller's X-Forwarded-For names the client; ',' trusts none. The login-security project passes the loopback set explicitly.

type string

readonly

enable_action_rate_limit?

Enable the action rate limiters (FUZ_ACTION_RATE_LIMIT_ENABLED=true) — one per-IP and one per-account limiter shared by the RPC and WS endpoints. Off by default, like enable_login_rate_limit; set only for the dedicated security cross project, which drives them through ws_action_rate_limit.ts. Mirrors TsSpineBackendConfigOptions.enable_action_rate_limit.

type boolean

readonly

action_rate_limit_max_attempts?

Replace both action limiters' max_attempts (FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS) so a suite reaches the cap in a few calls. Requires enable_action_rate_limit — the binary refuses to boot otherwise. Mirrors TsSpineBackendConfigOptions.action_rate_limit_max_attempts.

type number

readonly

audit_sse_keep_alive_ms?

Replace the audit stream's keep-alive interval (FUZ_AUDIT_SSE_KEEP_ALIVE_MS). Mirrors TsSpineBackendConfigOptions.audit_sse_keep_alive_ms.

type number

readonly

ws_receive_timeout_ms?

Replace the WS endpoints' receive timeout (FUZ_WS_RECEIVE_TIMEOUT_MS). Mirrors TsSpineBackendConfigOptions.ws_receive_timeout_ms.

type number

readonly

db_checkout_timeout_ms?

Replace the request pool checkout bound (FUZ_DB_CHECKOUT_TIMEOUT_MS). Mirrors TsSpineBackendConfigOptions.db_checkout_timeout_ms.

type number

readonly

db_audit_checkout_timeout_ms?

Replace the audit write pool checkout bound (FUZ_DB_AUDIT_CHECKOUT_TIMEOUT_MS). Mirrors TsSpineBackendConfigOptions.db_audit_checkout_timeout_ms.

type number

readonly

SpineSuiteRoles
#

testing/cross_backend/spine_suites.ts view source

SpineSuiteRoles import type {SpineSuiteRoles} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

The roles the role-shaped suites use, for a backend whose registry lacks the spine's own. Set them here rather than in overrides: the call seeds the holder each needs.

cell_grant?

The role cell_grant_role's grants name, seeded on its holder at reset. Default SPINE_CELL_EDITOR_ROLE. ROLE_KEEPER serves a backend with only the builtin roles; ROLE_ADMIN can't (an admin reaches every cell).

type string

readonly

conferral?

The admin-grantable role role_grant_offer_lifecycle offers and role_grant_participation confers. Default SPINE_PARTICIPANT_ROLE; ROLE_ADMIN serves a backend with only the builtin roles (the scoped assign case then skips, as a builtin is global-only).

type string

readonly

SSE_CONNECTED_COMMENT
#

realtime/sse_constants.ts view source

": connected\n\n" import {SSE_CONNECTED_COMMENT} from '@fuzdev/fuz_app/realtime/sse_constants.js';

The comment line written immediately on SSE stream open. Flushes headers + confirms the connection is live before the first real event. Cross-process SSE tests assert the stream emits this on connect.

SSE_FRAME_READ_TIMEOUT_MS
#

SSE_KEEP_ALIVE_COMMENT
#

realtime/sse_constants.ts view source

": keepalive\n\n" import {SSE_KEEP_ALIVE_COMMENT} from '@fuzdev/fuz_app/realtime/sse_constants.js';

The comment line an idle SSE stream writes every SSE_KEEP_ALIVE_INTERVAL_MS, so a proxy that cuts a connection idle past its read timeout (nginx's default is 60 s) keeps an open stream open. EventSource ignores comments.

SSE_KEEP_ALIVE_INTERVAL_MS
#

realtime/sse_constants.ts view source

15000 import {SSE_KEEP_ALIVE_INTERVAL_MS} from '@fuzdev/fuz_app/realtime/sse_constants.js';

How often an SSE stream writes SSE_KEEP_ALIVE_COMMENT — 15 s, a quarter of nginx's default 60 s read timeout. Per stream, from its open, whether or not data frames were written in between.

SSE_KEEP_ALIVE_INTERVAL_MS_MAX
#

realtime/sse_constants.ts view source

2147483647 import {SSE_KEEP_ALIVE_INTERVAL_MS_MAX} from '@fuzdev/fuz_app/realtime/sse_constants.js';

The longest keep-alive interval a timer honors — 2³¹ − 1 ms, about 24.8 days.

SSE_OUTBOUND_QUEUE_BYTES
#

realtime/sse_constants.ts view source

number import {SSE_OUTBOUND_QUEUE_BYTES} from '@fuzdev/fuz_app/realtime/sse_constants.js';

The byte budget of what one SSE stream holds for its client to read (realtime/sse.ts module doc, "Outbound bound").

1 MiB — equal to the default cap on one request body (DEFAULT_BODY_LIMIT_BYTES). An audit row is bounded by the request that produced it, so on a server that keeps that default the budget holds a burst of ordinary rows or one at the largest. The value is fixed and does not follow the server's body limit. Same value as the Rust spine's SSE_OUTBOUND_QUEUE_BYTES.

SseFrameReader
#

testing/transports/sse_frame_reader.ts view source

SseFrameReader import type {SseFrameReader} from '@fuzdev/fuz_app/testing/transports/sse_frame_reader.js';

Frame-level reader returned by create_sse_frame_reader.

read_frame

Read one complete SSE frame (up to the next \n\n), without the trailing terminator. Throws if the per-read timeout elapses or the stream ends before a frame arrives.

type (timeout_ms?: number | undefined): Promise<string>

timeout_ms?

type number
optional
returns Promise<string>

wait_for_close

Drain until the server closes the stream. Resolves true if the stream closes within timeout_ms, false on timeout.

type (timeout_ms?: number | undefined): Promise<boolean>

timeout_ms?

type number
optional
returns Promise<boolean>

cancel

Cancel the underlying reader. Safe to call when already closed.

type (): Promise<void>

returns Promise<void>

SseNotification
#

realtime/sse.ts view source

SseNotification import type {SseNotification} from '@fuzdev/fuz_app/realtime/sse.js';

Notification shape aligned with JSON-RPC 2.0.

Uses {method, params} to match the JSON-RPC notification format.

method

Notification method name (e.g. 'run_created', 'host_updated').

type string

params

Method-specific payload.

type unknown

SseRegistry
#

realtime/sse_registry.ts view source

import {SseRegistry} from '@fuzdev/fuz_app/realtime/sse_registry.js';

Generic registry of SSE streams with channel-based filtering and identity-keyed disconnection.

Subscribers connect with optional channel filters, a capped scope, and uncapped groups. Broadcasts go to a specific channel and reach only matching subscribers. close_by_identity force-closes all subscribers whose scope or groups contain the given key — use for auth revocation.

Two-phase registration

A stream is authorized once, at open, so a revocation has to be able to reach it from the moment its credential is read. A route's gates run before the stream exists, and each revocation closes only the streams *registered* under what it revoked — registering after the gates leaves a window in which a revocation closes nothing and the stream then opens on a dead session, or under a role its actor no longer holds.

So a route that authenticated a revocable credential registers in two phases: subscribe_pending registers the subscription un-admitted — closeable by every close_by_identity, and otherwise inert: it has no stream, so no broadcast reaches it, the cap neither counts nor evicts for it, and count does not see it (pending_count does). The route then re-reads the credential and the role, and admit attaches the stream and opens the feed — or refuses when a revocation closed the registration in between, evicting nothing. The cap's eviction happens at admission.

subscribe stays as the one-step form for a stream with nothing to revoke behind it. The twin of the Rust fuz_realtime SseRegistry (subscribe_pending / admit / subscribe).

Shutdown

close_all — the close AppServer.close runs — closes every stream, pending or admitted, and leaves the registry closing for good: every later registration is born closed. subscribe_pending registers nothing, so admit refuses the handle and the route's stream ends after its connect comment; subscribe closes the stream it is given at once. A stream still opening when the server shuts down therefore ends rather than outliving it. The flag is set in the same synchronous step that closes the live streams.

generics

SseRegistry<T>
T

examples

const registry = new SseRegistry<SseNotification>(); // subscriber connects (from SSE endpoint) const unsubscribe = registry.subscribe(stream, {channels: ['runs']}); // when a run changes registry.broadcast('runs', {method: 'run_created', params: {run}}); // subscriber disconnects unsubscribe();
// scope = session hash (capped), groups = [account id] (close-only) const unsubscribe = registry.subscribe(stream, { channels: ['audit_log'], scope: session_hash, groups: [account_id], }); // coarse — close all of a user's streams on role revocation registry.close_by_identity(account_id); // fine — close just the stream(s) tied to a specific session registry.close_by_identity(session_hash);
// two-phase, for a route behind a revocable credential const pending = registry.subscribe_pending({scope: session_hash, groups: [account_id]}); if (!(await credential_is_still_live())) { pending.unsubscribe(); return unauthorized(); } const {response, stream} = create_sse_response(c, log); // first: a client that already left closes the stream, its listener runs at // once, and `admit` then refuses the removed registration stream.on_close(pending.unsubscribe); if (!registry.admit(pending, stream)) { stream.close(); // the body is the connect comment alone } else { // expiry writes nothing a revocation closes on: end at the credential's deadline stream.on_close( schedule_credential_deadline(get_resolved_auth(c)?.credential_deadline ?? null, () => stream.close() ) ); } return response;

constructor

type new <T>(options?: SseRegistryOptions | undefined): SseRegistry<T>

options?

optional

subscribe

Add a subscriber, admitted at once — the one-step form of subscribe_pending + admit.

Not for a route that authenticated a revocable credential: a revocation landing between that route's gates and this call closes nothing, and the stream would open on a dead credential (class doc, "Two-phase registration").

Once the registry is closing, the subscriber is born closed: stream is closed at once, nothing is registered, and the returned function is a no-op.

type (stream: SseStream<T>, options?: SubscribeOptions | undefined): () => void

stream

SSE stream to send data to

type SseStream<T>

options?

channel filter and identity slots (scope + groups)

optional
returns () => void

unsubscribe function

mutates

  • registry — adds the new subscriber; closes oldest matching subscribers when `max_per_scope` is exceeded

subscribe_pending

Register a subscription un-admitted — phase one of the two-phase registration (class doc).

The registration is closeable at once by every close_by_identity matching its scope or groups, and until admit it receives no broadcast, is not counted, and evicts nothing. A caller that then refuses the request calls the returned handle's unsubscribe.

Once the registry is closing, the registration is born closed: nothing is registered, and admit refuses the handle.

type (options?: SubscribeOptions | undefined): PendingSubscription

options?

channel filter and identity slots (scope + groups)

optional

the pending handle admit takes

mutates

  • registry — adds the un-admitted registration, unless the registry is closing

admit

Admit a pending subscription, attaching the stream it delivers to — phase two (class doc).

If the registration is gone — a revocation or the shutdown closed it while it was pending, it was born closed, or it was unsubscribed — nothing is evicted and the subscription is refused. Otherwise the per-scope cap is enforced as in subscribe, counting only admitted subscribers, and the stream starts receiving broadcast data. Admitting twice is refused the second time.

type (pending: PendingSubscription, stream: SseStream<T>): boolean

pending

the handle subscribe_pending returned

stream

SSE stream to send data to

type SseStream<T>
returns boolean

false when the subscription was not admitted — the caller ends the stream

mutates

  • registry — attaches `stream`; closes oldest matching subscribers when `max_per_scope` is exceeded

broadcast

Broadcast data to all admitted subscribers on a channel.

Subscribers with no channel filter receive all broadcasts. Subscribers with a channel filter only receive matching broadcasts. A registration pending admission is not an open stream and is skipped — nothing is queued toward a subscriber not yet authorized.

type (channel: string, data: T): void

channel

type string

data

type T
returns void

close_by_identity

Force-close all subscribers whose scope or groups match the given key.

Closes each matching stream and removes the subscriber from the registry. Use for auth revocation — when a user's permissions change, close their SSE connections so they must reconnect and re-authenticate.

Matches every registration, pending or admitted — never filter on admission here: reaching a registration in flight is the point of the pending phase, and admit then refuses it.

A stream whose close throws does not stop the loop: every match is removed and closed, and the first error is thrown afterward.

type (identity: string): number

identity

the identity key to match (checked against scope and groups)

type string
returns number

the number of subscribers closed

throws

  • the - first error a stream's `close` threw, after every match was attempted

mutates

  • registry — removes matching subscribers and closes their streams

close_all

Force-close every subscriber, pending or admitted — the shutdown close AppServer.close runs through the registry's ConnectionCloser — and leave the registry closing, for good: every later registration is born closed (class doc, "Shutdown"). A second call closes nothing.

A stream whose close throws does not stop the loop: every subscriber is removed and closed, and the first error is thrown afterward.

type (): number

returns number

the number of subscribers closed

throws

  • the - first error a stream's `close` threw, after every subscriber was attempted

mutates

  • registry — sets the registry closing, removes every subscriber, and closes its stream

count

Number of open — admitted — subscribers. A registration still pending admission is counted by pending_count instead.

type number

getter

pending_count

Number of registrations awaiting admission — between subscribe_pending and admit (or their refusal). For telemetry and tests.

type number

getter

closing

Whether close_all has run — the registry is shutting down and every registration from now on is born closed (class doc, "Shutdown"). The audit stream route reads it to skip the re-reads for a registration admission will refuse anyway.

type boolean

getter

SseRegistryOptions
#

realtime/sse_registry.ts view source

SseRegistryOptions import type {SseRegistryOptions} from '@fuzdev/fuz_app/realtime/sse_registry.js';

Options for SseRegistry.

max_per_scope?

Max admitted subscribers sharing a single scope. When a subscriber is admitted and the count of admitted subscribers with the same scope has reached this limit, the oldest matching subscriber(s) are closed first. null (default) disables the cap. groups identities are never capped.

type number | null

log?

Logger for a stream whose close throws while the cap evicts it — the evicted stream is another subscriber's, so its failure is logged rather than failing the admission that evicted it. Unset falls back to an [sse] logger; null silences it.

type Logger | null

SseResponseOptions
#

realtime/sse.ts view source

SseResponseOptions import type {SseResponseOptions} from '@fuzdev/fuz_app/realtime/sse.js';

Options for create_sse_response.

account_id?

The account the stream belongs to, named in the warning logged when the stream ends on its outbound budget.

type string | null

keep_alive_ms?

How often the stream writes SSE_KEEP_ALIVE_COMMENT, in ms; null writes none. Checked by validate_sse_keep_alive_ms.

type number | null

default SSE_KEEP_ALIVE_INTERVAL_MS

SseRouteTestOptions
#

testing/sse_round_trip.ts view source

SseRouteTestOptions import type {SseRouteTestOptions} from '@fuzdev/fuz_app/testing/sse_round_trip.js';

session_options

Session config for cookie-based auth.

type SessionOptions<string>

create_route_specs

Route spec factory — same shape as production.

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

app_options?

Optional overrides for AppServerOptions.

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

db_factories?

Database factories to run tests against. Default: the default factory (create_default_db_factory) only.

type DbFactory[]

on_audit_event?

Backend audit event callback — threaded to create_test_app_server. Use to wire a close-on-revoke guard for consumer SSE registries (e.g. create_revocation_guard(create_sse_connection_closer(registry, role), log)) so session_revoke_all closes the tested streams — not needed for a registry the route joined to ctx.deps.connection_closer, which the revoking handler closes itself.

type (event: AuditLogEvent): void

event

returns void

rpc_endpoints

RPC endpoint specs — required so the close-on-revoke assertion can dispatch account_session_revoke_all via RPC (there is no REST equivalent). Hard-fails via require_rpc_endpoint_path on setup.

Accepts either an array (eager) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> — the factory form is required when action handlers must close over the per-test ctx.deps. The factory must return the same endpoint path regardless of ctx — it is invoked once at setup with a stub ctx for path lookup and again per-test by create_app_server for live dispatch.

type RpcEndpointsSuiteOption

routes

SSE routes to exercise.

type SseRouteTestSpec[]

SseRouteTestSpec
#

testing/sse_round_trip.ts view source

SseRouteTestSpec import type {SseRouteTestSpec} from '@fuzdev/fuz_app/testing/sse_round_trip.js';

Config for a single SSE route under test.

path

Full HTTP path of the SSE endpoint (e.g., '/api/zap/subscribe').

type string

trigger

Fire an event matching one of the declared event_specs that should reach the open stream. Called after the : connected comment is observed. The triggered frame must be a JSON-serializable {method, params} payload.

type (ctx: { test_app: TestApp; account: TestAccount; }): Promise<void>

ctx

type { test_app: TestApp; account: TestAccount; }
returns Promise<void>

event_specs?

Event specs to validate the triggered payload against. When omitted, the payload is only asserted to be well-formed {method, params}.

type EventSpec[]

assert_closes_on_revoke?

Whether to assert the stream closes after session_revoke_all. Default true. Set false for endpoints whose registry no revocation reaches — neither joined to the backend's closer nor guarded (leaves a TODO to fix, rather than silently passing).

type boolean

SseStream
#

realtime/sse.ts view source

SseStream<T> import type {SseStream} from '@fuzdev/fuz_app/realtime/sse.js';

Generic SSE stream controller interface.

Transport-agnostic — works with any serializable type.

generics

SseStream<T = unknown>
T
default unknown

send

Send data to the client as a JSON SSE event. Never waits: a frame the stream's outbound budget does not fit ends the stream instead (module doc, "Outbound bound").

type (data: T): void

data

type T
returns void

close

Close the stream.

type (): void

returns void

on_close

Register a listener called when the stream closes (client disconnect or explicit close). Called at once when the stream has already closed — a client can leave before the caller registers — so a cleanup registered through it always runs.

type (fn: () => void): void

fn

type () => void
returns void

SseTransport
#

testing/transports/sse_transport.ts view source

SseTransport import type {SseTransport} from '@fuzdev/fuz_app/testing/transports/sse_transport.js';

A cross-process SSE client: read frames, await server close, cancel.

read_frame

Read one complete SSE frame (up to the next \n\n), without the trailing terminator. Throws if the per-read timeout elapses or the stream ends before a frame arrives.

type (timeout_ms?: number | undefined): Promise<string>

timeout_ms?

type number
optional
returns Promise<string>

wait_for_close

Drain until the server closes the stream. Resolves true if the stream closes within timeout_ms, false on timeout. The signal for a revocation dropping a live stream — mirrors WsClient.wait_for_close.

type (timeout_ms?: number | undefined): Promise<boolean>

timeout_ms?

type number
optional
returns Promise<boolean>

close

Cancel the reader (client-initiated close). Safe to call when already closed.

type (): Promise<void>

returns Promise<void>

SseTransportOptions
#

testing/transports/sse_transport.ts view source

SseTransportOptions import type {SseTransportOptions} from '@fuzdev/fuz_app/testing/transports/sse_transport.js';

Construction options for create_sse_transport.

base_url

Base URL the binary is reachable at — e.g. http://localhost:1178.

type string

readonly

sse_path

SSE endpoint path on the binary (e.g. /api/admin/audit/stream).

type string

readonly

cookies

Session cookie values (full Set-Cookie strings as FetchTransport.cookies() returns them) threaded onto the request Cookie header. Without these the stream is anonymous and the connect is refused (the audit stream requires an admin session).

type readonly string[]

readonly

origin?

Origin header for the request. Backends running with ALLOWED_ORIGINS=http://localhost:* accept http://localhost:<port>. Defaults to base_url — acceptable because cross-process tests always run against localhost.

type string

readonly

default_timeout_ms?

Default per-read / wait-for-close timeout. Falls back to 2000ms.

type number

readonly

STANDALONE_CROSS_SUITES
#

StandaloneSuiteName
#

StandardAdminIntegrationTestOptions
#

testing/admin_integration.ts view source

StandardAdminIntegrationTestOptions import type {StandardAdminIntegrationTestOptions} from '@fuzdev/fuz_app/testing/admin_integration.js';

setup_test

Per-test fixture-producing function. The admin suite calls this in every test() body — auth_integration_truncate_tables clears account, so each test re-bootstraps the keeper.

type (): Promise<TestFixture>

returns Promise<TestFixture>

surface_source

App surface (with route specs + middleware specs) for route iteration and error-coverage scoping. Constructed in TS by the consumer (same shape for in-process and cross-process tests).

type AppSurfaceSpec

session_options

Session config — needed for cookie_name + factory-form rpc_endpoints resolution.

type SessionOptions<string>

roles

Role schema result from create_role_schema() — used to determine valid/invalid/web-grantable roles.

type RoleSchemaResult

rpc_endpoints

RPC endpoint specs — the source RpcAction arrays. Required; role_grant grant/revoke are RPC-only and the suite hard-fails without them.

type RpcEndpointsSuiteOption

admin_prefix?

Path prefix where admin routes are mounted (e.g., '/api/admin'). Used by the 401/403 error-coverage probe to scope to fuz_app admin routes only, avoiding app-specific admin-gated routes that may use stub deps. Default '/api/admin'.

type string

StandardAttackSurfaceOptions
#

testing/attack_surface.ts view source

StandardAttackSurfaceOptions import type {StandardAttackSurfaceOptions} from '@fuzdev/fuz_app/testing/attack_surface.js';

Options for the standard attack surface test suite.

build

Build the app surface bundle (surface + route specs + middleware specs).

type (): AppSurfaceSpec

snapshot_path

Absolute path to the committed snapshot JSON file.

type string

expected_public_routes

Expected public routes, e.g. ['GET /health', 'POST /api/account/login'].

type string[]

expected_api_middleware

Expected middleware names for API routes, e.g. ['trusted_proxy', 'origin', 'session', 'request_context', 'bearer_auth'].

type string[]

roles

All roles in the app (e.g. ['admin', 'keeper']).

type string[]

api_path_prefix?

Path prefix for middleware stack assertion. Default '/api/'.

type string

security_policy?

Security policy configuration. Omit for sensible defaults.

type SurfaceSecurityPolicyOptions

error_schema_tightness?

Error schema tightness assertion config. Defaults to default_error_schema_tightness (ignores 401/403/429, min_specificity: 'enum', allowlist seeded with fuz_app_stock_route_tightness_allowlist).

Consumer-supplied allowlist and ignore_statuses are additive — the suite merges them underneath the stock defaults, so project-specific entries don't need to re-list fuz_app's own stock routes. Pass a narrower config to extend either list or tighten min_specificity; pass null to skip the assertion and keep the audit log informational-only.

type ErrorSchemaTightnessOptions | null

skip_routes?

Routes the three adversarial sub-suites skip, in 'METHOD /path' form — the escape hatch every sibling suite carries (round_trip, data_exposure). Reach for it only when a route cannot be driven generically at all; a route whose path params merely carry a format is handled by the params-schema-aware synthesizer.

type string[]

StandardCrossProcessTestOptions
#

testing/cross_backend/standard.ts view source

StandardCrossProcessTestOptions import type {StandardCrossProcessTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/standard.js';

Configuration for describe_standard_cross_process_tests.

The in-process suites' shared options minus their in-process-only knobs (create_route_specs, rate_limiting_app_options).

setup_test

Per-test fixture-producing function.

type (): Promise<TestFixture>

returns Promise<TestFixture>

surface_source

App surface. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

capabilities

Backend capability declarations. Forwarded to the one bundled suite that gates on them (describe_standard_integration_tests); the other suites in the bundle read no flags and take no capability field.

type BackendCapabilities

session_options

Session config — needed for cookie_name + factory-form rpc_endpoints resolution.

type SessionOptions<string>

rpc_endpoints

RPC endpoint specs — required. The standard integration tests drive account_verify, account_session_*, account_token_* through the RPC surface (and admin tests, when wired, drive role_grant grant/revoke through it too).

type RpcEndpointsSuiteOption

roles?

Role schema result from create_role_schema(). When provided, the admin integration suite is included.

type RoleSchemaResult

admin_prefix?

Path prefix where admin routes are mounted. Default '/api/admin'.

type string

error_coverage_min?

Forwarded to describe_standard_integration_tests — overrides the default error-coverage threshold on the scoped REST surface. Set to 0 to skip the assertion entirely.

type number

round_trip_skip_routes?

Forwarded to describe_round_trip_validation as skip_routes ('METHOD /path' keys). For consumer REST routes whose responses aren't JSON-with-an-output-schema and so can't be round-tripped — e.g. fuz_forge's git smart-HTTP routes (git-upload-pack / git-receive-pack / info/refs) which stream git protocol bytes.

type string[]

data_exposure_skip_routes?

Forwarded to describe_data_exposure_tests as skip_routes ('METHOD /path' keys), as the in-process bundle's consumers pass it directly.

type string[]

rpc_success_fixtures?

Forwarded to describe_rpc_round_trip_tests as success_fixtures (method name → async params factory). Drives a populated success body for referential RPC reads (*_get, *_log) the nil-id round-trip can only ever error on, and validates it against the method's output schema on each backend — the success-shape parity check. See RpcRoundTripTestOptions.success_fixtures.

type RpcSuccessFixtures

rpc_skip_methods?

Forwarded to describe_rpc_round_trip_tests as skip_methods — the consumer escape hatch for a method the generic round-trip can't drive at all (a handler whose test wiring stubs out the deps it reaches, or reads tables the suite's database doesn't migrate). Each entry hides the method from both the crash check and the envelope check, so name the reason beside it.

type string[]

rpc_input_overrides?

Forwarded to describe_rpc_round_trip_tests as input_overrides (method name → params) — for a method whose generated params can't reach its handler cleanly. The response is held to the same rules as a generated one.

type Map<string, Record<string, unknown>>

rest_success_fixtures?

Forwarded to describe_round_trip_validation as success_fixtures ('METHOD /path' → async {url?, body?} factory) for referential REST routes. See RoundTripTestOptions.success_fixtures.

type Map<string, (fixture: TestFixture) => Promise<{ url?: string | undefined; body?: Record<string, unknown> | undefined; }>>

StandardIntegrationTestOptions
#

testing/integration.ts view source

StandardIntegrationTestOptions import type {StandardIntegrationTestOptions} from '@fuzdev/fuz_app/testing/integration.js';

setup_test

Per-test fixture-producing function. The integration suite calls this in every test() body — auth_integration_truncate_tables clears account, so each test re-bootstraps the keeper.

type (): Promise<TestFixture>

returns Promise<TestFixture>

surface_source

App surface (with route specs + middleware specs) for route iteration and error-coverage scoping. The same shape feeds both in-process and cross-process tests — the test process always constructs the spec in TS (via create_test_app_surface_spec or a consumer's equivalent); cross-process-ness is a property of the transport + per-test fixture, not the schema source. The on-disk auth_attack_surface.json is an observability artifact for human inspection + gen-time drift gating, not the source the test runtime reads from.

type AppSurfaceSpec

capabilities

Backend capability declarations for capability-gated cases.

type BackendCapabilities

session_options

Session config — needed to resolve factory-form rpc_endpoints against a stub AppServerContext at setup time and to read cookie_name for manual cookie composition in the session cases.

type SessionOptions<string>

rpc_endpoints

RPC endpoint specs — required. This suite dispatches account_verify, account_session_*, and account_token_* via rpc_call_for_spec (the /api/account/verify REST route is a status-only nginx shim with no payload). Hard-fails via require_rpc_endpoint_path on setup so consumer projects see a clear setup error instead of confusing test failures.

Accepts either an array (eager) or a factory — see testing/rpc_helpers.ts for the union semantics. The factory must return the same endpoint path regardless of ctx — invoked once at setup with a stub ctx for path lookup; the running backend handles live dispatch.

type RpcEndpointsSuiteOption

error_coverage_min?

Minimum error-coverage ratio to enforce on the scoped REST surface (login / logout / password / signup + the shared RPC endpoint). Default DEFAULT_INTEGRATION_ERROR_COVERAGE (0.2). Set to 0 to skip the assertion entirely — useful for consumers with minimal route sets whose declared error codes outpace the suite's denial-path drivers.

type number

StandardRpcActionsDeps
#

auth/standard_rpc_actions.ts view source

StandardRpcActionsDeps import type {StandardRpcActionsDeps} from '@fuzdev/fuz_app/auth/standard_rpc_actions.js';

Dependencies for create_standard_rpc_actions.

Stack-standard RevokingActionFactoryDeps (log, audit, connection_closer) plus an optional notification_sender consumed only by the role-grant-offer sub-factory for WS fan-out. Admin and account sub-factories ignore notification_sender.

inheritance

notification_sender?

type NotificationSender | null

StandardRpcActionsOptions
#

auth/standard_rpc_actions.ts view source

StandardRpcActionsOptions import type {StandardRpcActionsOptions} from '@fuzdev/fuz_app/auth/standard_rpc_actions.js';

Options for create_standard_rpc_actions.

Composes AdminActionOptions (roles), RoleGrantOfferActionOptions (roles, default_ttl_ms, authorize), and AccountActionOptions (max_tokens). roles is shared between admin and role-grant-offer — the caller supplies it once and the helper threads the same reference to both.

inheritance

start_auth_cleanup
#

auth/cleanup.ts view source

(deps: AuthCleanupDeps, options?: AuthCleanupScheduleOptions): PeriodicTask import {start_auth_cleanup} from '@fuzdev/fuz_app/auth/cleanup.js';

Run run_auth_cleanup beside the server: a pass now, then one every interval_ms, until stop is called.

The auth surface's upkeep in one call — start it once migrations have run and stop it before the database closes (see the module doc's § Scheduling). create_app_server's auth_cleanup option does both.

Built on start_periodic, which fixes the loop's rules: the startup pass starts before this returns, passes never overlap, a slow pass delays the schedule rather than bursting, a failed pass — the database out of reach — is logged at warn with its error and retried on the next interval, the timer is unref'd, and stop ends the schedule at once and resolves when a pass in progress has finished. A pass that swept something logs its counts at info, and one that closed a swept session's connections logs that count too.

Twin of the Rust spawn_auth_cleanup over fuz_sys::periodic::spawn_periodic. The schedule is the same; shutdown differs — the Rust task drops a pass in progress when its token is cancelled, which a promise cannot do.

deps

what a pass needs; backend.deps in a server

options?

the interval

optional default {}

returns

PeriodicTask

the schedule's stop

throws

  • Error - if `interval_ms` is not a positive finite number or exceeds `PERIODIC_INTERVAL_MS_MAX` — at the call site, before any pass starts

start_daemon_token_rotation
#

testing/daemon_token_rotation.ts view source

(runtime: DaemonTokenWriteDeps & FsRemoveDeps, deps: QueryDeps, options: DaemonTokenRotationOptions, log: Logger): Promise<...> import {start_daemon_token_rotation} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Start daemon token rotation.

Generates an initial token, writes it to disk, resolves the keeper account, and sets up periodic rotation. Returns the mutable state object and a stop function.

runtime

runtime with file and remove capabilities

deps

query dependencies for resolving keeper account

options

rotation configuration

log

the logger instance

type Logger

returns

Promise<DaemonTokenRotation>

rotation state and stop function

mutates

  • filesystem — writes the token file on each rotation; `stop` removes it

start_fail_floor
#

auth/fail_floor.ts view source

(floor_ms: number, jitter_ms: number): Promise<void> import {start_fail_floor} from '@fuzdev/fuz_app/auth/fail_floor.js';

Start the denial floor: a promise resolving floor_ms ± jitter_ms from now, or at once when floor_ms is 0 or less (the floor disabled).

Call it before the denial path's work and await it after, so the observed response time is max(work, delay), not work + delay. The timer starts at the call. Uniform jitter keeps a stable clamp point from leaking when a path occasionally exceeds the floor; Math.random suffices, since only the exact delay must be unpredictable.

floor_ms

the floor

type number

jitter_ms

the jitter window on either side of the floor; 0 or less for none

type number

returns

Promise<void>

a promise resolving when the floor has passed

start_periodic
#

periodic.ts view source

(name: string, interval_ms: number, pass: () => Promise<unknown>, log: Logger): PeriodicTask import {start_periodic} from '@fuzdev/fuz_app/periodic.js';

Run pass now, then once per interval_ms, until stop is called.

  • Startup pass. The first pass starts before this returns; later passes follow one interval_ms apart, start to start.
  • A slow pass delays the schedule. Passes never overlap, and a pass that overruns its interval pushes the next one back: the next pass starts when the slow one ends, the one after a full interval_ms later — no burst of catch-up passes.
  • A failed pass is logged, never fatal. A pass that throws or rejects is logged at warn with name and the error object itself — so the log carries its stack and its cause — and the loop goes on to the next interval, so an outage is retried rather than ending the upkeep for the life of the process.
  • stop finishes the pass in progress. It ends the schedule at once and resolves when a pass in progress has finished; nothing is dropped mid-flight.

The timer is unref'd where the runtime hands back a timer object (Node, Bun), so a task nobody stopped does not hold the process open.

pass is called once per pass and returns that pass's promise. name labels the task in the failure log.

Twin of the Rust spawn_periodic. The schedule is that loop's MissedTickBehavior::Delay. Shutdown differs: the Rust task drops a pass in progress when its token is cancelled, so joining it is prompt and a pass must be safe to drop; here the pass runs to its end and stop waits for it.

name

labels the task in the failure log

type string

interval_ms

milliseconds between the start of one pass and the start of the next

type number

pass

one pass; called once per pass, its resolved value is ignored

type () => Promise<unknown>

log

where a failed pass is logged

type Logger

returns

PeriodicTask

the task's stop

throws

  • Error - if `interval_ms` is not a positive finite number or exceeds `PERIODIC_INTERVAL_MS_MAX` — at the call site, before any pass starts

start_testing_server
#

testing/cross_backend/testing_server_core.ts view source

(options: StartTestingServerOptions): Promise<void> import {start_testing_server} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Boot a test-mode server using the supplied runtime adapter.

Mirrors a production start_server at the surface level — stale-daemon check, daemon-info write, bind, graceful shutdown — but the app is the caller's no-domain (or domain) StartTestingServerOptions.build_app and the runtime boundary is the TestingServerAdapter. Refuses any non-loopback bind host (the test binary must stay on loopback — see is_loopback_host).

options

returns

Promise<void>

StartTestingServerOptions
#

testing/cross_backend/testing_server_core.ts view source

StartTestingServerOptions import type {StartTestingServerOptions} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

adapter

Runtime-boundary adapter (Node or Deno).

type TestingServerAdapter

daemon_name

Daemon-info namespace — the app name the daemon.json is written under (e.g. 'fuz_app_spine'). The cross-process harness reads the daemon token from the rotation file, not this; daemon.json is for stale-process detection + parity with production daemon lifecycle.

type string

host

Bind host (e.g. 'localhost').

type string

port

Bind port.

type number

app_version?

App version recorded in daemon.json.

type string

build_app

Build the app. Closes over the entry's runtime + connection-IP getter + password deps + resolved config — so this core never touches the domain. Returns the assembled app and a close teardown.

type (context: BuildTestingAppContext): Promise<BuiltTestingApp>

context

returns Promise<BuiltTestingApp>

log?

Optional logger; defaults to a [daemon_name]-namespaced Logger.

type Logger

StatResult
#

runtime/deps.ts view source

StatResult import type {StatResult} from '@fuzdev/fuz_app/runtime/deps.js';

Result of a stat operation.

is_file

type boolean

is_directory

type boolean

size?

Byte length of a regular file. Meaningful only when is_file is true; for directories it is runtime-defined (real OS stat reports the directory entry's on-disk size, not 0 — only create_mock_runtime reports 0). Populated by every runtime factory (create_node_runtime / create_deno_runtime / create_mock_runtime); optional so loose test stubs that only assert is_file / is_directory don't have to supply it. Callers that need an exact size (e.g. a streaming upload's Content-Length) read it from a real runtime, where it is always present.

type number

mtime_ms?

Last-modification time in epoch milliseconds, when the runtime reports it. Populated by create_node_runtime / create_deno_runtime; create_mock_runtime omits it (so a mock-backed sweep treats every temp as unknown-age and never reaps). Optional so loose test stubs that only assert is_file / is_directory don't have to supply it. The orphan-temp sweep (db/fact_disk_storage.ts) reads it to age out stale .tmp spill files.

type number

STDERR_TAIL_MAX_BYTES
#

testing/cross_backend/spawn_backend.ts view source

number import {STDERR_TAIL_MAX_BYTES} from '@fuzdev/fuz_app/testing/cross_backend/spawn_backend.js';

How much of the child's stderr is kept (bytes) — the tail a startup failure or an unexpected exit reports. Bounded so a backend that logs to stderr for a whole suite doesn't grow the runner's memory with it.

stream_fact_to_disk
#

db/fact_disk_storage.ts view source

(deps: Pick<FactDiskStorageDeps, "mkdir" | "rename" | "fsync" | "write_file_stream" | "remove">, facts_dir: string | undefined, source: ReadableStream<Uint8Array<ArrayBufferLike>>, max_bytes: number, embedded_threshold: number): Promise<...> import {stream_fact_to_disk} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Stream source to storage with bounded memory: hash BLAKE3 incrementally, buffer in memory until the bytes cross embedded_threshold, then spill the buffer + remaining chunks through a temp file and atomically land it in the disk CAS. Peak heap is O(chunk + embedded_threshold), never O(artifact), so a multi-GB upload never buffers in RAM.

  • Embedded vs disk. A body <= embedded_threshold stays in memory and is returned as {kind: 'embedded'} for the PG bytes column. Above it (with a facts_dir), the buffer + remaining chunks spill to <facts_dir>/.tmp/…, then rename into <facts_dir>/<shard>/<rest> once the hash is known — {kind: 'disk'}. A body over the threshold with facts_dir === undefined throws FactPayloadTooLargeError (matches PgFactStore.put).
  • Cap enforcement. Aborts with FactPayloadTooLargeError the moment the running byte count passes max_bytes — the mid-stream backstop for a chunked or mis-declared Content-Length.
  • Disk-full. An ENOSPC from the temp-file write surfaces as FactStorageFullError.

deps

type Pick<FactDiskStorageDeps, "mkdir" | "rename" | "remove" | "write_file_stream" | "fsync">

facts_dir

type string | undefined

source

type ReadableStream<Uint8Array<ArrayBufferLike>>

max_bytes

type number

embedded_threshold

type number

returns

Promise<StreamFactToDiskResult>

mutates

  • facts_dir filesystem

StreamFactToDiskResult
#

db/fact_disk_storage.ts view source

StreamFactToDiskResult import type {StreamFactToDiskResult} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Outcome of streaming an upload to storage: the blake3:-prefixed fact hash, the byte count, and where the bytes landed. PgFactStore.put_stream turns this into the fact row insert.

hash

type FactHash

size

type number

placement

type StreamPlacement

StreamPlacement
#

db/fact_disk_storage.ts view source

StreamPlacement

type { kind: "embedded"; bytes: Uint8Array<ArrayBufferLike>; } | { kind: "disk"; external_url: string & $brand<"FileFactUrl">; }

import type {StreamPlacement} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Where a streamed body landed — embedded carries the in-memory bytes (under the embedded threshold, bound for the PG fact.bytes column); disk means the bytes are already at <facts_dir>/<shard>/<rest> and the row carries the file: URL.

stub
#

testing/stubs.ts view source

any import {stub} from '@fuzdev/fuz_app/testing/stubs.js';

Throwing stub — use for deps that should never be reached.

stub_handler
#

testing/stubs.ts view source

(): Response import {stub_handler} from '@fuzdev/fuz_app/testing/stubs.js';

Stub handler that returns a 200 response.

returns

Response

stub_mw
#

testing/stubs.ts view source

(_c: any, next: any): Promise<void> import {stub_mw} from '@fuzdev/fuz_app/testing/stubs.js';

Stub middleware that passes through.

_c

type any

next

type any

returns

Promise<void>

stub_password_deps
#

testing/app_server.ts view source

PasswordHashDeps import {stub_password_deps} from '@fuzdev/fuz_app/testing/app_server.js';

Fast password stub for tests that don't exercise login/password flows.

Hashes are deterministic (stub_hash_<password>) and verify correctly, so auth bootstrap and session creation work without Argon2 overhead.

StubUpgrade
#

testing/ws_round_trip.ts view source

StubUpgrade import type {StubUpgrade} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

The return of create_stub_upgrade — fake upgradeWebSocket + factory and upgrade capture.

upgradeWebSocket

type UpgradeWebSocket<unknown, any, WSEvents<unknown>>

get_create_events

The events factory of the most recently mounted endpoint, for tests that build events from a hand-made context. Throws when no endpoint was mounted.

type (): (c: Context<any, any, {}>) => WSEvents<unknown> | Promise<WSEvents<unknown>>

returns (c: Context<any, any, {}>) => WSEvents<unknown> | Promise<WSEvents<unknown>>

upgrades

Every upgrade request that reached a stub route, in arrival order — what app.request(path, {headers: {upgrade: 'websocket', ...}}) leaves behind once the app's middleware passed it. Build the socket's events with record.create_events(record.c).

type readonly StubUpgradeRecord[]

StubUpgradeRecord
#

testing/ws_round_trip.ts view source

StubUpgradeRecord import type {StubUpgradeRecord} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

One upgrade that reached a create_stub_upgrade route.

c

The real Hono Context the request carried through the app's middleware chain — session, request context, client IP, and every other value the middleware set.

type Context<any, any, {}>

create_events

The events factory of the endpoint the upgrade reached.

type (c: Context<any, any, {}>): WSEvents<unknown> | Promise<WSEvents<unknown>>

c

type Context<any, any, {}>
returns WSEvents<unknown> | Promise<WSEvents<unknown>>

SubscribeOptions
#

realtime/sse_registry.ts view source

SubscribeOptions import type {SubscribeOptions} from '@fuzdev/fuz_app/realtime/sse_registry.js';

Options for SseRegistry.subscribe and subscribe_pending.

channels?

Channels to subscribe to. Empty/absent = all channels.

type readonly string[]

scope?

Primary (capped) identity — e.g., session hash. Subject to max_per_scope and matched by close_by_identity.

type string

groups?

Grouping identities — e.g., account id. Matched by close_by_identity but NOT subject to the cap. Use for coarse-targeted close.

type readonly string[]

Subscriber
#

realtime/sse_registry.ts view source

Subscriber<T> import type {Subscriber} from '@fuzdev/fuz_app/realtime/sse_registry.js';

One admitted subscriber, as SseRegistry.broadcast sees it.

generics

Subscriber<T>
T

stream

type SseStream<T>

channels

Channels this subscriber listens to. null means all channels.

type Set<string> | null

scope

Primary (capped) identity. null when none.

type string | null

groups

Grouping identities for close_by_identity. null when none.

type Set<string> | null

SUITE_NAMES
#

testing/cross_backend/spine_suites.ts view source

readonly ["account_lifecycle", "actor_lookup", "app_settings", "body_size", "body_size_smuggling", "cell_crud", "cell_gated_create", "cell_grant_role", "cell_grant_steward", ... 34 more ..., "ws_round_trip"] import {SUITE_NAMES} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

Every shipped suite describe_spine_cross_suites runs, each named by its module under testing/cross_backend/ (ws_round_trip is describe_cross_process_ws_tests, standard the standard bundle). Closed: a guard test holds it, with STANDALONE_CROSS_SUITES, to every module that exports a describe_* suite, so a new suite fails until it is registered.

SuiteAppOptions
#

testing/app_server.ts view source

Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

type Partial<Omit<AppServerOptions, "backend" | "session_options" | "create_route_specs" | "rpc_endpoints" | "bootstrap">>

import type {SuiteAppOptions} from '@fuzdev/fuz_app/testing/app_server.js';

app_options shape accepted by create_test_app and the DB-backed suite helpers. Excludes fields the helpers manage directly — backend / session_options / create_route_specs are constructed by the helper itself; rpc_endpoints and bootstrap live on top-level options so setup-time surface lookup and runtime dispatch read from one source of truth.

login_ip_rate_limiter?

Per-IP rate limiter for login + password change — the distributed-spray backstop. Omit or undefined to use a default limiter (5 attempts per 15 minutes) under rate_limiters: 'enforced', none under 'disabled_for_testing'. Pass null to explicitly disable rate limiting. Also available on AppServerContext for route factory callbacks.

One instance per auth surface, not one shared across all four. These buckets are monotone within their window — a success never refunds them (see RateLimiter.reset) — so a shared instance let a failure on any surface spend the budget that bounds guessing on every other one, and let one caller's exhaustion deny four routes at once. Pass the same limiter to two of these fields to opt back into a shared budget.

type RateLimiter | null

signup_ip_rate_limiter?

Per-IP rate limiter for signup. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter because an open-signup deployment lets any unauthenticated caller spend it. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

bootstrap_ip_rate_limiter?

Per-IP rate limiter for bootstrap. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter so a fumbled bootstrap token can't spend the operator's login budget. Wired directly into the factory-managed bootstrap route; also on AppServerContext for symmetry.

type RateLimiter | null

login_account_rate_limiter?

Per-account rate limiter for login attempts. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

signup_account_rate_limiter?

Per-account rate limiter for signup attempts, keyed by submitted username. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

action_ip_rate_limiter?

Per-IP rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'ip' or 'both'. Same limiter applies across transports — one budget per action. Omit or undefined for the mode's default (600 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

action_account_rate_limiter?

Per-account rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on the authenticated account's id, so every actor and credential of one account shares a budget. Omit or undefined for the mode's default (1200 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

allowed_origins?

Parsed allowed origin patterns, as parse_allowed_origins returns them (validate_server_env's allowed_origins); assembly throws on a matcher built any other way, since its wildcards can't be checked. Must not be empty — an empty list would refuse every browser request, so assembly throws.

type RegExp[]

allow_origin_wildcards?

Admit a wildcard allowed_origins entry (:*, a * label) — the operator's FUZ_ALLOW_ORIGIN_WILDCARDS=true, as validate_server_env reads it. Without it assembly throws on one, naming each entry and what it admits; with it each is logged at warn, on every assembly. Covers each ws_endpoints spec's own allowed_origins too. Classifies the matchers parse_allowed_origins built (http/origin_wildcard.ts); assembly throws on any other matcher, flag or not. Defaults to false.

type boolean

origin_carve_outs?

Paths the origin check skips, each with its reason. The check covers every path by default (ORIGIN_MIDDLEWARE_PATH) — a request with no Origin passes it anyway, so probes and CLI clients need no carve-out; one is for a path that refuses browser callers by other means. Copied at assembly, so a later change to this array changes nothing; assembly throws on a carve-out with no reason, or one that overlaps the auth middleware's scope (AUTH_MIDDLEWARE_PATH) — every route that reads or mints a credential, and every RPC, WS, and bootstrap endpoint, sits there (assert_origin_carve_outs_valid).

type MiddlewareCarveOut[]

proxy?

Trusted proxy options.

type { trusted_proxies: string[]; get_connection_ip: (c: Context<any, any, {}>) => string | undefined; }

rate_limiters?

What an omitted rate limiter becomes — 'enforced' (the default) builds a default instance for each *_rate_limiter option left out, 'disabled_for_testing' leaves each of them null. An explicit option (an instance or null) always wins over the mode. The disabled mode adds one surface warning, and prints a stderr banner once per module instance (announce_rate_limiters_disabled). Never 'disabled_for_testing' in production — see RateLimiterMode.

type RateLimiterMode

max_body_size?

Maximum allowed request body size in bytes, a safe integer 0 or more (anything else throws at assembly). Omit or undefined to use the default (1 MiB). Pass null to explicitly disable body size limiting, which adds a security warning to the surface's diagnostics (logged at startup).

type number | null

daemon_token_state?

Daemon token state for keeper auth. Omit to disable.

type DaemonTokenState

surface_route?

Mount GET /api/surface, which serves this server's generated AppSurface (every route, its auth, and its schemas) to an admin UI such as AdminSurface.svelte. Off by default — the Rust spine serves no such route, and a map of the API is nothing to publish unasked.

When mounted the route is admin-only (roles: ['admin'], so it takes the ?acting= selector) and a rule-3 token surface (required_scope: 'surface:app_surface'): a narrowed API token is refused it whatever its method list says. See server/surface_route.ts.

type boolean

default false

transform_middleware?

Optional: transform middleware specs before applying.

Receives shallow copies of the trusted-proxy spec followed by the auth stack, in a fresh array — mutating them leaves the originals untouched. Add layers anywhere (before the proxy spec, between it and the auth stack, between the auth layers, or after), but keep each of those specs — its handler, not just its name — mounted once at its original path ('*' for the proxy and the origin check, AUTH_MIDDLEWARE_PATH for the rest of the auth stack) in its original order: the endpoint auth-scope check (assert_endpoint_in_auth_scope) and client-IP rate limiting (get_client_ip) assume it, so assembly throws (assert_middleware_stack_mounted) if the transform moves, drops, duplicates, reorders, wraps, or replaces one. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access.

type (specs: MiddlewareSpec[]): MiddlewareSpec[]

specs

returns MiddlewareSpec[]

audit_log_sse?

Enable factory-managed audit log SSE.

When truthy, creates an AuditLogSse instance internally, registers the SSE listener via backend.deps.audit.add_listener (composing with the consumer's on_audit_event callback rather than rebuilding AppDeps), adds its registry to backend.deps.connection_closer so revocations close its streams, and auto-includes audit_log_event_specs in the surface. The result is exposed on AppServerContext (for route factories) and AppServer (for the caller), always typed as AuditLogSse | null — when this option is set, the field is non-null. Use require_audit_sse(ctx) to assert the invariant in route factories that depend on it.

Pass true for defaults (admin role), or {role: 'custom'} for a custom role; keep_alive_ms sets the streams' keep-alive interval (a test server shortens it; default SSE_KEEP_ALIVE_INTERVAL_MS). Omit to wire audit SSE manually.

type true | { role?: string | undefined; keep_alive_ms?: number | null | undefined; }

auth_cleanup?

Schedule the auth cleanup beside the server: start_auth_cleanup over backend.deps — a pass as assembly finishes, then one per interval — stopped by AppServer.close. Each pass deletes expired sessions and closes the connections they opened through deps.connection_closer (every WebSocket connection registry and audit stream this server mounts), and audits each newly expired role_grant offer once.

Off by default; a production server should turn it on. Without it nothing sweeps: expired session rows accumulate and an offer's expiry is never audited. Connections do not depend on it — each closes at its credential's deadline (auth/credential_deadline.ts), and the sweep's close repeats that one. The Rust spine's servers all schedule the twin (spawn_auth_cleanup).

Pass true for the default interval (DEFAULT_AUTH_CLEANUP_INTERVAL_MS), or {interval_ms} for another. Leave it off in an in-process test harness — a background pass deletes rows and writes audit rows mid-test — and in a process that runs run_auth_cleanup on a schedule of its own.

type boolean | AuthCleanupScheduleOptions

event_specs?

SSE event specs for surface generation. Defaults to [] (no SSE events).

type EventSpec[]

create_upgrade_websocket?

Returns the Hono runtime adapter's upgradeWebSocket helper for the assembled app. create_app_server calls it once, with the app it builds (the same instance as AppServer.app), after creating the app and before mounting ws_endpoints — and only when ws_endpoints resolves non-empty, so it is never called for a server with no WebSocket endpoints. Required in that case: assembly throws without it. One helper services every mounted endpoint.

A factory rather than the helper itself because Node's adapter needs the app: @hono/node-ws has no module-level helper, only createNodeWebSocket({app}), and create_app_server is what builds the app. Deno and Bun ignore the argument:

import {upgradeWebSocket} from '@hono/deno'; // or '@hono/bun' create_upgrade_websocket: () => upgradeWebSocket,

Node keeps the adapter so it can attach to the server after serve():

let node_ws: NodeWebSocket | undefined; const server = await create_app_server({ ..., create_upgrade_websocket: (app) => (node_ws = createNodeWebSocket({app})).upgradeWebSocket, }); node_ws?.injectWebSocket(serve({fetch: server.app.fetch, port}));

Test harnesses pass () => stub.upgradeWebSocket with create_stub_upgrade from testing/ws_round_trip.ts.

type (app: Hono<BlankEnv, BlankSchema, "/">): UpgradeWebSocket<unknown, any, WSEvents<unknown>>

app

type Hono<BlankEnv, BlankSchema, "/">
returns UpgradeWebSocket<unknown, any, WSEvents<unknown>>

ws_endpoints?

WebSocket endpoint specs — single source of truth for both surface generation *and* live dispatch. Each entry is auto-mounted via register_action_ws against the assembled Hono app on every runtime, so consumers don't call register_action_ws themselves.

An entry that omits actions serves the actions of the sole rpc_endpoints entry (to_ws_actions: the protocol actions first, then the RPC set), so one action set runs on both transports; assembly throws unless exactly one RPC endpoint resolves.

Accepts either an array (evaluated eagerly) or a factory (ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec> (evaluated after the server context is assembled). Use the factory form when action lists depend on ctx.deps — e.g. when spreading create_standard_rpc_actions({...ctx.deps, notification_sender}) over WS.

When non-empty, create_upgrade_websocket must be supplied (throws otherwise). A factory returning [] does NOT trip the check — feature-flag gated WS surfaces stay safe.

Every mount gets what the HTTP surface gets: the auth middleware (a path outside AUTH_MIDDLEWARE_PATH throws at assembly, like rpc_endpoints), the server's allowed_origins unless the spec narrows them (a spec's own list gets the same wildcard check, allow_origin_wildcards), and the same action_ip_rate_limiter / action_account_rate_limiter instances the RPC endpoints charge — one budget per action across both transports. The upgrade routes register after the route specs and before post_route_middleware and static_serving, so neither runs ahead of an upgrade.

Duplicate path values across two WsEndpointSpecs, and a path a GET RouteSpec already claims, throw at assembly before any endpoint mounts (Hono would silently shadow them otherwise).

Every mounted connection registry is added to deps.connection_closer, so the revocation handlers' post-commit closes reach its sockets, and so does the one revocation guard create_app_server registers on that closer (create_revocation_guard) when it mounts a WS endpoint or the audit stream. AppServer.close removes it again.

type readonly WsEndpointSpec[] | ((context: AppServerContext) => readonly WsEndpointSpec[])

env_schema?

Env schema for surface generation. Defaults to BaseServerEnv — pass an extended schema (typically BaseServerEnv.extend({...})) when the consumer adds app-specific env vars.

type ZodObject<$ZodLooseShape, $strip>

post_route_middleware?

Middleware applied after routes, before static serving. Included in surface.

type MiddlewareSpec[]

static_serving?

Static file serving. Omit if not serving static files.

type { serve_static: ServeStaticFactory; root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; }

await_pending_effects?

Await all pending fire-and-forget effects before returning the response. Use in tests so audit log assertions don't need polling. Default false (production: true fire-and-forget).

type boolean

on_effect_error?

Called when an eager pending effect rejects, on HTTP requests and on the frames of every WS endpoint the server mounts, in either await_pending_effects mode. Use for monitoring, metrics, or alerting.

type (error: unknown, context: EffectErrorContext): void

error

type unknown

context

returns void

env_values?

Env values for startup summary logging.

type Record<string, unknown>

SuiteCaseExclusions
#

testing/cross_backend/spine_suites.ts view source

Readonly<Partial<Record<"cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | ... 33 more ... | "ws_round_trip", Readonly<...>>>>

type Readonly<Partial<Record<SuiteName, Readonly<Record<string, string>>>>>

import type {SuiteCaseExclusions} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

Per-suite test exclusions: each test's name path — its describe titles and its own name, joined with ' > ', as the reporter prints it after the file (and after any describe wrapping the call) — mapped to why it can't run on this backend.

cell_crud?

type Readonly<Record<string, string>>

readonly

cell_relations?

type Readonly<Record<string, string>>

readonly

cell_gated_create?

type Readonly<Record<string, string>>

readonly

fact_serving?

type Readonly<Record<string, string>>

readonly

ready?

type Readonly<Record<string, string>>

readonly

ws_role_gated?

type Readonly<Record<string, string>>

readonly

account_lifecycle?

type Readonly<Record<string, string>>

readonly

origin?

type Readonly<Record<string, string>>

readonly

actor_lookup?

type Readonly<Record<string, string>>

readonly

app_settings?

type Readonly<Record<string, string>>

readonly

body_size?

type Readonly<Record<string, string>>

readonly

body_size_smuggling?

type Readonly<Record<string, string>>

readonly

cell_grant_role?

type Readonly<Record<string, string>>

readonly

cell_grant_steward?

type Readonly<Record<string, string>>

readonly

cell_moderate?

type Readonly<Record<string, string>>

readonly

cell_ref_admission?

type Readonly<Record<string, string>>

readonly

cookie_attributes?

type Readonly<Record<string, string>>

readonly

credential_deadline?

type Readonly<Record<string, string>>

readonly

credential_header_robustness?

type Readonly<Record<string, string>>

readonly

identity_parity?

type Readonly<Record<string, string>>

readonly

origin_conformance?

type Readonly<Record<string, string>>

readonly

origin_scope?

type Readonly<Record<string, string>>

readonly

peer_ping_ws?

type Readonly<Record<string, string>>

readonly

query_shape?

type Readonly<Record<string, string>>

readonly

role_grant_offer_enumeration?

type Readonly<Record<string, string>>

readonly

role_grant_offer_lifecycle?

type Readonly<Record<string, string>>

readonly

role_grant_offer_notification_ws?

type Readonly<Record<string, string>>

readonly

role_grant_participation?

type Readonly<Record<string, string>>

readonly

role_grant_role_grammar?

type Readonly<Record<string, string>>

readonly

rpc_adversarial?

type Readonly<Record<string, string>>

readonly

session_cap?

type Readonly<Record<string, string>>

readonly

session_cookie_codec?

type Readonly<Record<string, string>>

readonly

simple_request?

type Readonly<Record<string, string>>

readonly

sse_round_trip?

type Readonly<Record<string, string>>

readonly

standard?

type Readonly<Record<string, string>>

readonly

testing_backdoor?

type Readonly<Record<string, string>>

readonly

token_lifetime?

type Readonly<Record<string, string>>

readonly

token_scope_surface?

type Readonly<Record<string, string>>

readonly

transport_composition?

type Readonly<Record<string, string>>

readonly

ws_connection_cap?

type Readonly<Record<string, string>>

readonly

ws_envelope?

type Readonly<Record<string, string>>

readonly

ws_inbound_dispatch?

type Readonly<Record<string, string>>

readonly

ws_outbound_overflow?

type Readonly<Record<string, string>>

readonly

ws_round_trip?

type Readonly<Record<string, string>>

readonly

SuiteName
#

testing/cross_backend/spine_suites.ts view source

"cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | "body_size" | ... 32 more ... | "ws_round_trip"

type "cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "ready" | "ws_role_gated" | "account_lifecycle" | "origin" | "actor_lookup" | "app_settings" | "body_size" | "body_size_smuggling" | "cell_grant_role" | "cell_grant_steward" | "cell_moderate" | "cell_ref_admission" | "cookie_attributes" | "credential_deadline" | "credential_header_robustness" | "identity_parity" | "origin_conformance" | "origin_scope" | "peer_ping_ws" | "query_shape" | "role_grant_offer_enumeration" | "role_grant_offer_lifecycle" | "role_grant_offer_notification_ws" | "role_grant_participation" | "role_grant_role_grammar" | "rpc_adversarial" | "session_cap" | "session_cookie_codec" | "simple_request" | "sse_round_trip" | "standard" | "testing_backdoor" | "token_lifetime" | "token_scope_surface" | "transport_composition" | "ws_connection_cap" | "ws_envelope" | "ws_inbound_dispatch" | "ws_outbound_overflow" | "ws_round_trip"

import type {SuiteName} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

A shipped single-backend cross suite, named by its module.

SuiteOptionsMap
#

testing/cross_backend/spine_suites.ts view source

SuiteOptionsMap import type {SuiteOptionsMap} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

The options each suite takes, by SuiteName — what overrides entries are partials of.

account_lifecycle

type RpcPathCapabilityGatedCrossSuiteOptions

actor_lookup

type RpcPathCrossSuiteOptions

app_settings

type RpcPathCrossSuiteOptions

body_size

type BodySizeCrossTestOptions

body_size_smuggling

type BodySizeSmugglingCrossTestOptions

cell_crud

type RpcPathCapabilityGatedCrossSuiteOptions

cell_gated_create

type RpcPathCapabilityGatedCrossSuiteOptions

cell_grant_role

type CellGrantRoleCrossTestOptions

cell_grant_steward

type RpcPathCapabilityGatedCrossSuiteOptions

cell_moderate

type RpcPathCapabilityGatedCrossSuiteOptions

cell_ref_admission

type RpcPathCapabilityGatedCrossSuiteOptions

cell_relations

type RpcPathCapabilityGatedCrossSuiteOptions

cookie_attributes

type CookieAttributesCrossTestOptions

credential_deadline

type CredentialDeadlineCrossTestOptions

credential_header_robustness

type CredentialHeaderRobustnessCrossTestOptions

fact_serving

type FactServingCrossTestOptions

identity_parity

type IdentityParityCrossTestOptions

origin

type RpcPathCrossSuiteOptions

origin_conformance

type RpcPathCrossSuiteOptions

origin_scope

type OriginScopeCrossTestOptions

peer_ping_ws

type PeerPingWsTestOptions

query_shape

type RpcPathCapabilityGatedCrossSuiteOptions

ready

type ReadyCrossTestOptions

role_grant_offer_enumeration

type RoleGrantOfferEnumerationCrossTestOptions

role_grant_offer_lifecycle

type RoleGrantOfferLifecycleCrossTestOptions

role_grant_offer_notification_ws

type RoleGrantOfferNotificationWsTestOptions

role_grant_participation

type RoleGrantParticipationCrossTestOptions

role_grant_role_grammar

type RpcPathCrossSuiteOptions

rpc_adversarial

type RpcAdversarialCrossTestOptions

session_cap

type SessionCapCrossTestOptions

session_cookie_codec

type SessionCookieCodecCrossTestOptions

simple_request

type SimpleRequestCrossTestOptions

sse_round_trip

type CrossProcessSseTestOptions

standard

type StandardCrossProcessTestOptions

testing_backdoor

type TestingBackdoorCrossTestOptions

token_lifetime

type TokenLifetimeCrossTestOptions

token_scope_surface

type TokenScopeSurfaceCrossTestOptions

transport_composition

type TransportCompositionCrossTestOptions

ws_connection_cap

type WsConnectionCapCrossTestOptions

ws_envelope

type WsEnvelopeCrossTestOptions

ws_inbound_dispatch

type WsInboundDispatchCrossTestOptions

ws_outbound_overflow

type WsOutboundOverflowCrossTestOptions

ws_role_gated

type WsRoleGatedCrossTestOptions

ws_round_trip

type CrossProcessWsTestOptions

SuiteOverride
#

testing/cross_backend/spine_suites.ts view source

SuiteOverride<K>

type Partial<SuiteOptionsMap[K]> | ((derived: SuiteOptionsMap[K]) => Partial<SuiteOptionsMap[K]>)

import type {SuiteOverride} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

One suite's override: options shallow-merged over the ones the handle gives, or a function of those derived options returning them — for a change that builds on a derived value (a seeded setup_test, the capabilities) rather than restating it.

generics

SuiteOverride<K extends SuiteName>
K
constraint SuiteName

SuiteOverrides
#

testing/cross_backend/spine_suites.ts view source

SuiteOverrides import type {SuiteOverrides} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

Per-suite option overrides, each merged over the options the handle gives.

cell_crud?

type SuiteOverride

readonly

cell_relations?

type SuiteOverride

readonly

cell_gated_create?

type SuiteOverride

readonly

fact_serving?

type SuiteOverride

readonly

ready?

type SuiteOverride

readonly

ws_role_gated?

type SuiteOverride

readonly

account_lifecycle?

type SuiteOverride

readonly

origin?

type SuiteOverride

readonly

actor_lookup?

type SuiteOverride

readonly

app_settings?

type SuiteOverride

readonly

body_size?

type SuiteOverride

readonly

body_size_smuggling?

type SuiteOverride

readonly

cell_grant_role?

type SuiteOverride

readonly

cell_grant_steward?

type SuiteOverride

readonly

cell_moderate?

type SuiteOverride

readonly

cell_ref_admission?

type SuiteOverride

readonly

cookie_attributes?

type SuiteOverride

readonly

credential_deadline?

type SuiteOverride

readonly

credential_header_robustness?

type SuiteOverride

readonly

identity_parity?

type SuiteOverride

readonly

origin_conformance?

type SuiteOverride

readonly

origin_scope?

type SuiteOverride

readonly

peer_ping_ws?

type SuiteOverride

readonly

query_shape?

type SuiteOverride

readonly

role_grant_offer_enumeration?

type SuiteOverride

readonly

role_grant_offer_lifecycle?

type SuiteOverride

readonly

role_grant_offer_notification_ws?

type SuiteOverride

readonly

role_grant_participation?

type SuiteOverride

readonly

role_grant_role_grammar?

type SuiteOverride

readonly

rpc_adversarial?

type SuiteOverride

readonly

session_cap?

type SuiteOverride

readonly

session_cookie_codec?

type SuiteOverride

readonly

simple_request?

type SuiteOverride

readonly

sse_round_trip?

type SuiteOverride

readonly

standard?

type SuiteOverride

readonly

testing_backdoor?

type SuiteOverride

readonly

token_lifetime?

type SuiteOverride

readonly

token_scope_surface?

type SuiteOverride

readonly

transport_composition?

type SuiteOverride

readonly

ws_connection_cap?

type SuiteOverride

readonly

ws_envelope?

type SuiteOverride

readonly

ws_inbound_dispatch?

type SuiteOverride

readonly

ws_outbound_overflow?

type SuiteOverride

readonly

ws_round_trip?

type SuiteOverride

readonly

SuiteTaskRange
#

testing/cross_backend/spine_suites.ts view source

SuiteTaskRange import type {SuiteTaskRange} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

The top-level tasks one suite registered, as indices into the calling suite's tasks.

name

type SuiteName

readonly

start

type number

readonly

end

type number

readonly

SupersededOffer
#

auth/role_grant_offer_schema.ts view source

SupersededOffer import type {SupersededOffer} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

A superseded offer row annotated with the grantor's account_id.

Carried by superseded_offers in accept/revoke query results so callers can fan out role_grant_offer_supersede notifications to the grantor's sockets without a second round-trip. Populated via a CTE join on actor in the supersede UPDATE.

inheritance

from_account_id

type Uuid

surface_auth_summary
#

http/surface_query.ts view source

(surface: AppSurface): { none: number; authenticated: number; optional: number; role: Map<string, number>; credential: Map<string, number>; keeper: number; other: number; } import {surface_auth_summary} from '@fuzdev/fuz_app/http/surface_query.js';

Summarize route auth distribution across the surface.

Categorical view over the flat auth record — none, keeper, role and credential (counted per name), authenticated, optional, other. Keeper outranks a role, and a role outranks a channel gate, so the audit SSE stream (admin role, session channel) counts under role. Multi-role and multi-channel specs contribute one count per role / channel they admit.

surface

returns

{ none: number; authenticated: number; optional: number; role: Map<string, number>; credential: Map<string, number>; keeper: number; other: number; }

counts by auth category, with role and channel counts broken out by name

SurfaceExplorer
#

SurfaceRouteOptions
#

server/surface_route.ts view source

SurfaceRouteOptions import type {SurfaceRouteOptions} from '@fuzdev/fuz_app/server/surface_route.js';

Options for the surface explorer route.

surface

The generated app surface to serve — read per request, so a backfilled ref serves the final surface.

type AppSurface

SurfaceSecurityPolicyOptions
#

testing/surface_invariants.ts view source

SurfaceSecurityPolicyOptions import type {SurfaceSecurityPolicyOptions} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Configuration for security policy invariants.

All fields have sensible defaults. Pass overrides for project-specific needs.

sensitive_route_patterns?

Path patterns for routes that should be rate-limited. Default: common sensitive REST patterns (login, password, bootstrap). account_token_create lives on the RPC surface; per-method RPC rate limiting is a separate invariant if consumers want it.

type (string | RegExp)[]

public_mutation_allowlist?

Routes explicitly allowed to be public mutations (e.g., webhooks, bootstrap). Format: 'METHOD /path' (e.g., 'POST /api/account/login').

type string[]

keeper_route_prefixes?

Allowed path prefixes for keeper-protected routes. Default: ['/api/']. Catches keeper routes outside expected namespaces.

type string[]

sweep_orphan_temps
#

db/fact_disk_storage.ts view source

(deps: Pick<FactDiskStorageDeps, "stat" | "readdir" | "remove">, facts_dir: string, options?: { max_age_ms?: number | undefined; log?: Pick<Logger, "warn"> | undefined; } | undefined): Promise<...> import {sweep_orphan_temps} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Reap stale temp files left under <facts_dir>/.tmp/ by a hard crash (SIGKILL / OOM / host crash) mid-write — the finally cleanup in the writers above never ran. Removes .tmp entries whose mtime is older than max_age_ms (so an in-flight upload isn't yanked out from under itself). The TS twin of the Rust sweep_orphan_temps; call on startup + on an interval.

Best-effort: a missing .tmp/ dir (no oversize upload has ever run) is a no-op; a runtime that doesn't report mtime_ms (a mock) leaves every temp untouched; a per-file stat/remove failure is logged and skipped rather than aborting the sweep. Returns the count removed.

deps

type Pick<FactDiskStorageDeps, "readdir" | "stat" | "remove">

facts_dir

type string

options?

type { max_age_ms?: number | undefined; log?: Pick<Logger, "warn"> | undefined; }
optional

returns

Promise<number>

mutates

  • facts_dir filesystem

sync_expected_schema_fixture
#

testing/schema_ready_fixture.ts view source

(options: SyncExpectedSchemaFixtureOptions): Promise<SyncExpectedSchemaFixtureResult> import {sync_expected_schema_fixture} from '@fuzdev/fuz_app/testing/schema_ready_fixture.js';

Introspect the live (bootstrapped) DB's columns, write them to the committed fixture when update, then read the committed fixture back. The caller asserts deepEqual(live, committed):

const {live, committed} = await sync_expected_schema_fixture({ db, fixture_url: new URL('../../lib/server/expected_schema.json', import.meta.url), update: process.env.UPDATE_SCHEMA_READY === '1', }); assert.deepEqual(live, committed);

When update writes the fixture it emits raw JSON.stringify (one array element per line). gro format leaves JSON untouched and the regen test compares values, not formatting, so the layout is cosmetic.

options

returns

Promise<SyncExpectedSchemaFixtureResult>

the live column map and the committed map (post-write when update)

SyncExpectedSchemaFixtureOptions
#

testing/schema_ready_fixture.ts view source

SyncExpectedSchemaFixtureOptions import type {SyncExpectedSchemaFixtureOptions} from '@fuzdev/fuz_app/testing/schema_ready_fixture.js';

db

A bootstrapped DB — the consumer has run its full migration chain on it.

type Db

fixture_url

Committed fixture location — an import.meta.url-relative URL or a path.

type URL | string

update?

When true, overwrite the fixture with the live column map instead of just reading it. Drive from an env flag (e.g. UPDATE_SCHEMA_READY === '1').

type boolean

SyncExpectedSchemaFixtureResult
#

testing/schema_ready_fixture.ts view source

SyncExpectedSchemaFixtureResult import type {SyncExpectedSchemaFixtureResult} from '@fuzdev/fuz_app/testing/schema_ready_fixture.js';

The live column map and the committed fixture, for a deepEqual assertion.

live

Columns introspected from the live, freshly-bootstrapped DB.

type Record<string, string[]>

committed

The committed fixture (re-read after writing when update).

type ExpectedSchema

TableInfo
#

http/db_routes.ts view source

TableInfo import type {TableInfo} from '@fuzdev/fuz_app/http/db_routes.js';

Table metadata from information_schema.

table_name

type string

TableSnapshot
#

testing/schema_introspect.ts view source

value + type

{ columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; } import {TableSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Per-table structural metadata.

columns

Column metadata keyed by column name (sorted on serialization).

type Record<string, ColumnSnapshot>

indexes

Index definitions as Postgres renders them via pg_indexes.indexdef.

type { name: string; definition: string; }[]

constraints

Constraint definitions as Postgres renders them via pg_get_constraintdef.

type { name: string; type: string; definition: string; }[]

TableState
#

ui/table_state.svelte.ts view source

import {TableState} from '@fuzdev/fuz_app/ui/table_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

table_name

type string

$state.raw

columns

type ColumnInfo[]

$state.raw

rows

type Record<string, unknown>[]

$state.raw

total

type number

$state.raw

offset

type number

$state.raw

limit

type number

$state.raw

primary_key

type string | null

$state.raw

deletable

Whether the server will accept a row DELETE on this table. False for a table with no single-column primary key *and* for one the server excludes by policy (the audit trail, singleton bookkeeping rows), which primary_key alone can't distinguish — it reports the key shape, not the policy.

type boolean

$state.raw

deleting

type string | null

$state.raw

delete_error

type string | null

$state.raw

can_delete

Whether to offer a delete affordance at all. Gate rendering on this rather than on primary_key, or a policy-excluded table shows a button whose only outcome is a table_not_deletable refusal.

type boolean

readonly $derived

showing_start

type number

readonly $derived

showing_end

type number

readonly $derived

has_prev

type boolean

readonly $derived

has_next

type boolean

readonly $derived

fetch

Fetch a page of rows for table_name from GET /api/db/tables/{table_name}. limit is clamped to [1, DB_TABLE_ROWS_LIMIT_MAX] — the same bounds the route 400s on, single-sourced so the client guard can't drift into sending a request the server refuses.

type (table_name: string, offset?: number, limit?: number): Promise<void>

table_name

type string

offset?

type number
optional default 0

limit?

type number
optional default 100
returns Promise<void>

mutates

  • this

go_prev

type (): void

returns void

go_next

type (): void

returns void

delete_row

Delete a row by its primary key via DELETE /api/db/tables/{table_name}/rows/{pk}. Optimistically drops it from rows and decrements total on success; surfaces server errors on delete_error.

type (row: Record<string, unknown>): Promise<boolean>

row

type Record<string, unknown>
returns Promise<boolean>

true when the row was removed; false when the table isn't deletable or the server refused

mutates

  • this

TableStatus
#

db/status.ts view source

TableStatus import type {TableStatus} from '@fuzdev/fuz_app/db/status.js';

Table info with row count.

name

type string

row_count

type number

TableWithCount
#

http/db_routes.ts view source

TableWithCount import type {TableWithCount} from '@fuzdev/fuz_app/http/db_routes.js';

Table info with row count.

name

type string

row_count

type number

TerminalDeps
#

runtime/deps.ts view source

TerminalDeps import type {TerminalDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Terminal I/O operations.

stdout_write

Write bytes to stdout.

type (data: Uint8Array<ArrayBufferLike>): Promise<number>

data

type Uint8Array<ArrayBufferLike>
returns Promise<number>

stdin_read

Read bytes from stdin, or null on EOF.

type (buffer: Uint8Array<ArrayBufferLike>): Promise<number | null>

buffer

type Uint8Array<ArrayBufferLike>
returns Promise<number | null>

test_cell_gated_create_authorize
#

testing/cross_backend/test_cell_gated_create_authorize.ts view source

CellCreateAuthorize import {test_cell_gated_create_authorize} from '@fuzdev/fuz_app/testing/cross_backend/test_cell_gated_create_authorize.js';

The directory-model test authorizer — authorize is a pure function of the input (it reads the governing root's policy off input.root_policy, supplied by the handler). Byte-equivalent with the Rust TestCellGatedCreateAuthorize.

test_cell_grant_steward_revoke_authorize
#

testing/cross_backend/test_cell_grant_steward_revoke_authorize.ts view source

(auth: RequestActorContext, input: CellGrantRevokeAuthorizeInput, ctx: ActionContext): boolean | Promise<...> import {test_cell_grant_steward_revoke_authorize} from '@fuzdev/fuz_app/testing/cross_backend/test_cell_grant_steward_revoke_authorize.js';

The steward-revoke test policy: a scoped SPINE_PARTICIPANT_ROLE grant stewards its scope.

auth

input

ctx

returns

boolean | Promise<boolean>

TEST_CLIENT_IP
#

testing/middleware.ts view source

"127.0.0.1" import {TEST_CLIENT_IP} from '@fuzdev/fuz_app/testing/middleware.js';

Default client IP set by the proxy stub in test apps.

TEST_CONTEXT_PRESET
#

hono_context.ts view source

unique symbol import {TEST_CONTEXT_PRESET} from '@fuzdev/fuz_app/hono_context.js';

The one value that sets the test-harness preset (TEST_CONTEXT_PRESET_KEY). A symbol, so only code that imports it can set the flag: a string, a true, or any other value a misconfiguration or a request could put under the key reads as unset. Testing only — no module outside testing/ writes it.

TEST_CONTEXT_PRESET_KEY
#

hono_context.ts view source

"test_context_preset" import {TEST_CONTEXT_PRESET_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the test-harness pre-baked context flag.

Test harnesses (create_test_app_from_specs, create_fake_hono_context, the WS round-trip connect() helper, plus per-test middleware that pre-populates REQUEST_CONTEXT_KEY) set it to TEST_CONTEXT_PRESET so apply_authorization_phase skips its DB-backed actor resolution and trusts the supplied RequestContext. Production middleware never sets this key — only test code does. The flag is the explicit escape hatch that replaced the implicit "is REQUEST_CONTEXT_KEY already set?" probe, so that future production code consulting REQUEST_CONTEXT_KEY cannot silently bypass the live build. Read it with has_test_context_preset.

TEST_COOKIE_SECRET
#

test_if
#

testing/cross_backend/capabilities.ts view source

(cond: boolean, name: string, fn: () => void | Promise<void>): void import {test_if} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

Conditional test() wrapper — registers a vitest case only when cond is true; otherwise registers it as .skip so the run still surfaces the gated coverage in the report.

Thin wrapper around vitest's test.skipIf(!cond) with the argument order flipped to match the more readable test_if(capabilities.ws, ...) call pattern.

cond

type boolean

name

type string

fn

type () => void | Promise<void>

returns

void

TEST_MIDDLEWARE_PATH
#

TEST_ORIGIN
#

testing/test_origin.ts view source

"http://localhost:5173" import {TEST_ORIGIN} from '@fuzdev/fuz_app/testing/test_origin.js';

The origin an in-process test request comes from — the dev server's, and the one origin create_loopback_app_server_options allows, so a request carrying it passes the origin check. It is also the Origin rpc_call sends cross-process, so a consumer's backend must allow it for rpc_call to pass the backend's origin check.

TEST_SPACE_POLICY
#

testing/cross_backend/test_cell_gated_create_authorize.ts view source

{ readonly post: { readonly min_role: "participant"; readonly moderation_required: true; }; readonly react: { readonly min_role: "participant"; readonly moderation_required: false; }; readonly comment: { ...; }; } import {TEST_SPACE_POLICY} from '@fuzdev/fuz_app/testing/cross_backend/test_cell_gated_create_authorize.js';

A space policy mirroring the test ladder: gated post (moderated) + react (live) + comment (live), each on PARTICIPATION_ROLE. comment exists so a suite can create a comment under a post (`root_id !== parent_id`) and prove the policy resolves from the governing root (the space), not the immediate parent. Shared by the gated-create and moderate cross suites, which pass it as a root's cell_create policy.

TestAccount
#

testing/app_server.ts view source

TestAccount import type {TestAccount} from '@fuzdev/fuz_app/testing/app_server.js';

A bootstrapped test account with credentials.

account

type { id: string & $brand<"Uuid">; username: string; }

actor

type { id: string & $brand<"Uuid">; }

session_cookie

Signed session cookie value.

type string

api_token

Raw API token for Bearer auth.

type string

create_session_headers

Build request headers with this account's session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

Build request headers with this account's Bearer token.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

TestAccountOverrides
#

testing/entities.ts view source

TestAccountOverrides

type Partial<Omit<Account, "id" | "created_by" | "updated_by">> & { id?: string | undefined; created_by?: string | null | undefined; updated_by?: string | null | undefined; }

import type {TestAccountOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_account — id-like fields accept plain string.

created_at?

type string

username?

type string

email?

type string | null

email_verified?

type boolean

updated_at?

type string

deleted_at?

Soft-delete tombstone. Non-null means the account is deleted (delete = soft); auth resolution treats it as absent. A hard purge removes the row entirely. See auth/account_queries.ts.

type string | null

password_hash?

type string

deleted_by?

Actor that performed the soft-delete (initiator: self / admin / keeper). Paired with deleted_at, mirroring role_grant's revoked_at / revoked_by. Plain UUID (no FK, like created_by / updated_by on this table).

type Uuid | null

id?

type string

created_by?

type string | null

updated_by?

type string | null

TestAccountWithActor
#

TestActorOverrides
#

testing/entities.ts view source

TestActorOverrides

type Partial<Omit<Actor, "id" | "account_id" | "updated_by">> & { id?: string | undefined; account_id?: string | undefined; updated_by?: string | null | undefined; }

import type {TestActorOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_actor — id-like fields accept plain string.

created_at?

type string

name?

type string

updated_at?

type string | null

deleted_at?

Soft-delete tombstone — set alongside the owning account's soft-delete.

type string | null

deleted_by?

Actor that performed the soft-delete. Paired with deleted_at.

type Uuid | null

id?

type string

account_id?

type string

updated_by?

type string | null

TestApp
#

testing/app_server.ts view source

TestApp import type {TestApp} from '@fuzdev/fuz_app/testing/app_server.js';

A fully assembled test app — Hono app + backend + helpers.

app

type Hono<BlankEnv, BlankSchema, "/">

backend

type TestAppServer

server

The assembled AppServer — reach for what create_app_server built beyond the fields lifted onto TestApp: audit_sse (with audit_log_sse in app_options), the ws_endpoints transports, bootstrap_status, migration_results.

type AppServer

surface_spec

type AppSurfaceSpec

surface

type AppSurface

route_specs

type RouteSpec[]

create_session_headers

Build request headers with the bootstrapped session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

Build request headers with the bootstrapped Bearer token.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_daemon_token_headers

Build request headers with the daemon token (keeper auth).

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_account

Create an additional account with credentials.

type (options?: CreateTestAppAccountArgs | undefined): Promise<TestAccount>

options?

optional
returns Promise<TestAccount>

cleanup

Cleanup resources — AppServer.close: stops an opted-in auth cleanup schedule, closes live connections, disposes the limiters the server built and removes the audit listeners and closer members it added, then the backend's close. Idempotent.

type (): Promise<void>

returns Promise<void>

TestAppServer
#

testing/app_server.ts view source

TestAppServer import type {TestAppServer} from '@fuzdev/fuz_app/testing/app_server.js';

An AppBackend with a bootstrapped account, API token, and session cookie.

inheritance

extends: AppBackend

account

The bootstrapped account.

type { id: string & $brand<"Uuid">; username: string; }

actor

The actor linked to the account.

type { id: string & $brand<"Uuid">; }

api_token

Raw API token for Bearer auth.

type string

session_cookie

Signed session cookie value for cookie auth.

type string

keyring

Keyring used for cookie signing — exposed for forging expired/tampered cookies in tests.

type Keyring

cleanup

Release test resources (no-op when DB is injected or factory-cached).

type (): Promise<void>

returns Promise<void>

TestAppServerOptions
#

testing/app_server.ts view source

TestAppServerOptions import type {TestAppServerOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Configuration for create_test_app_server.

session_options

Session options — needed for cookie signing.

type SessionOptions<string>

db?

Existing database — skips internal DB creation when provided. Caller owns the DB lifecycle.

type Db

db_type?

Database driver type — only used when db is provided. Default: 'pglet-memory'.

type DbType

migration_namespaces?

Extra migration namespaces run after the builtin auth namespace in the auto-created in-memory database, mirroring create_app_backend's migration_namespaces. For suites whose backend needs tables beyond auth — the cell parity suite passes [CELL_MIGRATION_NS]. The harness builds + caches a fresh-per-test factory migrating [AUTH_MIGRATION_NS, ...migration_namespaces]; the reset-on-create gives the same fresh-db isolation as the auth-only default. Mutually exclusive with db (which assumes the caller already migrated).

type readonly MigrationNamespace[]

password?

Password implementation. Default: stub_password_deps. Pass argon2_password_deps for tests that exercise login.

type PasswordHashDeps

username?

Username for the bootstrapped account. Default: 'keeper'.

type string

password_value?

Password for the bootstrapped account. Default: DEFAULT_TEST_PASSWORD.

type string

roles?

Roles to grant. Default: [ROLE_KEEPER].

type string[]

audit_factory?

Build the bound AuditEmitter used by the test backend. Defaults to default_audit_factory (a no-listener create_audit_emitter over the test backend's {db, log}). Pass a custom factory when a test needs:

  • to capture audit events (compose on_audit_event inside the body)
  • to register consumer event-type schemas (pass audit_log_config)
  • to instrument emit (emit_decorator)
  • to wrap or replace the emitter for some other reason

Matches the production shape — create_app_backend requires an audit_factory and create_test_app_server mirrors that contract end-to-end.

type (params: { db: Db; log: Logger; }): AuditEmitter

params

type { db: Db; log: Logger; }
returns AuditEmitter

TestAuditEventOverrides
#

testing/entities.ts view source

TestAuditEventOverrides

type Partial<Omit<AuditLogEvent, "id" | "actor_id" | "account_id" | "target_account_id" | "target_actor_id">> & { id?: string | undefined; actor_id?: string | null | undefined; account_id?: string | null | undefined; target_account_id?: string | null | undefined; target_actor_id?: string | null | undefined; }

import type {TestAuditEventOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_audit_event — id-like fields accept plain string.

ip?

type string | null

created_at?

type string

event_type?

type string

outcome?

type AuditOutcome

seq?

type number

metadata?

type Record<string, unknown> | null

id?

type string

actor_id?

type string | null

account_id?

type string | null

target_account_id?

type string | null

target_actor_id?

type string | null

TestBackendPaths
#

testing/cross_backend/build_test_backend_paths.ts view source

TestBackendPaths import type {TestBackendPaths} from '@fuzdev/fuz_app/testing/cross_backend/build_test_backend_paths.js';

Generic per-backend paths every cross-process test binary needs. Consumers extend this with their own domain paths.

  • root — the per-backend subtree under os.tmpdir(). Compose consumer-specific paths under here.
  • bootstrap_token_path — FUZ_BOOTSTRAP_TOKEN_PATH; harness writes the bootstrap token here before spawn.
  • daemon_token_path — where init_daemon_token (Rust) and the TS server's daemon-token writer land the token (under {root}/run/); spawn_backend deletes it before each spawn, so a token a previous run left is never read.

root

type string

readonly

bootstrap_token_path

type string

readonly

daemon_token_path

type string

readonly

TestFixture
#

testing/cross_backend/setup.ts view source

TestFixture import type {TestFixture} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

The per-test bundle returned by SetupTest. Every Tier 1 suite body reads exclusively from this shape — no test_app.backend.* reads remain in the suite bodies.

Transport-agnostic: in-process and cross-process producers return the same shape. Behaviors that once needed raw backend access (keyring for forging cookies) are reached through wire-shaped seams instead — mint_expired_session() mints over the _testing_mint_session channel cross-process and directly in-process, so suite bodies never branch on the transport.

Keeper ≠ admin. fixture.account / fixture.actor refer to the fresh keeper seeded per test. The keeper account holds ROLE_KEEPER + ROLE_ADMIN by default — matching the production bootstrap_account flow. The ROLE_KEEPER role itself does *not* grant admin reach; the bootstrap account just happens to hold both as separate grants. Tests probing the keeper-vs-admin separation (e.g. "non-admin cannot list accounts") declare a secondary at setup-time via extra_accounts: [{username, roles: [ROLE_KEEPER]}] and read it from fixture.extra_accounts[username].

transport

Transport for this test's HTTP requests. Typed as FetchTransport so cross-process tests can call transport.cookies() for WS upgrade cookie threading; in-process provides a no-op cookies() returning [] (in-process tests construct cookies via create_session_headers directly and don't thread WS through this channel).

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

fresh_transport

Build a brand-new FetchTransport with an empty cookie jar pinned to the same backend. Use for unauthed assertions (`no cookie on protected route returns 401`, bearer-only calls expected to fall through to the unauthenticated path) where the per-test session cookie carried by transport's jar would otherwise leak into the request and convert a 401 into a 200.

New-per-call, not memoized — each invocation returns a fresh instance. If a call mutates the jar (e.g. an unauthed login attempt returning Set-Cookie) it can't pollute sibling calls.

Pass origin: null for bearer-only probes that must look like non-browser callers — the auth middleware silently discards bearer credentials when Origin/Referer is present, so a default Origin: <base_url> would convert "bearer + no Origin → 200" into "bearer + Origin → discarded → 401" cross-process. In-process the wrapper is stateless and the option is a no-op (no auto-Origin to suppress).

In-process this is functionally identical to transport (the wrapper's cookies(): [] is a no-op already); cross-process the returned transport starts with an empty jar at the same base_url.

type (options?: { readonly origin?: string | null | undefined; } | undefined): FetchTransport

readonly

options?

type { readonly origin?: string | null | undefined; }
optional

account

The freshly-bootstrapped keeper account.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

actor

The actor linked to the keeper account.

type { readonly id: string & $brand<"Uuid">; }

readonly

create_session_headers

Build request headers with the keeper's session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

Build request headers with the keeper's bearer token.

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_daemon_token_headers

Build request headers with the daemon token (keeper auth).

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_account

Mint an additional bootstrapped account for cross-account / multi-user tests. In-process: re-uses create_test_account_with_credentials against the same DB; cross-process: goes through the consumer-supplied DB-admin channel.

type (options?: CreateTestAccountOptions | undefined): Promise<TestAccount>

readonly

options?

optional
returns Promise<TestAccount>

extra_accounts

Bootstrap-time-seeded secondaries, keyed by their declared username. Populated from the extra_accounts option passed to default_in_process_setup / default_cross_process_setup. Empty for suites that don't declare any.

type Readonly<Record<string, TestAccount>>

readonly

extra_actors

Additional actors seeded on the keeper account (beyond its single bootstrap actor), in declaration order. Populated from the extra_actors option. Empty unless a suite declares any. Use to drive the multi-actor acting-selector branches: with extra_actors non-empty the keeper has >1 actor, so a keeper request omitting acting resolves to actor_required with these ids in its available[] list. Each id is a valid acting value the keeper can supply explicitly.

type readonly { readonly id: string & $brand<"Uuid">; readonly name: string; }[]

readonly

mint_expired_session

Forge an *expired server-side session* for the keeper account and return the ready-to-send Cookie header value (name=value). The minted auth_session row is backdated while the signed cookie payload stays valid — so resolution clears the cookie-payload gate (parse_session) and is refused at the authoritative DB-row gate (query_session_get_valid — WHERE expires_at > NOW()). Backs the expired_session conformance principal. In-process mints directly via mint_test_session; cross-process drives the _testing_mint_session RPC over the keeper's daemon-token channel (the driver has no keyring).

type (): Promise<string>

readonly
returns Promise<string>

reject_unknown_audit_metadata_keys?

Whether the suites' audit reads refuse metadata keys an event type doesn't declare — the setup's reject_unknown_audit_metadata_keys. list_audit_events and the admin suite's readers pass it to assert_audit_event_metadata.

type boolean

readonly

TESTING_ACTION_AUTH
#

testing/cross_backend/spine_surface_constants.ts view source

{ readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly ["daemon_token"]; } import {TESTING_ACTION_AUTH} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Shared auth axis for every _testing_* action: keeper-only via the daemon-token credential, no acting actor. This is the entire structural fence on the backdoor surface (these actions run direct DB writes the production wire never exposes), so every _testing_* spec references this one const rather than re-declaring it — a single source of truth the gate test (testing_actions_auth.test.ts) pins. Mirrors the Rust DAEMON_TOKEN_ONLY / shared AuthSpec in fuz_testing.

testing_action_manifest_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_action_manifest"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_action_manifest_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_action_manifest — dump the backend's live RPC method set as a normalized ActionManifest (one entry per method: `{method, input, side_effects, account, actor, roles, credential_types, rate_limit}`) for cross-impl parity diffing. The action-surface twin of _testing_schema_snapshot: where that introspects the live *database*, this introspects the live *RPC registry*. The cross-backend harness calls it on each backend, then assert_action_manifests_equals the results (the Rust mirror is fuz_testing::create_testing_action_manifest_action_spec, whose normalization matches by design). Complements the in-repo spine_method_coverage gate (mounted ⟹ covered) by proving the TS mount-set ≡ the Rust mount-set, method-for-method and auth-shape-for-shape.

Unlike its _testing_* siblings this action is not bundled by create_testing_actions — the manifest must enumerate *every* mounted method, which create_testing_actions (one sub-factory among many) can't see. It's appended at the full-mount layer (build_full_spine_rpc_actions) via create_testing_action_manifest_action, where the complete list exists.

auth gates on the daemon-token credential, matching _testing_reset.

TESTING_BACKDOOR_METHODS
#

testing/cross_backend/spine_surface_constants.ts view source

readonly ["_testing_reset", "_testing_drain_effects", "_testing_mint_session", "_testing_put_fact", "_testing_flood_notifications", "_testing_schema_snapshot", "_testing_migration_tracker", "_testing_action_manifest", "_testing_origin_check", "_testing_shorten_credential_expiry"] import {TESTING_BACKDOOR_METHODS} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Every _testing_* backdoor the shipped cross suites know: the daemon-token actions a test binary may mount for the harness to drive. The one list a backend's capabilities.testing_backdoors declares from, the testing-backdoor suite probes, and describe_spine_cross_suites gates suites on. A consumer's own seams stay off it — the testing-backdoor suite takes those as extra_methods.

spine_surface_constants.testing_backdoors.test.ts holds it to the _testing_* methods fuz_app's spine binary mounts, so an action added there fails until it is listed here.

testing_drain_effects_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_drain_effects"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: "Test-binary only — await in-flight fire-and-forge... import {testing_drain_effects_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_drain_effects — await in-flight fire-and-forget audit writes so a following audit_log_list is authoritative. The deterministic barrier the cross-backend conformance suite uses in place of a poll/sleep before asserting on audit rows.

The handler awaits AuditEmitter.drain_inflight, which waits until no tracked emit write is outstanding — so the binary's emitter must be built with track_inflight: true, or the drain resolves at once and is no barrier. Tracking is what makes it a barrier whatever the transport: an HTTP mutation on a binary running await_pending_effects: true has its writes settled by the time it responds, but a WS mutation is answered before its effects flush (reply-then-close for a self-revocation), so its audit write can still be in flight when the caller reads the reply. The Rust spine does the same await in AuditEmitter::drain_inflight, over an emitter built with new_with_inflight_tracking.

Covered: every emit that ran before the drain was dispatched, and any started while it waits. A write that starts after the drain resolves is not — nor are eager writes outside emit (session touch, token usage), which only the dispatching transport's own flush awaits.

The output's tracked is AuditEmitter.is_tracking_inflight — whether the drain could wait on anything. cross_drain_effects refuses tracked: false, so a binary whose emitter was built without tracking fails its first audit read instead of racing it. The Rust twin answers the same shape.

auth gates on the daemon-token credential, matching _testing_reset.

TESTING_FLOOD_MAX_COUNT
#

testing/cross_backend/testing_reset_actions.ts view source

65536 import {TESTING_FLOOD_MAX_COUNT} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

The most notifications one _testing_flood_notifications call queues. Empty ones at this count are charged more than DEFAULT_WS_OUTBOUND_QUEUE_BYTES between them — each frame's envelope and its overhead — so a burst of tiny frames can overflow a connection on a default transport too. Same value as the Rust spine's.

TESTING_FLOOD_MAX_SIZE
#

TESTING_FLOOD_MAX_TOTAL_BYTES
#

testing/cross_backend/testing_reset_actions.ts view source

number import {TESTING_FLOOD_MAX_TOTAL_BYTES} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

The most payload bytes one _testing_flood_notifications call builds across all its notifications — the bound on count × size. The handler serializes each notification without yielding, so the product is how long one call holds the event loop.

TESTING_FLOOD_NOTIFICATION_METHOD
#

testing_flood_notifications_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_flood_notifications"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 5 more ...; readonly description: string; } import {testing_flood_notifications_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_flood_notifications — queue a burst of notifications onto the caller's own WebSocket connection, all in one synchronous run, so a suite driving the server from outside can push a connection's outbound queue past its byte budget deterministically — without a second producer, and whether or not its client is reading: the connection's write pump gets no turn while the handler runs, so the burst waits at once. Past the budget the server closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW.

Each notification is {"jsonrpc":"2.0","method":"_testing_flood","params":{"index":<i>,"payload":"<size × 'x'>"}} with index counting from 0, so every frame is a little longer than size bytes and a client can tell which frames reached it.

The result is {queued: count} — the number asked for, not the number that fit: when the burst overflows the queue the connection is closed and this response is never written. A burst that fits arrives in order, ahead of the response.

A call past TESTING_FLOOD_MAX_COUNT, TESTING_FLOOD_MAX_SIZE, or TESTING_FLOOD_MAX_TOTAL_BYTES (count × size) is refused with invalid_params and queues nothing. Over HTTP there is no connection to queue onto, so the call is refused with invalid_params, reason ERROR_TESTING_FLOOD_NO_CONNECTION, rather than answering as though it had flooded something.

auth gates on the daemon-token credential, matching _testing_reset. The Rust mirror is fuz_testing::create_testing_flood_notifications_action_spec.

TESTING_METHOD_PREFIX
#

testing/surface_invariants.ts view source

"_testing_" import {TESTING_METHOD_PREFIX} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Reserved method-name prefix for the daemon-token-gated test-backdoor actions (_testing_reset, _testing_mint_session, _testing_put_fact, _testing_drain_effects, _testing_schema_snapshot). Test binaries live-mount these on their RPC endpoint but they must never appear on a declared surface.

testing_migration_tracker_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_migration_tracker"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_migration_tracker_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_migration_tracker — dump the schema_version tracker rows as a normalized MigrationTracker ([{namespace, name, sequence}]) for cross-impl migration-identity diffing. The provenance half of _testing_schema_snapshot: where that captures the resulting *schema* (and excludes the tracker by design), this captures the tracker *itself*, so the cross-backend harness can assert the two spines record byte-identical migration identity. This closes the gap that let the cell/fact migration-name divergence reach the visiones cutover undetected (name-divergence-at-N — same schema, divergent recorded names).

auth gates on the daemon-token credential, matching _testing_reset. The Rust mirror is fuz_testing::create_testing_migration_tracker_action_spec.

testing_mint_session_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_mint_session"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_mint_session_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_mint_session — mint an expired-by-construction server-side session for an existing account and return its signed cookie value.

expires_in_seconds is constrained negative (z.number().int().negative()) so the action is structurally incapable of minting a *usable* session: it can only produce an already-backdated, already-dead auth_session row. The daemon-token gate + loopback binding already fence the backdoor, but the negative constraint is the make-impossible-states floor — even a misuse can't forge a valid session for an arbitrary account_id. The Rust mirror (fuz_testing::create_testing_mint_session_action_spec) enforces the same floor.

The minted auth_session row's expires_at is backdated while the returned cookie's own signed payload stays valid (future). Cross-process auth resolution therefore passes the cookie-payload gate (parse_session) and is refused by the authoritative DB-row gate (query_session_get_valid — WHERE expires_at > NOW()) — the gate the in-process payload-expiry tests never reach and the one that structurally needs a server-side mint (the cross-process driver has no keyring / DB access). The expired_session conformance principal drives this.

auth gates on the daemon-token credential, matching _testing_reset — effectively keeper-only. Like its siblings the action is internally privileged (a direct auth_session insert the production wire never exposes); daemon-token auth is the structural fence and the module's assert_dev_env import (TS) plus the Rust cargo xtask check-release dep-graph audit keep the _testing_ surface out of every shipped build.

testing_origin_check_action_spec
#

testing/cross_backend/testing_origin_check.ts view source

{ readonly method: "_testing_origin_check"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_origin_check_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_origin_check.js';

_testing_origin_check — run the origin allowlist's parse and request check for one {patterns, origin} pair, so the shared conformance table (origin_conformance.json) checks each backend's grammar and matcher over the wire, row by row. Pure: no database, no state. The Rust mirror is fuz_testing::create_testing_origin_check_action_spec.

Like _testing_action_manifest, it is not bundled by create_testing_actions: a consumer whose Rust binary doesn't mount the mirror would fail its action-manifest parity gate. The spine test binary appends it at the full-mount layer (build_full_spine_rpc_actions).

auth gates on the daemon-token credential, matching _testing_reset.

testing_put_fact_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_put_fact"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_put_fact_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_put_fact — seed an embedded fact (fact.bytes) for the cross-process fact-serving suite, which drives over real HTTP and has no PgFactStore to call. Hashes the UTF-8 content (blake3, via fact_hash_bytes — the same hash the Rust _testing_put_fact computes), inserts the row idempotently, and returns {hash}. holder names the actor the put is on behalf of — recorded in fact_holder (deduplicated puts included), as a real upload records its uploader, so the suites can drive cell ref admission; absent, the put is a system put with no holder. The referencing cell is seeded separately via the cell_create RPC. Embedded is enough for the authz assertions (cell-scoped admit, cross-owner-no-leak, 404-mask, bare-hash admin-only); external / X-Accel parity stays covered by the forge's own gate. external_url, when given, writes the row the way a writer past the store could — no bytes, that URL verbatim, unvalidated — so the suite can prove a serve refuses a URL that is not its hash's own address.

auth gates on the daemon-token credential, matching _testing_reset — the action does a direct fact insert the production wire never exposes. The Rust mirror is fuz_testing::create_testing_put_fact_action_spec.

testing_reset_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_reset"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: "Test-binary only — wipe auth tables, re-bootstrap a fresh... import {testing_reset_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

The _testing_reset action spec.

Input:

  • extra_keeper_roles — roles to grant the fresh keeper *in addition to* [ROLE_KEEPER, ROLE_ADMIN] (matching production bootstrap).
  • extra_accounts — additional accounts to seed at this same bootstrap-equivalent step. Each entry's roles are direct-granted (bypassing offer/accept) because the seed is *part of bootstrap*, not a post-bootstrap action. Use this for accounts whose required roles aren't admin-grantable via offer/accept (e.g. ROLE_KEEPER, whose RoleSpec.grant_paths is bootstrap-only). For admin-grantable roles, prefer fixture.create_account({roles}) (offer/accept production path).

Output: keeper credentials plus a parallel array of seeded extra_accounts (same order as input). The per-test fixture closes over the returned values; subsequent calls in the same test see the fresh keeper and any requested secondaries.

auth gates on the daemon-token credential — the keeper holds it exclusively. The action is internally privileged (it runs direct DB writes the production wire never exposes); daemon-token auth is the structural fence.

testing_schema_snapshot_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_schema_snapshot"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: "Test-binary only — introspect the live schema i... import {testing_schema_snapshot_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_schema_snapshot — introspect the live database into a normalized SchemaSnapshot for cross-impl parity diffing. The cross-backend harness calls this on each backend, then assert_schema_snapshots_equals the results (a Rust backend answers from fuz_db::query_schema_snapshot; the shapes match by design). Optional exclude_tables drops documented divergences from both sides before comparison.

A backend running on pglet (a TS backend config's memory:// default) answers with an error when its schema holds an index: the snapshot exists to be compared, and pglet's index definitions omit the modifiers a comparison needs (query_schema_snapshot). Run the backend on PostgreSQL.

auth gates on the daemon-token credential, matching _testing_reset.

testing_shorten_credential_expiry_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_shorten_credential_expiry"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_shorten_credential_expiry_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_shorten_credential_expiry — pull the expiry of an account's stored credentials (every auth_session and api_token row it owns) in to seconds from now, so the cross-backend credential-deadline suite can open a WebSocket or audit stream on a credential that expires a moment later and watch it close at the deadline (ResolvedAuth.credential_deadline) without waiting out a real lifetime.

Applied before the open: it rewrites the stored rows, and a connection reads its deadline when its credential resolves, so only one opened afterwards sees the near deadline.

Shorten-only. The new expiry is LEAST(expires_at, NOW() + seconds) — an eternal token gains one, and nothing already due sooner moves later, so the action can neither extend a credential's life nor revive an expired one; seconds is an integer from TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS to TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS. The Rust mirror is fuz_testing::create_testing_shorten_credential_expiry_action_spec.

Like _testing_origin_check, it is not bundled by create_testing_actions: a consumer whose Rust binary doesn't mount the mirror would fail its action-manifest parity gate. The spine test binary appends it at the full-mount layer (build_full_spine_rpc_actions).

auth gates on the daemon-token credential, matching _testing_reset.

TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS
#

testing/cross_backend/testing_reset_actions.ts view source

60 import {TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

The longest expiry _testing_shorten_credential_expiry sets, in seconds from now — a minute: the action exists to make a deadline arrive inside a test.

TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS
#

TestingBackdoorCrossTestOptions
#

testing/cross_backend/testing_backdoor.ts view source

TestingBackdoorCrossTestOptions import type {TestingBackdoorCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/testing_backdoor.js';

Options for the testing-backdoor suite.

inheritance

ws?

Where the backend's WebSocket endpoint is, for the session-socket cases. Pass it when the backend serves its _testing_* actions on its WS endpoint too; omitted, those cases are skipped.

type WsEndpointTarget

readonly

extra_methods?

The backend's own daemon-token seams beyond TESTING_BACKDOOR_METHODS, each with params its input accepts — fired with the same refused principals as the known backdoors. Each must start with _testing_ and not be a known backdoor; the call throws otherwise.

type readonly TestingBackdoorProbe[]

readonly

TestingBackdoorMethod
#

testing/cross_backend/spine_surface_constants.ts view source

"_testing_reset" | "_testing_drain_effects" | "_testing_mint_session" | "_testing_put_fact" | "_testing_flood_notifications" | "_testing_schema_snapshot" | "_testing_migration_tracker" | "_testing_action_manifest" | "_testing_origin_check" | "_testing_shorten_credential_expiry"

type "_testing_reset" | "_testing_drain_effects" | "_testing_mint_session" | "_testing_put_fact" | "_testing_flood_notifications" | "_testing_schema_snapshot" | "_testing_migration_tracker" | "_testing_action_manifest" | "_testing_origin_check" | "_testing_shorten_credential_expiry"

import type {TestingBackdoorMethod} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

A _testing_* backdoor the shipped cross suites know — see TESTING_BACKDOOR_METHODS.

TestingBackdoorProbe
#

testing/cross_backend/testing_backdoor.ts view source

TestingBackdoorProbe import type {TestingBackdoorProbe} from '@fuzdev/fuz_app/testing/cross_backend/testing_backdoor.js';

A backdoor the suite fires: its method and a params payload its input schema accepts, so a refusal lands on the credential gate, not on validation.

method

type string

readonly

params?

type unknown

readonly

TestingOriginCheckInput
#

testing/cross_backend/testing_origin_check.ts view source

value + type

{ patterns: string; origin: string | null; } import {TestingOriginCheckInput} from '@fuzdev/fuz_app/testing/cross_backend/testing_origin_check.js';

Input of _testing_origin_check.

patterns

An allowlist value, as FUZ_ALLOWED_ORIGINS would carry it.

type string

origin

One Origin header's value, or null for a request without one.

type string | null

TestingOriginCheckOutput
#

testing/cross_backend/testing_origin_check.ts view source

value + type

{ ok: true; allowed: boolean; } | { ok: false; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | ... 9 more ... | "no_origins"; index?: number | undefined; }[]; }

type { ok: true; allowed: boolean; } | { ok: false; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port" | "no_origins"; index?: number | undefined; }[]; }

import {TestingOriginCheckOutput} from '@fuzdev/fuz_app/testing/cross_backend/testing_origin_check.js';

Output of _testing_origin_check: the request-level answer when the list parses, else every refusal — index the 1-based entry position, absent for a list-level refusal.

TestingServerAdapter
#

testing/cross_backend/testing_server_core.ts view source

TestingServerAdapter import type {TestingServerAdapter} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Runtime adapter contract for the test-binary entry. Each adapter (testing/cross_backend/testing_server_node.ts, testing/cross_backend/testing_server_deno.ts, testing/cross_backend/testing_server_bun.ts) implements this and hands the shape to start_testing_server.

runtime_label

Human-readable runtime label for log output (e.g. "Node", "Deno").

type string

runtime

type RuntimeDeps

get_connection_ip

Extract the raw TCP connection IP from a Hono context.

type (c: Context<any, any, {}>): string | undefined

c

type Context<any, any, {}>
returns string | undefined

prepare_websocket

Prepare the runtime's WS upgrade helper and its post-serve attach step.

type (options?: PrepareWebsocketOptions | undefined): PreparedWebsocket

options?

optional

serve

Bind app.fetch to port on hostname; return a ServeHandle.

type (options: { fetch: (request: Request, env?: unknown, executionCtx?: ExecutionContext | undefined) => Response | Promise<Response>; port: number; hostname: string; }): ServeHandle

options

type { fetch: (request: Request, env?: unknown, executionCtx?: ExecutionContext | undefined) => Response | Promise<Response>; port: number; hostname: string; }
returns ServeHandle

pid

Current process pid (for daemon.json).

type number

register_shutdown_signals

Register SIGINT/SIGTERM listeners that invoke handler once each.

type (handler: () => Promise<void>): void

handler

type () => Promise<void>
returns void

exit

Forceful exit on graceful-shutdown completion or fatal error.

type (code: number): never

code

type number
returns never

TestMiddlewareStackApp
#

testing/middleware.ts view source

TestMiddlewareStackApp import type {TestMiddlewareStackApp} from '@fuzdev/fuz_app/testing/middleware.js';

app

type Hono<BlankEnv, BlankSchema, "/">

mock_validate

type Mock

mock_find_by_id

type Mock

mock_find_actor_by_id

type Mock

mock_find_actors_by_account

type Mock

mock_find_active_for_actor

type Mock

TestMiddlewareStackOptions
#

testing/middleware.ts view source

TestMiddlewareStackOptions import type {TestMiddlewareStackOptions} from '@fuzdev/fuz_app/testing/middleware.js';

trusted_proxies?

Trusted proxy IPs.

type string[]

default `['10.0.0.1']`

allowed_origins?

Comma-separated allowed origin patterns.

type string

default `'https://app.example.com'`

connection_ip?

Connection IP or factory.

type string | (() => string | undefined)

default first trusted proxy

TestResolvedAuthOptions
#

testing/resolved_auth.ts view source

TestResolvedAuthOptions import type {TestResolvedAuthOptions} from '@fuzdev/fuz_app/testing/resolved_auth.js';

credential_type

type BuiltinCredentialType

account_id

type string

session_id?

The raw session token a session credential carries, hashed into its session_hash the way the session leg hashes the cookie's. Default a fresh uuid. Ignored for other credential types.

type string

api_token_id?

The api_token credential's token id. Default a fresh uuid. Ignored for other credential types.

type string

scope?

The api_token credential's scope. Default full. Ignored for other credential types.

type TokenScope

credential_deadline?

The credential deadline. Default null; always null for a daemon token.

type number | null

TestRoleGrantOverrides
#

testing/entities.ts view source

TestRoleGrantOverrides

type Partial<Omit<RoleGrant, "id" | "actor_id" | "scope_kind" | "scope_id" | "revoked_by" | "granted_by" | "source_offer_id">> & { id?: string | undefined; actor_id?: string | undefined; scope_kind?: string | null | undefined; scope_id?: string | null | undefined; revoked_by?: string | null | undefined; granted_by?: string | null | undefined; source_offer_id?: string | null | undefined; }

import type {TestRoleGrantOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_role_grant — id-like fields accept plain string.

created_at?

type string

expires_at?

type string | null

role?

type string

revoked_at?

type string | null

revoked_reason?

Optional free-form reason attached on revoke (rides on the role_grant_revoke WS notification to the revokee).

type string | null

id?

type string

actor_id?

type string

scope_kind?

type string | null

scope_id?

type string | null

revoked_by?

type string | null

granted_by?

type string | null

source_offer_id?

type string | null

TestValidatedApiTokenOverrides
#

testing/entities.ts view source

TestValidatedApiTokenOverrides

type Partial<Omit<ValidatedApiToken, "account_id">> & { account_id?: string | undefined; }

import type {TestValidatedApiTokenOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_validated_api_token — account_id accepts plain string.

id?

type string

created_at?

type string

expires_at?

type string | null

name?

type string

scope?

type TokenScope

last_used_at?

type string | null

last_used_ip?

type string | null

token_hash?

type string

expires_in_ms?

Milliseconds until the token expires — expires_at - NOW(), measured on the database clock in the statement that found it live (EXPIRES_IN_MS_SQL); null for an eternal token. The Rust twin's ApiTokenRow.expires_in.

type number | null

account_id?

type string

ThrowingApi
#

actions/rpc_client.ts view source

ThrowingApi<TApi> import type {ThrowingApi} from '@fuzdev/fuz_app/actions/rpc_client.js';

Maps a typed ActionsApi to a throwing variant.

For each method whose return type matches the create_rpc_client shape (Promise<Result<{value: T}, {error: JsonrpcErrorObject}>>), the wrapped method returns Promise<T> directly. Other shapes (notifications typed as => void, sync local_call methods) pass through unchanged — there is nothing to unwrap.

Input + options parameters are preserved verbatim via ...args: infer TArgs so the conditional matches both required-input (input: T) and optional-input (input?: T / nullary) signatures uniformly. Required-input shapes (e.g. admin_session_revoke_all(input: AdminSessionRevokeAllInput)) are not assignable to a (input?: TInput) => … pattern under --strictFunctionTypes, so an earlier (input?, options?) => form silently fell through to TApi[K] and left those methods Result-shaped — create_admin_rpc_adapters(api) would then reject the typed throwing Proxy because half its surface still returned Result<...>. The rest-args form preserves both required and optional parameters and resolves the gap.

generics

ThrowingApi<TApi>
TApi

ThrownJsonrpcError
#

http/jsonrpc_errors.ts view source

import {ThrownJsonrpcError} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Error class carrying a JSON-RPC error code — thrown by handlers, read by to_thrown_jsonrpc_error, and mapped to HTTP status + JSON-RPC error response.

Named for what it is: an error with a JSON-RPC error code that gets thrown.

inheritance

extends: Error

code

type JsonrpcErrorCode

data?

type unknown

constructor

type new (code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">), message: string, data?: unknown, options?: ErrorOptions | undefined): ThrownJsonrpcError

code

message

type string

data?

type unknown
optional

options?

type ErrorOptions
optional

timing_env
#

testing/cross_backend/default_backend_configs.ts view source

(options: TimingEnvOptions): Record<string, string> import {timing_env} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

The extra_env entries carrying the timing overrides, shared by the TS and Rust backend configs so one option drives both impls. Each value is passed through as given, so the binary refuses a bad one instead of the harness hiding it.

options

returns

Record<string, string>

TimingEnvOptions
#

testing/cross_backend/default_backend_configs.ts view source

TimingEnvOptions import type {TimingEnvOptions} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

Options for timing_env.

audit_sse_keep_alive_ms?

The audit stream's keep-alive interval in ms (AUDIT_SSE_KEEP_ALIVE_MS_ENV).

type number

readonly

ws_receive_timeout_ms?

The WS endpoints' receive timeout in ms (WS_RECEIVE_TIMEOUT_MS_ENV).

type number

readonly

db_checkout_timeout_ms?

The pool checkout bound of a request in ms (FUZ_DB_CHECKOUT_TIMEOUT_MS).

type number

readonly

db_audit_checkout_timeout_ms?

The pool checkout bound of an audit write in ms (FUZ_DB_AUDIT_CHECKOUT_TIMEOUT_MS).

type number

readonly

to_admin_account
#

auth/account_schema.ts view source

(account: Account): { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<...>) | null; deleted_at: string | null; } import {to_admin_account} from '@fuzdev/fuz_app/auth/account_schema.js';

Convert an Account to an AdminAccountJson for admin listings.

account

the full account record

type Account

returns

AdminAccountJson

the admin-safe account with audit fields

to_cargo_binary_path
#

testing/cross_backend/cargo_binary.ts view source

(metadata_json: string, bin: string, profile: string): string import {to_cargo_binary_path} from '@fuzdev/fuz_app/testing/cross_backend/cargo_binary.js';

The binary path cargo build --profile <profile> --bin <bin> writes, from cargo metadata --no-deps --format-version 1 output.

metadata_json

the cargo metadata output

type string

bin

the binary's name

type string

profile

the Cargo profile

type string

returns

string

the binary's absolute path

throws

  • Error - when the output carries no `target_directory`

to_cargo_profile_dir
#

testing/cross_backend/cargo_binary.ts view source

(profile: string): string import {to_cargo_profile_dir} from '@fuzdev/fuz_app/testing/cross_backend/cargo_binary.js';

The directory under the target directory a profile's output lands in — debug for dev, the profile's own name otherwise (release, a custom cross-test).

profile

the Cargo profile

type string

returns

string

to_cell_json
#

auth/cell_actions.ts view source

(row: CellRow): { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; ... 12 more ...; grant_count: number; } import {to_cell_json} from '@fuzdev/fuz_app/auth/cell_actions.js';

row

type CellRow

returns

CellJson

to_connection_close_target
#

actions/connection_closer.ts view source

(event: AuditLogEvent): ConnectionCloseTarget | null import {to_connection_close_target} from '@fuzdev/fuz_app/actions/connection_closer.js';

The connections a revocation's audit row closes, dispatched on the RevocationScope its event declares in audit_event_revocation_scopes (auth/audit_log_schema.ts) — null when it closes nothing:

  • a row whose outcome is not success — a failure carries attacker-controlled identifiers (a session_revoke the DB rejected still records the submitted session id), so reacting to it would let any authenticated caller close another user's connection by guessing a session hash or token id; matching the success rather than skipping the failure keeps an outcome outside the enum inert
  • scope none — every event that revokes nothing, and any event type fuz_app does not define
  • a row missing the key its scope closes by (an empty string included)
  • a role row whose revoked grant was scoped (to_global_role_revocation)

Otherwise:

  • session → {kind: 'session', session_hash: metadata.session_id}
  • token → {kind: 'token', api_token_id: metadata.token_id}
  • account → {kind: 'account', account_id: target_account_id ?? account_id} — admin actions set target_account_id, self-service ones only account_id
  • role → {kind: 'role', account_id, role} of the revoked global grant

Twin of the Rust spine's ConnectionCloseTarget::from_audit_event.

event

the audit row announced after its commit

returns

ConnectionCloseTarget | null

the connections to close, or null

to_cross_suite_options
#

testing/cross_backend/spine_suites.ts view source

(handle: Pick<ReconstructedBootstrappedBackendHandle, "config">): CrossSuiteHandleOptions import {to_cross_suite_options} from '@fuzdev/fuz_app/testing/cross_backend/spine_suites.js';

The suite options a handle's config supplies — the fields most cross suites take beside setup_test. For a suite called on its own: describe_ready_cross_tests({setup_test, ...to_cross_suite_options(handle)}) passes extra fields the suite's type doesn't list, so destructure what it takes instead when calling it with an object literal.

handle

returns

CrossSuiteHandleOptions

to_daemon_token_dir
#

testing/cross_backend/default_backend_configs.ts view source

(daemon_token_path: string): string import {to_daemon_token_dir} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

The directory a binary writing its daemon token to {dir}/run/daemon_token is given, so the token lands at daemon_token_path.

daemon_token_path

where the harness reads the token

type string

returns

string

the {dir} of that layout

throws

  • Error - when the path is not `.../run/daemon_token`, which no such directory can produce

to_dropped_audit_row
#

auth/audit_emitter.ts view source

<T extends string>(input: AuditLogInput<T>, config: AuditLogConfig): Record<string, unknown> import {to_dropped_audit_row} from '@fuzdev/fuz_app/auth/audit_emitter.js';

The whole row a dropped pool write would have inserted, as the one JSON object its audit row dropped log line carries: event_type, outcome, the four id columns, ip, and metadata — with each metadata field whose schema in config is marked sensitivity: 'secret' replaced by MASKED_VALUE. No builtin event declares a secret field, so a builtin row logs whole, as the Rust twin's fuz_auth::dropped_row_json does.

The masking reads only the top-level fields of an object metadata schema. A secret field nested under an object field, or declared in a union or discriminated-union schema, is logged in clear, and so is every sensitivity: 'personal' field. Keep secrets out of audit metadata — they would land in audit_log too.

input

the row's input

config

the emitter's AuditLogConfig, whose event schemas mark secret fields

returns

Record<string, unknown>

the row, ready to JSON.stringify

generics

to_dropped_audit_row<T extends string>
T
constraint string

to_field_json
#

to_field_json_schema
#

testing/schema_generators.ts view source

(field_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): Record<string, unknown> | null import {to_field_json_schema} from '@fuzdev/fuz_app/testing/schema_generators.js';

The JSON Schema of a field's input side — where its format, pattern and bounds live — or null when JSON Schema can't render the field or any part of it (z.date(), z.custom(), a union with a z.date() branch).

Rendered with io: 'input', so a field carrying a .transform() or a pipe (Username) still yields the constraints its input is checked against; the default output side throws on a transform. A pipe yields its input side only, so constraints of the stage after it are not here. A .nullable() / .nullish() field renders as anyOf: [{…}, {type: 'null'}]; its one non-null branch is returned, so the bounds and pattern read the same as on the bare field. A union of two or more non-null branches returns the whole schema — its branches' bounds are no single field's bounds.

A .refine() is never here: JSON Schema can't express one, so a bound a refinement enforces (a code-point length, a cross-field rule) generates no boundary case.

field_schema

the field to read

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

Record<string, unknown> | null

the field's input-side JSON Schema, or null when it can't be rendered

to_global_role_revocation
#

auth/audit_log_schema.ts view source

(event: AuditLogEvent): { account_id: string & $brand<"Uuid">; role: string; } | null import {to_global_role_revocation} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

The account and role a successful role-scope row took away, when the revoked grant was global — null otherwise.

A role gate (register_action_ws's required_roles, the audit stream's require_role) admits on global grants only, so revoking a scoped grant takes away nothing a gate admitted on and closes nothing. A row with no metadata.scope_id reads as global: when unsure, closing is the safe side. The target is target_account_id ?? account_id, as for the account scope. The revocation guard reads it for every transport, so a socket and a stream gated on one role close on the same rows.

Twin of the role arm of the Rust spine's ConnectionCloseTarget::from_audit_event.

event

a row whose scope is role (the caller dispatched on it)

returns

{ account_id: string & $brand<"Uuid">; role: string; } | null

the account and role to close on, or null when the row names no role or target, or revoked a scoped grant

to_grant_json
#

auth/cell_grant_actions.ts view source

(row: CellGrantRow): { id: string & $brand<"Uuid">; cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; actor_id: (string & $brand<"Uuid">) | null; role: string | null; scope_id: (string & $brand<...>) | null; granted_by: (string & $brand<...>) | null; created_at: string; } import {to_grant_json} from '@fuzdev/fuz_app/auth/cell_grant_actions.js';

row

returns

GrantJson

to_input_params
#

http/schema_helpers.ts view source

(input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, raw_params: unknown): unknown import {to_input_params} from '@fuzdev/fuz_app/http/schema_helpers.js';

Normalize the raw params of an RPC call for input to validate: for a z.void() input, to_void_params; for any other input, an absent or null params reads as {}, so an all-optional object input accepts a call that omits them. Every receiving end — the server dispatch (perform_action) and a peer receiving a request or notification (ActionDispatcher) — reads params through this, so a peer is never stricter than the server.

input

the action's input schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

raw_params

the call's params as parsed off the wire

type unknown

returns

unknown

the value to run input.safeParse on

to_invalid_params_error
#

actions/perform_action.ts view source

(input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, issues: readonly $ZodIssue[]): { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; } import {to_invalid_params_error} from '@fuzdev/fuz_app/actions/perform_action.js';

The invalid_params error an input validation failure answers: the generic invalid params message, data.reason when the field the first issue lands on declares an invalid_reason and no issue sits on the input itself (resolve_issues_invalid_reason), and the Zod issues under DEV only (dev_only). With no declared reason, production sends no data at all.

input

the action's input schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

issues

the failed parse's issues, in Zod's order

type readonly $ZodIssue[]

returns

JsonrpcErrorObject

the JSON-RPC error object

to_iso8601_seconds
#

timestamp.ts view source

(date: Date): string import {to_iso8601_seconds} from '@fuzdev/fuz_app/timestamp.js';

Render a Date as the canonical second-precision UTC wire timestamp (YYYY-MM-DDTHH:MM:SSZ).

Truncates rather than rounds, mirroring Postgres' to_char(…, 'YYYY-MM-DD"T"HH24:MI:SS"Z"') and the Rust spine's fuz_sys::rfc3339_now.

date

the instant to render

type Date

returns

string

the 20-character UTC timestamp

to_item_json
#

auth/cell_item_actions.ts view source

(row: CellItemRow): { parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; } import {to_item_json} from '@fuzdev/fuz_app/auth/cell_item_actions.js';

row

returns

ItemJson

to_jsonrpc_message_id
#

http/jsonrpc_helpers.ts view source

(message_or_id: unknown): string | number | null import {to_jsonrpc_message_id} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Extracts a JSON-RPC request id from a message or raw value. Returns null if no valid id can be extracted.

message_or_id

type unknown

returns

JsonrpcRequestId | null

to_jsonrpc_params
#

http/jsonrpc_helpers.ts view source

(input: unknown): Record<string, any> | undefined import {to_jsonrpc_params} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Normalizes input to JSON-RPC params format. Returns undefined for null/undefined, wraps primitives in {value}.

input

type unknown

returns

Record<string, any> | undefined

to_maintenance_database_url
#

testing/cross_backend/postgres_database.ts view source

(server_url: string): string import {to_maintenance_database_url} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

The URL createdb connects through to create a database on the server server_url names — the same URL with its database replaced by postgres.

server_url

a postgres:// URL on the target server

type string

returns

string

the URL of that server's postgres maintenance database

to_origin_conformance_checks
#

testing/cross_backend/origin_conformance.ts view source

(table: { parse: ({ raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | ... 11 more ... | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; })[]; match: { ...; }[]; }): OriginConformanceCheck[] import {to_origin_conformance_checks} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

Flatten the table into action inputs and the answers they must get. A parse row sends a null origin, so a value that parses answers {ok: true, allowed: true}.

table

the conformance table

returns

OriginConformanceCheck[]

one check per row, parse rows first

to_principal_headers
#

testing/integration_helpers.ts view source

(principal: AuthPrincipal, keeper: KeeperHeaderProvider, authed_account: TestAccount, admin_account: TestAccount): Record<...> import {to_principal_headers} from '@fuzdev/fuz_app/testing/integration_helpers.js';

The request headers carrying a principal's credential.

principal

the principal pick_auth_principal chose

keeper

the keeper's header builders

authed_account

the account the 'authed' principal calls as

admin_account

the account the 'admin' principal calls as

returns

Record<string, string>

to_revocation_scope
#

auth/audit_log_schema.ts view source

(event_type: string): RevocationScope import {to_revocation_scope} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

The RevocationScope of an audit_log.event_type string.

An event type with no builtin entry — a consumer's own event — resolves to none: an event fuz_app has never heard of must not close connections. A consumer whose own event revokes closes the connections from its handler, with queue_connection_close.

event_type

type string

returns

RevocationScope

to_role_grant_offer_json
#

auth/role_grant_offer_schema.ts view source

(offer: RoleGrantOffer): { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; ... 12 more ...; resulting_role_grant_id: (string & $brand<...>) | null; } import {to_role_grant_offer_json} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Convert a RoleGrantOffer row to its JSON payload shape.

offer

returns

RoleGrantOfferJson

to_route_content_type
#

testing/schema_generators.ts view source

(route: { raw_body?: boolean | undefined; }): string import {to_route_content_type} from '@fuzdev/fuz_app/testing/schema_generators.js';

The Content-Type a well-formed request to a route sends: application/json, or application/octet-stream for a raw_body route. Every POST route refuses anything else with 415 before its other gates run (see http/simple_request.ts), so a probe of those gates must send it, body or not. Other methods don't check it, so sending it there is harmless.

route

the route spec or surface route (reads raw_body)

type { raw_body?: boolean | undefined; }

returns

string

to_rpc_envelope_request
#

testing/rpc_envelope_cases.ts view source

(endpoint_path: string, c: RpcEnvelopeCase, headers?: Record<string, string>): { url: string; init: RequestInit; } import {to_rpc_envelope_request} from '@fuzdev/fuz_app/testing/rpc_envelope_cases.js';

Build the RequestInit + path suffix for an envelope case.

endpoint_path

the RPC endpoint path

type string

c

the case

headers?

auth headers to merge in

type Record<string, string>
optional default {}

returns

{ url: string; init: RequestInit; }

the URL (path + query for GET) and the init

to_same_origin_ws_url
#

actions/transports_ws.ts view source

(path: string): string import {to_same_origin_ws_url} from '@fuzdev/fuz_app/actions/transports_ws.js';

The WebSocket URL of path on the page's own origin — ws: for an http: page, wss: for https: — the shape a backend served behind the same origin as the frontend (nginx in production, the Vite dev proxy in dev) is reached at. Absolute because older browsers reject a relative WebSocket URL.

Off the browser (SSR, prerender) there is no page origin and nothing connects, so path comes back unchanged.

path

the endpoint path, e.g. /api/ws

type string

returns

string

the absolute same-origin WebSocket URL in a browser, else path

to_session_account
#

auth/account_schema.ts view source

(account: Account): SessionAccount import {to_session_account} from '@fuzdev/fuz_app/auth/account_schema.js';

Convert an Account to a SessionAccount by stripping sensitive fields.

account

the full account record

type Account

returns

SessionAccount

the client-safe account

to_session_cookie
#

to_test_account
#

testing/cross_backend/setup.ts view source

(seeded: { account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; api_token: string; session_cookie: string; }, cookie_name: string): TestAccount import {to_test_account} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Build a TestAccount from a seeded `{account, actor, api_token, session_cookie}` bundle and the session cookie name — the header builders close over its credentials.

Every fixture path that hands out a secondary account builds it here: the bootstrap-time extra_accounts (in-process via create_test_account_with_credentials, cross-process from the _testing_reset output) and the cross-process fixture.create_account.

seeded

the account's identity and credentials

type { account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; api_token: string; session_cookie: string; }

cookie_name

the backend's session cookie name

type string

returns

TestAccount

the account with its header builders

to_thrown_jsonrpc_error
#

http/jsonrpc_errors.ts view source

(err: unknown): { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; } | null import {to_thrown_jsonrpc_error} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

The JSON-RPC error a thrown value carries: a ThrownJsonrpcError from any copy of fuz_app, recognized by its realm-global brand rather than instanceof (two resolved copies of fuz_app in one process have two classes), whose code is a JSON-RPC error code. Anything else is no JSON-RPC error — a foreign error class carrying a JSON-RPC-shaped code (another library's RPC error), or a ThrownJsonrpcError whose code was cast outside the JSON-RPC set — so each caller masks it.

A failed pool checkout (DbCheckoutError, db/db_checkout_error.ts) is the database unavailable: service_unavailable with DB_UNAVAILABLE_MESSAGE and no data (HTTP 503), the Rust spine's fuz_http::db_unavailable.

The one thrown → wire conversion: the REST catch layer (apply_route_specs), the RPC dispatch core (perform_action), create_jsonrpc_error_response_from_thrown, the frontend HTTP and WebSocket transports, and ActionEvent's handler-throw normalization all read a thrown error through it, each keeping its own masking of everything else.

err

the thrown value

type unknown

returns

JsonrpcErrorObject | null

the error object (data present only when the error carries one), or null when err is no JSON-RPC error

to_uint8
#

db/fact_store.ts view source

(value: Uint8Array<ArrayBufferLike>): Uint8Array<ArrayBufferLike> import {to_uint8} from '@fuzdev/fuz_app/db/fact_store.js';

Coerce whatever the driver returns for BYTEA into a Uint8Array.

pg returns Buffer (a Uint8Array subclass), pglite already returns Uint8Array. Wrapping Buffer in a fresh Uint8Array keeps the downstream type honest without a copy.

value

a BYTEA column value as the driver returned it

type Uint8Array<ArrayBufferLike>

returns

Uint8Array<ArrayBufferLike>

a plain Uint8Array over the same bytes

to_void_params
#

http/schema_helpers.ts view source

(raw_params: unknown): unknown import {to_void_params} from '@fuzdev/fuz_app/http/schema_helpers.js';

Normalize the raw params of a z.void() input: an absent params, an empty object, and the null a GET ?params=null parses to are all the no-arg call (undefined); anything else passes through for z.void() to refuse.

JSON-RPC 2.0 lets a parameterless call omit params, and an empty by-name structure carries no parameters, so refusing {} would only break clients that send it for a parameterless call. Arrays — [] included — are not normalized: RPC params are by-name only. The Rust spine's require_void_params accepts the same shapes. Receiving ends reach it through to_input_params.

raw_params

the request's params as parsed off the wire

type unknown

returns

unknown

undefined for an empty shape, else raw_params unchanged

to_ws_actions
#

actions/protocol.ts view source

(rpc_actions: readonly Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<...>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }>[]): Action<...>[] import {to_ws_actions} from '@fuzdev/fuz_app/actions/protocol.js';

The WebSocket action set for an RPC action list: protocol_actions first, then every action of rpc_actions that is not a protocol action — so one set of handler closures serves both transports, and a protocol action the RPC list carries (peer_ping_action, which an HTTP caller must reach to be refused peer_no_transport) is not registered twice.

What a WsEndpointSpec that omits actions serves, from the server's sole RPC endpoint (actions/ws_endpoint_spec.ts).

rpc_actions

the RPC endpoint's actions

type readonly Action<ActionSpecUnion>[]

returns

Action<ActionSpecUnion>[]

the WS endpoint's actions

token_lifetime_to_expires_at
#

auth/token_lifetime.ts view source

(lifetime: { kind: "eternal"; } | { kind: "ttl"; days: number; }, now?: Date): Date | null import {token_lifetime_to_expires_at} from '@fuzdev/fuz_app/auth/token_lifetime.js';

Resolve a lifetime input to the expires_at value the mint query stores.

lifetime

the validated lifetime input

now?

mint time (injectable for tests; defaults to the current time)

type Date
optional default new Date()

returns

Date | null

the expiry Date, or null for an eternal token

token_scope_admits_method
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }, method: string): boolean import {token_scope_admits_method} from '@fuzdev/fuz_app/auth/token_scope.js';

Does scope admit dispatching method?

Consulted by perform_action between the credential gate and the role gate.

scope

method

type string

returns

boolean

token_scope_admits_non_rpc
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }): boolean import {token_scope_admits_non_rpc} from '@fuzdev/fuz_app/auth/token_scope.js';

Does scope admit a non-RPC spine surface?

A narrowed token reaches no non-RPC surface that reads account data or carries authority — the load-bearing half of token scoping, and what "rule 3" names wherever this module's callers and the Rust twin use that shorthand. See docs/security.md §Token scoping.

A method-name allowlist alone would have been a false promise: the db-admin browser serves paginated rows of any public table (including account.password_hash, auth_session, and api_token) plus row DELETE, gated on a global role a bearer credential satisfies. A token whose UI badge read "scoped to cell_get" could still delete an account row.

The predicate is all-or-nothing: no per-surface vocabulary decides anything, so it fits in one sentence a user can hold — *a scoped token is RPC-only*. Placement is per site: a route that refuses anonymous callers declares auth.required_scope, one that answers them reads the credential through get_public_account_id and answers a narrowed token as anonymous, and the surface census pins every site.

scope

returns

boolean

token_scope_denied_body
#

auth/token_scope.ts view source

(capability: string): { error: "token_scope_required"; required_scope: string; } import {token_scope_denied_body} from '@fuzdev/fuz_app/auth/token_scope.js';

Build the flat denial body a non-RPC route returns when a narrowed token reaches a capability it lacks: {error: 'token_scope_required', required_scope: '<section>:<id>'} at 403.

One shape for every capability, matching the Rust token_scope_surface_denied_response. These are REST-ish routes whose sibling denials are already flat, so the JSON-RPC envelope stays with the dispatcher's per-method gate and stops there.

capability

type string

returns

{ error: "token_scope_required"; required_scope: string; }

token_scope_from_input
#

auth/token_scope.ts view source

(input: { kind: "full"; } | { kind: "methods"; methods: string[]; }): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } import {token_scope_from_input} from '@fuzdev/fuz_app/auth/token_scope.js';

Build the stored scope from a mint-time input.

Duplicate method names collapse into one, first occurrence kept: the set is what the scope admits, and the Rust spine stores a set, so both count the same methods (the account_token_list label reads N methods).

input

returns

TokenScope

token_scope_full
#

auth/token_scope.ts view source

(): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } import {token_scope_full} from '@fuzdev/fuz_app/auth/token_scope.js';

A deliberately-minted full-authority scope.

returns

TokenScope

token_scope_label
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }): string import {token_scope_label} from '@fuzdev/fuz_app/auth/token_scope.js';

Stable label for the token list / UI.

scope

returns

string

token_scope_method_capability
#

auth/token_scope.ts view source

(method: string): string import {token_scope_method_capability} from '@fuzdev/fuz_app/auth/token_scope.js';

The capability string an action method is refused under: rpc:<method>.

Computed by the dispatcher's per-method gate, where a surface: capability is always written as a literal on a route spec, so nothing computes one.

method

type string

returns

string

token_scope_methods
#

auth/token_scope.ts view source

(methods: string[]): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } import {token_scope_methods} from '@fuzdev/fuz_app/auth/token_scope.js';

A narrowed scope over methods.

methods

type string[]

returns

TokenScope

token_scope_surface_denial
#

auth/request_context.ts view source

(c: Context<any, any, {}>, capability: string): Response | null import {token_scope_surface_denial} from '@fuzdev/fuz_app/auth/request_context.js';

Refuse a non-RPC surface to a narrowed token — rule 3. Under the Rust twin's name (token_scope_surface_denied_response).

The decision every token-scope gate outside the action dispatcher shares: a route spec declares auth.required_scope and gets require_token_scope, which calls this; a consumer gating something that is not a route spec calls it directly.

The anonymous caller holds no credential to narrow, so it passes (resolved_auth_scope answers null). Sharing that branch is the point — hand-rolled per surface it is one && from refusing every anonymous read.

Takes the whole capability string, not a bare surface name — one spelling across route-spec declarations and direct calls alike, and the twin of the Rust signature. It needs no parse: the surface arm's identifier is pure label, since rule 3 never asks *which* surface. And it is deliberately not narrowed to the spine's own — the name decides nothing, so there is nothing for a closed set to protect, while a consumer surface would have nothing to pass. fuz_app's own uses are pinned two ways by the surface census: the exact call site, and the scan asserting every 'surface:<name>' literal in src/lib names a surface the spine actually mounts.

c

the request context, after the auth middleware chain

type Context<any, any, {}>

capability

the surface:<name> this route demands; names itself in the denial

type string

returns

Response | null

the 403 to return, or null when the caller may proceed

TOKEN_SCOPE_VERSION
#

TOKEN_SURFACE_NAME_REGEX
#

auth/token_scope.ts view source

RegExp import {TOKEN_SURFACE_NAME_REGEX} from '@fuzdev/fuz_app/auth/token_scope.js';

Name format for the identifier half of a capability string. Mirrors RoleName / CredentialTypeName / ScopeKindName.

Applied to the surface: arm only. The rpc: arm's identifier is an action method name, whose vocabulary belongs to ActionSpec (and includes shapes like peer/ping this regex would refuse); the dispatcher computes it from the registered method, so nothing parses one.

TOKEN_SURFACES
#

auth/token_scope.ts view source

readonly ["db_admin", "fact_bare", "audit_stream", "ws_upgrade", "app_surface", "account_verify"] import {TOKEN_SURFACES} from '@fuzdev/fuz_app/auth/token_scope.js';

The non-RPC surfaces the spine itself mounts (rule 3). Twin of the Rust TokenSurface enum, name for name, except the two only the TS spine serves: app_surface, the opt-in GET /api/surface explorer (server/surface_route.ts), and account_verify, the GET /api/account/verify nginx auth_request probe (auth/account_route_schema.ts).

Not a registry a consumer registers into. RouteAuth.required_scope accepts any well-formed surface:<name> (see is_route_scope_capability), so a consumer names its own non-RPC surface without an upstream release. This tuple is what the *spine* may declare, and what its surface census iterates — the closure is a check on fuz_app's own routes, not a vocabulary consumers have to ask permission to extend.

A surface name never decides anything: rule 3 is all-or-nothing (token_scope_admits_non_rpc), so the name only labels *which* surface refused, in the required_scope a denial reports — the same field whose rpc:<method> value, in a dispatcher denial, carries arbitrary consumer method names.

TOKEN_TTL_DAYS_MAX
#

auth/token_lifetime.ts view source

36500 import {TOKEN_TTL_DAYS_MAX} from '@fuzdev/fuz_app/auth/token_lifetime.js';

Upper bound on ttl.days — a sanity cap (100 years), not a policy ceiling. Shared with the Rust twin so out-of-range requests 400 identically on both spines.

TokenCreateInput
#

auth/account_action_specs.ts view source

value + type

{ name: string; scope: { kind: "full"; } | { kind: "methods"; methods: string[]; }; lifetime: { kind: "eternal"; } | { kind: "ttl"; days: number; }; } import {TokenCreateInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_token_create.

scope is required — default-deny at mint. There is deliberately no permissive default: a caller who wants full authority spells {kind: 'full'}, and the stored document records that they did. That is the reversal of the 2026-02 design, where the field existed but nothing depended on it. Because scope has no default, the object as a whole no longer carries .prefault({}).

lifetime follows the same rule — a never-expiring token is {kind: 'eternal'}, spelled out, so expires_at IS NULL always means "deliberately eternal" (see auth/token_lifetime.ts).

name

type string

scope

type TokenScopeInput

lifetime

type TokenLifetimeInput

TokenCreateOutput
#

auth/account_action_specs.ts view source

value + type

{ ok: true; token: string; id: string; name: string; expires_at: string | null; } import {TokenCreateOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_token_create. token is returned exactly once.

ok

type true

token

type string

id

type string

name

type string

expires_at

type string | null

TokenLifetimeCrossTestOptions
#

testing/cross_backend/token_lifetime.ts view source

TokenLifetimeCrossTestOptions import type {TokenLifetimeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/token_lifetime.js';

Options for the token-lifetime parity suite. Ungated — the mint + list pair is on every spine's standard surface, so there is no capability flag (the sibling of identity_parity.ts rather than the cell suites).

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

TokenLifetimeInput
#

auth/token_lifetime.ts view source

value + type

{ kind: "eternal"; } | { kind: "ttl"; days: number; }

type { kind: "eternal"; } | { kind: "ttl"; days: number; }

import {TokenLifetimeInput} from '@fuzdev/fuz_app/auth/token_lifetime.js';

Caller-provided lifetime for account_token_create.

eternal mints a never-expiring token (expires_at NULL); ttl bounds it to days from mint time. eternal is deliberately the first variant so schema-driven test generators produce it by default.

TokenListInput
#

TokenListOutput
#

auth/account_action_specs.ts view source

value + type

{ tokens: { id: string; account_id: string & $brand<"Uuid">; name: string; expires_at: string | null; last_used_at: string | null; last_used_ip: string | null; created_at: string; scope: string; }[]; } import {TokenListOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_token_list. Hashes are excluded.

tokens

type ClientApiTokenJson[]

TokenRevokeInput
#

auth/account_action_specs.ts view source

value + type

{ token_id: string; } import {TokenRevokeInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_token_revoke.

token_id

type string

TokenRevokeOutput
#

auth/account_action_specs.ts view source

value + type

{ ok: true; revoked: boolean; } import {TokenRevokeOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_token_revoke. revoked is false for IDOR misses.

ok

type true

revoked

type boolean

TokenScope
#

auth/token_scope.ts view source

value + type

{ version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }

type { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }

import {TokenScope} from '@fuzdev/fuz_app/auth/token_scope.js';

The stored api_token.scope document.

grandfathered is a fact about the *row* — the api_token_scope migration wrote it; no minter chose it — that only the token list cares about. The enforcement path never branches on it, which is what stops "grandfathered" from becoming a third authority level.

Strict on both arms so a v2 document carrying a grant arm a v1 reader doesn't understand is refused rather than parsed as permissively-narrower-than-stored. The discriminated union also refuses the contradiction {kind: 'full', methods: [...]}.

TokenScopeInput
#

auth/token_scope.ts view source

value + type

{ kind: "full"; } | { kind: "methods"; methods: string[]; }

type { kind: "full"; } | { kind: "methods"; methods: string[]; }

import {TokenScopeInput} from '@fuzdev/fuz_app/auth/token_scope.js';

The scope input accepted by account_token_create — the stored shape minus the row-owned fields.

A minter cannot set version or grandfathered: the first is stamped by token_scope_full / token_scope_methods, and the second is the migration's mark for tokens that predate scoping. Letting a caller forge grandfathered would turn a tracked debt into a laundered one.

TokenScopeRequiredError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "token_scope_required"; required_scope: string; } import {TokenScopeRequiredError} from '@fuzdev/fuz_app/http/error_schemas.js';

Token-scope error — returned by the require_token_scope guard (and its direct-call twin token_scope_surface_denial) when a narrowed api token reaches a capability its scope does not admit. See docs/security.md §Token scoping.

required_scope carries the capability string the route demands, verbatim from its auth.required_scope (surface:audit_stream, …) — matching the Rust token_scope_surface_denied_response and the dispatcher's own per-method denial. Third in the family with PermissionError and CredentialTypeRequiredError — each names what the route demanded rather than what the caller holds.

error

type "token_scope_required"

required_scope

type string

[key: string]

type unknown

TokenScopeSurfaceCrossTestOptions
#

TokenSurface
#

auth/token_scope.ts view source

"db_admin" | "fact_bare" | "audit_stream" | "ws_upgrade" | "app_surface" | "account_verify"

type "db_admin" | "fact_bare" | "audit_stream" | "ws_upgrade" | "app_surface" | "account_verify"

import type {TokenSurface} from '@fuzdev/fuz_app/auth/token_scope.js';

Transport
#

actions/transports.ts view source

Transport import type {Transport} from '@fuzdev/fuz_app/actions/transports.js';

transport_name

type string

send

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: TransportSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

is_ready

type (): boolean

returns boolean

queues?

Whether a request sent with queue: true waits while the transport is not ready — buffered and sent once it is — rather than failing. Read at each choice, so it can change: FrontendWebsocketTransport queues (its connection's durable queue) only while its connection will reopen on its own. A transport that leaves this unset never does, and is chosen only when ready.

type boolean

readonly

dispose?

type (): void

returns void

transport_composition_cases
#

transport_composition_multi_actor_cases
#

TransportCompositionCase
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionCase import type {TransportCompositionCase} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

One cell of the front-door table.

surface

type TransportCompositionSurface

readonly

probe

type TransportCompositionProbe

readonly

expect

type TransportCompositionExpect

readonly

gate?

The capability the surface needs; the case skips on a backend without it.

type "ws" | "sse" | "cell_crud" | "cell_relations" | "cell_gated_create" | "fact_serving" | "fact_serving_admin_hash" | "ready" | "peer_request" | "ws_account_actions" | "ws_role_gated" | "testing_backdoors" | "account_lifecycle" | "account_status" | "ws_handshake_pipelining" | "ws_refusal_body"

readonly

TransportCompositionCrossTestOptions
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionCrossTestOptions import type {TransportCompositionCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

inheritance

base_url

Base URL the spawned backend is reachable at, or null for an in-process run. Given a URL, every probe is written to the socket; with null the probes go through the fixture's transport and the WebSocket rows skip.

type string | null

readonly

origin?

An Origin the backend allows. Default: the origin of base_url, or TEST_ORIGIN in-process (the one origin create_test_app's allow list admits).

type string

readonly

rest_path?

Session-only REST POST route the REST row probes. Default /api/account/logout.

type string

readonly

password_path?

Password-change route the /password row probes. Default /api/account/password.

type string

readonly

ws_path?

WebSocket endpoint path. Default /api/ws.

type string

readonly

sse_path?

Audit-log SSE path. Default /api/admin/audit/stream.

type string

readonly

multi_actor?

Run the multi-actor rows (transport_composition_multi_actor_cases).

type TransportCompositionMultiActorOptions

readonly

rpc_max_body_bytes?

The backend's request-body cap on rpc_path, in bytes; the RPC row's over-cap probe declares a length one past it. Default SPINE_BODY_LIMIT_DEFAULT_BYTES. The REST rows probe past that default, the cap a backend keeps off its RPC path.

type number

readonly

TransportCompositionExpect
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionExpect import type {TransportCompositionExpect} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

The refusal a case expects.

status

type number

readonly

error

The refusal reason — the flat body's error, or the envelope's error.data.reason.

type string

readonly

fields?

Further fields the refusal must carry, beside the reason.

type Readonly<Record<string, unknown>>

readonly

envelope?

true when the refusal is a JSON-RPC error envelope rather than a flat {error} body.

type boolean

readonly

TransportCompositionMultiActorOptions
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionMultiActorOptions import type {TransportCompositionMultiActorOptions} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

The multi-actor rows' fixture, and how a backend known to diverge runs them.

setup_test

Per-test fixture whose keeper is seeded with at least one extra_actors entry.

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

xfail?

Run the multi-actor rows as xfail_until — for a backend known to admit them.

type { readonly tracking_id: string; readonly reason: string; }

readonly

TransportCompositionProbe
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionProbe

type "foreign_origin" | "unreadable_origin" | "duplicated_origin" | "over_cap_foreign_origin" | "no_credential" | "narrowed_token" | "multi_actor_no_acting"

import type {TransportCompositionProbe} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

What a probe sends, as the table's columns name it.

TransportCompositionSurface
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionSurface

type "rest_post" | "rest_password" | "rpc_post" | "rpc_get" | "ws_upgrade" | "sse"

import type {TransportCompositionSurface} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

A transport's front door, as the table's rows name it.

TransportForMethod
#

actions/rpc_client.ts view source

TransportForMethod import type {TransportForMethod} from '@fuzdev/fuz_app/actions/rpc_client.js';

Optional per-method transport selector. Return the transport to use for a given method, or undefined to let the peer pick via its fallback rules.

Useful when methods are registered on different backend dispatchers — e.g. a streaming action mounted on the WebSocket endpoint while the rest of the RPC surface lives on HTTP.

(call)

type (method: string): string | undefined

method

type string
returns string | undefined

TransportName
#

Transports
#

actions/transports.ts view source

import {Transports} from '@fuzdev/fuz_app/actions/transports.js';

The transports a peer sends through, by name. The first registered is the default a call that names none prefers.

register_transport

Registers a transport. The first transport registered is the default.

type (transport: Transport): void

transport

returns void

mutates

  • this — inserts into `#transport_by_name`; sets the default if none is set

get_transport

Resolve the transport a call goes out on: the preferred one — the named transport, or the default when no name is given — when it is ready, or when it is not but options.queue is set and it queues (the call waits for it); otherwise the first ready transport, the default first.

type (transport_name?: string | undefined, options?: GetTransportOptions | undefined): Transport | null

transport_name?

the transport the call prefers; the default when omitted

type string
optional

options?

whether the call is a queued request

optional
returns Transport | null

the resolved transport, or null when none can take the call

TransportSendOptions
#

actions/transports.ts view source

TransportSendOptions import type {TransportSendOptions} from '@fuzdev/fuz_app/actions/transports.js';

Per-call options accepted by every transport's send. Optional and extensible — adding a field is non-breaking. Source of truth for the shared option shape; ActionDispatcherSendOptions and RpcClientCallOptions extend it.

signal?

Per-call cancellation. Bottoms out at FrontendWebsocketClient.request({signal}) on the WS path (sends the shared cancel notification on abort) and at fetch({signal}) on HTTP.

type AbortSignal

queue?

Per-call durable-queue opt-in. Names the client-authoritative vs server-authoritative distinction — server-authoritative consumers (e.g. zzz completion calls) fail fast with service_unavailable when the transport is down; client-authoritative consumers (games, real-time apps) buffer and replay on reconnect because the user already committed to the action at click time. Honored only by FrontendWebsocketTransport on the request_response path (default false), and by Transports.get_transport, which chooses a transport that queues for such a request even while it is disconnected. HTTP ignores it; WS notifications also ignore it and always fail-fast when disconnected (fire-and-forget connection.send has no queue semantic).

type boolean

truncate_middle
#

ui/ui_format.ts view source

(str: string, max_length: number, separator?: string): string import {truncate_middle} from '@fuzdev/fuz_app/ui/ui_format.js';

Truncate a string by keeping the start and end, with a separator in the middle.

str

type string

max_length

total length including separator

type number

separator?

type string
optional default '…'

returns

string

the truncated string, or the original if it fits

truncate_uuid
#

ui/ui_format.ts view source

(uuid: string): string import {truncate_uuid} from '@fuzdev/fuz_app/ui/ui_format.js';

Truncate a UUID for display, keeping start and end visible.

uuid

type string

returns

string

a 12-character truncated UUID like a1b2c…7890

try_reserve_optional
#

rate_limiter.ts view source

(limiter: RateLimiter | null, key: string | null, now?: number): RateLimitReserveResult import {try_reserve_optional} from '@fuzdev/fuz_app/rate_limiter.js';

RateLimiter.try_reserve against a limiter that may be absent.

An inert reservation when limiter is null (disabled) or key is null (no resolvable key — e.g. no client IP), so a handler reserves and settles the same way whether or not the surface is limited. Twin of the Rust spine's try_reserve_optional.

limiter

the limiter, or null when disabled

type RateLimiter | null

key

rate limit key, or null to skip

type string | null

now?

current timestamp in ms (defaults to Date.now())

type number
optional default Date.now()

returns

RateLimitReserveResult

ts_default_capabilities
#

testing/cross_backend/default_backend_configs.ts view source

FamilyDefaultCapabilities import {ts_default_capabilities} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

Capabilities shared by TS-family backends — only what the family guarantees: the declared spine surface's mounts and the behavior of its reference runtime, Node. Carries no MountFlag and no testing_backdoors; each backend config states those itself (FamilyDefaultCapabilities).

ts_spine_bun_backend_config
#

TS_SPINE_BUN_DEFAULT_PORT
#

TS_SPINE_BUN_ENTRY
#

ts_spine_deno_backend_config
#

testing/cross_backend/ts_spine_backend_config.ts view source

(options?: TsSpineBackendConfigOptions): BackendConfig import {ts_spine_deno_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/ts_spine_backend_config.js';

BackendConfig for the Deno TS spine binary. The --allow-* set mirrors the cross-process needs (net + read/write for the daemon-token file + env + sys); --unstable-detect-cjs matches the ecosystem's Deno test entries.

options?

optional default {}

returns

BackendConfig

TS_SPINE_DENO_DEFAULT_PORT
#

TS_SPINE_DENO_ENTRY
#

TS_SPINE_DIR_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_TESTING_TS_SPINE_DIR" import {TS_SPINE_DIR_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var naming the TS spine backend's root dir, set by ts_spine_backend_config; the spawned binary writes its daemon token to {dir}/run/daemon_token, which must match bootstrap.daemon_token_path.

ts_spine_node_backend_config
#

TS_SPINE_NODE_DEFAULT_PORT
#

TS_SPINE_NODE_ENTRY
#

TsSpineBackendConfigOptions
#

testing/cross_backend/ts_spine_backend_config.ts view source

TsSpineBackendConfigOptions import type {TsSpineBackendConfigOptions} from '@fuzdev/fuz_app/testing/cross_backend/ts_spine_backend_config.js';

port?

Listening port. Defaults per runtime (1178 Node, 1179 Deno, 1180 Bun).

type number

readonly

database_url?

Database URL. Default 'memory://' (in-memory pglet).

type string

readonly

name?

Backend name — labels the spawn in errors and logs, and names its directory under os.tmpdir() (default_test_backend_paths, namespaced by the running repo). Defaults per runtime (ts_spine_node, ts_spine_deno, ts_spine_bun). Give each backend spawned alongside another of its runtime its own name, so their bootstrap and daemon-token files don't collide.

type string

readonly

enable_login_rate_limit?

Enable the per-IP + per-account login rate limiters on the spawned binary (FUZ_LOGIN_RATE_LIMIT_ENABLED=true). Off by default — the standard cross suites fire many loopback logins a live limiter would 429. Set true only for the dedicated login-security cross project (global_setup_login_security.ts), which drives the 429 + Retry-After path and XFF-keyed bucketing over the wire (login_security.ts). The binary always wires trusted_proxies for 127.0.0.1/::1, so the limiter keys on the resolved X-Forwarded-For client IP. Mirrors SpineStubBackendConfigOptions.enable_login_rate_limit.

type boolean

readonly

enable_action_rate_limit?

Enable the action rate limiters (FUZ_ACTION_RATE_LIMIT_ENABLED=true) — one per-IP and one per-account limiter shared by the RPC and WS endpoints. Off by default, like enable_login_rate_limit; set only for the dedicated security cross project, which drives them through ws_action_rate_limit.ts. Mirrors SpineStubBackendConfigOptions.enable_action_rate_limit.

type boolean

readonly

action_rate_limit_max_attempts?

Replace both action limiters' max_attempts (FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS) so a suite reaches the cap in a few calls. Requires enable_action_rate_limit — the binary refuses to boot otherwise. Mirrors SpineStubBackendConfigOptions.action_rate_limit_max_attempts.

type number

readonly

audit_sse_keep_alive_ms?

Replace the audit stream's keep-alive interval (FUZ_AUDIT_SSE_KEEP_ALIVE_MS), so a suite sees keep-alives without waiting out the 15 s default. Mirrors SpineStubBackendConfigOptions.audit_sse_keep_alive_ms.

type number

readonly

ws_receive_timeout_ms?

Replace the WS endpoints' receive timeout (FUZ_WS_RECEIVE_TIMEOUT_MS), so a suite sees a silent socket closed without waiting out the 60 s default. Mirrors SpineStubBackendConfigOptions.ws_receive_timeout_ms.

type number

readonly

db_checkout_timeout_ms?

Replace the request pool checkout bound (FUZ_DB_CHECKOUT_TIMEOUT_MS). Postgres only — over memory:// the binary warns and runs unbounded. Mirrors SpineStubBackendConfigOptions.db_checkout_timeout_ms.

type number

readonly

db_audit_checkout_timeout_ms?

Replace the audit write pool checkout bound (FUZ_DB_AUDIT_CHECKOUT_TIMEOUT_MS), Postgres only. Mirrors SpineStubBackendConfigOptions.db_audit_checkout_timeout_ms.

type number

readonly

ui_fetch
#

ui/ui_fetch.ts view source

(input: URL | RequestInfo, init?: RequestInit | undefined): Promise<Response> import {ui_fetch} from '@fuzdev/fuz_app/ui/ui_fetch.js';

Fetch with credentials included (sends cookies).

input

type RequestInfo | URL

init?

type RequestInit
optional

returns

Promise<Response>

UncoveredEntry
#

testing/error_coverage.ts view source

UncoveredEntry import type {UncoveredEntry} from '@fuzdev/fuz_app/testing/error_coverage.js';

Uncovered entry — either a status-level row (no code) or a specific-code row.

method

type string

path

type string

status

type number

code?

Declared code value missing, when the status's error schema names specific codes.

type string

UNKNOWN_ERROR_MESSAGE
#

http/jsonrpc_errors.ts view source

"unknown error" import {UNKNOWN_ERROR_MESSAGE} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Default message for unknown errors.

UnsupportedContentTypeError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "unsupported_content_type"; } import {UnsupportedContentTypeError} from '@fuzdev/fuz_app/http/error_schemas.js';

Unsupported content type error — returned when a POST route refuses the request's Content-Type.

error

type "unsupported_content_type"

[key: string]

type unknown

update_env_content
#

env/update_env_variable.ts view source

(content: string, key: string, value: string): string import {update_env_content} from '@fuzdev/fuz_app/env/update_env_variable.js';

Set key to value in dotenv content, returning the new content. Preserves existing formatting, comments, and other variables.

Behavior:

  • Duplicate keys: updates the LAST occurrence (matches dotenv behavior)
  • Inline comments: preserved after the value (e.g., KEY=value # comment)
  • Quote style: a quoted value stays quoted; an unquoted one stays bare unless the new value would not read back bare (surrounding whitespace, a # comment marker, a leading quote, a line break), which is quoted
  • export prefix: preserved when updating an export KEY=… line
  • Absent key: appended as a quoted assignment

The line is rebuilt from its parts, so no character of value ($&, `` $ `, $', \) is interpreted.

content

dotenv file content ('' for a new file)

type string

key

the environment variable name

type string

value

the new value

type string

returns

string

the updated content

update_env_variable
#

env/update_env_variable.ts view source

(deps: FsReadDeps & FsWriteDeps & Partial<FsRemoveDeps>, env_path: string, key: string, value: string): Promise<void> import {update_env_variable} from '@fuzdev/fuz_app/env/update_env_variable.js';

Updates or adds an environment variable in an env file — read, update_env_content, then an atomic replace at mode 0o600 (an env file holds secrets, so the fresh owner-only inode also tightens a permissive hand-made one). A missing file is created.

A read-modify-write: a concurrent writer's update can be lost (the last replace wins), though the file is never left half-written.

deps

file read and write capabilities; pass remove too for best-effort temp cleanup on a failed write

env_path

path to the env file

type string

key

the environment variable name (e.g., 'SOME_CONFIGURATION_KEY')

type string

value

the new value for the environment variable

type string

returns

Promise<void>

throws

  • Error - if the read or the write fails

mutates

  • filesystem — atomically replaces `env_path` at mode `0o600`

UpdateAppSettingsInput
#

auth/app_settings_schema.ts view source

value + type

{ open_signup: boolean; } import {UpdateAppSettingsInput} from '@fuzdev/fuz_app/auth/app_settings_schema.js';

Zod schema for updating app settings.

open_signup

type boolean

Username
#

primitive_schemas.ts view source

value + type

string import {Username} from '@fuzdev/fuz_app/primitive_schemas.js';

Username for account creation — starts with letter, alphanumeric/dash/underscore middle, ends with alphanumeric. No @ or . allowed.

Canonicalized to lowercase at parse time. The regex rejects whitespace outright, so .trim() is unnecessary here. Storage is canonical across every creation site (bootstrap, signup, admin-create, invite acceptance) because the schema is the single source of truth — eliminates the per-caller trim().toLowerCase() ritual and keeps the LOWER(username) = LOWER($1) lookup contract simple.

USERNAME_LENGTH_MAX
#

primitive_schemas.ts view source

39 import {USERNAME_LENGTH_MAX} from '@fuzdev/fuz_app/primitive_schemas.js';

Maximum username length (matches GitHub's limit). The grammar is ASCII, so units and characters agree.

USERNAME_LENGTH_MIN
#

primitive_schemas.ts view source

3 import {USERNAME_LENGTH_MIN} from '@fuzdev/fuz_app/primitive_schemas.js';

Minimum username length (must have start + middle + end characters).

USERNAME_PROVIDED_LENGTH_MAX
#

primitive_schemas.ts view source

255 import {USERNAME_PROVIDED_LENGTH_MAX} from '@fuzdev/fuz_app/primitive_schemas.js';

Maximum length for username input on login/lookup, in UTF-16 code units, measured before trimming — more permissive than USERNAME_LENGTH_MAX for forward-compatibility if the creation limit is raised.

UsernameProvided
#

primitive_schemas.ts view source

value + type

string import {UsernameProvided} from '@fuzdev/fuz_app/primitive_schemas.js';

Username submitted for login or lookup — minimal validation for forward-compatibility if format rules change.

Canonicalized via .trim().toLowerCase() at parse time so login's per-account rate-limit key and DB lookup see a uniform value regardless of casing or surrounding whitespace. Mirrors the storage canonicalization on Username so submission and storage agree.

The trailing .refine rejects a whitespace-only identifier: .min(1) runs on the raw string (so " " passes it), and without the post-trim check the value would canonicalize to "" and fall through to a lookup-miss 401 instead of a 400 — an empty identifier is malformed input, not a wrong credential. Keeps the Rust spine's account_login (which rejects empty-after-trim) in parity.

utf8_length
#

actions/transports.ts view source

(text: string): number import {utf8_length} from '@fuzdev/fuz_app/actions/transports.js';

The UTF-8 byte length of text.

text

the string to measure

type string

returns

number

its length in UTF-8 bytes

utf8_length_over
#

actions/transports.ts view source

(text: string, max_bytes: number): number | null import {utf8_length_over} from '@fuzdev/fuz_app/actions/transports.js';

The UTF-8 byte length of text when it exceeds max_bytes, else null. Skips encoding when the UTF-16 length alone rules it out — a code unit takes at most 3 UTF-8 bytes.

text

the string to measure

type string

max_bytes

the byte cap

type number

returns

number | null

the UTF-8 byte length when it exceeds max_bytes, else null

Uuid
#

primitive_schemas.ts view source

value + type

string & $brand<"Uuid">

type string & $brand<"Uuid">

import {Uuid} from '@fuzdev/fuz_app/primitive_schemas.js';

Branded UUID schema. Accepts either case and outputs lowercase — the form Postgres renders a uuid in and every id the server mints — so a parsed id compares equal to the same id read back from the database. The Rust spine canonicalizes the same way.

.toLowerCase() rather than .transform(): an overwrite keeps the output type and stays invisible to z.toJSONSchema, where a transform would leave no input schema to render.

external types

z.infer<typeof Uuid>

UuidWithDefault
#

primitive_schemas.ts view source

value + type

string & $brand<"Uuid">

type string & $brand<"Uuid">

import {UuidWithDefault} from '@fuzdev/fuz_app/primitive_schemas.js';

Uuid that defaults to a fresh create_uuid() when the input is undefined.

external types

z.infer<typeof UuidWithDefault>

VALID_SQL_IDENTIFIER
#

db/sql_identifier.ts view source

RegExp import {VALID_SQL_IDENTIFIER} from '@fuzdev/fuz_app/db/sql_identifier.js';

Pattern matching valid SQL identifiers: starts with a letter or underscore, followed by letters, digits, or underscores.

validate_daemon_token
#

auth/daemon_token.ts view source

(provided: string, state: DaemonTokenState): boolean import {validate_daemon_token} from '@fuzdev/fuz_app/auth/daemon_token.js';

Validate a daemon token against the current state.

Accepts both the current and previous token (2-token race window). Uses timing-safe comparison.

provided

the token from the X-Daemon-Token header

type string

state

the daemon token state

returns

boolean

true if the token is valid

validate_facts_internal_location
#

server/x_accel.ts view source

(config: string, facts_location: string): NginxFactsValidation import {validate_facts_internal_location} from '@fuzdev/fuz_app/server/x_accel.js';

Assert the nginx config serves the X-Accel facts prefix only from internal; locations carrying the fact hardening headers. See the module docs for the rules; every violation is collected, so one call reports them all.

config

the nginx config string to check

type string

facts_location

the facts location path the redirect prefix points at (e.g. /_facts/)

type string

returns

NginxFactsValidation

{ok, errors} — ok is true only when every rule passes

validate_ip_strict
#

http/proxy.ts view source

(ip: string): "IPv4" | "IPv6" | undefined import {validate_ip_strict} from '@fuzdev/fuz_app/http/proxy.js';

Strict IP validity check.

Defense in depth around Hono's hono/utils/ipaddr helpers, which are lax in two ways:

  1. distinctRemoteAddr classifies anything-with-a-colon as 'IPv6', including 'host:port', 'attacker:controlled', '203.0.113.1:8080'.
  2. convertIPv6ToBinary silently accepts malformed forms like '[::1]:8080' and '::1\n', parsing them as inconsistent binary values that would still serve as distinct rate-limit keys for an attacker rotating the suffix.

Strict validation here is two-layered: a character-set pre-filter (IP_LITERAL_CHARS), then a round-trip through convertIPv*ToBinary to confirm the input parses cleanly. Either layer alone has holes; together they reject every input form we've seen Hono mis-handle.

Used as the security primitive for any code path that takes an IP string from an untrusted source (XFF, query params) and uses it as a key (rate limiting, audit subject) or compares it against trusted proxies via CIDR (where the latent throw would otherwise bubble out).

ip

type string

returns

"IPv4" | "IPv6" | undefined

the address family on success, undefined if the string is not a strictly-valid IP

validate_keyring
#

auth/keyring.ts view source

(env_value: string | undefined): string[] import {validate_keyring} from '@fuzdev/fuz_app/auth/keyring.js';

Validate key ring configuration.

Returns an error when no keys are configured (undefined, empty string, or all-separator input like '____'), and for each key shorter than MIN_KEY_LENGTH characters.

env_value

the SECRET_FUZ_COOKIE_KEYS environment variable

type string | undefined

returns

string[]

array of validation errors (empty if valid)

validate_phase_transition
#

actions/action_event_helpers.ts view source

(from: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", to: "send_request" | "receive_request" | ... 6 more ... | "execute"): void import {validate_phase_transition} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

Validate that a phase chain is legal per action_event_phase_transitions.

from

to

returns

void

throws

  • Error - if `from → to` is not the permitted next phase (or `from` is terminal)

validate_self_service_eligible_roles
#

auth/self_service_role_actions.ts view source

(roles: Iterable<string>): void import {validate_self_service_eligible_roles} from '@fuzdev/fuz_app/auth/self_service_role_actions.js';

Validate a deployment's self-service eligible-role set at construction.

Refuses a builtin role (admin / keeper) — self-toggling one would hand any account instance-wide authority, so no eligible set may name it — and a name outside the RoleName grammar. The same fail-loud-at-boot shape as create_role_schema, which refuses a builtin name where only app roles belong. Twin of the Rust validate_self_service_eligible_roles.

roles

the eligible role names

type Iterable<string, any, any>

returns

void

throws

  • Error - on a builtin role or a name outside the role grammar

validate_server_env
#

server/env.ts view source

(env: { NODE_ENV: "development" | "production"; PORT: number; HOST: string; DATABASE_URL: string; SECRET_FUZ_COOKIE_KEYS: string; FUZ_ALLOWED_ORIGINS: string; FUZ_ALLOW_ORIGIN_WILDCARDS?: "true" | ... 1 more ... | undefined; FUZ_BOOTSTRAP_TOKEN_PATH?: string | undefined; FUZ_DB_CHECKOUT_TIMEOUT_MS?: number | undefined; FUZ_DB_AUDIT_CHECKOUT_TIMEOUT_MS?: number | undefined; }): Result<...> import {validate_server_env} from '@fuzdev/fuz_app/server/env.js';

Validate a loaded BaseServerEnv and produce the artifacts needed for server init.

Handles keyring validation, origin parsing (every refused allowlist entry is reported, each on its own line), the wildcard opt-in (a wildcard entry without FUZ_ALLOW_ORIGIN_WILDCARDS=true is refused with admit_origin_wildcards' text, as create_app_server would), and bootstrap token path extraction. Returns a Result so callers handle errors their own way (exit, logging, etc).

env

a loaded and Zod-validated BaseServerEnv

returns

Result

{ok: true, keyring, allowed_origins, allow_origin_wildcards, bootstrap_token_path} or {ok: false, field, errors}

validate_sse_keep_alive_ms
#

realtime/sse_constants.ts view source

(keep_alive_ms: number | null | undefined): void import {validate_sse_keep_alive_ms} from '@fuzdev/fuz_app/realtime/sse_constants.js';

Throw unless keep_alive_ms is a valid keep-alive interval (SseResponseOptions.keep_alive_ms): undefined (the default), null (off), or a positive number of ms up to SSE_KEEP_ALIVE_INTERVAL_MS_MAX. Timers read anything else — zero, a negative, NaN, Infinity, or past the max — as an interval of about 1 ms, which would write keep-alives as fast as the event loop turns.

keep_alive_ms

the interval to check

type number | null | undefined

returns

void

throws

  • Error - when it is a number outside `(0, SSE_KEEP_ALIVE_INTERVAL_MS_MAX]`

validate_step_transition
#

ValidatedApiToken
#

auth/api_token_queries.ts view source

ValidatedApiToken

type Omit<ApiToken, "scope"> & { scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }; expires_in_ms: number | null; }

import type {ValidatedApiToken} from '@fuzdev/fuz_app/auth/api_token_queries.js';

A live api_token row as query_validate_api_token returns it: the stored scope document already parsed, so a caller holds the narrowing the credential was minted with and never decides what an unreadable one means. The Rust twin's ApiTokenRow carries the parsed scope the same way.

id

type string

account_id

type Uuid

created_at

type string

expires_at

type string | null

name

type string

last_used_at

type string | null

last_used_ip

type string | null

token_hash

type string

scope

type TokenScope

expires_in_ms

Milliseconds until the token expires — expires_at - NOW(), measured on the database clock in the statement that found it live (EXPIRES_IN_MS_SQL); null for an eternal token. The Rust twin's ApiTokenRow.expires_in.

type number | null

ValidatedServerEnv
#

server/env.ts view source

ValidatedServerEnv import type {ValidatedServerEnv} from '@fuzdev/fuz_app/server/env.js';

Validated server env config — the artifacts create_app_server() needs.

keyring

type Keyring

allowed_origins

type RegExp[]

allow_origin_wildcards

FUZ_ALLOW_ORIGIN_WILDCARDS=true — pass to create_app_server's allow_origin_wildcards, which refuses a wildcard entry without it.

type boolean

bootstrap_token_path

type string | null

ValidationError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "invalid_request_body" | "invalid_json_body" | "invalid_route_params" | "invalid_query_params"; issues?: { [x: string]: unknown; code: string; message: string; path: (string | number)[]; }[] | undefined; } import {ValidationError} from '@fuzdev/fuz_app/http/error_schemas.js';

Input validation error — returned when params / query / body fails Zod parsing, or when the request body is not valid JSON.

error is one of the four validation codes the framework emits. issues carries Zod's validation issues for diagnostic display on the three schema-failure cases (invalid_request_body, invalid_route_params, invalid_query_params). The field is optional: the invalid_json_body case (request body parse failure or non-object root) emits no issues, and the schema-failure cases emit them only in development — production omits them (via dev_only) so error responses don't leak input-schema structure to callers.

error

type "invalid_request_body" | "invalid_json_body" | "invalid_route_params" | "invalid_query_params"

issues?

type { [x: string]: unknown; code: string; message: string; path: (string | number)[]; }[]

[key: string]

type unknown

ValidAuthSession
#

auth/session_queries.ts view source

ValidAuthSession import type {ValidAuthSession} from '@fuzdev/fuz_app/auth/session_queries.js';

A live auth_session row as query_session_get_valid returns it, with how long it has left.

inheritance

extends: AuthSession

expires_in_ms

Milliseconds until the session expires — expires_at - NOW(), measured on the database clock in the statement that found it unexpired (EXPIRES_IN_MS_SQL).

type number

verify_dummy
#

auth/password_argon2.ts view source

(password: string): Promise<boolean> import {verify_dummy} from '@fuzdev/fuz_app/auth/password_argon2.js';

Verify a password against a dummy hash for timing attack resistance.

Always returns false, but takes the same time as a real verification. Call when account lookup fails to prevent timing-based user enumeration.

password

the plaintext password to "verify"

type string

returns

Promise<boolean>

always false

verify_password
#

auth/password_argon2.ts view source

(password: string, password_hash: string): Promise<boolean> import {verify_password} from '@fuzdev/fuz_app/auth/password_argon2.js';

Verify a password against an Argon2id hash.

password

the plaintext password to verify

type string

password_hash

the Argon2id hash to verify against

type string

returns

Promise<boolean>

true if the password matches

verify_request_source
#

http/origin.ts view source

(allowed_patterns: readonly RegExp[], carve_outs?: readonly MiddlewareCarveOut[]): Handler import {verify_request_source} from '@fuzdev/fuz_app/http/origin.js';

Middleware that verifies the request source against an allowlist.

Origin allowlisting (not the CSRF layer — that's SameSite: strict cookies plus http/simple_request.ts):

  • Checks the Origin header (if present) against the allowlist
  • Allows requests without an Origin header (direct access, curl, etc.)
  • Refuses an Origin that isn't visible ASCII, and a duplicated one — the runtimes join a repeated header into one value (a, b), which carries a space and so matches nothing

Origin-only by design — Fetch spec mandates Origin on every unsafe method (POST / PUT / DELETE / PATCH) regardless of Referrer-Policy, so every real browser request on the state-changing surface carries it. Non-browser clients (curl, server-to-server, CLI) don't ship auto- attached session cookies, so CSRF isn't the relevant threat there — auth (bearer / daemon token) is the actual control. A Referer fallback would only widen the accepted-shape envelope without closing a real CSRF hole. The Rust spine's origin_layer is the twin.

create_app_server mounts it on every path (ORIGIN_MIDDLEWARE_PATH); a request whose path a carve_outs entry matches passes unchecked — the named, explicit exception, each with its reason, as the Rust stack's origin_carve_out is.

allowed_patterns

compiled regex patterns from parse_allowed_origins

type readonly RegExp[]

carve_outs?

paths the check skips, matched like a middleware path (middleware_applies)

type readonly MiddlewareCarveOut[]
optional default []

returns

Handler

VerifyInput
#

auth/account_action_specs.ts view source

value + type

void import {VerifyInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_verify. No parameters — the caller is the subject.

VOLATILE_HEADER_NAMES
#

testing/cross_backend/differential_normalize.ts view source

readonly string[] import {VOLATILE_HEADER_NAMES} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

Response headers left out of header_names: framing and per-request headers each HTTP server sets its own way, which say nothing about the application. Lowercased.

VOLATILE_VALUE_PATTERNS
#

testing/cross_backend/differential_normalize.ts view source

readonly VolatileValuePattern[]

type readonly VolatileValuePattern[]

import {VOLATILE_VALUE_PATTERNS} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

The volatile values normalize_differential_response placeholders, applied in order to every string (object keys included). Tokens come first, so the random tail of a token is never read as a hash. A blake3:<64 hex> content address matches none of them and is compared verbatim.

VolatileValuePattern
#

testing/cross_backend/differential_normalize.ts view source

VolatileValuePattern import type {VolatileValuePattern} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

One kind of volatile value: its placeholder name and what matches it.

kind

The placeholder's kind, as in <uuid:1>.

type string

readonly

pattern

Matches every occurrence inside a string; must carry the g flag.

type RegExp

readonly

warn_if_fail_floor_disabled
#

auth/fail_floor.ts view source

(floor: FailFloor, floor_ms: number, log: Logger): void import {warn_if_fail_floor_disabled} from '@fuzdev/fuz_app/auth/fail_floor.js';

Announce, once per module instance per floor, that a denial-timing floor is off.

Called by the route factories (create_account_route_specs, create_signup_route_specs) at assembly when the configured floor is 0 or less. A disabled floor turns off the username-enumeration defense, so — like the rate limiters' announce_rate_limiters_disabled — it goes to stderr as well as the log: a deployment that copied a test binary's 0 shows it in its first lines of output. A positive floor prints nothing.

floor

which floor

floor_ms

the configured floor

type number

log

where the warning is logged beside the stderr banner

type Logger

returns

void

WebsocketConnection
#

actions/transports_ws.ts view source

WebsocketConnection import type {WebsocketConnection} from '@fuzdev/fuz_app/actions/transports_ws.js';

Minimal interface for a WebSocket connection, decoupled from the concrete Socket Cell.

send

type (data: object): boolean

data

type object
returns boolean

connected

type boolean

readonly

add_message_handler

type (handler: (event: MessageEvent<any>) => void): () => void

handler

type (event: MessageEvent<any>) => void
returns () => void

add_error_handler

type (handler: (event: Event) => void): () => void

handler

type (event: Event) => void
returns () => void

WebsocketRpcConnection
#

actions/transports_ws.ts view source

WebsocketRpcConnection import type {WebsocketRpcConnection} from '@fuzdev/fuz_app/actions/transports_ws.js';

RPC-capable WebSocket connection — a WebsocketConnection that also handles request/response correlation with timeout, queue, AbortSignal cancel, and explicit-id support. Required by FrontendWebsocketTransport so it can delegate the pending-map bookkeeping to one canonical implementation (FrontendWebsocketClient) instead of running a parallel one.

Consumer wrappers around FrontendWebsocketClient (e.g. zzz's Socket) implement this by adding a one-line delegate to the underlying client's request.

inheritance

will_reconnect

Whether the connection is down but opening on its own (connecting, or waiting out a scheduled reconnect), so a request queued now will be sent without a connect() call. FrontendWebsocketTransport queues only while this holds.

type boolean

readonly

request

type (method: string, params: unknown, options?: { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; } | undefined): Promise<unknown>

method

type string

params

type unknown

options?

type { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; }
optional
returns Promise<unknown>

with_database_name
#

testing/cross_backend/postgres_database.ts view source

(database_url: string, database: string): string import {with_database_name} from '@fuzdev/fuz_app/testing/cross_backend/postgres_database.js';

database_url with its database replaced by database — how a setup that spawns a second backend lands on the same server under a database nothing else uses.

database_url

a postgres:// URL on the target server

type string

database

the database name to put in it

type string

returns

string

the URL naming database on the same server

write_daemon_info
#

testing/daemon_info.ts view source

(runtime: Pick<EnvDeps, "env_get"> & Pick<FsWriteDeps, "mkdir" | "rename" | "write_text_file">, name: string, info: { version: number; pid: number; port: number; started: string; app_version: string; }): Promise<...> import {write_daemon_info} from '@fuzdev/fuz_app/testing/daemon_info.js';

Write daemon info to the PID file, creating directories as needed.

runtime

runtime with file write and env capabilities

type Pick<EnvDeps, "env_get"> & Pick<FsWriteDeps, "mkdir" | "write_text_file" | "rename">

name

application name

type string

info

daemon info to write

returns

Promise<void>

throws

  • Error - if `$HOME` is not set

mutates

  • filesystem — creates `~/.{name}/run/` and atomically writes `daemon.json`

write_daemon_token
#

testing/daemon_token_rotation.ts view source

(runtime: DaemonTokenWriteDeps, token_path: string, token: string): Promise<void> import {write_daemon_token} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Write the current token to disk atomically at mode 0600.

On-disk format is JSON {"token": "..."} — the wrapper leaves room for future fields (rotated_at, version) without changing every reader. Both the TS cross-backend harness reader (spawn_backend.read_daemon_token) and the Rust daemon-token writer match this shape.

runtime

runtime with file write capabilities

token_path

path to write the token

type string

token

the raw token string

type string

returns

Promise<void>

mutates

  • filesystem — writes `token_path` atomically at mode `0600`

write_fact_bytes_to_disk
#

db/fact_disk_storage.ts view source

(deps: Pick<FactDiskStorageDeps, "mkdir" | "rename" | "write_file" | "fsync" | "remove">, facts_dir: string, hash: FactHash, bytes: Uint8Array<ArrayBufferLike>): Promise<...> import {write_fact_bytes_to_disk} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Write fully-buffered bytes for hash to the canonical <facts_dir>/<shard>/<rest> path, then publish via commit_temp_to_cas (fsync'd temp + atomic rename). The buffering twin of stream_fact_to_disk, used by PgFactStore.put for oversize sync bytes. Returns the file: external_url for the fact row.

deps

type Pick<FactDiskStorageDeps, "mkdir" | "rename" | "remove" | "write_file" | "fsync">

facts_dir

type string

hash

type FactHash

bytes

type Uint8Array<ArrayBufferLike>

returns

Promise<FileFactUrl>

mutates

  • facts_dir filesystem

write_file_atomic
#

runtime/fs.ts view source

(deps: Pick<FsWriteDeps, "rename" | "write_text_file"> & Partial<FsRemoveDeps>, path: string, content: string, options?: WriteFileAtomicOptions | undefined): Promise<...> import {write_file_atomic} from '@fuzdev/fuz_app/runtime/fs.js';

Write a file atomically via temp file + rename.

Writes to a unique .{name}.tmp.{pid}.{counter} sibling with exclusive create (O_EXCL), then renames over path so readers either see the old contents or the full new contents — never a partial write. The unique name + exclusive create are load-bearing for secret files: a fixed temp name would let a crash-leftover temp be reopened O_TRUNC with its old (possibly permissive) mode — mode only applies at creation — and the rename would publish that stale inode. Twin of the Rust spine's fuz_sys::secure_file::write_secure_file naming scheme.

deps

write capabilities; pass remove too for best-effort temp cleanup on failure

type Pick<FsWriteDeps, "write_text_file" | "rename"> & Partial<FsRemoveDeps>

path

destination path

type string

content

full file content

type string

options?

optional mode applied at creation

optional

returns

Promise<void>

throws

  • Error - if `write_text_file` or `rename` rejects (permissions, disk full, cross-device rename, etc.)

mutates

  • filesystem — creates the unique temp file then renames it to `path`

WriteFileAtomicOptions
#

runtime/fs.ts view source

WriteFileAtomicOptions import type {WriteFileAtomicOptions} from '@fuzdev/fuz_app/runtime/fs.js';

Options for write_file_atomic.

mode?

POSIX mode for the written file (e.g. 0o600 for secrets). Applied at temp-file creation, so the published file never exists with a laxer mode. Omitted = the runtime default (umask).

type number

WriteFileOptions
#

runtime/deps.ts view source

WriteFileOptions import type {WriteFileOptions} from '@fuzdev/fuz_app/runtime/deps.js';

Options for FsWriteDeps.write_text_file.

mode?

POSIX mode applied at creation (e.g. 0o600). Creation-time only — an existing file keeps its mode (pair with exclusive when the mode must hold). No-op where modes don't apply (mock runtime, Windows).

type number

exclusive?

Fail if the path already exists (O_EXCL). Load-bearing for secret files: without it a crash-leftover temp file is reopened O_TRUNC and keeps its old (possibly permissive) mode, since mode only applies at creation.

type boolean

WS_ADMISSION_PROBE_ID
#

WS_CLIENT_DEFAULT_TIMEOUT_MS
#

testing/transports/ws_client.ts view source

1000 import {WS_CLIENT_DEFAULT_TIMEOUT_MS} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Default wait-for timeout shared across in-process + cross-process impls. Tunable per-call via the timeout_ms parameter.

WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT
#

actions/transports.ts view source

4002 import {WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — client timed out waiting for a response.

WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT_REASON
#

WS_CLOSE_CONNECTION_LIMIT
#

actions/transports.ts view source

4004 import {WS_CLOSE_CONNECTION_LIMIT} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server closed this socket to admit a newer connection on the same account, past the per-account connection cap (ConnectionRegistry's max_connections_per_account, evict-oldest).

Distinct from WS_CLOSE_SESSION_REVOKED: the credential is still good, so a client must not enter its revoked state — but it must not auto-reconnect either, since a reconnect would supersede a newer socket in turn and two tabs past the cap would close each other in a loop. FrontendWebsocketClient treats it as closed until the user acts (superseded). The twin of the Rust spine's WS_CLOSE_CONNECTION_LIMIT.

WS_CLOSE_CONNECTION_LIMIT_REASON
#

WS_CLOSE_CONNECTION_UNREGISTERED_REASON
#

actions/transports.ts view source

"connection unregistered" import {WS_CLOSE_CONNECTION_UNREGISTERED_REASON} from '@fuzdev/fuz_app/actions/transports.js';

The close reason sent with WS_CLOSE_INTERNAL_ERROR when a frame arrives on a socket whose connection was removed from the registry without a close (a bare remove_connection). TS-only: the Rust spine sends no such close.

WS_CLOSE_CREDENTIAL_EXPIRED_REASON
#

actions/transports.ts view source

"Credential expired" import {WS_CLOSE_CREDENTIAL_EXPIRED_REASON} from '@fuzdev/fuz_app/actions/transports.js';

The close reason sent with WS_CLOSE_SESSION_REVOKED when the socket's credential expired while it was open — its session or API token passed its expiry (ResolvedAuth.credential_deadline). The code is the revocation's on purpose: the client cannot reconnect on the credential either way, so FrontendWebsocketClient enters the same revoked state. The Rust spine sends the same text.

WS_CLOSE_GOING_AWAY
#

actions/transports.ts view source

1001 import {WS_CLOSE_GOING_AWAY} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server is shutting down (RFC 6455 §7.4.1 "Going Away"). Sent to every live socket by close_all_connections, which AppServer.close runs, and to an upgrade the shutdown refuses — one pending at the close-all, or born closed after it. Not a revocation: the credential is still good, so a client reconnects under its ordinary backoff once a server is back.

WS_CLOSE_GOING_AWAY_REASON
#

WS_CLOSE_INTERNAL_ERROR
#

actions/transports.ts view source

1011 import {WS_CLOSE_INTERNAL_ERROR} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server hit an unexpected condition (RFC 6455 §7.4.1 "Internal Error").

Sent when an upgraded socket can't be admitted because its credential re-check itself failed (a database error), when an on_socket_open hook throws, and when a frame arrives on a socket whose connection was removed from the registry without a close (a bare remove_connection). The handshake has already answered 101, so an HTTP 500 is no longer available. Not a revocation: the client's credential may be fine, so it reconnects under its ordinary backoff rather than entering its revoked state. The twin of the Rust spine's WS_CLOSE_INTERNAL_ERROR.

WS_CLOSE_INTERNAL_ERROR_REASON
#

actions/transports.ts view source

"internal error" import {WS_CLOSE_INTERNAL_ERROR_REASON} from '@fuzdev/fuz_app/actions/transports.js';

The close reason sent with WS_CLOSE_INTERNAL_ERROR when an upgraded socket can't be admitted — its credential re-check failed, or opening it threw. The Rust spine sends the same text.

WS_CLOSE_MESSAGE_TOO_BIG
#

actions/transports.ts view source

1009 import {WS_CLOSE_MESSAGE_TOO_BIG} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — an inbound message exceeded the receiver's size cap (RFC 6455 §7.4.1 "Message Too Big").

WS_CLOSE_MESSAGE_TOO_BIG_REASON
#

WS_CLOSE_OUTBOUND_OVERFLOW
#

actions/transports.ts view source

4005 import {WS_CLOSE_OUTBOUND_OVERFLOW} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server had more waiting to be written to this socket than the connection's outbound budget holds (ConnectionRegistry's outbound_queue_bytes), because the client is reading slower than the server is sending to it, or not at all. The frame that did not fit and everything still waiting are dropped, so the close is the client's notice that it missed messages.

Distinct from WS_CLOSE_SESSION_REVOKED: the credential is still good, so a client must not enter its revoked state. Unlike WS_CLOSE_CONNECTION_LIMIT, nothing took the connection's place: FrontendWebsocketClient reconnects under its ordinary backoff, and the consumer re-reads whatever state it tracks. A client that reads nothing at all may never see the frame — a socket whose write makes no progress is dropped without one, which a browser reports as 1006. The twin of the Rust spine's WS_CLOSE_OUTBOUND_OVERFLOW.

WS_CLOSE_OUTBOUND_OVERFLOW_REASON
#

WS_CLOSE_PERMISSION_REVOKED
#

actions/transports.ts view source

4006 import {WS_CLOSE_PERMISSION_REVOKED} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the socket's account lost a role its endpoint requires (register_action_ws's required_roles): a global grant of one of those roles was revoked while the socket was open, or while its upgrade was in flight (the admission re-read, register_action_ws). Nothing more is sent on the socket.

Distinct from WS_CLOSE_SESSION_REVOKED: the credential is still good, so a client must not enter its revoked state — but a reconnect would be refused at the role gate, so FrontendWebsocketClient stays closed until connect() is called and rejects its pending work as forbidden. The twin of the Rust spine's WS_CLOSE_PERMISSION_REVOKED.

WS_CLOSE_PERMISSION_REVOKED_REASON
#

WS_CLOSE_POLICY_VIOLATION
#

actions/transports.ts view source

1008 import {WS_CLOSE_POLICY_VIOLATION} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server closed a socket that sent too much while it was still opening: past MAX_PRE_ADMISSION_FRAMES frames or the PRE_ADMISSION_QUEUE_BYTES_FACTOR byte budget, before its admission and on_socket_open had completed (RFC 6455 §7.4.1 "Policy Violation"). Not a revocation: the client reconnects under its ordinary backoff.

WS_CLOSE_PRE_ADMISSION_OVERFLOW_REASON
#

actions/transports.ts view source

"too much sent before the socket opened" import {WS_CLOSE_PRE_ADMISSION_OVERFLOW_REASON} from '@fuzdev/fuz_app/actions/transports.js';

The close reason sent with WS_CLOSE_POLICY_VIOLATION when a socket overflows its pre-admission queue, by count or by bytes. TS-only: the Rust spine sends no such close.

WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT
#

actions/transports.ts view source

4003 import {WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — server timed out with no incoming activity.

WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT_REASON
#

WS_CLOSE_SESSION_REVOKED
#

actions/transports.ts view source

4001 import {WS_CLOSE_SESSION_REVOKED} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code for session revocation — and for a credential that expired while the socket was open (WS_CLOSE_CREDENTIAL_EXPIRED_REASON).

Also the close for an upgrade refused at admission because its credential was revoked while the upgrade was in flight (register_action_ws) — the same outcome, reached a moment earlier.

WS_CLOSE_SESSION_REVOKED_REASON
#

actions/transports.ts view source

"Session revoked" import {WS_CLOSE_SESSION_REVOKED_REASON} from '@fuzdev/fuz_app/actions/transports.js';

The close reason sent with WS_CLOSE_SESSION_REVOKED — one definition, so a socket revoked mid-life and an upgrade refused at admission are indistinguishable on the wire. The Rust spine sends the same text.

WS_CLOSE_SOCKET_OPEN_FAILED_REASON
#

actions/transports.ts view source

"socket bootstrap failed" import {WS_CLOSE_SOCKET_OPEN_FAILED_REASON} from '@fuzdev/fuz_app/actions/transports.js';

The close reason sent with WS_CLOSE_INTERNAL_ERROR when an on_socket_open hook throws. TS-only: the Rust spine sends no such close.

WS_CLOSE_WRITE_TIMEOUT
#

WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE
#

WS_RECEIVE_TIMEOUT_MAX
#

actions/register_action_ws.ts view source

2147483647 import {WS_RECEIVE_TIMEOUT_MAX} from '@fuzdev/fuz_app/actions/register_action_ws.js';

The longest receive_timeout register_action_ws accepts — 2³¹ − 1 ms, about 24.8 days, the longest delay a timer honors (the same bound as the SSE keep-alive). It keeps the receive check's half-timeout tick well inside that delay; Infinity, or a timeout of about 2³² ms and up, would make the tick fire every millisecond on every socket. TS-only: the Rust spine's Duration takes any length.

WS_RECEIVE_TIMEOUT_MS_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_WS_RECEIVE_TIMEOUT_MS" import {WS_RECEIVE_TIMEOUT_MS_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var replacing the WS endpoints' receive timeout on both spine binaries (a positive integer of ms up to 2³¹ − 1; unset keeps DEFAULT_WS_RECEIVE_TIMEOUT). Both refuse to boot on anything else. The cross-language contract for the timing cross project, which shortens it so a silent socket's close lands within seconds.

WS_WRITE_HIGH_WATER_BYTES
#

actions/ws_outbound_writer.ts view source

number import {WS_WRITE_HIGH_WATER_BYTES} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

How many unwritten bytes a socket's buffer may hold before the pump stops handing it waiting frames. Below it the pump keeps writing, so a runtime that reports a frame as buffered until its write completes (Deno) is not held to one frame per turn; at or over it frames wait in the budgeted queue instead of the runtime's unbounded buffer.

WS_WRITE_STALL_TIMEOUT
#

actions/ws_outbound_writer.ts view source

10000 import {WS_WRITE_STALL_TIMEOUT} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

How long bytes handed to a socket may stay unwritten in its buffer before the connection is dropped (module doc, "Deadlines"). Equal to DEFAULT_PEER_REQUEST_TIMEOUT: a server-initiated request that cannot even be written within its own reply deadline has already failed. Same value as the Rust spine's WS_WRITE_STALL_TIMEOUT.

The deadline covers the bytes in the buffer when it starts, not progress: a client taking them too slowly to finish inside it is dropped the same way, and reconnects.

WsActionRateLimitCrossTestOptions
#

testing/cross_backend/ws_action_rate_limit.ts view source

WsActionRateLimitCrossTestOptions import type {WsActionRateLimitCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_action_rate_limit.js';

inheritance

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

rpc_path?

JSON-RPC endpoint path. Default /api/rpc (the spine convention).

type string

readonly

max_attempts

The max_attempts both action limiters were started with (action_rate_limit_max_attempts on the backend config). At least 2 — the shared-bucket case splits the budget across the two transports.

type number

readonly

WsClient
#

testing/transports/ws_client.ts view source

WsClient import type {WsClient} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

A test WS client: send requests, inspect / await incoming messages.

send

Send a JSON-RPC message (request or notification) to the server.

type (message: unknown): Promise<void>

message

type unknown
returns Promise<void>

throws

  • Error - if called after `close()` resolves — every impl rejects sends on a closed socket so post-close test bugs surface immediately rather than silently dropping.

request

Send a JSON-RPC request and await its response. Resolves with the result; throws with a useful message (code, text, and any data payload) on an error frame — without this, asserting on result.foo for a failed request throws Cannot read property 'foo' of undefined, hiding the real cause. Use send + wait_for(is_response_for(id)) directly when the test needs to assert on the error frame itself.

type <R = unknown>(id: string | number, method: string, params: unknown, timeout_ms?: number | undefined): Promise<R>

id

type number | string

method

type string

params

type unknown

timeout_ms?

type number
optional
returns Promise<R>

throws

  • Error - if the server returns a JSON-RPC error frame for `id`, or if `wait_for` times out before a matching response arrives.

close

Close the connection. Returns a promise that resolves once the transport's own cleanup (and any on_socket_close for the in-process driver) has completed — tests that assert on post-close state should await.

type (code?: number | undefined, reason?: string | undefined): Promise<void>

code?

type number
optional

reason?

type string
optional
returns Promise<void>

wait_for_close

Wait for the server to close the connection. Resolves true if the socket closed within timeout_ms, false on timeout. The signal for server-initiated close — used by close-on-revoke tests that fire a revocation over a side channel and assert the live socket drops.

Resolves true immediately when the socket is already closed. Distinct from close() (client-initiated): this awaits a close the test did not request. Mirrors wait_for_close on the SSE frame reader in testing/sse_round_trip.ts.

type (timeout_ms?: number | undefined): Promise<boolean>

timeout_ms?

type number
optional
returns Promise<boolean>

close_code

The close code of the connection once it has closed — the server's (e.g. WS_CLOSE_MESSAGE_TOO_BIG), the test's own, or 1006 when the connection dropped without a close frame. null while open.

type number | null

readonly

close_reason

The close reason of the connection once it has closed — the text sent with close_code (e.g. 'connection limit' with WS_CLOSE_CONNECTION_LIMIT), empty when the close carried none. null while open.

type string | null

readonly

messages

Every message the server has sent, in arrival order.

type readonly unknown[]

readonly

wait_for

Wait until a message satisfies predicate. Matches are checked against already-received messages first, then new arrivals until the timeout (defaults to WS_CLIENT_DEFAULT_TIMEOUT_MS).

When predicate is a type guard (e.g. is_notification_with<P>), the result is narrowed automatically and callers don't need to spell <JsonrpcNotificationFrame<P>> on the call site.

type <T>(predicate: (msg: unknown) => msg is T, timeout_ms?: number | undefined): Promise<T>

predicate

type (msg: unknown) => msg is T

timeout_ms?

type number
optional
returns Promise<T>

throws

  • Error - if `timeout_ms` elapses before a matching message arrives — the pending waiter is dropped from the internal list so later messages don't keep iterating it.

WsConnectIdentity
#

testing/ws_round_trip.ts view source

WsConnectIdentity import type {WsConnectIdentity} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Auth identity for a mock connection.

account_id?

Account id for the connection. Defaults to a fresh uuid per call.

type Uuid

credential_type?

Credential type. Defaults to 'session'. Keeper actions require 'daemon_token'.

type BuiltinCredentialType

session_id?

Session id (any string). Defaults to a fresh uuid. The connection is registered under hash_session_token(session_id), the hash the request context middleware would have resolved.

type string

api_token_id?

Api token id of an api_token connection. Defaults to a fresh uuid; ignored for other credentials.

type string

roles?

Roles to grant via active role_grants. Pass [ROLE_KEEPER] for keeper actions.

type string[]

WsConnectionCapCrossTestOptions
#

testing/cross_backend/ws_connection_cap.ts view source

WsConnectionCapCrossTestOptions import type {WsConnectionCapCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_connection_cap.js';

Options for the WebSocket connection-cap parity suite.

inheritance

setup_test

Per-test fixture producer (default_cross_process_setup(handle)). Every socket in a case upgrades on the fresh-per-test keeper's session cookie, so they all land on one account.

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags; every case gates on capabilities.ws.

type BackendCapabilities

readonly

max_connections_per_account?

The backend's per-account connection cap, which each case opens past. Defaults to DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT, the cap both spines apply unless overridden; pass the consumer's value when it sets one, or null (a backend with the cap disabled) to skip the cases.

type number | null

readonly

WsEndpointSpec
#

actions/ws_endpoint_spec.ts view source

WsEndpointSpec import type {WsEndpointSpec} from '@fuzdev/fuz_app/actions/ws_endpoint_spec.js';

Declarative description of a WebSocket endpoint to be auto-mounted by create_app_server.

Single source of truth for mount + surface — the same array drives register_action_ws upgrade wiring AND the surface.ws_endpoints slot emitted into AppSurface, so consumers cannot drift their declared actions from what dispatch actually serves.

path

Hono mount path (e.g. /api/ws).

type string

allowed_origins?

Origin allowlist regexes for the upgrade — passed straight to verify_request_source. Defaults to the server's AppServerOptions.allowed_origins, the list the HTTP routes check. Set it only to narrow one endpoint: the auth middleware's origin check runs on the upgrade too, so a request reaches the endpoint only when its origin passes both lists. An empty list throws at assembly.

type readonly RegExp[]

actions?

The actions registered on this endpoint. Spread protocol_actions from actions/protocol.ts first to complete the disconnect-detection + per-request cancel pairing with the frontend client.

Omit to serve the actions of the server's sole rpc_endpoints entry — to_ws_actions of its list, the protocol actions first — so one action set (one set of handler closures) runs on both transports. Assembly throws when it is omitted and the server resolves no RPC endpoint or more than one.

type readonly Action<ActionSpecUnion>[]

required_roles?

Roles permitted to upgrade — any-of disjunction, global grants only. Omit (or pass []) to skip the upgrade-time role gate; per-action auth on each spec still applies at dispatch time via perform_action. Pass [ROLE_ADMIN] for an admin-only WS endpoint. Held for the socket's life — see register_action_ws's required_roles.

type readonly string[]

registry?

Existing registry to register connections with. Auto-created when omitted. Either way the mounted registry is reachable on AppServer.ws_endpoints[path] for broadcast / fan-out.

type ConnectionRegistry

max_connections_per_account?

Per-account connection cap for the auto-created registry. See RegisterActionWsOptions.max_connections_per_account — rejected alongside registry, which carries its own cap.

type number | null

default DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT

outbound_queue_bytes?

Byte budget of the frames waiting to be written to one connection, for the auto-created registry. See RegisterActionWsOptions.outbound_queue_bytes — rejected alongside registry, which carries its own budget.

type number

default DEFAULT_WS_OUTBOUND_QUEUE_BYTES

receive_timeout?

Receive silence (ms) past which the server closes a socket with WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT; null keeps silent sockets open. See RegisterActionWsOptions.receive_timeout.

type number | null

default DEFAULT_WS_RECEIVE_TIMEOUT

max_message_bytes?

Cap on one inbound message, in bytes. See RegisterActionWsOptions.max_message_bytes.

type number

default DEFAULT_WS_MAX_MESSAGE_BYTES

on_socket_open?

Called once per socket after the connection is admitted — registered and its credential re-checked — and before the first message dispatches. See RegisterActionWsOptions.on_socket_open.

type (ctx: SocketOpenContext): void | Promise<void>

ctx

returns void | Promise<void>

on_socket_close?

Called once per admitted socket on close, after registry.remove_connection. See RegisterActionWsOptions.on_socket_close.

type (ctx: SocketCloseContext): void | Promise<void>

ctx

returns void | Promise<void>

WsEndpointTarget
#

testing/cross_backend/ws_endpoint.ts view source

WsEndpointTarget import type {WsEndpointTarget} from '@fuzdev/fuz_app/testing/cross_backend/ws_endpoint.js';

Where a backend's WebSocket endpoint is.

base_url

Base URL the backend is reachable at (e.g. http://localhost:1178).

type string

readonly

ws_path

WebSocket endpoint path on the backend (e.g. /api/ws).

type string

readonly

origin?

Origin for a browser-context upgrade. Defaults to base_url.

type string

readonly

WsEnvelopeCrossTestOptions
#

testing/cross_backend/ws_envelope.ts view source

WsEnvelopeCrossTestOptions import type {WsEnvelopeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_envelope.js';

inheritance

setup_test

Per-test fixture producer (default_cross_process_setup(handle)).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags — ws gates every case, peer_request the in-flight cancel.

type BackendCapabilities

readonly

WsInboundDispatchCrossTestOptions
#

testing/cross_backend/ws_inbound_dispatch.ts view source

WsInboundDispatchCrossTestOptions import type {WsInboundDispatchCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_inbound_dispatch.js';

inheritance

setup_test

Per-test fixture producer (default_cross_process_setup(handle)).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags; every case gates on capabilities.peer_request.

type BackendCapabilities

readonly

WsOutboundBacklog
#

actions/ws_outbound_writer.ts view source

WsOutboundBacklog

type "flush" | "discard"

import type {WsOutboundBacklog} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

What WsOutboundWriter.close does with the frames still waiting.

WsOutboundOverflowCrossTestOptions
#

testing/cross_backend/ws_outbound_overflow.ts view source

WsOutboundOverflowCrossTestOptions import type {WsOutboundOverflowCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_outbound_overflow.js';

Options for the outbound-overflow parity suite.

inheritance

setup_test

Per-test fixture producer (default_cross_process_setup(handle)).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags; every WebSocket case gates on capabilities.ws.

type BackendCapabilities

readonly

rpc_path?

The backend's RPC endpoint path, for the over-HTTP refusal. Omitted, that case is skipped.

type string

readonly

WsOutboundPush
#

actions/ws_outbound_writer.ts view source

WsOutboundPush

type { kind: "accepted"; } | { kind: "refused"; } | { kind: "overflow"; overflow: OutboundOverflow; }

import type {WsOutboundPush} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

The outcome of one WsOutboundWriter.push.

WsOutboundStall
#

actions/ws_outbound_writer.ts view source

WsOutboundStall import type {WsOutboundStall} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

What a stalled connection held when its write deadline passed.

queued_bytes

Payload bytes that were waiting, now discarded.

type number

queued_frames

Frames that were waiting, now discarded.

type number

buffered_bytes

Bytes the socket's buffer still held.

type number

WsOutboundWriter
#

actions/ws_outbound_writer.ts view source

import {WsOutboundWriter} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

The byte-budgeted outbound queue and write pump of one WebSocket connection (module doc).

constructor

type new (options: WsOutboundWriterOptions): WsOutboundWriter

options

push

Send text as one frame: straight to the socket when the connection is idle, else queued behind what waits when it fits the budget (module doc, "What waits"). Never waits and never throws.

type (text: string, bytes?: number | undefined): WsOutboundPush

text

the frame's text

type string

bytes?

the UTF-8 length of text when the caller has measured it — a fan-out measures one frame once for its recipients; measured here when omitted. Trusted, not checked against text: a wrong value corrupts the budget

type number
optional

accepted, refused once the writer has closed, or overflow when the frame did not fit — the caller closes the connection

mutates

  • this — queues the frame or writes it to the socket, and schedules the pump

close

Close the socket with code and reason, and take nothing more.

With backlog: 'flush' the frames still waiting are handed to the socket ahead of the close frame under the pump's rule — while the socket's buffer is below WS_WRITE_HIGH_WATER_BYTES — so a reply sent to an otherwise idle connection just before a close is not lost to it. What still waits after that is dropped, as all of it is with 'discard' (module doc, "Closing"). Either way the close frame gets WS_CLOSE_WRITE_TIMEOUT to be written, after which a socket still holding unwritten bytes is cut where the runtime can cut one. A no-op once the writer has closed.

type (code: number, reason: string, backlog: WsOutboundBacklog, on_settled?: (() => void) | undefined): void

code

the close code

type number

reason

the close reason

type string

backlog

what to do with the frames still waiting

on_settled?

called once the close deadline has passed, unless dispose ran first

type () => void
optional
returns void

throws

  • whatever - the socket's `close` throws, with the writer already closed

mutates

  • this — empties the queue, stops the pump, starts the close deadline

dispose

The socket is gone: drop what waits and stop every timer. Idempotent.

type (): void

returns void

mutates

  • this — empties the queue and clears the pump and deadline timers

queued_bytes

Payload bytes waiting for the socket, without the per-frame overhead.

type number

getter

queued_frames

Frames waiting for the socket.

type number

getter

WsOutboundWriterOptions
#

actions/ws_outbound_writer.ts view source

WsOutboundWriterOptions import type {WsOutboundWriterOptions} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

Options for WsOutboundWriter.

ws

The socket to write to.

type WSContext<unknown>

max_bytes

The byte budget of the frames waiting for the socket.

type number

on_stall

Called once when a write deadline passes, after the writer discarded what was waiting and before it cuts the socket. The owner removes the connection and logs.

type (stall: WsOutboundStall): void

stall

returns void

log

Logger for a socket that throws on a send or a cut; null silences it.

type Logger | null

WsRequestResponder
#

testing/transports/ws_client.ts view source

WsRequestResponder import type {WsRequestResponder} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Answers a server-initiated request (the server→client direction ActionPeer adds). Passed at client construction so it's attached before the upgrade completes — otherwise it races a connect-time ping. Receives the parsed request frame and returns the reply outcome (or a promise of it).

(call)

type (request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }): WsResponderOutcome | Promise<WsResponderOutcome>

request

WsResponderOutcome
#

testing/transports/ws_client.ts view source

WsResponderOutcome

type { result: unknown; } | { error: { code: number; message: string; data?: unknown; }; } | undefined

import type {WsResponderOutcome} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

What a WsRequestResponder returns for a server-initiated request:

  • {result} → reply with a JSON-RPC success
  • {error} → reply with a JSON-RPC error envelope
  • undefined → send nothing (models a never-replying peer — exercises the server-side Timeout)

WsRoleGatedCrossTestOptions
#

testing/cross_backend/ws_role_gated.ts view source

WsRoleGatedCrossTestOptions import type {WsRoleGatedCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_role_gated.js';

inheritance

ws_path

The ungated WebSocket endpoint path (e.g. /api/ws).

type string

readonly

setup_test

Per-test fixture producer (default_cross_process_setup(handle)).

type (): Promise<TestFixture>

readonly
returns Promise<TestFixture>

capabilities

Backend capability flags; every case gates on ws and ws_role_gated.

type BackendCapabilities

readonly

gated_ws_path

The role-gated WebSocket endpoint path.

type string

readonly

gated_roles

The roles gated_ws_path requires: ROLE_ADMIN and scoped_role among them.

type readonly string[]

readonly

scoped_role

A role in gated_roles an admin can grant both globally and scoped.

type string

readonly

rpc_path?

JSON-RPC endpoint path. Default SPINE_RPC_PATH.

type string

readonly

WsSessionAccount
#

testing/cross_backend/ws_endpoint.ts view source

WsSessionAccount import type {WsSessionAccount} from '@fuzdev/fuz_app/testing/cross_backend/ws_endpoint.js';

An account a session socket opens as — a fixture or a minted account.

create_session_headers

Build request headers carrying the account's session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

WsSocketEndpoint
#

actions/ws_socket_session.ts view source

internal

WsSocketEndpoint import type {WsSocketEndpoint} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

The endpoint-level values every socket of an endpoint reads — derived once by register_action_ws, shared by all of its sockets.

path

Mount path, reported in on_effect_error contexts.

type string

db

Pool-level DB: the admission re-read and per-message dispatch read through it.

type Db

log

Logger for the endpoint's sockets.

type Logger

registry

The registry the endpoint's connections register with.

type ConnectionRegistry

action_map

Per-method dispatch lookup — request_response specs with a handler.

type ReadonlyMap<string, RpcAction>

receive_timeout

Receive silence (ms) past which a socket is closed; null keeps silent sockets open.

type number | null

receive_tick_interval

The receive check's period (ms); null exactly when receive_timeout is.

type number | null

max_message_bytes

Cap on one inbound message, in bytes.

type number

pre_admission_queue_max_bytes

Byte budget of one socket's pre-admission queue.

type number

on_socket_open

Hook called once per socket after admission; undefined for none.

type ((ctx: SocketOpenContext) => void | Promise<void>) | undefined

on_socket_close

Hook called once per admitted socket after its connection is removed; undefined for none.

type ((ctx: SocketCloseContext) => void | Promise<void>) | undefined

action_ip_rate_limiter

Per-IP action rate limiter; null turns the IP check off.

type RateLimiter | null

action_account_rate_limiter

Per-account action rate limiter; null turns the account check off.

type RateLimiter | null

on_effect_error

Called when an eager pending effect of a frame's action rejects; undefined for none.

type ((error: unknown, context: EffectErrorContext) => void) | undefined

required_roles

Roles the endpoint's upgrade is gated on; empty for an ungated endpoint.

type readonly string[]

WsSocketUpgrade
#

actions/ws_socket_session.ts view source

internal

WsSocketUpgrade import type {WsSocketUpgrade} from '@fuzdev/fuz_app/actions/ws_socket_session.js';

What register_action_ws captures from the upgrade request for one socket. The socket reads these for its whole life; the Hono context itself is not kept.

client_ip

The client IP, keyed by the per-IP action rate limiter.

type string

upgrade_preset

The test-context preset, which skips the admission re-read and the live authorization phase.

type { request_context: RequestContext | null; } | undefined

resolved

The credential the auth middleware resolved — re-read at admission, passed to every message's dispatch, and the source of the connection's identity.

type ResolvedAuth

upgrade_context

The request context the upgrade's authorization phase built, on the credential's account.

type RequestContext

WsTestHarness
#

testing/ws_round_trip.ts view source

WsTestHarness import type {WsTestHarness} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

A harness instance — registry handle + connection factory.

registry

type ConnectionRegistry

connect

Open a mock connection. Resolves after on_socket_open (and the registry's register_ws) completes, so broadcasts issued immediately after the await reach the connection.

Returns the shared WsClient interface — same surface the cross-process driver in transports/ws_transport.ts implements, so assertion helpers and suite bodies work against either impl.

type (identity?: WsConnectIdentity | undefined): Promise<WsClient>

identity?

optional
returns Promise<WsClient>

WsTransportClient
#

testing/transports/ws_transport.ts view source

WsTransportClient import type {WsTransportClient} from '@fuzdev/fuz_app/testing/transports/ws_transport.js';

A cross-process WsClient with one addition: send_text writes a frame verbatim, for the cases a JSON-serializing send cannot express (a frame that is not JSON at all).

inheritance

extends: WsClient

send_text

Send text as one text frame, unserialized.

type (text: string): Promise<void>

text

type string
returns Promise<void>

throws

  • Error - if called after the socket closed

WsTransportOptions
#

testing/transports/ws_transport.ts view source

WsTransportOptions import type {WsTransportOptions} from '@fuzdev/fuz_app/testing/transports/ws_transport.js';

Construction options for create_ws_transport.

base_url

Base URL the binary is reachable at — e.g. http://localhost:8788. Converted to ws:// for the upgrade.

type string

readonly

ws_path

WebSocket endpoint path on the binary (e.g. /api/ws).

type string

readonly

cookies

Session cookie values (full Set-Cookie strings as FetchTransport.cookies() returns them) threaded onto the upgrade Cookie header. Without these the upgrade is anonymous and per-action auth fails on the first message.

type readonly string[]

readonly

origin?

Origin header for the upgrade. Backends running with ALLOWED_ORIGINS=http://localhost:* accept http://localhost:<port>. Defaults to base_url — acceptable because cross-process tests always run against localhost. null sends no Origin at all, which a daemon-token upgrade needs: both spines discard a daemon token (and a bearer token) presented in a browser context.

type string | null

readonly

default_timeout_ms?

Optional per-call default for wait_for timeouts. Falls back to WS_CLIENT_DEFAULT_TIMEOUT_MS if omitted.

type number

readonly

on_request?

Optional responder for server-initiated requests (the server→client direction ActionPeer adds). Attached before the upgrade so a connect-time or on-demand peer/ping is answered as soon as it arrives. Omit for the default observe-only client — a server-initiated request is then surfaced as a normal message (today's behavior).

type (request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }): WsResponderOutcome | Promise<WsResponderOutcome>

readonly

request

headers?

Extra headers for the upgrade request — e.g. an X-Forwarded-For that a backend trusting the loopback proxy resolves as the connection's client IP, which keys the per-IP action rate limiter for every message on the socket. cookies and origin set their own headers.

type Readonly<Record<string, string>>

readonly

WsWaiter
#

testing/transports/ws_client.ts view source

WsWaiter import type {WsWaiter} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

A pending wait_for entry — predicate + its resolver.

predicate

type (msg: unknown): boolean

msg

type unknown
returns boolean

resolve

type (msg: unknown): void

msg

type unknown
returns void

XAccelConfig
#

server/x_accel.ts view source

value + type

{ redirect_prefix: string; } & $brand<"XAccelConfig">

type { redirect_prefix: string; } & $brand<"XAccelConfig">

import {XAccelConfig} from '@fuzdev/fuz_app/server/x_accel.js';

A validated X-Accel redirect configuration — the only handle that enables the X-Accel-Redirect serving path in server/serve_fact_route.ts.

The redirect prefix can be obtained only by passing the nginx config through validate_facts_internal_location (via create_x_accel_config), so X-Accel serving is impossible to enable without proving at boot that the facts location is internal; and carries the fact hardening headers — a public facts location would bypass every cell-visibility check. A Zod-branded type: the brand can't be forged without an explicit cast, so the factory is the only ordinary construction path.

redirect_prefix

The validated redirect prefix the serving path prepends (e.g. /_facts/).

type string

XAccelConfigError
#

server/x_accel.ts view source

import {XAccelConfigError} from '@fuzdev/fuz_app/server/x_accel.js';

A misconfigured X-Accel facts location — a fail-loud boot error thrown by create_x_accel_config.

inheritance

extends: Error

errors

The validator errors that made the location unsafe.

type string[]

readonly

constructor

type new (errors: string[]): XAccelConfigError

errors

type string[]

xfail_until
#

testing/cross_backend/xfail.ts view source

(tracking_id: string, reason: string, name: string, fn: () => void | Promise<void>): void import {xfail_until} from '@fuzdev/fuz_app/testing/cross_backend/xfail.js';

Register fn as an expected-failure test. Passes while fn throws / rejects; fails once fn succeeds (signalling the gap closed and the marker should be removed).

tracking_id

descriptive id of the tracked gap (e.g. 'audit-log-sse-rust-spine') — a feature/behavior name, not a process/milestone label.

type string

reason

why the case is deferred-by-design.

type string

name

the assertion / test label.

type string

fn

the test body (expected to throw/reject until the gap closes).

type () => void | Promise<void>

returns

void

zod_extract_fields
#

zod.ts view source

(schema: ZodObject<$ZodLooseShape, $strip>): ZodFieldInfo[] import {zod_extract_fields} from '@fuzdev/fuz_app/zod.js';

Extract field metadata from a Zod object schema.

schema

type ZodObject<$ZodLooseShape, $strip>

returns

ZodFieldInfo[]

zod_get_base_type
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): string import {zod_get_base_type} from '@fuzdev/fuz_app/zod.js';

Get the base type name for a Zod schema, unwrapping all wrappers.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

string

base type name (e.g. 'string', 'object', 'uuid')

zod_get_field_schema
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, key: string): ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> import {zod_get_field_schema} from '@fuzdev/fuz_app/zod.js';

Get the field schema for a key in an object schema.

schema

Zod schema (typically a ZodObject with possible wrappers)

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

key

type string

returns

ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

throws

  • Error - if the schema is not an object or the key is missing

zod_get_innermost_type
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> import {zod_get_innermost_type} from '@fuzdev/fuz_app/zod.js';

Unwrap a schema through every wrapper type (optional, nullable, default, transform, pipe, prefault) to reach the innermost schema. Like zod_unwrap_def but returns the schema (so callers can .shape, instanceof-check, etc.) instead of the def.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

the innermost non-wrapper schema

zod_get_schema_keys
#

zod.ts view source

<T extends z.ZodType>(schema: T): (keyof output<T> & string)[] import {zod_get_schema_keys} from '@fuzdev/fuz_app/zod.js';

Get all property keys from an object schema, unwrapping wrappers. Returns an empty array if the innermost type is not an object.

schema

Zod schema (typically a ZodObject with possible wrappers)

type T

returns

((keyof output<T>) & string)[]

generics

zod_get_schema_keys<T extends z.ZodType>
T
constraint z.ZodType

zod_get_schema_layers
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>[] import {zod_get_schema_layers} from '@fuzdev/fuz_app/zod.js';

Every layer of a schema, from the schema itself through each wrapper (optional, nullable, default, transform, pipe, prefault) to the innermost non-wrapper — for reading .meta() a wrapper does not carry over from the schema it wraps.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>[]

the layers, outermost first, ending at zod_get_innermost_type(schema)

zod_maybe_get_field_schema
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, key: string): ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | undefined import {zod_maybe_get_field_schema} from '@fuzdev/fuz_app/zod.js';

Get the field schema for a key in an object schema, returning undefined if the schema is not an object or the key is missing.

schema

Zod schema (typically a ZodObject with possible wrappers)

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

key

the property name

type string

returns

ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | undefined

the field's schema, or undefined

zod_unwrap_def
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): $ZodTypeDef import {zod_unwrap_def} from '@fuzdev/fuz_app/zod.js';

Unwrap Zod wrappers (optional, default, nullable, pipe, transform) to get the base type definition.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

$ZodTypeDef

the innermost non-wrapper type definition

zod_unwrap_to_object
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): ZodObject<$ZodLooseShape, $strip> | null import {zod_unwrap_to_object} from '@fuzdev/fuz_app/zod.js';

Unwrap a schema to find the inner ZodObject, or null if not an object schema. Handles wrappers like z.strictObject({...}).default({...}).

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

ZodObject<$ZodLooseShape, $strip> | null

ZodFieldInfo
#

zod.ts view source

ZodFieldInfo import type {ZodFieldInfo} from '@fuzdev/fuz_app/zod.js';

Metadata extracted from a single field of a Zod object schema.

name

type string

base_type

type string

required

type boolean

has_default

type boolean

nullable

type boolean