Skip to content

[feat] Add Wallets (ongoing and far from properly tested) - #6050

Draft
junaway wants to merge 23 commits into
mainfrom
feat/add-wallets
Draft

[feat] Add Wallets (ongoing and far from properly tested)#6050
junaway wants to merge 23 commits into
mainfrom
feat/add-wallets

Conversation

@junaway

@junaway junaway commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What this adds

A wallet: one balance per organization, computed from immutable rows, that a gateway can spend against and a subscription can fund.

Value arrives as wallet_credits and leaves as wallet_debits. Neither is ever updated. wallet_balances is the mutable projection those two explain, holding one general row per organization plus one row per credit. A charge that spans several credits writes one debit per funding source rather than one debit and an allocation table.

Nothing here is on a user-facing path yet. The gateway that would call it is designed separately, and the only producers wired today are local fakes.

The design lives in docs/design/wallets-research/v1/. Start at README.md; entities.md is canonical for names and schema.

How a charge becomes a balance change

fake LLM/MCP result
  -> streams:measurements -> measurement worker -> measurement + measurement_values (tracing_ee)
  -> streams:debits       -> debit worker       -> one atomic settlement (core_ee)

The caller does not wait for measurement, pricing or settlement. A failed initial XADD produces neither a measurement nor a charge, and that is the only place work is dropped on purpose. Past it, ordinary consumer-group redelivery applies, which is safe because both hops are idempotent.

The gateway decides the amount. The wallet never derives price from tokens, duration or provider data, and the debit command carries no metric, cost or measurement id to tempt it. A test asserts that over the message's field names so the boundary breaks loudly if it is ever widened.

The replay rule, which is the part worth reviewing

One posting may split across several credits. Each resulting debit's debit_key comes from the posting's opaque idempotency key plus that row's actual funding source: its wallet_credit_id, or the literal deficit when no credit covers it. Never a sequence number.

A retry recomputes the same split and derives byte-identical keys, so it collides on UNIQUE (organization_id, debit_key) instead of double-charging. A sequence would produce different keys on the second attempt and charge twice. Settlement locks the organization's general balance row first, which serialises two simultaneous first deliveries of one posting.

Settlement is never refused for insufficient funds. The value was already consumed upstream, so refusing to record it loses money and hides it. The uncovered remainder becomes an explicit deficit debit; the general balance may go below its floor, and a per-credit balance never goes below zero.

Funding

Provisioning, allowances and grants ship with it, so the wallet is not inert:

  • A general balance row is created when an organization is created, and a migration backfills every existing organization.
  • A mid-period plan change writes an immutable debit for the unused pro-rata remainder of the outgoing allowance and mints a new credit for the incoming one. It never rewrites a credit row.
  • A grant catalog awards credit for named activities. Signup is the only entry today, at $1, once per organization, on the signup path only. Adding a referral or a contribution award later is a catalog row, not a code path.

Allowances per period: hobby $0, pro $5, business $50. Floor 0 everywhere, so it is a hard stop. These are product decisions recorded as such, not derived numbers.

Migrations

core_ee gains ee0000000004 (wallet tables) and ee0000000005 (backfill). tracing_ee gains ee0000000002 (measurements). Both chains have a single head.

The planning documents reserved ee0000000006/ee0000000005 for the wallet tables, expecting sandbox-metering Track B and C to take ee0000000004/ee0000000005 first. Those revisions exist only on unmerged draft branches and do not resolve on this base, so the wallet took ee0000000004. The metering drafts renumber past ee0000000005 when they land.

Tests

384 EE unit tests pass. They need nothing running.

Every integration test in this branch is written and has never been run, including the concurrency test that proves competing deliveries cannot overspend one credit. No database has been touched. docs/design/wallets-research/v1/nodes/im-1-02-pipeline/acceptance.md is the local-deployment procedure, and it lists the suites with that one first.

What is not done

  • Nothing calls the wallet from a real request path. No route, no gateway wiring.
  • Pricing is a fixture, wallet-v1-fake-1, charging only when endpoint_kind == "managed".
  • No holds, no admission control, no exposure estimate, no rollups, no Stripe checkout, no recurring period-start issuance.
  • Spend order is (priority, end_time, credit_id). report.md chose expiry-first and this reverses it. Live once a short-lived lot sits at a higher priority number than a long-lived one, which the earning path will introduce. Recorded as open design item 13, along with whether earned value should expire at all.
  • provider_credit was dropped from the credit kinds. It named provider-funded value, which is the funding source behind the project. Cheap to add back before rows exist.

Review notes

Read docs/design/wallets-research/v1/seams.md first if you want the why. The per-node specifications under v1/nodes/ say what each package was allowed to touch, and each merge point has a recorded review.

jp-agenta and others added 23 commits August 13, 2026 15:16
…d four prior efforts

Collects everything written on credits, metering and billing into one branch: the
decision document and its two independent proposals, eight research reports, and
verbatim archives of the activation-credits spike, the sandbox-metering tracks B/C/D,
the shipped meter and plan-catalog work, and the phantom-usage investigation.

seams.md is the only new writing: what each of the four efforts already settled, the
three live meanings of the word credit, the mechanical collisions, and what nobody owns.

The gateway request path is deliberately absent — docs/design/gateways-research/ owns it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…stimate

The gateway owns the request path and enforces; the wallet accounts. seams.md now
states the split as a table and a port — an authorization call before dispatch that
returns a spending ceiling, a usage call after the response that hands over raw
measurement — and names the four things the gateway must carry from day one because
they cannot be added retroactively.

Pricing moves to the wallet with the rate card, which makes model calls, tool calls and
sandbox time three callers of one interface.

Separately: duration estimates removed from every document above prior-work/. Phases
and their ordering stand; the calendar attached to them did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…movement enumerated

Separates meters (periodic allowances), the usage journal (physical measurement) and
the wallet ledger (value with provenance), and states the two functions between them:
pricing journal->ledger, entitlement over meters. Value never lives in a meter; the
sandbox track's monthly-period wallet key is the concrete instance of that error.

Enumerates thirteen inbound kinds and eleven outbound ones, each with its idempotency
key, and separates charges from adjustments. Names the rows so no two systems share a
generic word, and applies the metering track's unit-in-the-name rule to ledger columns.

Two conversions, governed differently: a frozen peg for money in, a versioned price book
for consumption out, so every price change is visible rather than a devaluation. States
the three things a subscription actually does, including the commercial terms it sets on
the wallet. Replaces the hard-stop/allow-negative boolean with a credit line, and defines
balance, available, headroom and runway separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of them

Internal resources are handled separately from external ones but by the same ledger.
The two questions that separate them: what one more unit costs us in cash, and whether
it can be refused before it happens.

Class A, vendor pass-through (models, sandboxes, tools): real per-unit cash, cost
unknown until after, refusable, fail closed, hold required, one run-attributed movement
per call, reconciles to a vendor invoice.

Class B, platform capacity (spans, events, records, evaluations, storage): near-zero
marginal cash, already arrived by the time it is counted, fail open, no hold, one rollup
movement per organization per period read from the meter. One movement per span is not
viable, and that settles the shape. Meters are the journal here, not legacy.

Class C, entitlements (audit, RBAC, SSO, seats, retention): never enters the wallet. The
audit capability is a flag; only the volume of events is ever priced.

Falls out of it: moving traces from Stripe arrears to prepaid touches one periodic job,
not the ingestion path, and the failure-posture question seams.md left unowned is
answered per class rather than per call site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… order at the wave

preflight.md is the graph/specification review waves.md requires before any node forks a
worktree. Blocking: two live vocabularies with the reading order aimed at the superseded
one; no migration chain or head named in any WP while ee0000000004/5 are already claimed
by the sandbox-metering drafts; a wallet check in the checkpoint-1 boundary that no node
builds; and fake LLM/MCP provider paths owned by the gateways wave under its
one-owner-per-file rule.

Also records the verified chain heads including the two parked traps, confirms the stream
transport claim against api/entrypoints/worker_streams.py, and states what holds up so
review does not relitigate it — the replay invariant in particular.

README reading order now leads with the wave documents and names entities.md canonical for
schema and names; mechanics.md carries a superseded-in-part note scoping what of it stands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tail

Closes preflight G2: every node now names its owned paths, its migration chain and
revision, the repo pattern it copies, and the tests that close it — enough for an agent
whose only context is v1/.

Grounded in facts verified against origin/main: the compressed-data producer shape in
api/oss/src/core/events/streaming.py, the StreamConsumer lifecycle in
api/oss/src/tasks/asyncio/shared/consumer.py, the ALL_STREAMS/selector/builder registration
in api/entrypoints/worker_streams.py, TransactionsEngine vs AnalyticsEngine, the EE
migration file shape, and the four live chains plus the two parked traps.

Tasks realigned onto the seed vocabulary — MeasurementCommandV1, DebitCommandV1,
WalletCheckPort, WalletSettlementPort, measurement_values — so specs and tasks no longer
carry two names for one thing. The seed's DebitWorker shell is what lets WP-1-02 own every
worker_streams.py edit while WP-1-03 supplies only a body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds MeasurementCommandV1/DebitCommandV1 (version 1) with compressed-JSON
serializers, WalletCheckPort/WalletSettlementPort (bodies raise
NotImplementedError), a terminal/retryable error taxonomy, an unimplemented
settlement-port factory, and a constructible DebitWorker shell whose
process_batch raises NotImplementedError. This is the reviewed seed commit
WP-1-01/WP-1-02/WP-1-03 fork from; no DB, Redis client, route, or
worker_streams.py change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WP-1-01 and WP-1-02 fork from this commit; recorded per im-1-00-seed/tasks.md
step 6 after a full checklist review passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…1 stream registration

Adds WP-1-02: the wallet-owned fake gateway measurement chain.

- tracing_ee migration ee0000000002 (down_revision ee0000000001): measurements
  + measurement_values tables, no organization/workspace column, no wallet-debit FK.
- api/ee/src/dbs/postgres/measurements/: AnalyticsEngine-backed DAO, idempotent
  parent+children insert in one tracing transaction, explicit component->row mapping.
- api/ee/src/core/measurements/: DAO/org-resolver ports, Wave 1 fixture pricing.
- api/ee/src/tasks/asyncio/measurements/: StreamConsumer worker — validate, resolve
  org, persist, price, publish DebitCommandV1; terminal-ACK malformed/unsupported,
  pending on tracing/publish failure.
- Extends the seed's ee/src/core/wallets/streaming.py with concrete best-effort
  RedisMeasurementPublisher/RedisDebitPublisher (unchanged seed envelope types).
- api/entrypoints/worker_streams.py: registers measurements/debits (EE-only,
  excluded from ALL_STREAMS in OSS builds) and wires the seeded DebitWorker shell
  through the seeded runtime factory.
- Wallet-owned deterministic LLM/MCP fakes under
  api/ee/tests/pytest/acceptance/wallets/fakes/ (not under core/gateways/*/providers/fake/).
- Unit tests (24, passing) and integration tests (written, not run — no
  dedicated Redis/tracing-Postgres deployment for this task).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…k, atomic settle

Implements WP-1-01: migration ee0000000004 (wallet_credits, wallet_debits,
wallet_balances), the Postgres DAO/mappings, and the concrete WalletsService
adapter for WalletCheckPort/WalletSettlementPort. settle() locks the
organization's general balance first, replays idempotently by
idempotency_key, and derives every debit_key from the posting key plus its
actual funding source (never a sequence). plan_settlement is a pure function
so the eligibility/ordering/split-funding/deficit algorithm is unit-testable
without Postgres.

Deviates from specs.md: uses ee0000000004 (down_revision ee0000000003), not
the documented ee0000000006/ee0000000005 — those revisions only exist on
unmerged draft branches; ee0000000003 is this base's actual core_ee head.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fills in the seeded DebitWorker.process_batch: deserializes DebitCommandV1,
calls WalletSettlementPort.settle(command) with the idempotency_key passed
through unmodified, and ACKs only after settlement succeeds. Terminal
envelope errors ACK without retry; settlement failures (including a
duplicate-delivery replay, which settle() already treats as a no-op) leave
the entry pending or ACK per the measurement worker's exact contract.

Adds FakeWalletSettlementPort and unit/integration coverage; updates the
now-obsolete raising-shell assertion in test_wallets_ports_and_worker.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the exact env/run.sh selectors, alembic revisions, fake LLM/MCP
trigger scripts, SQL proofs for one measurement/debit/balance-change and
for idempotent replay, and the individual integration pytest commands
(concurrency test called out first) for the pipeline this node fans in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corrects the migration ids the design docs claimed (ee0000000006/ee0000000005
were never reachable on this base; the wave shipped ee0000000004 as an
approved deviation) across entities.md, wave-1.md, preflight.md, and the
WP-1-01/IM-1-01 node docs. Records the delivered stream names, consumer
groups, and MAXLEN in entities.md and wave-1.md. Marks the "two stream
contracts" open-design item decided now that WP-1-00's seed contract shipped
unchanged through WP-1-01/02/03.

Fixes acceptance.md's env-file bug: run.sh resolves a bare --env-file name
relative to hosting/docker-compose/ee/, not the repo root, so the cp target
and load-env call now match; switches to the --ee/--dev aliases used
elsewhere in the repo's docs. Adds a HANDOFF section carrying forward the
three items IM-1-02 conditioned its approval on (poison-message wallet
provisioning gap, unread WalletCheckPort.check amount_musd, per-call thread
pool in WalletsService._run_blocking) plus the not-yet-run integration
suites, concurrency test first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nge proration

Closes the three IM-1-02 handoff items:

(i)  Organization creation now provisions the general wallet_balances row
     (WalletsService.provision_general_balance, idempotent on the partial
     unique index uq_wallet_balances_org_general), and a new backfill
     migration (ee0000000006) covers pre-existing organizations.
(ii) WalletCheckPort.check drops its unread amount_musd parameter — a
     pre-dispatch check cannot know the eventual cost, so a signature
     advertising one was misleading.
(iii) WalletCheckPort.check/WalletsService.check are now async end to end;
     the per-call ThreadPoolExecutor/asyncio.run bridge (_run_blocking) is
     deleted.

Also adds subscription plan-change proration (WalletsService.apply_plan_change):
on a mid-period plan change, prorate the outgoing plan's unused allowance
into an immutable adjustment debit against its credit, mint a new credit for
the incoming plan's prorated share, and update the general balance's floor —
all in one replay-safe transaction guarded by a new wallet_plan_changes
idempotency ledger (migration ee0000000005). The plan->allowance and
plan->floor mappings are undefined product decisions and return 0 for every
plan today (ee.src.core.wallets.plans); the machinery exercises the same
code path regardless, ready for a real mapping later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…backfill, key/wording cleanup

Four fixes to the WP-1-04 provisioning/plan-change commit:

1. Delete the invented `wallet_plan_changes` idempotency ledger entirely (table,
   DBA/DBE, migration, integration test). apply_plan_change's replay guard now
   reads the actual financial rows a prior application would have written — the
   same (organization_id, idempotency_key) lookup against wallet_debits that
   settle() already uses for the outgoing side, plus the wallet_credits row's
   existing plan_change_idempotency_key data reference for the incoming side —
   no second guard. A zero-value change now simply writes nothing.
   ee0000000006 (backfill) renumbers to ee0000000005, down_revision ee0000000004;
   the core_ee chain is a single linear chain with one head, ee0000000005.

2. The backfill migration minted ids with gen_random_uuid() (uuid4). Alembic has
   no equivalent of Column(default=uuid.uuid7) and there is no pure-SQL uuidv7
   generator on this repo's Postgres version, so ids are now minted in Python
   with uuid_utils.compat.uuid7 (this repo's convention) and passed down as
   literal values, one INSERT per organization instead of INSERT...SELECT.

3. Spelled out "organization" in the wave's own comments/log text that
   abbreviated it to "org" (service.py, fakes.py, and SQL bind params /dict
   keys in the wave's own integration tests). Pre-existing code outside this
   wave is left untouched.

4. Simplified the plan-change idempotency key from the stringly-typed
   plan_change:{org}:{event}:{subscription_id}:{plan}:{anchor} to
   plan_change:{subscription_id}:{period_start}, following the measurement
   worker's measurement:{measurement_id} shape — one prefix, one identifier
   pair. period_start is the internal, already-computed identifier of which
   billing period this change lands in; plan name and the raw anchor
   day-of-month are not identifiers and no longer appear in the key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces plans.py's constant-zero placeholder with product-decided per-plan
allowance ($0/$5/$50/$50/$0 for hobby/pro/business/agenta_ai/self-hosted) and
floor (0 everywhere) mappings, so plan-change proration now moves real value.

Adds ee.src.core.wallets.grants: a catalog of named activities that award
wallet credit outside the plan-change path, seeded with one entry (signup,
$1, once per organization, twelve-month expiry). WalletsService.award() and
WalletsDAO.award_credit() are the idempotent entry point, keyed
award:{activity}:organization:{organization_id}. Wired into
provision_signup_subscription only (never the explicit-organization-creation
path), after the general balance row exists.

No new migration — core_ee head stays ee0000000005.
… kinds

A signup grant and a contribution award were both recorded as credit_kind
"award", making provenance unrecoverable from the row alone.
GENERAL_CREDIT_KINDS now carries the eight delivered mechanics.md §4 kinds
(signup_grant, plan_allowance, purchase, promotion, contribution_award,
referral_bonus, goodwill, correction), with the five deferred kinds named in
a comment so nobody re-invents them. The grant catalog's signup entry now
mints signup_grant instead of award; plan changes already minted
plan_allowance. credit_kind is a plain text column (no CHECK, no Postgres
enum), so no migration is needed.
…dit kinds

The eight delivered kinds replaced a set that also held adjustment and provider_credit.
adjustment stays a debit_kind, where it is actually used; a positive adjustment is
correction or goodwill, which say why. provider_credit is not a naming variant of any of
the eight — it named provider-funded value, the funding source behind this project — so
its removal is recorded as an open, cheap-to-close question rather than a silent drop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 14, 2026 17:53
@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Blocked Blocked Aug 14, 2026 5:53pm

Request Review

@junaway

junaway commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

WIP -- @mahmoud

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 6918e3d9-50ac-49f2-a252-f57dc7558648

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces the initial “wallets v1” foundation (EE-only) with Redis-stream-driven measurement → debit → settlement plumbing, plus a substantial set of design/workflow documentation for delivering the work in waves/work-packages.

Changes:

  • Add wallet stream contracts (streams:measurements, streams:debits), workers, and fixture pricing to drive an end-to-end pipeline.
  • Add core wallet schema/DAO/service wiring hooks (org provisioning + subscription plan-change proration) and accompanying unit/integration tests.
  • Add/organize wallets research + execution workflow docs under docs/design/wallets-research/v1/.

Reviewed changes

Copilot reviewed 115 out of 160 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/design/wallets-research/v1/wps-1.md Wave 1 work package graph summary
docs/design/wallets-research/v1/waves.md Wave/checkpoint workflow + node types
docs/design/wallets-research/v1/README.md Wallets v1 reading order + provenance
docs/design/wallets-research/v1/prior-work/track-d-byos/tasks.md Archived Track D task list
docs/design/wallets-research/v1/prior-work/track-d-byos/specs.md Archived Track D spec
docs/design/wallets-research/v1/prior-work/track-c-billing/tasks.md Archived Track C task list
docs/design/wallets-research/v1/prior-work/track-b-metering/tasks.md Archived Track B task list
docs/design/wallets-research/v1/prior-work/track-b-metering/specs.md Archived Track B spec
docs/design/wallets-research/v1/prior-work/sandbox-metering/NAMING.md Archived sandbox naming notes
docs/design/wallets-research/v1/prior-work/gateway-spike/openrouter-api-facts.md Archived gateway API facts
docs/design/wallets-research/v1/prior-work/billing-phantom-usage/status.md Archived incident status
docs/design/wallets-research/v1/prior-work/billing-phantom-usage/research.md Archived incident analysis
docs/design/wallets-research/v1/prior-work/billing-phantom-usage/README.md Archived incident index
docs/design/wallets-research/v1/prior-work/billing-phantom-usage/context.md Archived billing context
docs/design/wallets-research/v1/prior-work/activation-credits/rfc.md Archived activation credits RFC
docs/design/wallets-research/v1/prior-work/activation-credits/context.md Archived activation credits context
docs/design/wallets-research/v1/out-of-scope.md Deferred wallet scope register
docs/design/wallets-research/v1/nodes/wp-1-03-debit-worker/tasks.md WP-1-03 worker task spec
docs/design/wallets-research/v1/nodes/wp-1-03-debit-worker/specs.md WP-1-03 worker boundary/spec
docs/design/wallets-research/v1/nodes/wp-1-02-measurements/tasks.md WP-1-02 measurement chain tasks
docs/design/wallets-research/v1/nodes/wp-1-02-measurements/specs.md WP-1-02 measurement chain spec
docs/design/wallets-research/v1/nodes/wp-1-01-core-wallet/tasks.md WP-1-01 core wallet tasks
docs/design/wallets-research/v1/nodes/wp-1-01-core-wallet/specs.md WP-1-01 core wallet spec
docs/design/wallets-research/v1/nodes/wp-1-00-contracts/tasks.md WP-1-00 contracts tasks
docs/design/wallets-research/v1/nodes/wp-1-00-contracts/specs.md WP-1-00 contracts spec
docs/design/wallets-research/v1/nodes/im-1-02-pipeline/tasks.md IM-1-02 pipeline checklist
docs/design/wallets-research/v1/nodes/im-1-02-pipeline/specs.md IM-1-02 merge criteria
docs/design/wallets-research/v1/nodes/im-1-01-foundations/tasks.md IM-1-01 fan-in checklist
docs/design/wallets-research/v1/nodes/im-1-01-foundations/specs.md IM-1-01 merge criteria
docs/design/wallets-research/v1/nodes/im-1-00-seed/tasks.md IM-1-00 seed review steps
docs/design/wallets-research/v1/nodes/im-1-00-seed/specs.md IM-1-00 seed merge criteria
docs/design/wallets-research/v1/nodes/cu-1-01-finalize/tasks.md CU-1-01 cleanup tasks
docs/design/wallets-research/v1/nodes/cu-1-01-finalize/specs.md CU-1-01 cleanup scope
docs/design/wallets-research/v1/ims-1.md Wave 1 intermediate merge map
docs/design/wallets-research/v1/cus-1.md Wave 1 cleanup node summary
docs/design/wallets-research/README.md Wallets design folder index
api/entrypoints/worker_streams.py Register EE measurement/debit workers + stream selection
api/ee/tests/pytest/utils/wallets/builders.py Wallet/stream DTO test builders
api/ee/tests/pytest/utils/wallets/init.py Test utils package marker
api/ee/tests/pytest/utils/measurements/fakes.py In-memory fakes for measurement worker ports
api/ee/tests/pytest/utils/measurements/init.py Measurements test utils marker
api/ee/tests/pytest/unit/wallets/test_wallets_streaming.py Serializer/version tests for wallet streams
api/ee/tests/pytest/unit/wallets/test_wallets_service.py Wallet service unit tests
api/ee/tests/pytest/unit/wallets/test_wallets_ports_and_worker.py Port/runtime factory + worker constructibility tests
api/ee/tests/pytest/unit/wallets/test_wallets_plan_change_wiring.py Subscription→wallet plan-change hook wiring tests
api/ee/tests/pytest/unit/wallets/test_wallets_contracts.py Contract validation + boundary tests
api/ee/tests/pytest/unit/wallets/init.py Wallet unit tests marker
api/ee/tests/pytest/unit/measurements/test_measurements_serializer.py Measurement/debit serializer use tests
api/ee/tests/pytest/unit/measurements/test_measurements_pricing.py Fixture pricing behavior tests
api/ee/tests/pytest/unit/measurements/test_measurements_fakes.py Wallet-owned fake LLM/MCP tests
api/ee/tests/pytest/unit/measurements/test_measurements_dao_contract.py Measurement DAO idempotency/atomicity contract tests
api/ee/tests/pytest/unit/measurements/init.py Measurements unit tests marker
api/ee/tests/pytest/integration/wallets/test_wallets_settlement_concurrency_postgres.py Postgres concurrency settlement invariant test
api/ee/tests/pytest/integration/wallets/test_wallets_debit_worker_integration.py Redis+Postgres debit worker replay integration test
api/ee/tests/pytest/integration/wallets/conftest.py Skip logic when core Postgres unreachable
api/ee/tests/pytest/integration/wallets/init.py Wallet integration tests marker
api/ee/tests/pytest/integration/measurements/conftest.py Skip logic for tracing Postgres + durable Redis
api/ee/tests/pytest/integration/measurements/init.py Measurements integration tests marker
api/ee/tests/pytest/acceptance/wallets/fakes/results.py Shared fake result DTO
api/ee/tests/pytest/acceptance/wallets/fakes/mcp.py Deterministic fake MCP producer
api/ee/tests/pytest/acceptance/wallets/fakes/llm.py Deterministic fake LLM producer
api/ee/tests/pytest/acceptance/wallets/fakes/init.py Acceptance fakes package marker
api/ee/tests/pytest/acceptance/wallets/init.py Acceptance wallets marker
api/ee/src/tasks/asyncio/wallets/worker.py Debit stream consumer worker
api/ee/src/tasks/asyncio/wallets/init.py Wallets tasks package marker
api/ee/src/tasks/asyncio/measurements/worker.py Measurement stream consumer worker
api/ee/src/tasks/asyncio/measurements/init.py Measurements tasks package marker
api/ee/src/tasks/asyncio/init.py Tasks asyncio package marker
api/ee/src/tasks/init.py EE tasks package marker
api/ee/src/dbs/postgres/wallets/mappings.py Wallet DBE↔DTO mappings
api/ee/src/dbs/postgres/wallets/dbes.py Wallet SQLAlchemy entities + indexes
api/ee/src/dbs/postgres/wallets/dbas.py Wallet column mixins
api/ee/src/dbs/postgres/wallets/init.py Wallet DB package marker
api/ee/src/dbs/postgres/measurements/organization.py Project→organization resolver for worker
api/ee/src/dbs/postgres/measurements/mappings.py Measurement command→row mapping
api/ee/src/dbs/postgres/measurements/dbes.py Measurement SQLAlchemy entities
api/ee/src/dbs/postgres/measurements/dao.py Tracing DB measurement DAO
api/ee/src/dbs/postgres/measurements/init.py Measurements DB package marker
api/ee/src/core/wallets/streaming.py Stream serializers + Redis publishers
api/ee/src/core/wallets/service.py Wallet service adapter (check/settle/provision/award)
api/ee/src/core/wallets/runtime.py Wallet service/settlement singleton factory
api/ee/src/core/wallets/proration.py Plan-change proration + billing bounds helpers
api/ee/src/core/wallets/plans.py Plan→allowance/floor mapping constants
api/ee/src/core/wallets/interfaces.py Wallet port contracts
api/ee/src/core/wallets/grants.py Grant catalog + idempotency key logic
api/ee/src/core/wallets/errors.py Wallet stream error taxonomy
api/ee/src/core/wallets/contracts.py Pydantic stream contracts for measurement/debit
api/ee/src/core/wallets/init.py Public wallet contract import surface
api/ee/src/core/subscriptions/service.py Call wallet plan-change hook on subscription changes
api/ee/src/core/organizations/service.py Provision wallet general balance + award signup grant
api/ee/src/core/measurements/pricing.py Wave 1 fixture pricing rules
api/ee/src/core/measurements/interfaces.py Measurement persistence + org-resolution ports
api/ee/src/core/measurements/dtos.py Measurement persistence result DTOs
api/ee/src/core/measurements/init.py Measurements core package marker
api/ee/databases/postgres/migrations/tracing_ee/versions/ee0000000002_add_measurements.py Tracing DB measurements schema migration
api/ee/databases/postgres/migrations/core_ee/versions/ee0000000005_backfill_wallet_general_balances.py Backfill general wallet balances migration

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +70 to +87
for msg_id, data in batch:
try:
command = deserialize_debit_command(payload=data[b"data"])
except WalletTerminalError as e:
log.error(
"[WALLETS] Terminal envelope error, ACKing without retry",
msg_id=repr(msg_id),
error=str(e),
)
processed_ids.append(msg_id)
continue
except Exception:
log.error(
"[WALLETS] Unexpected deserialization error, leaving pending",
msg_id=repr(msg_id),
exc_info=True,
)
continue
Comment on lines +131 to +148
for msg_id, data in batch:
try:
command = deserialize_measurement_command(payload=data[b"data"])
except WalletTerminalError as e:
log.error(
"[MEASUREMENTS] Terminal envelope error, ACKing without retry",
msg_id=repr(msg_id),
error=str(e),
)
processed_ids.append(msg_id)
continue
except Exception:
log.error(
"[MEASUREMENTS] Unexpected deserialization error, leaving pending",
msg_id=repr(msg_id),
exc_info=True,
)
continue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants