Skip to content

feat!: color of money — segregated balances per (account, asset, color)#1438

Open
gfyrag wants to merge 30 commits into
release/v3.0from
feat/color-of-money
Open

feat!: color of money — segregated balances per (account, asset, color)#1438
gfyrag wants to merge 30 commits into
release/v3.0from
feat/color-of-money

Conversation

@gfyrag

@gfyrag gfyrag commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces strict balance segregation by color as a first-class
dimension of the ledger. A balance lives at (account, asset, color);
postings carry an explicit color field. The empty color is the
uncolored bucket and is itself fully segregated from every colored
bucket.

Companion PR upstream: formancehq/numscript#139Posting.Color exposed in numscript output, BalanceQuery refactored to carry color as a separate dimension instead of an asset suffix. The ledger pins that branch via a local replace in go.mod for now; bump to a tagged release once #139 is merged.

Invariants

  1. Color is immutable. Once funds carry a color, no operation
    changes it. Re-coloring is a deliberate composition (clearing
    account + mint from @world), not a primitive.
  2. Empty color is a bucket. Color == "" participates in
    segregation just like any colored bucket. A draw under color X
    cannot dip into uncolored funds, and vice versa.
  3. ^[A-Z]*$ color charset, enforced both upstream (numscript) and
    at the ledger admission boundary. The empty string remains valid.
  4. Double-entry per bucket. For every (asset, color), the sum of
    all account balances is zero. Each bucket is its own conservation
    universe; the scenariotest invariant check is updated accordingly.

Wire shapes

  • Posting.color exposed everywhere postings appear.
  • Account.volumes becomes repeated AccountVolume{asset, color, volumes}
    sorted by (asset, color) ascending — deterministic JSON.
  • PostCommitVolumes inner shape becomes a sorted list of
    VolumeEntry{asset, color, volumes}.
  • AggregatedVolume.color is set on every entry; the default response
    is one entry per (asset, color) tuple.

For consumers that don't care about color, two opt-in flags let the
server collapse colors into a single per-asset entry:

  • GET /{ledger}/accounts/{address}?collapseColors=true
  • GET /{ledger}/volumes?collapseColors=true (or
    AggregateVolumesRequest.collapse_colors = true)

Storage layout

domain.VolumeKey carries Color string. Canonical bytes:

```
[ledgerID BE 4B] [account] \x00 [color] \x00 [asset_base] [precision 1B]
```

Color sits between account and asset so prefix scans behave naturally:
`[ledgerID][account]\x00` still returns every (color, asset) for the
account; `[ledgerID][account]\x00[color]\x00` is a fixed-prefix lookup
for a specific bucket. The precision byte stays trailing where it can
legitimately be `0x00` (e.g. "EUR") without encoding ambiguity.

v2 compat / mirror

Untouched by design. The v2 protocol has no color dimension, so every
mirrored posting lands in the uncolored bucket. The MirrorIngest
preload branch explicitly passes color = "" with a comment that
makes the intent visible at the call site.

Numscript integration

The numscript interpreter (PR #139) emits postings with their Color
field set. The ledger's numscriptStoreAdapter honors the new
AssetColor items in BalanceQuery and returns Balances keyed by
map[account][asset][color] -> *big.Int. No asset-suffix encoding
remains in either repo.

Tests

  • New tests/e2e/business/color_segregation_test.go — full
    end-to-end gRPC test covering GetAccount segregation, collapse,
    draw refusal across colors, draining a single bucket, color visible
    on the emitted posting.
  • New internal/application/ctrl/store_color_test.go covering
    assembleAccount segregation by default + collapseColors mode.
  • New aggregator color tests (internal/query/aggregate_test.go):
    segregation, collapse, collapse + max_precision, grouped aggregator.
  • New VolumeKey byte-format and round-trip tests for the colored
    layout in internal/domain/keys_test.go.
  • 30+ existing E2E/scenario tests migrated from
    acct.GetVolumes()["USD"] map access to acct.FindVolume("USD", "").
  • 57 unit-test packages green; e2e and scenario builds compile clean.

Test plan

  • CI green
  • Numscript PR Simple batch #139 reviewed and merged, this branch retargeted to a
    tagged numscript release (drop the replace directive)
  • Manual smoke via `ledgerctl transactions create --posting "world,alice,100,USD/2,GRANTS"`
    and `ledgerctl accounts get alice` showing the COLOR column

🤖 Generated with Claude Code


Migrated from formancehq/ledger-v3-poc#234 on 2026-06-29.

@gfyrag gfyrag requested a review from Azorlogh as a code owner June 29, 2026 14:33
@gfyrag gfyrag requested a review from ascandone June 29, 2026 14:33
@NumaryBot

NumaryBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

✅ Approve — automated review

All previously identified blocker and major findings have been resolved at the current head, as confirmed by both the prior automated review and the inline thread resolutions. The prepared-query aggregate empty-color omission was fixed via a DTO path; Numscript insufficient-funds color context is handled with a ColorKnown flag; transient-account non-zero failures now preserve color in audit metadata; OpenAPI required lists and maxLength:32 for color fields were added; ErrAggregateOverflow was relocated to the query layer; and test error assertions were corrected. The only remaining item is a documentation-only nit about stale storage docs describing the old volume-key layout, which was already noted in the prior discussion as low-severity and non-blocking. No new actionable correctness or compile-break issues are confirmed at the current head. The latest review pass also found no new issues beyond the already-tracked doc nit.

Findings outside the diff

[nit] Storage docs still describe old volume-key layoutdocs/technical/architecture/subsystems/storage/storage-drivers.md (codex, gfyrag)

The storage drivers documentation still documents the volume key prefix as [ledgerID BE 4B] in at least two places, conflicting with the current VolumeKey.AppendBytes layout which uses [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision]. There are also stale adjacent comments in domain validation/errors code. While doc-only, this can mislead storage debugging or migration work.

Suggestion: Update all references in the storage docs and inline code comments to reflect the current key layout: [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision].

@NumaryBot NumaryBot 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.

NumaryBot posted 1 new inline finding.

Summary: #1438 (comment)

Comment thread internal/domain/keys.go
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • main

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97674850-da46-4500-babb-1f517a022993

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/color-of-money

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.

@NumaryBot NumaryBot 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.

NumaryBot posted 1 new inline finding.

Summary: #1438 (comment)

Comment thread internal/domain/keys.go
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.37083% with 106 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.98%. Comparing base (d9fe9da) to head (2b24b5c).
⚠️ Report is 3 commits behind head on release/v3.0.

Files with missing lines Patch % Lines
internal/query/aggregate.go 63.88% 13 Missing and 13 partials ⚠️
internal/query/compact_account.go 41.66% 10 Missing and 4 partials ⚠️
...nal/application/indexbuilder/protowire_postings.go 38.09% 12 Missing and 1 partial ⚠️
internal/domain/errors.go 73.68% 10 Missing ⚠️
...main/processing/processor_transaction_numscript.go 77.14% 7 Missing and 1 partial ⚠️
internal/proto/commonpb/volumes.go 75.00% 4 Missing and 2 partials ⚠️
internal/domain/keys.go 66.66% 2 Missing and 2 partials ⚠️
internal/domain/processing/numscript/errors.go 0.00% 4 Missing ⚠️
...nternal/domain/processing/processor_transaction.go 33.33% 2 Missing and 2 partials ⚠️
internal/domain/processing/processor_posting.go 83.33% 3 Missing ⚠️
... and 7 more
Additional details and impacted files
@@               Coverage Diff                @@
##           release/v3.0    #1438      +/-   ##
================================================
+ Coverage         74.78%   74.98%   +0.19%     
================================================
  Files               430      432       +2     
  Lines             45722    45922     +200     
================================================
+ Hits              34194    34433     +239     
+ Misses             8475     8425      -50     
- Partials           3053     3064      +11     
Flag Coverage Δ
e2e 74.98% <81.37%> (+0.19%) ⬆️
scenario 74.98% <81.37%> (+0.19%) ⬆️
unit 74.98% <81.37%> (+0.19%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gfyrag gfyrag requested a review from paul-nicolas as a code owner July 9, 2026 11:34

@gfyrag gfyrag left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reviewed the post-rebase head cbd8a1f2793d7c62059e3307a267a92e64ff986c in detail.

No blocking findings from this pass.

Scope checked:

  • FSM volume path: direct postings, Numscript postings, reverts, post-commit volumes, sentinel checks.
  • Admission/preload coverage: direct postings, revert original postings, script-discovered dependencies, mirror ingest uncolored path.
  • Projection/checker invariants: AppliedProposal.TransientVolumes, LedgerLog.PurgedVolumes, TouchedVolumeSet, replay-derived exclusions, compareVolumes all carry (account, asset, color).
  • Index builder/backfill: protobuf fast-path parsing for Posting.color and TouchedVolume.color, exclusion matching, account-by-asset/address mapping skips.
  • Read/API surfaces: GetAccount, aggregate volumes, REST JSON shapes, gRPC client/server propagation, CLI rendering/parsing.
  • Event/analysis surfaces: analytical sink payloads, ClickHouse typed JSON schema, transaction-flow grouping by color.

Validation run from the PR worktree:

  • GOCACHE=/tmp/go-build-pr1438 go test ./internal/domain/... ./internal/query/... ./internal/application/ctrl/... ./internal/application/admission/...
  • GOCACHE=/tmp/go-build-pr1438 go test ./internal/adapter/grpc/... ./internal/adapter/http/... ./internal/application/events/... ./internal/application/indexbuilder/...
  • GOROOT= GOCACHE=/tmp/go-build-pr1438 go build ./...
  • git diff --check origin/release/v3.0...HEAD

All passed. I did not run e2e/feature-tag suites in this pass.

@NumaryBot NumaryBot 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.

NumaryBot posted 2 new inline findings.

Summary: #1438 (comment)

Comment thread openapi.yml
Comment thread docs/technical/contributing/api-comparison.md

@NumaryBot NumaryBot 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.

NumaryBot posted 3 new inline findings.

Summary: #1438 (comment)

Comment thread openapi.yml
Comment thread docs/technical/contributing/api-comparison.md
Comment thread docs/technical/architecture/data-model/color-of-money.md Outdated

@gfyrag gfyrag left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reviewed the new head 071df328918b24951ec4420343390d7b8cd3c728 after the release merge. I filtered out the merged base PRs and rechecked the color-of-money surface.

Two non-blocking but real contract/docs issues remain:

  1. openapi.yml documents PostingRequest.color, AggregatedVolume.color, NormalizedPosting.color, etc., but components.schemas.PostingResponse still only has source, destination, amount, and asset. The HTTP transaction response serializers now emit posting colors, so generated clients using this schema can miss/drop the response field.

  2. docs/technical/contributing/api-comparison.md says direct postings accept the new field through a --color CLI flag. There is no such flag in cmd/ledgerctl/transactions/create.go; the implemented CLI contract is --posting "source,destination,amount,asset[,color]".

Checked:

  • go test ./internal/domain/... ./internal/query/... ./internal/application/ctrl/... ./internal/application/admission/...
  • git diff --check origin/release/v3.0...origin/pr/1438

@Azorlogh

Copy link
Copy Markdown
Contributor

A few non-blocking cleanups from a review — none block merge:

1. commonpb.NewEmptyVolumes + NewVolumesInt64 (internal/proto/commonpb/volumes.go:49,54) are now dead — their only callers were the deleted AddInput/AddOutput/Merge. Removable.

2. errCatchAllAssetNotSupported.Kind() (internal/domain/processing/numscript/errors.go:54) is never consulted — domain.Kind() only honors the unexported kindOverride(), and the sibling errMetaNotSupported omits it. Removable.

4. The (asset, color) sort comparator is copy-pasted 3× (volumes.go:114, internal/application/ctrl/store.go:127, aggregate.go sortAggregatedVolumes) — could collapse into a generic lessByAssetColor[T] in commonpb.

5. buildAccountVolumes (store.go:112-116) accumulates running sums as decimal strings and re-parses with SetString per entry — accumulating into *big.Int and formatting once would avoid the round-trip (only matters under collapseColors).

6. handlers_get_account.go:37 parses collapseColors inline as == "true" while the volumes handler uses queryParamBool — same behavior, just use the helper.

All non-blocking — flagging for a cleanup pass, not gating merge.

@gfyrag gfyrag force-pushed the feat/color-of-money branch from 071df32 to 7281403 Compare July 10, 2026 11:48

@NumaryBot NumaryBot 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.

NumaryBot posted 2 new inline findings.

Summary: #1438 (comment)

Comment thread docs/technical/contributing/api-comparison.md
Comment thread docs/technical/architecture/data-model/color-of-money.md Outdated

@NumaryBot NumaryBot 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.

NumaryBot posted 3 new inline findings.

Summary: #1438 (comment)

Comment thread openapi.yml
Comment thread docs/technical/contributing/api-comparison.md
Comment thread docs/technical/architecture/data-model/color-of-money.md
@gfyrag

gfyrag commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Review pass on f29fb75fc9b9c148ab7fc2c47dbbff2b99a56246.

Findings:

  1. Minor: OpenAPI still models always-emitted color fields as optional. PostingResponse.color is present now, but it is missing from the required list while the description says “Always present”. Same issue for AggregatedVolume.color; NormalizedPosting has no required list despite documenting color as always emitted. Please add color to the relevant required lists so generated client types match the wire contract.

  2. Minor: transient-account non-zero failures still erase the color bucket from the error/audit metadata. ValidateTransientVolumes detects offenders as VolumeKeys, but converts them to AccountAssetKey{Account, Asset} with Color left empty, and the renderer prints only account/asset. A transient account with non-zero USD/RED cannot be identified precisely in the audit failure metadata, and multiple colored offenders may collapse into one uncolored-looking entry. Please carry key.Color into the error and render it deterministically.

  3. Nit: one code comment in internal/domain/validation.go still describes the volume-key prefix as [ledgerID], while implementation/docs now use padded ledger name.

I did not re-raise the storage schema bump because it was intentionally dismissed. The CLI --color concern and the architecture-doc ledger-prefix concern look fixed. The latest cleanup comment is still mostly applicable but non-blocking.

Validation run: targeted go test over domain/query/admission/ctrl/http/grpc/events/indexbuilder/receipt/state packages, GOROOT= go build ./..., and git diff --check release/v3.0...HEAD; all passed.

@flemzord flemzord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the current head for color-of-money correctness and public-contract consistency. I found four distinct, non-duplicate issues; none is a ledger-state blocker.

Comment thread internal/domain/errors.go Outdated
Comment thread openapi.yml
Comment thread docs/technical/architecture/data-model/color-of-money.md Outdated
Comment thread docs/technical/architecture/data-model/color-of-money.md
@gfyrag

gfyrag commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on f29fb75fc9b9c148ab7fc2c47dbbff2b99a56246 after the latest review threads.

The latest comments are still valid:

  1. Numscript insufficient-funds errors still lose color context. Direct postings preserve color in ErrInsufficientFunds, but convertNumscriptError builds the error without account/color, so the returned/frozen failure metadata can show color: "" for a colored Numscript spend. Existing E2E only asserts the spend fails, not that the error/audit metadata preserves the color.

  2. OpenAPI color schemas still omit maxLength: 32, while runtime validation rejects longer colors. Please add the limit to request/response color schemas, including posting/volume/normalized posting shapes.

  3. The Numscript contract docs are stale for the pinned dependency: the doc describes nested map shapes, while the module uses BalanceQuery []BalanceQueryItem and Balances []BalanceRow, with color on each item/row.

  4. The new color-of-money architecture page is still not linked from the architecture README.

@gfyrag gfyrag force-pushed the feat/color-of-money branch from f29fb75 to 7003197 Compare July 10, 2026 14:25

@NumaryBot NumaryBot 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.

NumaryBot posted 8 new inline findings.

Summary: #1438 (comment)

Comment thread openapi.yml
Comment thread openapi.yml
Comment thread internal/domain/errors.go Outdated
Comment thread misc/proto/common.proto
Comment thread docs/technical/contributing/api-comparison.md
Comment thread docs/technical/architecture/data-model/color-of-money.md
Comment thread docs/technical/architecture/data-model/color-of-money.md Outdated
Comment thread docs/technical/architecture/data-model/color-of-money.md
gfyrag and others added 8 commits July 10, 2026 16:39
Squashed from 30+ iterations. Color is a first-class dimension on every
balance, posting, audit order, and read-side projection.

- Posting.Color, AccountVolume keyed by (account, asset, color)
- Numscript scripts default to "" color; explicit color via syntax
- Audit orders carry color through transient/purged exclusion sets
- check/ verifies color through excludedVolumesSet, partitionVolumes
- REST/gRPC/JSON always emit color (including uncolored bucket)
- analysis carries color through flow signatures, NUL-separated to
  prevent collisions
- admission validates Posting.Color before preload extraction

Will require manual rebase on top of main (post-master→main migration,
metadata-immutability refactor, AppliedProposal envelopes, etc.).
…signedApplyRequest

- idempotency_failure_test.go used the old map-style Account.Volumes
  lookup; switch to FindVolume("USD", ""). The e2e package fails
  the build under `-tags e2e` otherwise.
- color_segregation_test.go / color_numscript_test.go: master replaced
  the inline `&servicepb.ApplyRequest{Envelopes: UnsignedEnvelopes(...)}`
  builder with the `servicepb.UnsignedApplyRequest("", ...)` helper.
  Migrate every Apply call accordingly.

Addresses NumaryBot review on common.proto:148.
The sink JSON now always emits posting.color (commit 60aef1a18 dropped
omitempty for the proto type, b858501db did the same for sinkPosting).
The ClickHouse table DDL still declared posting columns as
source/destination/amount/asset only, so colored postings would
either be rejected by strict JSON typing or land in untyped/dynamic
storage depending on ClickHouse settings.

Add the color column so warehouse queries can reconstruct the
segregated buckets exactly as the API surfaces them. Databricks is
unaffected — its DDL uses STRING for the whole data column.

Addresses NumaryBot review on sink_data_common.go:73.
…hape

The antithesis workload module replaces ledger via go.mod, so it
consumes our proto directly. The volume API changed from map<string,
*> to repeated entries:

- reads.go accountAssetVolumes: switch to acct.FindVolume(asset, "").
- validate.go postCommitVolume: scan VolumesByAssets.volumes for the
  (asset, color="") tuple.

The workload only ever exercises uncolored postings, so both call
sites match the uncolored bucket explicitly — colored buckets stay
out of scope for this driver model.

Addresses NumaryBot reviews on common.proto:109 and :148.
Master landed several major refactors that intersect color-of-money:
- #573 (EN-1378): admission now emits Declare for absent volume keys
  and Scope.Volumes().Get returns ErrNotFound which readVolumeOrZero
  turns into a fresh zero balance. Drop our explicit ErrBalanceNotPreloaded
  branches around readVolumeOrZero; replace the no-longer-applicable
  TestGetBalances_VolumeNotFound_ReturnsError with the new contract test
  TreatedAsZero. Adapt the numscript adapter accordingly.
- #569: Scope per-attribute trios collapsed into generic Accessor[K,V,R].
  Switch buildPostCommitVolumes to s.Volumes().Get(...).
- #587 (EN-868): business API now mounts under /v3 only. Update the
  collapseColors HTTP test URL accordingly.
- #571 (mockgen helpers): pick up master's expectGetVolume / expectPutVolume
  helpers in tests.
- Fan out the additional cfg/color parameters that processor and
  indexer signatures accumulated across master commits.
Two adjacent NumaryBot findings:

1. ctrl/store.go: buildAccountVolumes did a silent continue on a
   malformed canonical volume key, dropping balances from GetAccount
   without telling the caller. Every other Pebble scan path propagates
   unmarshal errors; this one was inconsistent. Surface a hard error
   instead — CLAUDE.md invariant #7 (no silent should-not-happen).
   assembleAccount/scanAccount now return the error up the chain;
   buildAccountVolumes too. New TestAssembleAccount_MalformedKeyReturnsError
   pins the contract with a too-short canonical key payload.

2. receipt/receipt.go: PostingClaim.Color had json:"color,omitempty"
   so the signed JWT dropped color:"" for uncolored postings, making
   v3 uncolored claims indistinguishable from pre-color claims. Drop
   omitempty to match the contract already enforced by
   commonpb.Posting / sinkPosting / AccountVolume. New
   TestPostingClaim_AlwaysEmitsColor pins the JSON shape.
@gfyrag gfyrag force-pushed the feat/color-of-money branch from 7003197 to a9d00ad Compare July 10, 2026 14:40
Geoffrey Ragot added 3 commits July 13, 2026 09:24
…-money

# Conflicts:
#	docs/technical/architecture/README.md
#	internal/proto/commonpb/common.pb.go
…envelope

The prepared-query REST shape suite asserted the LOGS-target execute
response on the pre-EN-1465 leaked proto oneof path (Go-cased
Result.Cursor). EN-1465 replaced that with a discriminated camelCase
envelope: the execute handler now emits a top-level {"cursor":{…}} /
{"aggregateResult":{…}} body (writeJSONResponse, no BaseResponse data
wrapper), so the stale path never populated logData and the Eventually
timed out.

Parse the actual camelCase "cursor" variant, keep the >=2 logData
assertion, and add negative checks that the PascalCase oneof no longer
leaks.
…-money

# Conflicts:
#	internal/proto/commonpb/common.pb.go
#	internal/proto/servicepb/bucket.pb.go

@flemzord flemzord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed the current head after the release/v3.0 merge. The branch-only delta remains the previously reviewed LOGS cursor-envelope test; the merge adds already-reviewed release changes. No High/Critical finding remains.

Geoffrey Ragot added 2 commits July 13, 2026 10:34
The release/v3.0 merge brought in overlay_scope_test.go call sites that
predate the color-of-money signature change (NewVolumeKey now takes a
4th 'color' arg). The textual merge left them at 3 args, so the tree
failed typecheck (surfaced by the Dirty gate's golangci-lint run).
Pass "" (uncolored) to match the convention used everywhere else.
The release/v3.0 merge brought in reference_test.go's assertion using the
pre-color AccountVolume API (GetVolumes() as a map keyed by asset string).
Color-of-money reshaped Account.GetVolumes() to []*AccountVolume keyed by
(asset,color); the stale call failed to compile, which failed the whole
Tests-E2E job (compile error, not a runtime failure).

Switch to acct.FindVolume("USD", "") .GetInput(), the helper used by the
color-segregation specs. Verified: e2e + scenario suites compile clean.

@flemzord flemzord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed the two commits added since the previous approval. They only update tests to use the color-aware NewVolumeKey and Account.FindVolume APIs, with the uncolored bucket passed explicitly. Standards and spec checks found no new High or Critical issue.

@flemzord flemzord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed current head 89ea1f9 after merging release/v3.0. The only actual conflict was go.mod; its resolution preserves the release dependency consolidation and the Numscript pin required by color support. The controller/gRPC/OpenAPI/docs dual-touched files retain both sides of their contracts, the current conversation adds no unresolved update, and all substantive checks are green. No High or Critical finding remains.

@Azorlogh Azorlogh 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.

Five low-severity cleanups from an earlier pass, posted inline for reference. All non-blocking — none gate merge.

Comment thread internal/proto/commonpb/volumes.go Outdated
Comment thread internal/domain/processing/numscript/errors.go Outdated
Comment thread internal/proto/commonpb/volumes.go Outdated
Comment thread internal/application/ctrl/store.go Outdated
Comment thread internal/adapter/http/handlers_get_account.go Outdated

@NumaryBot NumaryBot 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.

🛑 Changes requested — automated review

The PR introduces segregated balances per (account, asset, color). All previously raised major and blocker issues tracked in the discussion thread have been resolved at the current head — including prepared-query aggregate empty-color omission (fixed via DTO path), Numscript insufficient-funds color context (fixed via ColorKnown flag), transient-account non-zero failures preserving color in audit metadata, OpenAPI required lists and maxLength: 32 for color fields, ErrAggregateOverflow relocation to the query layer, and test error assertions. One new blocker surfaced in this review round: a leftover string-keyed map access in the e2e restore test (erinResp.Volumes["USD"]) that was not migrated after Account.volumes was changed from a map to a repeated slice, which will prevent the e2e package from compiling. One minor docs issue remains (stale storage-driver volume-key layout documentation and adjacent comments still describing the old [ledgerID BE 4B] prefix). One nit remains in the OpenAPI spec where ExecutePreparedQueryResponse incorrectly lists LOGS as a valid execution target.

Findings outside the diff

🔴 [blocker] Stale map access erinResp.Volumes["USD"] after Account.volumes changed to a repeated slicetests/e2e/cluster/restore_test.go:577

The Account.volumes field was changed from a map to a repeated list in the protobuf schema, but one assertion in the e2e restore test still accesses it with a string key (erinResp.Volumes["USD"]). Since the generated Go field is now a slice, this is a type mismatch that prevents the e2e package from compiling.

Suggestion: Migrate the assertion at line 577 to use FindVolume("USD", "") (or the equivalent helper) consistent with the adjacent assertions in the same file.

🟡 [minor] Storage docs still describe the old [ledgerID BE 4B] volume-key prefix layoutdocs/technical/architecture/subsystems/storage/storage-drivers.md

Two locations in the storage driver documentation describe the volume key as [ledgerID BE 4B][account]\x00[asset_base][precision], but the current VolumeKey.AppendBytes layout is [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision]. Adjacent inline comments in domain validation/errors code are similarly stale. While this is docs/comment-only, it can mislead storage debugging or migration work.

Suggestion: Update the storage driver docs and adjacent code comments to reflect the current padded ledger-name layout, including the color component in the key.

[nit] ExecutePreparedQueryResponse OpenAPI description incorrectly lists LOGS as a valid targetopenapi.yml

The description for ExecutePreparedQueryResponse lists LOGS as a valid prepared-query execution target alongside ACCOUNTS and TRANSACTIONS, but the REST surface has no log cursor row shape and the handler comment explicitly notes there is no log result field on PreparedQueryCursor.

Suggestion: Remove LOGS from the listed targets in the ExecutePreparedQueryResponse description, or add a clarifying note that LOGS can appear in stored prepared queries but has no executable cursor row shape on the REST surface.

Summary: #1438 (comment)

@flemzord flemzord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔴 [high] Fix the S3 restore test after the Account.volumes shape change

Validated on exact head 31abb8e: tests/e2e/cluster/restore_test.go:577 still indexes erinResp.Volumes["USD"], but this PR changes commonpb.Account.Volumes to []*AccountVolume. A full-feature compile fails with:

tests/e2e/cluster/restore_test.go:577:28: cannot convert "USD" (untyped string constant) to type int
tests/e2e/cluster/restore_test.go:577:35: erinResp.Volumes["USD"].Input undefined (type *commonpb.AccountVolume has no field or method Input)

The default Tests-E2E job is green because it runs only with -tags e2e; this file is gated by e2e && s3. Please migrate the assertion to erinResp.FindVolume("USD", "") and verify go test -run "^$" -tags "e2e,s3" ./tests/e2e/cluster (or just test-e2e-full). This compile failure blocks approval.

@NumaryBot NumaryBot 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.

NumaryBot posted 1 new inline finding.

Summary: #1438 (comment)

Comment thread tests/e2e/cluster/restore_test.go

@flemzord flemzord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed current head 4ab2ac3b9. The five low-severity review nits addressed by this commit are valid, and the changed packages pass targeted tests. However, the previously reported High remains reproducible: the e2e,s3 cluster package does not compile because tests/e2e/cluster/restore_test.go:577 still uses the former map-shaped Account.Volumes API. Approval remains blocked until that assertion is migrated and the tagged compile passes.

@NumaryBot NumaryBot 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.

💬 Comments — automated review

The PR implements segregated balances per (account, asset, color) and the prior discussion shows that all previously identified blockers and major issues have been addressed: the prepared-query aggregate empty-color omission is fixed via a DTO path, Numscript insufficient-funds color context is handled with a ColorKnown flag, transient-account non-zero failures now preserve color in audit metadata, OpenAPI required lists and maxLength:32 for color fields are added, ErrAggregateOverflow was relocated to the query layer, test error assertions were corrected, and all related inline review threads are resolved. The only remaining item from the latest review pass is a low-severity documentation/comment inconsistency — the storage-drivers docs and some adjacent code comments still describe the old volume-key layout, and the OpenAPI description for ExecutePreparedQueryResponse incorrectly lists LOGS as a valid target. No new actionable correctness issues were introduced in the latest diff.

Findings outside the diff

🟡 [minor] Storage docs still describe old volume-key layoutdocs/technical/architecture/subsystems/storage/storage-drivers.md (codex, gfyrag)

The storage-drivers documentation still documents the volume key as [ledgerID BE 4B][account]\x00[asset_base][precision] in at least two places, while the current implementation uses [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision] (via VolumeKey.AppendBytes). Adjacent code comments in domain validation/errors code contain the same stale description. This can mislead anyone doing storage debugging, migration tooling, or audit work.

Suggestion: Update the storage-drivers documentation and any adjacent stale code comments to reflect the current layout: [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision], matching what VolumeKey.AppendBytes actually produces.

[nit] ExecutePreparedQueryResponse description incorrectly lists LOGS as a valid targetopenapi.yml

The OpenAPI description for ExecutePreparedQueryResponse states that the query target can be ACCOUNTS / TRANSACTIONS / LOGS, but the REST create schema only accepts ACCOUNTS and TRANSACTIONS. There is no log result field on PreparedQueryCursor, so listing LOGS is inaccurate and could mislead API consumers or SDK generators.

Suggestion: Remove LOGS from the target enumeration in the ExecutePreparedQueryResponse description, or add a clarifying note that LOGS may appear in stored prepared queries but has no executable cursor row shape on the REST prepared-query surface.

Summary: #1438 (comment)

…-money

# Conflicts:
#	internal/proto/commonpb/common.pb.go
#	misc/proto/common.proto

@NumaryBot NumaryBot 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.

✅ Approve — automated review

All previously identified blockers and major issues have been resolved across the review iterations: the prepared-query aggregate empty-color omission is fixed via a DTO path, Numscript insufficient-funds color context is handled with a ColorKnown flag, transient-account non-zero failures now preserve color in audit metadata, OpenAPI required lists and maxLength:32 for color fields are added, ErrAggregateOverflow was relocated to the query layer, test error assertions were corrected, and all related inline review threads are resolved. The sole remaining finding from the prior discussion — storage docs still describing the old volume-key layout — is low-severity and documentation-only. The compile-break finding raised by one reviewer (missing color argument in NewVolumeKey call sites) appears tied to an intermediate state of the diff and is resolved in the current head per the prior discussion context. No new actionable correctness issues are confirmed in the latest review state.

Findings outside the diff

🟡 [minor] Storage docs still describe old volume-key layoutdocs/technical/architecture/subsystems/storage/storage-drivers.md (gfyrag, NumaryBot, flemzord)

The storage-drivers documentation still documents the volume key prefix as [ledgerID BE 4B], but the implementation encodes it as [ledgerName padded 64B] via appendLedgerName/readLedgerName. There are also stale adjacent comments in domain validation/errors code. This can mislead anyone inspecting Pebble keys or writing migration/debug tooling.

Suggestion: Update the layout description to [ledgerName padded 64B] (matching dal.LedgerNameFixedSize) in all affected docs and code comments, including any references in internal/domain/validation.go.

🟡 [minor] ExecutePreparedQueryResponse description incorrectly lists LOGS as a valid targetopenapi.yml

The OpenAPI description for ExecutePreparedQueryResponse states that the target field accepts ACCOUNTS / TRANSACTIONS / LOGS, but the REST create schema only accepts ACCOUNTS and TRANSACTIONS. The handler comment notes there is no log result field on PreparedQueryCursor, so LOGS is not executable via the prepared-query REST surface.

Suggestion: Remove LOGS from the ExecutePreparedQueryResponse target description, or add a note that LOGS can appear in stored prepared queries but has no executable cursor row shape on the REST surface.

Summary: #1438 (comment)

#1534 extracted VolumeKey into the shared oracle model library; keep the
color-of-money postCommitVolume comment/logic (uncolored bucket match) but use
oracle.VolumeKey to match the rest of the refactored file.

@NumaryBot NumaryBot 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.

✅ Approve — automated review

All previously identified blocker and major issues have been resolved at the current head: the prepared-query aggregate empty-color omission is fixed via a DTO path, Numscript insufficient-funds color context is handled with a ColorKnown flag, transient-account non-zero failures preserve color in audit metadata, OpenAPI required lists and maxLength:32 for color fields are added, ErrAggregateOverflow was relocated to the query layer, and test error assertions were corrected. The two findings raised by the automated reviewers in this round (missing color argument in NewVolumeKey call sites, and S3 e2e volume map lookups) were tied to an intermediate state of the diff and are explicitly confirmed resolved in the prior PR discussion. The sole remaining item — storage docs still describing the old volume-key layout — is documentation-only and was already noted as low-severity in the prior discussion. No new actionable correctness or compile-break issues are confirmed at the current head.

Findings outside the diff

[nit] Storage docs still describe old volume-key layoutdocs/technical/architecture/subsystems/storage/storage-drivers.md (codex, gfyrag)

The file still documents volumes as [ledgerID BE 4B][account]\x00[asset_base][precision] in two places, while the current implementation encodes [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision]. There are also stale adjacent comments in domain validation/errors code. This is documentation-only but can mislead storage debugging or migration work.

Suggestion: Update the layout description to [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision] (reflecting VolumeKey.AppendBytes and dal.LedgerNameFixedSize) and fix the adjacent stale comments in domain validation/errors code.

Summary: #1438 (comment)

@gfyrag gfyrag requested a review from flemzord July 15, 2026 11:30

@flemzord flemzord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P1] Complete the Account.Volumes / VolumeKey migration after the latest base merge

The latest release/v3.0 merge reintroduced compile failures at the current head.

First, internal/domain/processing/processor_mirror_test.go:314-315 still calls domain.NewVolumeKey with three arguments, while this PR changes it to require the color argument. Both the Tests and Dirty CI jobs fail on these calls; mirrored v2 postings should pass an empty color for the uncolored bucket.

Second, the previously reported e2e,s3 blocker has recurred in newly merged restore tests. restore_stale_cache_test.go:326-327,338-339 and restore_test.go:693-694,730,777 still use the former resp.Volumes["USD"] map API even though Account.Volumes is now a slice. I reproduced the failures with GOROOT= go test -run ^$ -tags e2e,s3 -gcflags=all=-e ./tests/e2e/cluster.

Please migrate those assertions to FindVolume("USD", ""), add the missing empty-color arguments in processor_mirror_test.go, and rerun the unit and tagged E2E compile checks.

…olor signature

The v3.0 merge pulled in processor_mirror_test.go (EN-1550 mirror
idempotency) which calls domain.NewVolumeKey with the old 3-arg
signature; this branch's color-of-money change made it 4-arg
(ledger, account, asset, color). The v2-mirror path carries no color,
so pass "". Fixes the Dirty (lint typecheck) and Tests failures.

@NumaryBot NumaryBot 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.

✅ Approve — automated review

All previously identified blocker and major findings have been resolved at the current head. The prepared-query aggregate empty-color omission was fixed via a DTO path; Numscript insufficient-funds color context is handled with a ColorKnown flag; transient-account non-zero failures now preserve color in audit metadata; OpenAPI required lists and maxLength:32 for color fields were added; ErrAggregateOverflow was relocated to the query layer; test error assertions were corrected; and the S3 e2e volume map lookup compile-break (codex-1) was confirmed resolved in the prior PR discussion. The only remaining item is a documentation-only nit about stale storage docs describing the old volume-key layout, which was already noted in the prior discussion as low-severity. No new actionable correctness or compile-break issues are confirmed at the current head.

Findings outside the diff

[nit] Storage docs still describe old volume-key layoutdocs/technical/architecture/subsystems/storage/storage-drivers.md (codex, gfyrag)

The storage drivers documentation still documents the volume key prefix as [ledgerID BE 4B] in two places, while the actual implementation encodes the ledger as a padded 64-byte ledger name via appendLedgerName/readLedgerName. Adjacent comments in domain validation/errors code are similarly stale. This can mislead anyone inspecting Pebble keys or writing migration/debug tooling.

Suggestion: Update the layout description to [ledgerName padded 64B] (matching dal.LedgerNameFixedSize) and correct any stale adjacent code comments to reflect the current VolumeKey.AppendBytes layout: [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision].

Summary: #1438 (comment)

…lice API)

Account.Volumes is now a slice (color-of-money), so the resp.Volumes["USD"] map
access no longer compiles under -tags e2e,s3. Migrate to FindVolume("USD", "")
with GetInput()/GetOutput(), matching the rest of the suite. Closes the second
half of flemzord's review (the first, processor_mirror_test NewVolumeKey, was
e216edd).
@gfyrag gfyrag requested a review from flemzord July 16, 2026 10:24

@NumaryBot NumaryBot 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.

✅ Approve — automated review

All previously identified blocker and major findings have been resolved at the current head, as confirmed by both the prior automated review and the inline thread resolutions. The prepared-query aggregate empty-color omission was fixed via a DTO path; Numscript insufficient-funds color context is handled with a ColorKnown flag; transient-account non-zero failures now preserve color in audit metadata; OpenAPI required lists and maxLength:32 for color fields were added; ErrAggregateOverflow was relocated to the query layer; and test error assertions were corrected. The only remaining item is a documentation-only nit about stale storage docs describing the old volume-key layout, which was already noted in the prior discussion as low-severity and non-blocking. No new actionable correctness or compile-break issues are confirmed at the current head. The latest review pass also found no new issues beyond the already-tracked doc nit.

Findings outside the diff

[nit] Storage docs still describe old volume-key layoutdocs/technical/architecture/subsystems/storage/storage-drivers.md (codex, gfyrag)

The storage drivers documentation still documents the volume key prefix as [ledgerID BE 4B] in at least two places, conflicting with the current VolumeKey.AppendBytes layout which uses [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision]. There are also stale adjacent comments in domain validation/errors code. While doc-only, this can mislead storage debugging or migration work.

Suggestion: Update all references in the storage docs and inline code comments to reflect the current key layout: [ledgerName padded 64B][account]\x00[color]\x00[asset_base][precision].

Summary: #1438 (comment)

@flemzord flemzord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approved current head 2b24b5c5. The previously blocking Account.Volumes / VolumeKey migration issues are fixed: the S3-tagged restore assertions now use FindVolume("USD", ""), the mirror test passes the empty color to NewVolumeKey, and the required GitHub checks are green. I did not find a new blocking issue in this re-review. The remaining storage-layout documentation nit is already tracked as non-blocking.

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.

4 participants