Files
dev-procfile/SHARED-TYPES-EVALUATION.md
T

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.