Architecture

Extends the free core’s architecture page, which sets out the three regions of state, the process tree and the seams. Read that first; this page covers only what Pro adds.

For what Pro promises, read Correctness. This page is the map.

1. What Pro owns

Pro owns eight tables and reads none of core’s directly:

Table Holds Written by
aurora_meter_outbox_items one delivery intent per subject, with its state, lease and fencing token the enqueue hook (inside core’s record transaction) and Outbox.Deliverer
aurora_meter_usage_reports per (tenant, feature, period) high-water mark of what has been reported UsageReporter
aurora_meter_source_cutovers the watermark at which a feature changed reporting source Cutover
aurora_meter_reconciliation_items a difference a person has to resolve Reconcile and Outbox.Reconciler
aurora_meter_credit_accounts the provider customer, the saved card, auto top-up state and the pending payment machine Pro.Credits
aurora_meter_rollups daily and monthly aggregates for reporting Rollup
aurora_meter_alerts one dedup row per quota-threshold crossing Alerts
aurora_meter_audit_events one row per audited request the Plugs.AuditLog before-send hook

2. Which direction the dependency runs

Core never references Pro. Pro reaches core only through core’s public API: the facade, the Storage callbacks, AuroraMeter.Credits and Credits.Lots, Plans, Subscriptions, Period, Clock and Operations. It copies no core schema and queries no core table directly.

Pro implements four core behaviours:

  • AuroraMeter.Billing.Provider, so core knows who the customer is at Stripe;
  • AuroraMeter.Exporter, as the Stripe meter-event exporter;
  • AuroraMeter.Events.Outbox, which is how a committed fact becomes a delivery intent inside the same transaction;
  • AuroraMeter.Period, as the subscription-aligned period source.

That is the whole surface. A host can remove Pro and keep every free capability.

3. The export path, end to end

a fact is committed                      a buffered window is staged
(core record transaction)                (UsageReporter, on its schedule)
        |                                          |
        +----------------> outbox item <-----------+
                            (pending)
                                |
                          Outbox.Deliverer
                     leases (token), calls Exporter.deliver/2
                                |
        +-----------+-----------+-----------+-----------+
        |           |           |           |           |
    accepted     retry       uncertain   rejected   quarantined
        |        (backoff)       |       (terminal,  (mapping, mode
        |         pending        |        visible)    or period problem;
   Outbox.Reconciler        Pro.Recovery              operator requeues)
        |                        |
    confirmed             accepted | pending | rejected

Three properties of that picture are worth stating plainly, because they are what the shape is for.

Delivery is at least once, and no stronger than that. Each send carries an identifier Stripe deduplicates on, so a retry of the same window does not bill those units twice. A send whose answer never arrives becomes uncertain rather than being retried blindly or dropped. Correctness I15.

Every transition is conditional on the lease token. A worker whose lease expired writes zero rows and is logged, rather than overwriting a newer worker’s result. A lease timestamp answers “when does this expire”; the fencing token answers “whose write wins”, and only the second is a correctness property.

The payload is immutable once enqueued. The item carries the customer, the meter, the quantity, the timestamp, the identifier, the feature and the period as they were when the intent was created. A later change to the mapping does not rewrite an item in flight; it quarantines the ones that no longer fit.

Outbox has the states and the columns. Usage reporting has the reporter’s half.

4. The payment path

A top-up is a Stripe Checkout Session or an off-session charge against a saved card. The rule the machine is built around: Stripe is the payment rail and the ledger is the source of truth.

  • The pending payment parameters are persisted before the POST, so an unknown outcome can be resumed with the same idempotency key rather than guessed at.
  • Once the PaymentIntent id is known, resumption is GET only: the machine never re-POSTs a charge whose intent it can look up.
  • The grant is keyed on the PaymentIntent id under the wallet’s balance row lock, so a redelivered webhook cannot fund an account twice. Correctness I14.
  • An empty search result is not proof that no charge occurred. Recovery says what to do instead.

Top-ups has the customer-facing behaviour.

5. Transaction boundaries Pro adds

The enqueue. Pro.Outbox.enqueue/2 runs inside core’s record transaction. If your host transaction wraps the record/4 call, the outbox item rolls back with everything else. That is deliberate: an export intent that outlives the fact it exports is worse than one that never existed.

The webhook handler. Each handler takes an advisory lock on the tenant key for the duration of its work, so two deliveries of the same event serialise rather than racing. AuroraMeter.Pro.Credits.with_lock/2 is the same lock, made public so a host can serialise its own work on the same key.

The audit log write. Outside the request’s transaction, after the response is sent, in a task. A failure to write it is a Logger.warning, because the audit log never breaks the request it is auditing.

6. What Pro does not do

  • It does not count. Metering, quotas, plans, events and the credit ledger are all in the free core.
  • It does not guarantee that a Stripe invoice is finalised or paid. It records the state Stripe reports and opens a reconciliation item for every difference it finds. Correctness I13.
  • It does not resolve every unknown provider outcome on its own. Past the configured uncertainty horizon an item stays uncertain and needs a person. Recovery.
  • It does not repair drift. Reconcile reports differences; a human closes them. Reconciliation.

7. Further reading