feat!: color of money — segregated balances per (account, asset, color)#1438
feat!: color of money — segregated balances per (account, asset, color)#1438gfyrag wants to merge 30 commits into
Conversation
✅ Approve — automated reviewAll 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 layout — The storage drivers documentation still documents the volume key prefix as Suggestion: Update all references in the storage docs and inline code comments to reflect the current key layout: |
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1438 (comment)
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1438 (comment)
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
gfyrag
left a comment
There was a problem hiding this comment.
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,compareVolumesall carry(account, asset, color). - Index builder/backfill: protobuf fast-path parsing for
Posting.colorandTouchedVolume.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
left a comment
There was a problem hiding this comment.
NumaryBot posted 2 new inline findings.
Summary: #1438 (comment)
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 3 new inline findings.
Summary: #1438 (comment)
gfyrag
left a comment
There was a problem hiding this comment.
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:
-
openapi.ymldocumentsPostingRequest.color,AggregatedVolume.color,NormalizedPosting.color, etc., butcomponents.schemas.PostingResponsestill only hassource,destination,amount, andasset. The HTTP transaction response serializers now emit posting colors, so generated clients using this schema can miss/drop the response field. -
docs/technical/contributing/api-comparison.mdsays direct postings accept the new field through a--colorCLI flag. There is no such flag incmd/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
|
A few non-blocking cleanups from a review — none block merge: 1. 2. 4. The 5. 6. All non-blocking — flagging for a cleanup pass, not gating merge. |
071df32 to
7281403
Compare
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 2 new inline findings.
Summary: #1438 (comment)
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 3 new inline findings.
Summary: #1438 (comment)
|
Review pass on Findings:
I did not re-raise the storage schema bump because it was intentionally dismissed. The CLI Validation run: targeted |
flemzord
left a comment
There was a problem hiding this comment.
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.
|
Follow-up on The latest comments are still valid:
|
f29fb75 to
7003197
Compare
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 8 new inline findings.
Summary: #1438 (comment)
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.
7003197 to
a9d00ad
Compare
…-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
left a comment
There was a problem hiding this comment.
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.
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
left a comment
There was a problem hiding this comment.
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.
…-money # Conflicts: # go.mod
flemzord
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
Five low-severity cleanups from an earlier pass, posted inline for reference. All non-blocking — none gate merge.
NumaryBot
left a comment
There was a problem hiding this comment.
🛑 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 slice — tests/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 layout — docs/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 target — openapi.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
left a comment
There was a problem hiding this comment.
🔴 [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.
…r, big.Int accumulation, queryParamBool
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1438 (comment)
flemzord
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
💬 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 layout — docs/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 target — openapi.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
left a comment
There was a problem hiding this comment.
✅ 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 layout — docs/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 target — openapi.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
left a comment
There was a problem hiding this comment.
✅ 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 layout — docs/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)
flemzord
left a comment
There was a problem hiding this comment.
[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
left a comment
There was a problem hiding this comment.
✅ 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 layout — docs/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).
NumaryBot
left a comment
There was a problem hiding this comment.
✅ 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 layout — docs/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
left a comment
There was a problem hiding this comment.
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.
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
colorfield. The empty color is theuncolored bucket and is itself fully segregated from every colored
bucket.
Companion PR upstream: formancehq/numscript#139 —
Posting.Colorexposed in numscript output,BalanceQueryrefactored to carry color as a separate dimension instead of an asset suffix. The ledger pins that branch via a localreplaceingo.modfor now; bump to a tagged release once #139 is merged.Invariants
changes it. Re-coloring is a deliberate composition (clearing
account + mint from
@world), not a primitive.Color == ""participates insegregation just like any colored bucket. A draw under color
Xcannot dip into uncolored funds, and vice versa.
^[A-Z]*$color charset, enforced both upstream (numscript) andat the ledger admission boundary. The empty string remains valid.
(asset, color), the sum ofall account balances is zero. Each bucket is its own conservation
universe; the
scenariotestinvariant check is updated accordingly.Wire shapes
Posting.colorexposed everywhere postings appear.Account.volumesbecomesrepeated AccountVolume{asset, color, volumes}sorted by
(asset, color)ascending — deterministic JSON.PostCommitVolumesinner shape becomes a sorted list ofVolumeEntry{asset, color, volumes}.AggregatedVolume.coloris set on every entry; the default responseis 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=trueGET /{ledger}/volumes?collapseColors=true(orAggregateVolumesRequest.collapse_colors = true)Storage layout
domain.VolumeKeycarriesColor 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 theaccount; `[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
MirrorIngestpreload branch explicitly passes
color = ""with a comment thatmakes the intent visible at the call site.
Numscript integration
The numscript interpreter (PR #139) emits postings with their
Colorfield set. The ledger's
numscriptStoreAdapterhonors the newAssetColoritems inBalanceQueryand returnsBalanceskeyed bymap[account][asset][color] -> *big.Int. No asset-suffix encodingremains in either repo.
Tests
tests/e2e/business/color_segregation_test.go— fullend-to-end gRPC test covering GetAccount segregation, collapse,
draw refusal across colors, draining a single bucket, color visible
on the emitted posting.
internal/application/ctrl/store_color_test.gocoveringassembleAccountsegregation by default +collapseColorsmode.internal/query/aggregate_test.go):segregation, collapse, collapse + max_precision, grouped aggregator.
layout in
internal/domain/keys_test.go.acct.GetVolumes()["USD"]map access toacct.FindVolume("USD", "").Test plan
tagged numscript release (drop the
replacedirective)and `ledgerctl accounts get alice` showing the COLOR column
🤖 Generated with Claude Code
Migrated from formancehq/ledger-v3-poc#234 on 2026-06-29.