5.7 KiB
Shared ID and monetary type evaluation
Date: 2026-09-01
Scope: api, auth, wallet, GL, proto, and AdminPanel
Tasks: F001, F002
Decision
No new cross-repository source package or universal protobuf wrapper is approved. The superficially similar values do not currently have identical invariants. Keep domain value objects local and perform explicit conversion at service boundaries.
This is a positive architectural decision, not deferred implementation. A shared type may be proposed later only if its producer, consumers, precision, nullability, validation, versioning, and compatibility behavior are proven identical.
Monetary semantics
| Context | Representation | Invariants | Decision |
|---|---|---|---|
| Wallet domain | domain/money.Amount, fixed-scale integer units backed by big.Int |
Scale 7; mirrors numeric(23,7) and Stellar stroops; supports exact wallet arithmetic and explicit legacy float64 conversion |
Keep Wallet-owned. |
| Wallet nullable persistence values | money.NullAmount |
Distinguishes SQL null from zero | Keep Wallet-owned; nullability is persistence/domain-specific. |
| GL domain | domain/ledger.Amount, fixed-scale big.Int |
Precision 38, scale 18; canonical parsing; immutable double-entry journal values | Keep GL-owned. It intentionally has more precision and stricter canonical input than Wallet. |
| Wallet ↔ GL contract | Canonical base-10 string in ledger.v1.JournalEntry.amount |
Lossless, language-neutral boundary; Wallet formats its exact amount and GL reparses under GL constraints | Retain. This is the correct explicit conversion boundary. |
| Existing public Wallet/Market contracts | double plus some integer IRR/raw values |
Backward-compatibility surface with explicit conversion/finite/range checks inside Wallet | Do not replace during this refactor; changing wire types is breaking. Track separately if a versioned v2 contract is approved. |
| AdminPanel | Python Decimal/Django decimal fields, serialized to canonical strings for asset administration |
Projection/form representation; Wallet owns final validation | Keep adapter-local. |
Why Wallet and GL amounts must not be aliased:
- scale 7 and scale 18 represent different accepted value sets;
- their database precision limits differ;
- Wallet includes Stellar raw-unit conversion and commercial rounding operations;
- GL requires canonical journal serialization, conservation, and immutable audit precision;
- importing either implementation into the other repository would reverse the service dependency direction;
- a common implementation would still require context-specific wrappers, eliminating its claimed benefit.
Identifier semantics
| Identifier family | Current representation | Meaning | Decision |
|---|---|---|---|
| Auth/User/Identity database IDs | Predominantly signed int64; national ID is validated auth/model.NationalID string |
Auth-owned persistence identity versus a regulated external identifier | Keep NationalID Auth-owned. Do not conflate it with database IDs. |
| Wallet asset/user/transaction IDs | Predominantly signed int64 |
Positive database keys; zero commonly represents absent/default at protobuf boundaries | Keep explicit field names and validate positivity per operation. |
| Wallet market/order/legacy entity IDs | Mixture of uint, uint64, and int64 |
Historical GORM/protobuf choices with deployed wire compatibility | Do not hide signedness differences behind a shared alias. Normalize only in a versioned migration with database and protobuf evidence. |
| GL journal/event IDs | string |
Service-generated opaque IDs, not database sequence numbers | Keep opaque strings. |
| GL source transaction/actor/owner IDs | string |
Cross-service references supporting multiple owner/source namespaces | Keep strings and explicit source/owner type; converting to numeric IDs would remove namespace flexibility. |
Generic base.v1.IdReq/IdRes |
int64 |
Existing transport convenience only | Retain for compatibility, but do not use it as a domain-wide ID abstraction. |
A source-level UserID, AssetID, or generic ID package is not approved because:
- repositories are independently versioned and should not acquire a shared-code release dependency;
- protobuf field types are already deployed and inconsistent across legacy contracts;
- aliases would not enforce positivity, ownership, namespace, or existence;
- strong local types are useful only when named for a bounded context and validated there;
- API is an adapter and should map contracts, not become the owner of domain identity types.
Approved boundary rules
- Financial values crossing new internal boundaries use canonical base-10 strings when exact decimal fidelity is required.
- Each receiving service parses into its own domain amount and applies its own precision, sign, and business rules.
- IDs remain explicitly named (
user_id,asset_id,journal_id, etc.); generic IDs must not cross a boundary without the message or operation supplying meaning. - Opaque IDs remain strings. Database sequence IDs retain their deployed signedness until a separately versioned contract migration is justified.
float64money is allowed only at existing compatibility edges; new domain code must use exact local values.- No service imports another service's domain package. Generated protobuf contracts and explicit adapter conversions remain the sharing mechanism.
F002 outcome
There are no approved shared types to introduce. Therefore F002 completes with no runtime or contract change. The existing Wallet-to-GL canonical decimal string is already the correct shared representation at the boundary, while both sides retain their distinct domain types.