Run the whole thing on your own machine

A complete Phoenix application that meters a fake AI product, runs with no API key and no card, and breaks itself eight different ways on demand so you can read what each failure actually leaves behind.

Free core 15 min Start here

Reading about a metering library and running one are different experiences. The sample is a whole Phoenix application, MIT licensed, that meters a fake AI product: it holds credit before the work, records the tokens as durable facts, settles the hold for what the work actually cost, and shows you the ledger.

It runs with Elixir and a Postgres server. No API key, no Stripe account, no network call of any kind.

#Getting it running

It lives inside the aurora_meter repository at examples/aurora_meter_example_ai.

cd examples/aurora_meter_example_ai
mix setup
mix sample.seed
PORT=4021 mix phx.server

mix setup fetches dependencies, creates and migrates the database and builds assets. mix sample.seed creates two organisations, four users and four credit lots, and prints the accounts and the shared password. Log in at /users/log-in.

The database defaults to localhost:5490, which is the Postgres the package’s own test suite uses. Override DB_HOST and DB_PORT if yours is elsewhere, and mix ecto.reset starts again from nothing.

Five pages are worth visiting in order: /generate, /history, /ops, /dev/tools, and the POST /api/generate endpoint behind the plug.

#The work is deterministic, and it is obviously not an AI

AuroraMeterExampleAi.Tokens computes a token count from the prompt and then assembles a paragraph out of the words of the prompt itself. It does that deliberately, so that nothing it produces could be mistaken for a model’s answer.

The generator is seeded from a hash of the prompt and the model, and it threads its own state rather than touching the calling process’s, so the same prompt and model give you the same token count and the same bytes on every machine, and running the sample does not make your own tests non deterministic.

Two models, nimbus-1 and nimbus-1-mini, at ten millionths of a dollar per token, so ten thousand tokens is ten cents.

A prompt starting fail: raises, which is how the failure recipes reach into the middle of the work.

#One string, three jobs

The line worth reading twice is in AuroraMeterExampleAi.Generations.create/3, and it is the pattern the whole library is designed around:

AuroraMeter.with_quota(org, :images, 1, fn ->
  AuroraMeter.Credits.with_credits(org, estimate, "gen:" <> request_id, fn ->
    result = Tokens.run(prompt, model)
    AuroraMeter.record(org, :tokens, result.total, id: "gen:" <> request_id, occurred_at: at)
    {:ok, result, result.total * 10}
  end)
end)

request_id is the primary key of the application’s own row, the reference of the credit hold, and the identity of the durable event. When somebody asks what happened to one generation, one string answers in three tables. The callback’s third element is the real cost, so the hold settles for what the work actually took rather than for the estimate.

#Eight failure recipes

This is the part of the sample worth the download. Each recipe reaches into the middle of a real flow, breaks one thing, and prints what the application, the database and the ledger each believe afterwards.

mix sample.failure                       # list them
mix sample.failure untrappable_death     # run one
mix sample.failure all                   # the seven that need no Pro key
mix sample.failure --check               # re-assert the last run against its saved report
Recipe What it shows
insufficient_balance a refusal before any work, and the two different reasons a wallet can refuse
callback_raise a raise inside the work, and the hold and the reservation coming back
untrappable_death a process killed between the durable event and the application’s own row
worker_retry two delivery attempts and one effect
duplicate_event one identity recorded twice: one event, one delta, one intent
exporter_timeout an outcome nobody knows, which is never retried automatically
late_correction a correction after the period, bounded and immutable
customer_cancellation a cancelled subscription and a refunded top up, reversed once (needs Pro)

untrappable_death is the one to run first. It is the failure people assume a library handles and then discover it does not, and the report shows you exactly which of the three records moved and which did not.

Every report has the same seven sections: setup, trigger, application, database, ledger, recovery, uncertain. That last section is the important one. It is where a recipe says what nobody can know from inside the system, which is the honest answer for exporter_timeout and the reason that state needs a person.

No recovery step edits a row. The sample’s own test suite fails if the failure documentation ever grows an UPDATE, an INSERT or a DELETE, and cross checks the document’s headings against the implemented recipes in both directions, so a recipe cannot be documented and missing, or implemented and undocumented.

A Pro recipe run without the Pro profile aborts by name. It does not degrade into a simulation, because a simulated proof is worse than no proof.

#Turning the Pro half on

Optional, and it needs a Pro key.

mix hex.organization auth phxtemplates --key "$AURORA_HEX_READ_KEY"
cp .env.example .env
export AURORA_SAMPLE_PRO=1
mix deps.get
mix ecto.migrate
mix ecto.migrate --migrations-path priv/repo/pro_migrations
PORT=4021 mix phx.server

The flag switches lockfile and build directory as well as configuration, because two dependency resolutions need two of each. It adds Oban, the Pro supervision tree, a /billing page, the Stripe webhook mounted in the endpoint above Plug.Parsers, and an outbox that calls both halves.

The guard that makes this trustworthy is test/pro_absent_test.exs: it reads every compiled module’s import table and fails if anything outside the two guarded trees references AuroraMeter.Pro. The core profile is not “Pro with the calls commented out”; it is an application that could not call Pro if it tried.

#What to take from it

Three things, and none of them is the code.

The first is the shape of with_quota wrapping with_credits wrapping the work, and one identity threaded through all three.

The second is that every interesting failure leaves the system in a state you can describe, and the recipes describe them rather than asserting that the system recovered.

The third is the uncertain section. A system that can tell you what it does not know is one you can operate.

Keep going

The core is free. Pro is the Stripe half.

Everything the free MIT package does is yours with no account and no expiry. Aurora Meter Pro adds Stripe checkout, metered usage reporting, prepaid top-ups, dashboards and alerts, and every plan starts with a free trial.