A queryable trail of the actions that matter — a sign-in, a refund, an entitlement granted, a secret rotated, a config change made from a browser — as durable rows, each attributed to the actor that caused it.
@pithy-sh/audit
pithy add auditA durable, queryable record of the actions that matter: logins, token refreshes, entitlement grants, admin configuration changes — each attributed to the right actor, including the dashboard when it is the one acting.
What it sits on
The contract — the event shape and the emit method on the request context — lives in core, not here. Any capability records an event without importing this package, and with no audit capability composed, emit is a no-op recorder.
So an audited action can never break for want of auditing.
License
FSL-1.1-MIT
The source is public, and you can read it, run it, change it and ship it in your product. Every other capability in the kit is MIT; this one is the exception, and it is the only one.
The single thing it forbids is a competing use — taking this and offering it to others as a substitute for it, or for something we already sell with it. Your own product is not that, and neither is consulting on it, teaching from it or researching it: those are named as permitted in the text.
Each version converts to MIT on the second anniversary of its release. That is not a promise we could quietly drop later; the grant is written into the license you already received.
The honest bit
Signing in is one of the things worth recording, and it is the one every auth library will hand you. It is also the least interesting: the questions an audit answers are who refunded that order, who granted this entitlement, who rotated that secret, and who changed the configuration on a Friday evening.
Those actions happen in payments, in the ledger, in secrets, in the dashboard — not in auth. A trail that lives inside an auth library can only ever see the door, which is why this is a capability of its own and why the contract lives in core rather than here. Any capability records an event without importing this package.
It also has to answer for the vendor. When the dashboard acts on your Worker it lands in this trail under its own actor kind, so what a tool did is answerable separately from what your people did.
If you build it yourself
The question is never what happened. It is who did it, and how you know.
A user, an admin, a scheduled job or the dashboard — distinguished, so what a vendor did is answerable separately.
If recording an event requires importing the audit package, half of them will not, so the contract lives in core.
An audited operation must not fail because auditing did. With nothing composed, the recorder is a no-op.
A trail is long-lived and queryable, which makes it exactly the wrong place for a receipt or a token.
Append-only, high volume, read by range — the shape a transactional database is worst at.
pithy add auditComposes with
Passwordless sessions, mobile and web both first-class. Magic link, email OTP, Google, Apple.
Five payment rails resolving to one cross-rail entitlement, in your own Worker and your own D1.
One encrypted store per environment, a worker-only master key, and automatic at-rest rotation.
In the dashboard
The trail itself, filterable by actor, action and environment — which is most of what an audit screen ever needs to be.
The dashboard is the admin interface for your own application: whatever this capability writes, your team can look up, check and act on from a browser. Reading is free, forever, with no time limit. Connect the project when you deploy.
Everything else
Each one composes the same way. Nothing you skip costs you anything.
Designed, not shipped
Written down, argued out, and not built yet. Each one links to the issue it is being designed in, so you can read the reasoning or disagree with it.
An audit trail is append-only, high volume, and read by range and aggregate — which is the shape D1 is worst at. An analytical tier over R2 Data Catalog and R2 SQL is designed: one schema object derives the ingest stream, the table and the query types, so the trail stays queryable at a volume the transactional store cannot hold.
Issue #101 on GitHub
Coming soonA field will declare its own privacy class, and the inventory of what personal data you process becomes something derived from your schemas rather than a document that drifts from them.
Issue #132 on GitHub
One command installs the package, wires its bindings into every environment your project declares, and runs its migrations.