← Back to Documentation
Native Backend

Native Backend — Infrastructure & Database

The new Revique healthcare / medspa business platform backend — catalog, packages, gift cards, loyalty, promotions, payments, patient portal, notifications and a robust RBAC system — running on Aurora PostgreSQL. Built by Codex, separate from the existing Portal (React / CI-CD) stack.

Engine
Aurora PostgreSQL 16.13
Region
us-east-1
Tables
36
Access
SSM Tunnel · Bastion
🔒 Values masked — account ID, cluster endpoint, bastion instance ID and other identifiers are redacted (first 3 chars + ****). The DB password is never printed. Retrieve real values from AWS Secrets Manager / your account before running commands.
Table of Contents
01

Overview

The native backend is a purpose-built data platform for the Revique healthcare / medspa business. Where the existing Portal is a React front-end shipped through an AWS CI/CD pipeline (documented on the main page), the native backend is the system of record behind it: a normalized PostgreSQL database that owns catalog & inventory, service packages, gift cards, a loyalty program, promotions, native card payments, the patient portal, cross-system notifications, and a fine-grained role-based access-control (RBAC) system.

It was built by Codex as a fresh, "native" replacement layer that runs alongside the legacy HPT system — many tables carry legacy_* columns (legacy IDs / payloads) so records can be reconciled back to the old platform during migration.

What it is

  • Aurora PostgreSQL 16.13 cluster in us-east-1
  • 36 tables across 10 functional domains
  • UUID / identity-based primary keys (no sequences)
  • pgcrypto enabled for hashing & UUID generation
  • Append-only ledgers for money & balance movements

How it relates to the Portal

  • Portal (React/CI-CD) = presentation & delivery
  • Native backend = authoritative business data
  • Bridged to legacy HPT via legacy_* keys
  • Notifications ingest from both legacy_hpt and revique
  • Payments processed natively via the FluidPay gateway
02

Architecture at a Glance

The database has no public IP. Engineers reach it through an AWS SSM Session Manager port-forwarding tunnel that hops through a tiny bastion EC2 instance sitting in the same VPC as the cluster. There is no SSH key and no open inbound port — your AWS credentials are the authentication, and Session Manager brokers the encrypted channel.

💻
Your machine
aws cli + SSM plugin
psql → 127.0.0.1:5433
🛡️
Bastion EC2
revique-api-dev-db-tunnel
t4g.nano · i-0****
🗄️
Aurora PostgreSQL
rev****.amazonaws.com
db: revique · :5432

Engine

Aurora PostgreSQL 16.13, region us-east-1. UUID PKs via gen_random_uuid().

Cryptography

pgcrypto extension enabled — used for UUID generation and for hashing sensitive values (gift-card numbers, portal login identifiers, emulation tokens).

Access model

Private cluster · bastion i-0**** · SSM port-forward tunnel · IAM-based auth (no SSH key, no public endpoint).

Credentials live in AWS Secrets Manager at /revique/revique-api/dev/database/admin (host, port, dbname, username, password). Reference the path — never copy the password into code, tickets, or this page.
03

How to Access the Database

A reproducible runbook for opening a read/write session against the dev cluster. All sensitive values are masked — substitute the real ones from describe-* output and Secrets Manager. Commands assume an AWS CLI profile named hptdev.

All eight steps use the masked identifiers shown on this page (i-0****, rev****.amazonaws.com). Replace them with the real values you retrieve in steps b and c.
a

Verify your AWS identity

# Confirm you're authenticated into the right account
aws sts get-caller-identity --profile hptdev
# → Account: 292****   (masked)  ·  Arn: arn:aws:sts::292****:...
b

Find the Aurora cluster endpoint

aws rds describe-db-clusters --region us-east-1 --profile hptdev \
  --query "DBClusters[?contains(DBClusterIdentifier, 'revique')].[DBClusterIdentifier,Endpoint,EngineVersion]" \
  --output table
# → Endpoint: rev****.us-east-1.rds.amazonaws.com   ·  EngineVersion: 16.13   (masked)
c

Find the bastion instance

aws ssm describe-instance-information --region us-east-1 --profile hptdev \
  --query "InstanceInformationList[?contains(ComputerName, 'revique')].[InstanceId,ComputerName]" \
  --output table
# → i-0****   revique-api-dev-db-tunnel   (t4g.nano, masked)
d

Install the Session Manager plugin (Ubuntu) — if missing

curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" \
  -o "session-manager-plugin.deb"
sudo dpkg -i session-manager-plugin.deb
session-manager-plugin --version   # verify install
e

Get credentials from Secrets Manager (never print the password)

# Retrieve the secret — inspect it in a secure shell; do NOT echo/paste the password anywhere
aws secretsmanager get-secret-value --region us-east-1 --profile hptdev \
  --secret-id /revique/revique-api/dev/database/admin \
  --query SecretString --output text
# → JSON: { "username": "revique_admin", "host": "rev****...", "port": 5432, "dbname": "revique", "password": "•••••" }
Treat the password as write-only: let psql/PGAdmin prompt for it, or export it to an env var in a private shell. Do not commit it or include it in shared docs.
f

Open the SSM port-forward tunnel (local port 5433)

aws ssm start-session --profile hptdev --region us-east-1 \
  --target i-0**** \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"host":["rev****.us-east-1.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["5433"]}'
# Leave this session running — it forwards 127.0.0.1:5433 → Aurora:5432
Gotcha we hit: use local port 5433, not 5432. If you already run a local PostgreSQL it will occupy 5432, and the tunnel will silently collide with it — you'll end up connecting to your own machine instead of Aurora. 5433 keeps them separate.
g

Connect with psql / PGAdmin

# In a second terminal (tunnel stays open in the first):
psql "host=127.0.0.1 port=5433 dbname=revique user=revique_admin sslmode=require"
# PGAdmin → New Server → Host 127.0.0.1  Port 5433  DB revique  User revique_admin
# Password: from Secrets Manager (step e)
h

Export the schema (structure only, no data)

pg_dump "host=127.0.0.1 port=5433 dbname=revique user=revique_admin sslmode=require" \
  --schema-only --no-owner --no-privileges \
  -f revique-native-backend-schema.sql
# This is exactly how the schema behind this page was produced.
The raw revique-native-backend-schema.sql still contains unmasked identifiers. Keep it local — do not upload it with the shared site.
04

Database Summary

36
Tables
84
Indexes
27
Foreign Keys
4
Functions
0
Sequences
1
Extension
10
Domains
3
Append-only Ledgers

Design characteristics

  • UUID identity: every entity PK is uuid DEFAULT gen_random_uuid() — zero sequences.
  • Money in cents: integer *_cents columns with non-negative CHECK constraints.
  • Append-only ledgers: gift-card, package and promotion movements are immutable (enforced by triggers).
  • Multi-location: pricing / configs scoped by practice_location_id, often with partial unique indexes for a "global default" vs per-location row.

Cross-cutting conventions

  • created_at / updated_at on virtually every table.
  • set_updated_at() trigger auto-touches updated_at on UPDATE.
  • is_active / is_deleted soft-state flags; partial indexes exclude deleted rows.
  • legacy_* columns + legacy_payload jsonb preserve the migration lineage from HPT.
  • idempotency_key + unique indexes protect money operations from double-apply.
05

Schema by Functional Group

Tables grouped into their business domains. For each, the key columns are listed with type, nullability and default. marks a primary-key column; marks a foreign-key column. Not every CHECK constraint is shown — see the notes for the important ones.

primary key foreign key PK / UNIQUE / APPEND-ONLY

Catalog / Inventory

4 tables

The product / service / fee catalog and its per-location pricing, inventory lots and provider mappings.

catalog_items— master items: products, services & feesPK id
ColumnTypeNullNotes
iduuidnot nullgen_random_uuid()
practice_idtextnot nulltenant scope
kindtextnot nullCHECK in (product, service, fee)
nametextnot nullindexed lower(name) per practice
sku / upctextnullpartial-unique per practice+kind
service_duration_minutesintegernullCHECK > 0
is_procedural / is_lot_tracked / is_taxablebooleannot nulldefault false
is_active / is_deletedbooleannot nullsoft-state flags
legacy_item_id / legacy_service_id / legacy_fee_id / legacy_payloadtext / jsonbnullHPT migration lineage
catalog_item_location_pricing— per-location prices, tax & discounts◆ catalog_item_id → catalog_items
ColumnTypeNullNotes
iduuidnot nullgen_random_uuid()
catalog_item_iduuidnot null→ catalog_items (CASCADE)
practice_location_idtextnullNULL row = global default (partial unique)
cash_price_cents / card_price_cents / unit_price_centsintegermixednon-negative CHECKs
cost_cents / overhead_cents / discount_centsintegernullmargin inputs
discount_percent / tax_percentnumeric(7,4)mixed0–100 range CHECK
catalog_item_lots— inventory lots (lot #, expiry, quantities)◆ catalog_item_id
ColumnTypeNullNotes
id · catalog_item_iduuidnot null→ catalog_items (CASCADE)
practice_location_id / lot_numbertextnot nullstock kept per location
expiration_date / received_ondatenull
received_quantity / remaining_quantityintegernot nullCHECK remaining ≤ received
catalog_item_provider_mappings— which providers can deliver an item◆ catalog_item_id
ColumnTypeNullNotes
id · catalog_item_iduuidnot null→ catalog_items (CASCADE)
provider_idtextnot nullglobal- and location-scoped partial uniques
practice_location_idtextnullNULL = applies everywhere

Gift Cards

4 tables

Definitions (templates), issued cards, per-location availability and an append-only ledger of every balance movement.

gift_card_definitions— gift-card templatesPK id
ColumnTypeNullNotes
iduuidnot null
practice_id / nametextnot nullunique on (practice, lower(name))
amount_modetextnot nullCHECK in (fixed, custom)
fixed_amount_centsintegernullrequired iff amount_mode=fixed
currency_codechar(3)not nulldefault USD, ^[A-Z]{3}$
gift_cards— issued cards & live balance◆ gift_card_definition_id
ColumnTypeNullNotes
iduuidnot null
gift_card_definition_iduuidnot null→ gift_card_definitions (RESTRICT)
patient_id / practice_id / location_idtextnot null
card_number / card_number_hash / card_number_last4textnot nullhash is uniquely indexed (pgcrypto)
original_amount_cents / current_balance_centsintegernot nullCHECK balance ≤ original, ≥ 0
statustextnot nullactive / exhausted / voided / refunded
gift_card_ledger_entries— immutable balance movementsAPPEND-ONLY◆ gift_card_id
ColumnTypeNullNotes
id · gift_card_iduuidnot null→ gift_cards (RESTRICT)
event_kindtextnot nullissue / redemption / reload / adjustment / void / refund
amount_delta_cents / balance_after_centsintegernot nullsign rules enforced by CHECK
source_kind / source_id / actor_user_idtextmixedcheckout / invoice / payment / manual / system
idempotency_keytextnullpartial-unique (double-apply guard)
gift_card_definition_locations— per-location availability & taxability◆ gift_card_definition_id
ColumnTypeNullNotes
id · gift_card_definition_iduuidnot null→ gift_card_definitions (CASCADE)
practice_location_idtextnot nullunique (definition, location)
is_active / taxablebooleannot null

Loyalty

3 tables

A points-based loyalty program (default program key colorscience), its per-patient profiles, point adjustments and eligible products.

loyalty_profiles— per-patient loyalty membershipUNIQUE (practice, patient, program)
ColumnTypeNullNotes
iduuidnot null
practice_id / patient_idtextnot null
program_keytextnot nulldefault colorscience
status / points_balancetext / integernot nullregistered / inactive · balance ≥ 0
email / phone / postal_code / birth_month / birth_daytext / intnullenrollment profile; month 1–12, day 1–31
consented_at / registered_attimestamptzmixed
loyalty_point_adjustments— point earn/burn history◆ loyalty_profile_id
ColumnTypeNullNotes
id · loyalty_profile_iduuidnot null→ loyalty_profiles (CASCADE)
points_delta / points_balance_afterintegernot nulldelta ≠ 0, balance ≥ 0
source_type / source_idtextnullpartial-unique per (profile, source) — idempotent
eligible_amount_centsintegernull
loyalty_program_products— products that earn pointsUNIQUE (program, sku)
ColumnTypeNullNotes
iduuidnot null
program_key / name / skutextnot nullsku non-empty
upctextnullCHECK digits only

Packages

6 tables

Sellable service/product bundles: definitions, their line items and per-location prices, plus the per-patient instances, per-item balances and an append-only package ledger.

package_definitions— sellable package templatesPK id
ColumnTypeNullNotes
iduuidnot null
practice_id / nametextnot nullunique (practice, lower(name))
price_cents / currency_codeinteger / char(3)not nullprice ≥ 0
starts_at / expires_after_daystimestamptz / intnullexpiry > 0 if set
package_items— what a package contains◆ package_definition_id
ColumnTypeNullNotes
id · package_definition_iduuidnot null→ package_definitions (CASCADE)
catalog_item_kind / catalog_item_id / catalog_item_nametextmixedkind ∈ (product, service)
quantityintegernot nullCHECK > 0; unique per (def, kind, item)
package_definition_location_prices— per-location price overrides◆ package_definition_id
ColumnTypeNullNotes
id · package_definition_iduuidnot null→ package_definitions (CASCADE)
practice_location_id / price_centstext / intnot nullunique (def, location); price ≥ 0
patient_package_instances— a package a patient bought◆ package_definition_id
ColumnTypeNullNotes
id · package_definition_iduuidnot null→ package_definitions (RESTRICT)
practice_id / patient_id / location_idtextmixed
source_checkout_id / source_invoice_id / source_payment_idtextnullpurchase provenance
purchased_at / expires_at / statustimestamptz / textmixedactive / exhausted / expired / voided / refunded
price_paid_centsintegernot null≥ 0
patient_package_item_balances— remaining redemptions per item◆ instance, item
ColumnTypeNullNotes
iduuidnot null
patient_package_instance_iduuidnot null→ patient_package_instances (CASCADE)
package_item_iduuidnot null→ package_items (RESTRICT)
original_quantity / remaining_quantityintegernot nullCHECK remaining ≤ original, ≥ 0
package_ledger_entries— immutable package movementsAPPEND-ONLY4 FKs
ColumnTypeNullNotes
iduuidnot null
patient_package_instance_iduuidnot null→ patient_package_instances (RESTRICT)
patient_package_item_balance_iduuidnull→ patient_package_item_balances (RESTRICT)
package_definition_id · package_item_iduuidmixed→ package_definitions / package_items (RESTRICT)
event_kind / quantity_delta / remaining_quantity_aftertext / intmixedpurchase / redemption / adjustment / void / refund / expiration
idempotency_keytextnullpartial-unique

Payments

2 tables

Native card payments through the FluidPay gateway: per-practice/location gateway configs and the transaction log. Card data is tokenized — only card_token method is allowed and no PAN is stored.

payment_gateway_configs— gateway credentials/config (secret by reference)PK id
ColumnTypeNullNotes
iduuidnot null
practice_id / location_idtextmixedglobal- & location-scoped partial uniques
gateway / gateway_environmenttextnot nullgateway = fluidpay; env sandbox / production
public_keytextnullCHECK begins with pub_
private_api_key_secret_idtextnullreference to a secret — not the key itself
processor_id / api_base_urltextnull
native_payment_transactions— payment attempts & results◆ payment_gateway_config_id
ColumnTypeNullNotes
iduuidnot null
payment_gateway_config_iduuidnot null→ payment_gateway_configs (RESTRICT)
practice_id / location_id / patient_id / invoice_id / checkout_idtextmixedorder context
payment_method_typetextnot nullCHECK = card_token (no raw PAN)
amount_cents / currency_codeinteger / char(3)not nullamount > 0
statustextnot nullpending / approved / declined / gateway_declined / processor_error / gateway_error
idempotency_key / gateway_idempotency_keytext / uuidmixedpartial-unique per (practice, key)
gateway_transaction_id / gateway_response* / processor_idtext / intnullraw gateway result

Promotions

3 tables

Discount definitions, the catalog items they target, and an append-only record of every time a promotion was applied to a cart / invoice.

promotion_definitions— discount rulesPK id
ColumnTypeNullNotes
iduuidnot null
practice_id / nametextnot nullunique (practice, lower(name))
discount_typetextnot nullpercent | fixed_amount (exactly one of the two amounts set)
discount_percent / discount_amount_centsnumeric / intnullpercent 0–100
starts_at / ends_attimestamptznullCHECK ends > starts
promotion_linked_items— items a promotion applies to◆ promotion_definition_id
ColumnTypeNullNotes
id · promotion_definition_iduuidnot null→ promotion_definitions (CASCADE)
catalog_item_kind / catalog_item_idtextnot nullunique per (def, kind, item)
promotion_applications— every applied discountAPPEND-ONLYUNIQUE idempotency_key
ColumnTypeNullNotes
id · promotion_definition_iduuidnot null→ promotion_definitions (RESTRICT)
practice_id / patient_id / location_idtextmixed
source_kind / source_invoice_id / source_checkout_idtextmixedcheckout / invoice / payment / manual / system
discount_centsintegernot nullCHECK > 0
promotion_snapshot / cart_contextjsonbnot nullfrozen quote context
idempotency_keytextnot nullglobally UNIQUE

Patient Portal

3 tables

Patient-facing portal accounts, the providers each account is linked to, and patient file uploads (S3/R2).

patient_portal_accounts— portal logins (bridged to legacy patient/user)UNIQUE legacy_patient_id
ColumnTypeNullNotes
iduuidnot null
legacy_patient_id / legacy_user_idtextnot nullpatient_id is unique
login_identifier_hashtextnullhashed login (pgcrypto), partial-unique
status / last_login_attext / timestamptzmixedactive / disabled
patient_portal_provider_links— providers visible to an account◆ account_id
ColumnTypeNullNotes
id · account_iduuidnot null→ patient_portal_accounts (CASCADE)
legacy_provider_id / provider_name / provider_url_suffixtextmixedunique (account, provider)
is_active / last_verified_atboolean / timestamptznot null
patient_files— uploaded documents / photos / mediaUNIQUE (provider, object_key)
ColumnTypeNullNotes
iduuidnot null
practice_id / patient_id / categorytextnot nullcategory ∈ documents / photos / media
file_name / content_type / size_bytestext / bigintmixed
storage_provider / object_keytextnot nulls3 / r2; object_key unique per provider
upload_status / completed_at / deleted_attext / timestamptzmixedpending / uploaded; soft-delete

Notifications

2 tables

A cross-system notification feed (ingesting from legacy HPT and native Revique) with per-user read/dismiss/action state.

notifications— notification feed itemsUNIQUE (practice, system, type, entity)
ColumnTypeNullNotes
iduuidnot null
practice_id / location_idtextmixed
source_system / source_type / source_entity_idtextnot nullsystem ∈ legacy_hpt / revique; type ∈ patient_message / lead_message / form_submission / alert
title / preview / body / prioritytextmixedpriority low / normal / high / urgent
target / metadatajsonbnot nullrouting / audience
notification_user_states— per-user read/dismiss/action◆ notification_id
ColumnTypeNullNotes
id · notification_iduuidnot null→ notifications (CASCADE)
user_idtextnot nullunique (notification, user)
read_at / dismissed_at / actioned_attimestamptznullindexed for unread queries

RBAC (Access Control)

8 tables

The largest domain — a full role-based permission model with per-location scoping, role emulation and an audit trail. See the RBAC deep-dive for how these fit together.

rbac_users— platform usersUNIQUE (practice, normalized_user_name)
ColumnTypeNullNotes
iduuidnot null
practice_id / user_name / normalized_user_nametextnot null
legacy_user_id / user_typetext / intnullHPT bridge
first_name / last_name / email / phone / is_activetext / boolmixed
rbac_roles— roles per practiceUNIQUE (practice, normalized_name)
ColumnTypeNullNotes
iduuidnot null
practice_id / name / normalized_nametextnot nullCHECK normalized_name ≠ 'master'
is_system / is_activebooleannot nullsystem roles protected
rbac_feature_catalog— catalog of features & allowed actionsPK feature_key
ColumnTypeNullNotes
feature_keytextnot nullnatural PK (no uuid)
module_key / name / descriptiontextmixedgrouped by module
actionstext[]not nullsubset of view/create/edit/delete/export/manage/assign/refund
sort_order / is_activeint / boolnot null
rbac_role_permissions— which (feature, action) a role grantsPK (role, feature, action)
ColumnTypeNullNotes
role_iduuidnot null→ rbac_roles (CASCADE)
feature_keytextnot null→ rbac_feature_catalog
actiontextnot nullCHECK ∈ 8 verbs
rbac_user_roles— user ⇄ role assignmentsPK (user, role)
ColumnTypeNullNotes
user_iduuidnot null→ rbac_users (CASCADE)
role_iduuidnot null→ rbac_roles (CASCADE)
rbac_user_locations— which locations a user is scoped toPK (user, location)
ColumnTypeNullNotes
user_iduuidnot null→ rbac_users (CASCADE)
practice_location_idtextnot nullper-location access scoping
is_activebooleannot null
rbac_role_emulation_grants— temporary "act as role / super-admin" tokensUNIQUE token_hash
ColumnTypeNullNotes
iduuidnot null
role_iduuidnull→ rbac_roles (CASCADE); NULL for super_admin
token_hashtextnot nullhashed (pgcrypto), globally unique
emulation_kindtextnot nullrole (role_id set) | super_admin (role_id NULL)
granted_by_user_id / expires_attext / timestamptzmixedCHECK expires_at > created_at
rbac_audit_events— access-control audit trailPK id
ColumnTypeNullNotes
iduuidnot null
practice_id / actor_user_id / actor_user_nametextmixedwho did it
action / target_type / target_idtextmixedwhat & on which object
metadatajsonbnot nullindexed by (practice, created_at)

System

1 table
schema_migrations— applied migration ledgerPK id
ColumnTypeNullNotes
idtextnot nullmigration identifier
name / applied_attext / timestamptznot nulltracks Codex migrations
06

RBAC Deep-Dive

RBAC is the most detailed domain in the schema. It implements a classic users → roles → permissions model, extended with per-location scoping, time-boxed role emulation and a dedicated audit trail. Everything is tenant-scoped by practice_id.

The permission chain

rbac_users  ──(rbac_user_roles)──▶  rbac_roles  ──(rbac_role_permissions)──▶  (feature_key, action)
     │                                                                        │
     └──(rbac_user_locations)──▶ practice_location_id          rbac_feature_catalog defines the
        (which sites the user may act in)                       valid features & their allowed actions

1 · Users & roles

rbac_users holds each platform user (bridged to the legacy user via legacy_user_id). rbac_roles defines named roles per practice. The join table rbac_user_roles (composite PK) grants a user one or more roles. A user with no role has no permissions.

2 · Permissions via a feature catalog

rbac_feature_catalog is the source of truth for what can be permissioned: each feature declares an actions text[] drawn from view, create, edit, delete, export, manage, assign, refund. rbac_role_permissions then grants a role a specific (feature_key, action) pair — its composite PK makes each grant unique, and the FK to the catalog stops invalid feature keys.

3 · Per-location scoping

rbac_user_locations (composite PK of user + practice_location_id) restricts a user to specific sites. Roles say what you can do; locations say where. This lets a "Front Desk" role at Location A be entirely separate from the same role at Location B.

4 · Role emulation

rbac_role_emulation_grants issues a time-boxed, hashed token that lets an authorized user temporarily "act as" another role — or, when emulation_kind = super_admin (with role_id NULL), as a super-admin. A CHECK enforces that expires_at > created_at, and only the token_hash is stored (never the raw token). Ideal for support/impersonation with a built-in expiry.

5 · Audit. rbac_audit_events records who (actor_user_id/name) did what (action) to which object (target_type/target_id), with a JSON metadata blob and a (practice_id, created_at DESC) index for fast per-tenant timelines. Combined with emulation grants, every "act as" is traceable.
Design note. A CHECK on rbac_roles forbids the normalized name 'master', and is_system marks protected built-in roles — guardrails so the reserved super-role can't be spoofed by a practice-created role.
07

Relationships Overview

All 27 foreign keys, grouped by domain. CASCADE = children are removed with their parent; RESTRICT = the parent can't be deleted while children exist (used to protect ledgers & financial history).

Child columnReferencesOn delete
catalog_item_location_pricing.catalog_item_idcatalog_items.idCASCADE
catalog_item_lots.catalog_item_idcatalog_items.idCASCADE
catalog_item_provider_mappings.catalog_item_idcatalog_items.idCASCADE
gift_card_definition_locations.gift_card_definition_idgift_card_definitions.idCASCADE
gift_cards.gift_card_definition_idgift_card_definitions.idRESTRICT
gift_card_ledger_entries.gift_card_idgift_cards.idRESTRICT
loyalty_point_adjustments.loyalty_profile_idloyalty_profiles.idCASCADE
native_payment_transactions.payment_gateway_config_idpayment_gateway_configs.idRESTRICT
notification_user_states.notification_idnotifications.idCASCADE
package_definition_location_prices.package_definition_idpackage_definitions.idCASCADE
package_items.package_definition_idpackage_definitions.idCASCADE
package_ledger_entries.package_definition_idpackage_definitions.idRESTRICT
package_ledger_entries.package_item_idpackage_items.idRESTRICT
package_ledger_entries.patient_package_instance_idpatient_package_instances.idRESTRICT
package_ledger_entries.patient_package_item_balance_idpatient_package_item_balances.idRESTRICT
patient_package_instances.package_definition_idpackage_definitions.idRESTRICT
patient_package_item_balances.package_item_idpackage_items.idRESTRICT
patient_package_item_balances.patient_package_instance_idpatient_package_instances.idCASCADE
patient_portal_provider_links.account_idpatient_portal_accounts.idCASCADE
promotion_applications.promotion_definition_idpromotion_definitions.idRESTRICT
promotion_linked_items.promotion_definition_idpromotion_definitions.idCASCADE
rbac_role_emulation_grants.role_idrbac_roles.idCASCADE
rbac_role_permissions.feature_keyrbac_feature_catalog.feature_keyNO ACTION
rbac_role_permissions.role_idrbac_roles.idCASCADE
rbac_user_locations.user_idrbac_users.idCASCADE
rbac_user_roles.role_idrbac_roles.idCASCADE
rbac_user_roles.user_idrbac_users.idCASCADE
Pattern: definition/config parents CASCADE to their pure children (locations, items, links), but anything that touches money or history — issued gift cards, package instances, all three ledgers, payment configs — is RESTRICT, so financial records can never be silently deleted by removing a parent.
08

Functions & Extensions

set_updated_at()

Trigger function — sets NEW.updated_at = now() on every row UPDATE. Wired as a BEFORE UPDATE trigger on ~24 tables so updated_at is always accurate without application code.

prevent_gift_card_ledger_mutation()

Raises 'gift_card_ledger_entries is append-only'. Attached as BEFORE UPDATE and BEFORE DELETE triggers, making the gift-card ledger strictly insert-only.

prevent_package_ledger_mutation()

Raises 'package_ledger_entries is append-only'. Same UPDATE/DELETE guard for the package ledger.

prevent_promotion_application_mutation()

Raises 'promotion_applications is append-only'. Locks the promotion-application log against edits/deletes.

Extension — pgcrypto. Enabled at the database level (CREATE EXTENSION pgcrypto). Supplies gen_random_uuid() for every UUID primary key, and cryptographic hashing for sensitive values stored as *_hash columns (gift-card numbers, portal login identifiers, role-emulation tokens) so raw secrets are never persisted.
Why append-only ledgers? Money and entitlement movements (gift-card balances, package redemptions, applied promotions) must be auditable and irreversible. Instead of updating a balance in place, the app inserts a signed delta row and the trigger forbids editing history — corrections are new compensating entries, not edits.
09

Operational Notes / Next Steps

Open items aligned with what leadership asked for — captured here as a working checklist to review and document as the native backend hardens toward production.

Logging & events

  • Confirm application & DB logs (slow queries, errors) ship to CloudWatch / central logging.
  • Verify rbac_audit_events is written on every privileged action.
  • Retention policy for the append-only ledgers & audit trail.

Scaling & availability

  • Enable / tune Aurora auto-scaling (read replicas, ACU limits for Serverless v2).
  • Confirm Multi-AZ & automated backup / PITR windows.
  • Right-size the bastion and document its lifecycle.

Monitoring & alerting

  • CloudWatch alarms: CPU, connections, replica lag, storage.
  • Enable Performance Insights / Enhanced Monitoring.
  • Alert on failed payment transactions & gateway errors.

Security & access

  • Rotate the Secrets Manager DB credential; scope least-privilege DB roles.
  • Restrict SSM start-session to a named IAM group; log all sessions.
  • Confirm encryption at rest (KMS) & TLS in transit are enforced.
Do-not-publish reminder. The raw revique-native-backend-schema.sql dump contains unmasked identifiers and must be kept local — exclude it from anything uploaded with this shared site.