main. GitHub Actions
verifies the code and archives a release manifest. The production frontend is
served through Render and the API through Supabase project
fdrjqcyjklatdrmjdnys. The Docker main function is a self-host router and must
never be included in the cloud function inventory.
Release checks
Install frontend dependencies first; the release CLI reuses the repository’s TypeScript and Zod dependencies. It performs read-only checks and does not deploy, apply migrations, repair migration history, or delete remote functions.SUPABASE_ACCESS_TOKEN. On macOS it can use the existing Supabase CLI native
credential. Never paste the token into a command argument or report. The check
downloads each cloud function into its own temporary directory, compares all
runtime source files, reads schema metadata through Supabase’s read-only SQL API,
and removes the temporary downloads. It checks function inventory again afterward
to reject a snapshot collected during another deployment.
The function-only check reports deployed source and gateway drift without querying
the database, so it can run before a pending schema migration. The full check
evaluates each distinct migration requirement query once and identifies failed
predicates by check name and predicate ID.
For checks larger than the management API request limit, use the same full gate
with a direct database transport:
postgres database. Do not place the URL or password in command
arguments. This option requires psql with libpq 17 or newer and system CA roots.
For a project CA, set PGSSLROOTCERT to the absolute path of the certificate
downloaded from Supabase Database settings. The default is system.
It verifies the TLS hostname and certificate, disables inherited libpq connection
overrides, runs each original requirement in a read-only transaction with bounded
statement/lock timeouts, and verifies the server’s read-only witness before accepting
rows. It does not execute migrations. Function downloads still use the management
credential. Transport failures and malformed results stop verification; checks are
never shortened or skipped to fit the API.
The manifest records the commit, dirty-worktree state, every cloud entry point,
explicit gateway policy, shared runtime dependencies, declarations, import map,
Deno lockfile, migration hashes and schema requirements. A shared helper change
therefore invalidates every function that imports it. Remote verification fails
on missing or extra functions, unexpected modules, old source bytes, changed
gateway settings, changed schema effects or an uncommitted checkout. It verifies
source/configuration compatibility, not provider secrets or remote package/CDN
integrity. Preserve the manifest and verification report with each release.
CI runs the local checks without production credentials. The owner confirms that
Render automatically deploys main. Run the remote gate against the reviewed
commit before merging, then verify that the live frontend publishes the new
artifact and passes its smoke checks. A Render dashboard login is not needed to
trigger that deployment.
Render’s exact build command, cache behavior, rollback configuration and automatic
enforcement of this gate remain infrastructure audit follow-ups. Until automatic
enforcement is verified, the release operator must require a passing remote report
before merging to main. Any provider release step should use the same command
above, rather than maintaining a second inventory or schema checker.
Reconciled database baseline
Production had nosupabase_migrations.schema_migrations ledger when inspected
on September 5, 2026. supabase/release/baseline.json records the hashes of the
seven historical migration files and 134 observed schema objects/grants. This
confirms their resulting effects, not the order or mechanism by which they ran:
- Character
updated_atis non-null with its update trigger and function. - Anonymous and authenticated roles can read public profile/campaign columns,
and cannot read
public_user.api,public_user.patreonorcampaign.join_key. - The campaign join-grant table has the expected keys, RLS with no public policies, and membership trigger/function.
- All 13 content tables have timestamp columns/triggers, 12 child tables bump
source timestamps, and both functions exclude generated
search_tsvcolumns when checking whether content changed. - All 12 content metadata columns are JSONB with GIN indexes.
supabase/release/schema-state.sql. Baseline format 2 names
each fingerprint with a sha256 field, making its algorithm explicit even when
the schema identifier names a protected column. These fingerprints describe schema
definitions and migration files; they contain no account credentials. Definition hashes pin
the inspected database functions, enabled triggers, columns, indexes, constraints
and grants. A difference requires investigation and a reviewed compatibility
update. Do not overwrite the baseline from an unreviewed production snapshot just
to make a failing gate green.
Do not run supabase db push against this project to replay historical files.
Do not run migration repair merely to create a ledger. The baseline historical
migration bytes remain immutable; use new migrations for new effects. The reviewed
Treasure Vault compatibility wrappers preserve their original migration bodies
and release queries byte-for-byte and depend on the versioned terminal helper.
They do not authorize rewriting historical effects.
The terminal helper also recognizes one exact source-correction successor: both
Dragon Damage Types tables use Bludgeoning for conspirator or horned dragons, and
Major Resonating Fork uses +19 in its description and casting metadata, with DC 29
unchanged. All three complete corrected records must match together. Mixed states
and unrelated field changes still fail. Historical comparisons retain the original
ledger; this allowance does not modify saved items or permit later arbitrary edits.
The helper permits direct execution by postgres, service_role and the provider’s
existing supabase_read_only_user. Its exact ACL excludes PUBLIC, anon and
authenticated, has no grant options, and does not grant role membership.
Every new migration needs an entry and read-only effect check
in supabase/release/requirements.json. If a ledger is introduced in a separately
reviewed reconciliation, the gate also checks recorded post-baseline versions and
rejects unknown versions. Absence of a ledger never substitutes for effect checks.
20261015000000_rust_cloud_release_compatibility.sql recognizes only the exact
cleaned Rust Cloud description already present in the catalog. The complete row
must match, including all rules, identity and source fields. The shared comparison
then projects it to the historical representation without writing spell data.
The upgrade requires the exact predecessor body, metadata and permissions, and
verifies the complete successor. Historical repair bodies and ledgers remain
unchanged. Native checks cover both descriptions, rejected drift, pending edits,
upgrade replay, full historical replays and complete rollback preservation.
Legacy Heal Companion and Incarnate recovery
20261011000000_restore_heal_companion.sql restores only the missing deprecated
Heal Companion feat, ID 22108, UUID 7476666523704164. It retains the original
source, citation, description and repaired Ranger operations. An exact existing
row is a no-op; conflicting IDs, UUIDs, names or citation aliases, changed source
dependencies and pending curator edits abort. It does not edit saved characters,
moderation state or sequences. The insert must refresh source 14’s cache token;
a missing or disabled cache trigger, different readback or sequence change aborts
the transaction. The original grant requirement remains, with an additional
whole-row recovery requirement.
Before production application, independently verify that the actual ability-block
identity sequence safely allocates beyond the restored historical ID. This is
an apply precondition, not established by the migration’s unchanged-sequence
postcondition or an isolated fixture’s synthetic allocations. Run this bounded
read-only witness using the approved connection and require passed=true:
20261011000100_incarnate_release_compatibility.sql validates one exact Incarnate
trait successor: ID 2584 retains every field and UUID while its source changes
from Rage of Elements (15) to Common Core (3). It does not move or edit
content. The read-only compatibility wrapper preserves the complete original
Tech Core predicate and its complete source-only successor: all 82 spells,
92 dependencies, seven source projections and eight binding literals remain
required. Normalized identity-alias and pending-edit guards apply to both states,
including when the complete original predicate passes without the successor guard.
Other source moves, partial states, duplicate identities and unrelated edits fail.
The historical migrations and original release queries remain unchanged.
The Tech Core citation checks additionally accept one complete, paired successor:
Junk (5225) cites its Tech Core trait page and Station (5236) cites its own
trait page. Every other field in both records remains exact. The original
introductory state still permits Station to be absent; the cited state requires
both complete records. Mixed old and cited records, unrelated metadata changes,
identity aliases and pending curator edits fail. Both original Incarnate source
choices retain the complete spell, dependency, source and binding predicates.
The historical query files remain explicit native-fixture inputs; the three
requirement registrations use the additive citation checks without changing the
registered migration inventory.
For Tech Core’s source header, NULL and an empty required-source array both mean
no dependencies. The additive checks accept only these two representations;
nonempty arrays, including arrays containing NULL, remain invalid. All other
source fields and content guards remain exact. Docker bootstrap and the two CI
replay passes use this complete successor check instead of the obsolete guard.
The focused construction and native-plan contracts run with:
npm run test:release includes the inert native-fixture preparation contracts.
The reviewed 360-case plan can also be inspected without Docker, SQL or network:
--execute-owned-native --output /private/tmp/<new-attempt> runs one
bounded, network-disabled fixture and removes only its verified owned container.
The runner requires the reviewed cached PostgreSQL 17 linux/arm64 image by full
image ID, never pulls an image and never controls existing containers. That
execution route is local, not a GitHub amd64 CI job. Independently pin and review
the matching fixture image before extending native execution to another platform;
do not replace the image identity with a mutable tag. The fixture uses actual
content-table constraints and cache migrations, synthetic moderation data and
explicit synthetic spell IDs resolved by real UUIDs. It is not a production,
Supabase Auth or RLS proof. Preserve the SQL pins and native receipt for review.
Expected SQL rejections require psql’s completed ON_ERROR_STOP script-error exit
code 3 and the expected diagnostic. Timeouts, signals, buffer failures and other
exit codes fail the attempt even if partial stderr contains that diagnostic.
Each SQL receipt retains the process termination fields before validation.
Use ON_ERROR_STOP and one transaction for each reviewed migration. Recheck the
fresh mutation guards at execution, independently read back both effects, and
rerun the full read-only release gate against the frozen candidate commit.
A private proposal or partial gate is not release approval. Do not merge until
all required checks pass; do not replay historical migrations to restore this row.
Rollout order
- Freeze the reviewed commit and preserve the previous compatible release manifest, function source, frontend artifact and provider configuration. Check CI/E2E and the current remote report. Investigate differences instead of treating them as permission to overwrite production.
- Apply additive prerequisites before dependent functions. Apply
20260905000000_edge_work_budget.sqlusing the approved database connection,ON_ERROR_STOPand a transaction. Its quota table/RPC is compatible with old handlers; new paid-work handlers return 503 if it is missing. The read-only requirement verifies the reviewed RPC body and service-role-only access. Apply20260921000000_fix_dominion_epithet.sqlin the same phase; it replaces only the duplicated nested identifiers for the Trespasser option. Its read-only requirement verifies both Dominion options and all four nested operation identifiers remain distinct. No historical migration replay is needed. Apply20260924000000_fix_reclaimant_plea.sqlfor the three Reclaimant Plea records. It repairs their boolean heightening switch and third innate spell, preserves saved selection identifiers, and aborts on unexpected content changes or pending curator submissions. Its read-only requirement checks all three records; it does not modify characters or homebrew. Apply20260925000000_fix_rootball_intelligence.sqlfor Rootball Chair’s Intelligence modifier. It changes only the published template’s +4 to -4, preserves operation identifiers and other attributes, and aborts on an unexpected value or pending curator submission. Its read-only requirement verifies the corrected modifier. Existing saved companion copies are untouched. - Deploy the complete cloud inventory from the reviewed checkout. Use
node scripts/release.mjs function-namesas the name source. Deploy those names explicitly with Supabase CLI and the repositorysupabase/config.toml; do not use an unscoped deploy-all command or a global--no-verify-jwtoverride. A shared-module change must deploy all its dependents, and deploying the full 55-function inventory is the simple safe default. - For a future restrictive grant/schema change, first deploy handlers compatible
with both schemas, smoke-test them, then apply the restriction. July 18’s
public_userpermission failure is the reason this order matters. - Run semantic smoke checks: public content and a public profile; authenticated own-profile and character reads; an authorized test-character save followed by a read; conflict handling; admin authorization and rejected anonymous paid-work requests. Use dedicated test identities/data. HTTP 200 alone is not success: inspect the JSend status and saved value. Expensive service success tests should use a controlled quota and approved test input.
- Retire obsolete functions after consumer review, then run
release:verifyagain and require a passing report before publishing the frontend. On September 5 the unexpected live routes werefind-campaigns,find-characters,find-encounters,get-sheet-contentand self-hostmain. Seven complete UTC days (August 29–September 4) contained 1,764,530 function-edge events and zero events for all five routes, including OPTIONS. Their source bundles and gateway inventory are backed up under.agents/legacy/infra-release-2026-09-05/. This supports retirement but does not establish that dormant clients do not exist. A retained compatibility route must be brought back into reviewed source with current authorization and tests; do not simply allowlist an opaque old bundle. - Merge the verified commit to
mainto trigger Render’s auto-deploy. Confirm the live entry point references the new frontend artifact, inspect the rendered app, repeat public/authenticated/save smoke checks, and inspect available logs for permission, auth, worker and paid-work failures. Archive the final manifest and successful report. Provider telemetry and semantic checks remain necessary even when hashes agree.
20260928010000_hazard_type.sql.
Then deploy create-creature, find-creature, and search-data with the current
shared helper; deploy the full cloud inventory to satisfy the release gate.
Smoke-test ordinary creature reads, search, and an authorized test create before
applying 20260928020000_war_of_immortals_hazards.sql. Afterward, confirm the
five hazards appear only in hazard results and that hazard edits are rejected.
Old find and search handlers do not distinguish hazards from creatures.
Rollback
Stop promotion on any failed gate or smoke check. Restore the previous verified frontend artifact and compatible function set using their saved manifest. Restore all affected shared-module dependents together; restoring one entry point does not restore the helper bundled into its neighbors. Keep additive quota schema in place when rolling back callers. Never roll back to a handler that reopens anonymous paid work; disable the affected costly route or retain its authorization patch while resolving an upstream problem. Never restore public SELECT access to protected account columns or campaign join keys to make an old handler work. Prefer a forward handler fix when a schema restriction makes the old release incompatible. After rollback, run the remote gate against the rollback checkout and repeat semantic profile/save checks. If no compatible rollback artifact has been tested, record that limitation and agree on the recovery plan before promotion; do not claim rollback readiness. Dashboard configuration review and a restore drill remain necessary to establish that capability.Sanitized content snapshots
The GitHub content-snapshot workflow runs weekly on Tuesday at 09:00 UTC or by manual dispatch. Code pushes no longer trigger production exports. Exports are serialized, have a 15-minute job deadline, a 60-second statement deadline and a 5-second lock deadline. An export failure does not deploy or change production. Account, character, campaign, encounter, moderation, join-grant and quota state are excluded from the production data export. Sanitized content supports local development; it is not a backup of user data. Production backups, restore drills and provider resource monitoring are separate operational responsibilities.Authorization rollout for the next release
Deployupdate-user, gm-add-to-group, gm-regenerate-code, and
create-content-update from the reviewed commit before applying
20260905010000_account_moderation_writes.sql. These handlers support both permission
sets. Run the account-setting and moderation smoke checks, then apply this new
restriction transactionally. Never restore broad profile or moderation writes to
make an old handler work. The added release requirement checks INSERT/UPDATE column
grants as well as table privileges and policies; the old baseline remains immutable.
No existing profiles are deleted or demoted by this migration. The production preflight found no duplicate user IDs and confirmed the server-owned
signup trigger. Existing elevated accounts require a separate identity review before cleanup.
Browser diagnostics and semantic monitoring
The app reports a maximum of one event per failure category per page toreport-client-error. Supabase function logs receive a fixed category, page category,
build revision and random trace ID. The endpoint rejects extra fields, accepts at
most 512 bytes and caps logging at 60 reports per minute per isolate. These are
sampled operational signals, not an exhaustive error census. Provider log retention
and alert delivery still need to be configured for the desired response window.
The Production health workflow runs four read-only probes every 15 minutes:
frontend HTML, Auth health, a known content source and source timestamps. It rejects
HTTP-200 failure envelopes and malformed data. It does not submit content, write
characters or invoke paid services. Configure the public anonymous key in repository
variable WG_MONITOR_ANON_KEY. Repository owners receive failures through their
GitHub Actions notification settings; verify that the responsible operator has
workflow failure notifications enabled. GitHub schedules can be delayed and are not
an availability guarantee. Controlled authenticated save checks remain a release
step, not a recurring mutation of a real user’s character.
App updates are checked quietly and activate after existing tabs close. They never
show a reload toast or interrupt an open editor. Fatal page errors keep technical
details in logs and offer a simple link home.
Quiet character saving
Normal saving, successful retries and compatible remote updates run without a status row, toast or recovery controls on the character sheet and builder. Changes to that layout require an intentional UI scope decision. Temporary network failures retry quietly while input remains in the editor. Session expiry, lost editing permission, rejected saves, and unavailable draft storage also stay quiet. An explicitly rejected payload stays buffered and is not repeatedly submitted; a corrected edit can save again. Conflicting edits pause saving while retaining the local input and its original base. Background reads and ordinary edits recheck the competing version, and saving resumes when merging is safe. Calculation failures retry quietly without publishing partial math. There is no download or discard workflow for save history. Calculations also reject a content package that cannot resolve a saved primary or secondary class, ancestry, or background. Unselected Builder choices remain valid; missing selected content must recover before a new result can be published. Retained drafts stay on the device under the same character ID. They do not create additional server characters. Versioned drafts with a known common ancestor can be reconciled automatically. Older drafts without enough information for a safe merge remain untouched; their presence alone does not interrupt the user or replace a newer server save. Recovery verification covers reload, account changes and builder/sheet navigation, with server reads proving saved values and local checks proving unreplayable drafts are preserved. The CypresssaveRecovery scenario also checks that the ordinary
sheet has no added saving controls or notifications at desktop and phone widths.
Connection, encounter and remote-update scenarios verify real acknowledgements,
reopening, and quiet recovery rather than relying on a displayed Saved label.
Recovery evidence and next drill
On September 5, 2026, production had seven completed daily physical backups (August 30 through September 5), with PITR disabled. The newest backup was at 04:44:18 UTC. A daily backup implies up to roughly one day of potential database changes to recover separately; it does not prove a restore time. Database backups cover Storage metadata, not uploaded object bytes. Back up bothportraits and backgrounds to an independently recoverable, private destination.
See Supabase backups.
Before declaring recovery ready:
- Select an isolated restore destination, encrypted object-backup destination, recovery-point target and recovery-time target. Keep encryption-key recovery separate from the backup; never put plaintext user exports into Git or CI artifacts.
- Restore a database backup into that isolated destination, with outbound Discord, email, Patreon and paid integrations disabled. Never restore over production as a test. Record backup age, start/finish times and any missing schema/functions.
- Verify profiles, Auth, RLS denial tests, controlled character reads/saves, campaign joins and moderation state. Use dedicated test identities rather than messaging real users. Preserve an evidence report without account data or tokens.
- Restore object bytes with their matching metadata and compare counts, sizes and checksums. Load sample restored images through the isolated app.
- Record measured recovery time, cleanup and gaps. A sanitized development dump or successful backup listing is not evidence that production can be restored.
Shared base ancestries
If production still has the original terminal helper, first apply20261008105800_treasure_vault_terminal_catalog_compatibility.sql. Its wrapper
upgrades the exact original helper through the reviewed intermediate definition
to the current successor without writing content. Then apply
20261010000000_shared_ancestry_release_compatibility.sql before
20261010000100_shared_ancestries.sql. The first upgrades only the exact reviewed
terminal helper and retains its complete historical ledger, metadata and grants.
The second moves Elf, Dwarf, Gnome, Goblin, Halfling and Orc and their canonical
traits from Player Core to Common Core. Existing IDs, descriptions, operations,
languages, senses and saved characters remain unchanged. Source-derived UUIDs are
recomputed using the existing upload rule. Human already belongs to Common Core.
The compatibility proof accepts only all twelve original rows or all twelve
complete successors. Partial moves, unrelated edits, duplicate identities and
pending submissions under either source or UUID remain failures. Historical
migration bodies and release predicates remain intact inside their wrappers.
Existing Bagpipes classifications and the three source corrections retain their
separate exact allowances.
Rival Academies’ reviewed feat-count successor is limited to 78 becoming 79. Every
other source field must match, and the live feat count must independently be 79.
This accounts for the published Lesson of Memory addition without rewriting book
metadata or accepting arbitrary later changes. Preserve full preflight, backup,
independent readback and the remote release report for the rollout.
Incarnate release compatibility
The Incarnate trait was moved from Rage of Elements to Common Core after the Tech Core general spell import.20261011000100_incarnate_release_compatibility.sql
is a guard-only transaction. It accepts the exact original trait or the exact
source-only successor; it does not move content or write spell or trait rows.
Both registered requirements use
tech-core-general-spells-incarnate-compatibility.sql. Its two branches retain
the complete original spell, reference, source and binding checks. All other row
fields must match, Common Core must be published, and duplicate identities or
pending submissions for either source remain failures. The original migration
and release file remain unchanged and captured in the native input manifest.
Native verification exercises both source states, alias and pending-submission
rejections, complete no-op replay preservation and unrelated dependency failures
in addition to the original general-spell control matrix. Historical migration
replays and the full release gate must pass before this compatibility change is merged.
October curator compatibility
20261026010000_curator_release_compatibility.sql recognizes the exact reviewed
Scent link correction and Enthrall rules correction while retaining the complete
original Treasure Vault ledgers. It writes no content. Every other row field,
reference, source, helper setting and grant remains pinned. Enthrall’s successor
UUID also receives duplicate-identity and pending-submission checks.
The early helper installer includes this same successor for complete historical
replays. Existing upgrade installers retain their exact previous SQL inside an
outer compatibility wrapper. Native rollback controls cover both original and
successor combinations, upgrade/replay, unrelated edits, queue aliases and helper
drift. Apply only the new dated compatibility migration to production.
20261027000000_curator_book_count_compatibility.sql additionally recognizes the
published Litany Against Wrath and Litany Against Sloth feats in Core Rulebook.
The book counter may change from 282 to 284 only when the real feat count is 284,
both complete published rows match, no duplicate identity or unresolved matching
submission exists, and every other book field is unchanged. A counter edit alone
fails. The original content, template, source and curator checks remain intact;
this migration changes only the helper definition. Native rollback controls cover
the original counter, the complete successor, missing or altered feats, unrelated
metadata and content drift, rejected helper predecessors, and historical replay.
20261026000000_mine_mod_rules.sql repairs eight existing Tech Core records:
Maneuvering Mines, Healing Mines, Aerial Hologram, Aerobatics Mastery, Expert
Mechanic, Programmer, Auto Spellshape and Magical Shorthand. It compares complete
original rows and refuses pending curator work before adding operations. Verify
its registered release predicate and source cache timestamp after application.
