
Identity issues the user
A signup on identity-kit mints the opaque UserId. Every other layer keys on it — no second notion of who someone is.
One app you can read and run: identity-kit issues the user, tenant-kit scopes every row, billing-kit meters and bills the usage, and ai-member answers questions about it — all over the same SqlExecutor. The keystone is the stone that locks the arch: proof the four compose.


A signup on identity-kit mints the opaque UserId. Every other layer keys on it — no second notion of who someone is.

tenant-kit resolves the request to a tenant and binds the scoped executor, so every query the demo runs is already isolated by RLS.

Each action the demo takes is metered idempotently and priced to exact Money; the double-entry ledger and the invoice update live.

ai-member reads the same state and answers plain questions — “what does this tenant owe?” — grounded in the ledger it can see.

The whole app runs on a single executor and pool. The kits compose because they share the seam, not because glue code forces them to.

It is a demo, not a black box: open any file and follow a request from sign-in through the bill it produces.
Four kits, one SqlExecutor. The keystone locks the arch — they compose, not collide.
identity-kit verifies the credential and issues a revocable session bound to the opaque UserId.
tenant-kit checks membership and scopes the transaction — from here every row belongs to one tenant.
billing-kit meters each event idempotently, prices it to exact Money, posts the balanced ledger entry and updates the invoice.
ai-member answers questions about the running state — the total, the isolation, the idempotency — from the same data.
import { login } from 'identity-kit';
import { resolve, protect } from 'tenant-kit';
import { meter, price, post, accrual } from 'billing-kit';
import { ground } from 'ai-member';
// one signed-in user, one tenant, one executor — the whole arch
const session = await login(sql, { email, password, pepper });
const tenant = await resolve(sql, { host, user: session.userId });
await protect(sql, tenant, async (db) => {
await meter(db, { subject: session.userId, event: 'api.call', quantity: 1n, key });
await post(db, accrual(price(usage, plan), { debit: 'ar', credit: 'revenue' }));
});
const answer = await ground('what does this tenant owe?', { ledger });Four libraries, one seam. The demo is the proof that they compose instead of colliding.