1 · Collection — emit billable events
Events are validated, queued, archived to R2 and folded into their event-time bucket. Backdate one past the 72h lateness window, or emit into a closed period, to watch it route to adjustments instead.
2 · Aggregation — buckets by event hour
Derived, never incremented: each write recomputes SUM(quantity) over deduplicated events, so replay and reordering are no-ops. Polls every 2s.
| meter | event hour (UTC) | quantity | events |
|---|---|---|---|
| no buckets yet | |||
3 · 4 · Rating and invoicing
Rated against the price book in KV, in integer cents, with the version stamped on every line. Closing is keyed on (account, period) and one-way.
| meter | quantity | free | billable | rate | amount |
|---|---|---|---|---|---|
| no usage in this period | |||||
Adjustments — usage that could not move the number
Events that arrived after the period closed, or beyond the lateness window. Recorded and priced at the marginal rate, never folded into an issued invoice.
| event_id | meter | qty | event time | reason |
|---|---|---|---|---|
| none | ||||
Reconciliation — recompute from the R2 archive, diff against D1
Aggregates the raw archive independently and compares it to the ledger. Findings are keyed, so re-running updates them rather than duplicating them — and nothing here ever resolves itself. A period cannot close while an exception is open.
| kind | meter | event hour | archive | D1 | detail | status | |
|---|---|---|---|---|---|---|---|
| no exceptions — the ledger matches the archive | |||||||
Audit trail — append-only
Who closed a period, who resolved an exception. The actor is public-demo until PROJECT 3 puts Cloudflare Access in front of /admin/* and validates the JWT in the Worker.
| # | when | actor | action | target | detail |
|---|---|---|---|---|---|
| nothing yet | |||||