auth/permit_offer_queries.ts

Permit offer database queries.

Covers the offer side of the consentful-permits flow: create (with re-offer upsert), decline, retract, list, find-pending, sweep-expired, and the atomic query_accept_offer that bridges offer → permit.

IDOR guards are expressed in each helper's signature — decline/accept require the recipient's to_account_id, retract requires the grantor's from_actor_id.

Declarations
#

14 declarations

view source

AcceptOfferInput
#

auth/permit_offer_queries.ts view source

AcceptOfferInput

offer_id

type Uuid

to_account_id

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

type Uuid

ip

Optional IP to stamp on the audit events.

type string | null

AcceptOfferResult
#

auth/permit_offer_queries.ts view source

AcceptOfferResult

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

permit

type Permit

offer

created

true if this call is the one that accepted the offer (new permit inserted); false on a race returning the already-created permit.

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 permit_offer_supersede notifications without a second round-trip.

type Array<SupersededOffer>

audit_events

Audit events emitted in-transaction — fed back through the normal on_audit_event broadcast chain by the caller. Includes one permit_offer_supersede per superseded sibling.

type Array<AuditLogEvent>

PermitOfferAlreadyTerminalError
#

auth/permit_offer_queries.ts view source

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 PermitOfferExpiredError — 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): PermitOfferAlreadyTerminalError

offer_id
type string

PermitOfferExpiredError
#

auth/permit_offer_queries.ts view source

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): PermitOfferExpiredError

offer_id
type string

PermitOfferNotFoundError
#

auth/permit_offer_queries.ts view source

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): PermitOfferNotFoundError

offer_id
type string

PermitOfferSelfTargetError
#

auth/permit_offer_queries.ts view source

Error thrown when a grantor attempts to offer a permit to their own account.

Enforced here (rather than via a CHECK constraint) so the constraint can be expressed as a cross-row JOIN on actor.account_id without requiring denormalized columns.

inheritance

extends:
  • Error

constructor

type new (): PermitOfferSelfTargetError

query_accept_offer
#

auth/permit_offer_queries.ts view source

(deps: QueryDeps, input: AcceptOfferInput): Promise<AcceptOfferResult>

Accept an offer atomically: mark accepted, insert the permit, stamp resulting_permit_id, supersede sibling pending offers for the same (to_account, role, scope), and emit permit_offer_accept + permit_grant + one permit_offer_supersede per sibling. Must run inside a transaction — the caller's route spec should declare transaction: true (or wrap explicitly).

Idempotent on race: if a second concurrent call observes the offer already accepted, returns the existing permit rather than creating a duplicate or throwing.

Error map: - PermitOfferNotFoundError — offer does not exist, or belongs to a different recipient (IDOR guard). The offer row is untouched. - PermitOfferAlreadyTerminalError — offer is declined, retracted, or superseded. - PermitOfferExpiredError — offer is pending but past expires_at.

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 permit is later revoked.

deps

input

returns

Promise<AcceptOfferResult>

throws

  • PermitOfferNotFoundError - if the offer is missing or belongs to another recipient
  • PermitOfferAlreadyTerminalError - if the offer is declined, retracted, or superseded
  • PermitOfferExpiredError - if the offer is pending but past `expires_at`
  • Error - if the accepting account has no actor (1:1 invariant) or invariant assertions fail

query_permit_offer_create
#

auth/permit_offer_queries.ts view source

(deps: QueryDeps, input: CreatePermitOfferInput): Promise<PermitOffer>

Create a new permit 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. 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 PermitOfferSelfTargetError if the offering actor belongs to the recipient account.

deps

input

returns

Promise<PermitOffer>

throws

  • PermitOfferSelfTargetError - if the offering actor belongs to `to_account_id`

query_permit_offer_decline
#

auth/permit_offer_queries.ts view source

(deps: QueryDeps, offer_id: string, to_account_id: string, reason: string | null): Promise<PermitOffer | null>

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 PermitOfferAlreadyTerminalError if the offer exists for the caller but is already in a terminal state.

deps

offer_id

type string

to_account_id

type string

reason

type string | null

returns

Promise<PermitOffer | null>

throws

  • PermitOfferAlreadyTerminalError - if the offer is already accepted, declined, retracted, or superseded

query_permit_offer_find_pending
#

auth/permit_offer_queries.ts view source

(deps: QueryDeps, offer_id: string): Promise<PermitOffer | null>

Look up a pending offer by id. Returns null if the offer is terminal, expired (server-side filter), or missing.

deps

offer_id

type string

returns

Promise<PermitOffer | null>

query_permit_offer_history_for_account
#

auth/permit_offer_queries.ts view source

(deps: QueryDeps, account_id: string, limit?: number, offset?: number): Promise<PermitOffer[]>

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
default 100

offset

type number
default 0

returns

Promise<PermitOffer[]>

query_permit_offer_list
#

auth/permit_offer_queries.ts view source

(deps: QueryDeps, to_account_id: string): Promise<PermitOffer[]>

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_permit_offer_sweep_expired) handles audit tombstoning.

deps

to_account_id

type string

returns

Promise<PermitOffer[]>

query_permit_offer_retract
#

auth/permit_offer_queries.ts view source

(deps: QueryDeps, offer_id: string, from_actor_id: string): Promise<PermitOffer | null>

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 PermitOfferAlreadyTerminalError 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<PermitOffer | null>

throws

  • PermitOfferAlreadyTerminalError - if the offer is already accepted, declined, retracted, or superseded

query_permit_offer_sweep_expired
#

auth/permit_offer_queries.ts view source

(deps: QueryDeps): Promise<PermitOffer[]>

Return pending offers whose expires_at has passed.

Callers fire permit_offer_expire audit events for each row. The schema does not tombstone the row, so callers are responsible for their own idempotency (e.g. check whether a permit_offer_expire audit event already exists for the offer id).

deps

returns

Promise<PermitOffer[]>

Depends on
#

Imported by
#