refactor: relocate QuotaTracker DO behind capability entrypoints - #102
Merged
Conversation
… entrypoints
Move the QuotaTracker Durable Object from gateway-worker to agent-worker,
where its consumers (run admission, Composio metering, accrual flush) live.
Cross-worker access now goes through capability-scoped WorkerEntrypoints with
strict-Zod ctx.props validation instead of full cross-script DO namespaces:
- agent-worker: local QUOTA_TRACKER binding + appended new_sqlite_classes v3;
GatewayQuotaEntrypoint {history, peek, setLimit} and QuotaDeletionEntrypoint
{deleteAllState} exported as named entrypoints
- gateway-worker: class export + local binding removed; service binding to
GatewayQuotaEntrypoint; appended deleted_classes v4 (fires only after all
bindings/code refs are gone)
- webhooks-worker: cross-script DO binding replaced by QUOTA_DELETION service
binding scoped to deleteAllState only
- dependency-cruiser: quota-runtime imports restricted to the agent DO shell
(rule proven firing via probe)
Net privilege reduction: gateway/webhooks previously held full 8-method DO
namespaces; each is now confined to its exact baseline call set. All 503
service_maintenance_unavailable translations preserved at every call site.
Deploy is roll-forward-only per the approved rev-5.2 sequence (agent ->
webhooks -> gateway). Pre-cutover quota counters are discarded at the gateway
deploy; sanctioned pre-launch with disposable data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes A2 Phase 2 of the architecture-hardening plan: the QuotaTracker Durable Object moves from gateway-worker to agent-worker, where its consumers (run admission, Composio metering, accrual flush) actually live. Cross-worker access is no longer a full cross-script
DurableObjectNamespace— it goes through capability-scopedWorkerEntrypoints with strict-Zodctx.propsvalidation, matching the existingAgentLifecycleEntrypoint/ResourceDeletionEntrypointhouse pattern.QUOTA_TRACKERnamespace (owner); appendednew_sqlite_classesv3GatewayQuotaEntrypointservice binding scoped to{history, peek, setLimit}; class export/binding removed; appendeddeleted_classesv4QuotaDeletionEntrypointservice binding scoped to{deleteAllState}onlyThis is a net privilege reduction: gateway and webhooks previously could invoke every RPC method (including
deleteAllStateandtryConsume) against any user's DO; each is now confined to its exact baseline call set.QuotaTrackerRpcin@cheatcode/typesremains the single contract — both entrypoint binding types are derived projections, zero hand-written method signatures. The dependency-cruiser guard restricting@cheatcode/billing/quota-runtimeimports to the agent DO shell was proven firing via an executed probe.All call-site semantics are preserved byte-for-byte, including every
503 service_maintenance_unavailabletranslation (entrypoint methods deliberately don't catch, so DO exceptions propagate into the callers' existing handlers).Deploy plan — roll-forward only
The standard
deploy-cloudflare.ymlsequential loop (agent → webhooks → preview-proxy → gateway) is exactly the approved rev-5.2 cutover order. Each old deployment's bindings stay valid until its own deploy; the webhooks deploy requires the agent deploy first (entrypoint must exist for deploy-time validation);deleted_classesfires last, after every binding/code reference to the gateway-hosted namespace is gone (zeroscript_namebindings remain repo-wide). Re-running the loop is idempotent. If a step fails: fix forward and re-dispatch — never roll back.Quota split-brain window (agent deploy → gateway deploy). From the moment
cheatcode-agentdeploys untilcheatcode-gatewaydeploys, two QuotaTracker namespaces are live simultaneously. agent-worker writes and reads its NEW, initially-empty local namespace, while the still-old gateway and — until its own deploy — the still-old webhooks read and write the OLD gateway-hosted namespace. During the window: (1) run admission uses the NEW namespace, so exhausted users are re-admitted; (2) new consumption meters into the NEW namespace; (3) gateway/usageand/activityread the OLD namespace, so displayed usage appears frozen; (4) gateway limit writes land in the OLD namespace and are discarded at cutover — harmless, both agent call sites re-issuesetLimitbefore every gate, so limits self-heal; (5) an account deleted during the window leaves residue in the NEW namespace (self-limiting: 366-day retention alarm, or re-run the deletion job). At gateway deploy thedeleted_classesmigration destroys the old namespace and both surfaces converge.Pre-cutover quota usage is permanently reset, not just windowed:
deleted_classesdestroys all counters andusage_eventhistory —/activitycharts start empty and/usagetotals restart at 0 for every user. Sanctioned pre-launch (no users, disposable data); Postgres remains authoritative for entitlements.Brief transient at the gateway cutover: old gateway isolates can outlive the migration by seconds and surface 503
service_maintenance_unavailableon/usage//activity/limit-sync. Already caught and retriable at every call site — expected, not a regression.Verification
turbo build --force, deadcode, architecture:check, skills:build — all greenwrangler deploy --dry-runfor all four workers — all passdeploy-cloudflare.ymldispatch, convergence on merge SHA, then topology/authorization/function verification (admission, usage, limits)