77 lines
5.7 KiB
Markdown
77 lines
5.7 KiB
Markdown
# 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
|
|
|
|
1. Financial values crossing new internal boundaries use canonical base-10 strings
|
|
when exact decimal fidelity is required.
|
|
2. Each receiving service parses into its own domain amount and applies its own
|
|
precision, sign, and business rules.
|
|
3. 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.
|
|
4. Opaque IDs remain strings. Database sequence IDs retain their deployed signedness
|
|
until a separately versioned contract migration is justified.
|
|
5. `float64` money is allowed only at existing compatibility edges; new domain code
|
|
must use exact local values.
|
|
6. 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.
|