Completes the lifecycle family from #25 — usage caps were the third
sibling (after expiry #24 and revoke), written-but-never-enforced.
Model:
- PolicyRule gains windowSeconds; drops the never-enforced mutable
currentUsageCount. A cap = (maxUsageCount, windowSeconds): at most N
signings of this (method,kind) per rolling window. windowSeconds NULL
= lifetime; maxUsageCount NULL = uncapped.
- New SigningLog: durable append-only record of allowed signings — the
source of truth caps count against (derive-don't-count; no counter to
drift).
Enforcement (checkIfPubkeyAllowed step 4): among the live token's
matching rules, every capped rule must have remaining budget in its
window (COUNT(SigningLog) < maxUsageCount), counted live. Stacked caps
all bind — 20/hr AND 200/day enforced together. recordSigning() writes
a SigningLog row from the permit callback when a consequential request
(sign_event / encrypt / decrypt) is allowed.
Retune live: new update_policy_rule admin RPC patches maxUsageCount/
windowSeconds/method/kind in place; takes effect next request, no
re-pairing (a payoff of the #27 Option D design). get_policies now
returns each rule's id + window_seconds so callers can target it.
Retention/pruning of SigningLog is a follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Additive, non-breaking schema prep for the Option D live-evaluation ACL:
- Request gains keyUserId (FK) + @@index([keyUserId, method]) so token
usage caps can be derived live by COUNTing allowed Requests, replacing
the never-enforced mutable PolicyRule.currentUsageCount (derive-don't-count,
per lnbits/nostr_bunker prior art).
- SigningCondition gains createdAt/expiresAt/revokedAt so the manual-override
layer carries its own lifecycle and runs through the same grantIsLive(now)
predicate as token grants (D1: two typed sources, one shared rule).
No behavior change yet; the ACL hot path and applyToken de-materialization
follow in subsequent commits.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pre-requisite for the live-policy auth rewrite in #11. The new
revoke_token admin RPC needs a way to mark a single Token as
revoked without nuking the whole KeyUser (revoke_user) or
conflating with future expiry cleanup (deletedAt).
Nullable DateTime — existing rows default to NULL (active), no
data migration needed.
refs: #11