Generates an Ecto migration that creates the persistence backing the
Ecto-based stores shipped with attesto_phoenix.
The migration creates eleven tables, named to match the runtime schemas exactly so a by-the-docs deploy installs tables the Ecto-backed stores can use without modification:
attesto_authorization_codes- the authorization code grant store (AttestoPhoenix.Schema.Authorization). Holds one row per issued authorization code (RFC 6749, section 4.1) plus the PKCE binding (RFC 7636), the optionalcnfkey binding (RFC 7800), the OIDCnonce, mappedclaims, the descendantfamily_id, consumed markers, and the access-tokenjtiissued from a successful redemption so code reuse can revoke it. Keyed oncode_hashas its PRIMARY KEY (no surrogate id); consulted exactly once at the token endpoint.attesto_refresh_tokens- the refresh token store (AttestoPhoenix.Schema.RefreshToken, RFC 6749, section 6). Each row carries the rotationfamily_idandgenerationit belongs to, theconsumed/consumed_atidempotency markers,successorretry payload,family_revokedsticky revocation flag, thecnfkey binding, mappedclaims, and the diagnosticparent_hash, so that reuse of a rotated token can be detected and the whole family revoked (RFC 6819, section 5.2.2.3 - refresh token rotation / replay detection).attesto_refresh_family_revocations- durable refresh-family revocation tombstones. Refresh-token rows are eligible for expiry cleanup, so this separate table preserves a revocation after every row in the family has been swept. Existing installations upgrading to a release that uses this table must apply a forward migration before starting the new code; see the upgrade notes in the README and CHANGELOG.attesto_device_codes- the device authorization grant store (AttestoPhoenix.Schema.DeviceCode, RFC 8628). One row per device code, keyed ondevice_code_hash(the poll key) anduser_code(the verification key), carrying the boundscope/resource/dpop_jkt, thestatusstate machine (pending → approved|denied → consumed), the approvedsubject/granted_scope/granted_claims, andlast_polled_atfor the section 3.5 poll-interval guard.attesto_ciba_requests- the OpenID Connect CIBA authentication-request store (AttestoPhoenix.Schema.CIBARequest/EctoCIBAStore, CIBA Core 1.0). One row perauth_req_id, keyed onauth_req_id_hash, carrying the boundscope/resource/dpop_jkt, thedelivery_mode, the pingclient_notification_token, the hint-resolvedhint_subject, thestatusstate machine (pending → approved|denied → consumed), the approvedsubject/acr/auth_time/granted_scope/granted_claims, the frozen pollinterval, andlast_polled_atfor the §7.3 poll-interval guard.attesto_logout_sessions- the logout session store (AttestoPhoenix.Schema.LogoutSession, OpenID Connect Back-Channel Logout 1.0 + Front-Channel Logout 1.0). One row per(session, Relying Party)pair, recorded at ID-Token mint and read at the end-session endpoint to deliver alogout_tokenand/or render the RP'sfrontchannel_logout_uri. Upserted on(sid, client_id); carries thesubject, the RP'sbackchannel_logout_uri/session_requiredandfrontchannel_logout_uri/frontchannel_session_required, and theexpires_atthat bounds an abandoned session.dpop_nonces- server-issued DPoP nonces (AttestoPhoenix.Schema.DPoPNonce, RFC 9449, section 8). Each row is a single-use nonce carryingissued_at,expires_at, and theused_atconsumption marker.dpop_replays- the DPoP proof replay cache keyed by the proof'sjtias its PRIMARY KEY (AttestoPhoenix.Schema.DPoPReplay, RFC 9449, section 11.1). A row is the record that a given proof JWT has already been seen within its acceptance window.attesto_pushed_authorization_requests- the Pushed Authorization Request store (AttestoPhoenix.Schema.PushedAuthorizationRequest, RFC 9126). Each row maps a one-timerequest_urireference (the PRIMARY KEY) to the stored, validated authorization requestparamsand the referenceexpires_at, so arequest_uripushed to one node is resolvable on every node (FAPI 2.0 requires PAR).attesto_client_id_metadata- the Client ID Metadata Document cache (AttestoPhoenix.Schema.ClientIdMetadata,draft-ietf-oauth-client-id-metadata-document-01). Each row caches one validated CIMD document under itsclient_idURL (the PRIMARY KEY), as a jsonbmetadatamap plus theexpires_atderived from the response's HTTP freshness directives (RFC 9111). Keeps every authorization request from re-fetching the URL and, being shared, makes the cache coherent across a cluster and bounds the outbound fetch fan-out.attesto_consent_grants- the single-use, request-bound consent grant store (AttestoPhoenix.Schema.ConsentGrant/EctoConsentGrantStore, RFC 6749 §4.1.1). Each row records one consent decision keyed on an unguessabletoken(the PRIMARY KEY), with abinding_hashover the exact request the user saw and a shortexpires_at;consumed_atmarks single use. The host consent screen mints a row; the host's:consentcallback consumes it before a code is issued, so one consent click cannot approve a different client/redirect/scope/challenge.
Usage
# Fresh installation:
mix attesto_phoenix.gen.migration --repo MyApp.Repo
# Upgrading an existing database to 3.0:
mix attesto_phoenix.gen.migration --upgrade 3.0 --repo MyApp.Repo
# Promoting the historical authorization-code unique index to its
# primary key (run after the 3.0 migration when upgrading 2.x):
mix attesto_phoenix.gen.migration --upgrade 3.1 --repo MyApp.RepoOptions
--upgrade- generate a migration to upgrade an existing database rather than creating fresh tables. Supported values:3.0/3.0.0and3.1/3.1.0. The 3.0 migration adopts or creates the exact unique index onattesto_refresh_tokens(family_id, generation), creates or adopts theattesto_refresh_family_revocationstable, and safely backfills every currently-revoked refresh-token family. The 3.1 migration promotes the historical unique index onattesto_authorization_codes(code_hash)to the primary key required by current schemas. Run both, in order, for a 2.x database. Each upgrade validates any pre-existing object before backfilling or changing it; malformed collisions fail the migration transaction rather than being silently accepted.--repo,-r- the Ecto repo module the migration is generated for. May be given more than once to target several repos. When omitted the repos configured for the host application are used (the same resolutionmix ecto.gen.migrationperforms).--schema-prefix- an optional PostgreSQL schema selected by Ecto'sprefix:option for every generated table and index (for example--schema-prefix oauthcreatesattesto_authorization_codesin schemaoauth). Runtime Ecto queries use the sameprefix:option; the table names themselves remain canonical. When omitted, the prefix configured for the host (:schema_prefixon theAttestoPhoenix.Configthe host puts in its application environment) is used so the generated schema matches the prefix the Ecto stores read at runtime. If the host has no configured prefix, the generated migration defers to the migrator or repo default at execution time. The task never invents a prefix. The 2.x--table-prefixoption is rejected because it controlled literal names in generated migrations, not one coherent runtime layout: most 2.x stores queried canonical tables inpublic, while only the CIBA store and sweeper treated the value as an Ecto schema prefix. Inventory an existing database before choosing a 3.0 schema; without--upgrade, this task is for fresh migrations only.--migrations-path- directory the migration file is written to. Defaults to the repo'spriv/<repo>/migrationsdirectory, the same locationmix ecto.gen.migrationuses.--otp-app- the host application whose environment holds theAttestoPhoenix.Configkeyword or struct to read:schema_prefixfrom when--schema-prefixis omitted. Optional; when omitted, the task first usesconfig :attesto_phoenix, otp_app: ...and then the current Mix project's:app. If neither application has a configured prefix, the task embeds no explicit prefix and the migration uses the migrator or repo default at execution time (normallypublic). Keep a custom migration default aligned with the runtime database connection's search path, or pass--schema-prefixexplicitly.--config-key- the application environment key the host stores itsAttestoPhoenix.Configkeyword under. Defaults toAttestoPhoenix.Config, matchingAttestoPhoenix.Config.from_otp_app/2.
Fresh-install migrations are reversible. The 3.0 upgrade migration has a guarded rollback that refuses to discard revocations which 2.x cannot represent. An application downgrade also requires stopped writers and a drain of active 3.x refresh-retry deadlines because 2.x cannot recover 3.x successor envelopes.