D-06 — FINMON: Money Side — Billing, Desks, and Proof of Value
Status: 2026-11-09. Audience: engineers and product teams working on billing, splits, wallets, and the money spine. Contract: every claim below is a mechanism stated as PROBLEM → MECHANISM → INTERFACES → INVARIANTS; no schema, field, or storage detail is listed unless it is the only way the interface contracts. (Source: Doc 36 §0.1 money ownership; Doc 32 pack contract; AGENTS.md commerce guards.)
1. Money Side — Ownership and Scope
PROBLEM — billing logic hardcodes itself into verticals. Revenue splits, wallet credits, and subscription gates then drift per vertical, and evidence of commercial fairness cannot be exported independently of fulfilment. (Source: Doc 36 §0.1 money ownership; Doc 32 pack contract.)
MECHANISM — the money side owns commerce subscriptions, financial desks, and the billing catalogue as consumed packs. Revenue-split profiles and wallet credits are configured declaratively rather than coded per vertical. Proof of billing fairness is produced by evidence export and proof verification, not by the fulfilment path itself. (Source: Doc 36 §0.1; Doc 32 pack contract.)
INTERFACES — billing and evidence surfaces. Clients interact with subscription and billing decision points and request evidence packages that can be verified independently. Money subjects travel on their own families distinct from lifecycle subjects. (Source: Doc 32 money subject families; Doc 36 pack consumption.)
INVARIANTS — configuration over code, honesty over completeness. Split and wallet rules are data in declarative planes, versioned and auditable. A soft or composed capability never reports a live gate as met merely because the configuration exists. (Source: operational memory declarative-first and honesty mandates; Doc 36 honesty gaps.)
2. Revenue-Split Profiles
PROBLEM — splits hidden in code drift per vertical; no auditability. Percentages in code cannot be changed without a deploy, and there is no audit trail for who changed what and when. (Source: Doc 36 §0.1; Doc 32 pack contract.)
MECHANISM — revenue-split profiles are declarative data in a shared plane, versioned and auditable. Each profile expresses which parties receive which percentages of which invoices, with named parties, exact totals that sum to 100%, and effective windows. Changes require two-person approval with a full audit trail. (Source: Doc 35 playbook monetization backlog; Doc 41 week in review.)
INTERFACES.
POST /v1/money/split-profiles— create or update a split profile (requires two-person approval workflow).GET /v1/money/split-profiles/:id— read a split profile.PATCH /v1/money/split-profiles/:id— apply a change (draft → co-sign → land).GET /v1/money/split-profiles/:id/audit-trail— read the full audit history for a profile.
INVARIANTS. Every split must add up to exactly 100% of the invoice; a creator's share is visible as a named party; changes to a split profile require two-person approval with a full audit trail; life-safety features degrade gracefully rather than stop when payment is late. (Source: Doc 35; Doc 41.)
3. Wallet Binding
PROBLEM — wallet adapter class is unset or guessed; the platform refuses rather than invents. If the wallet-binding interface cannot determine which adapter class an account uses, it refuses the request rather than guessing an adapter and silently succeeding. (Source: Doc 36 §0.1; operational memory wallet-binding design.)
MECHANISM — the wallet-binding interface (finmon-wallet.js) refuses when adapter_class:payments is unset. An account must declare its adapter class; the platform then routes payment flow through the named adapter. No container-level secrets in code; no .json/.yaml reads from filesystem at runtime; no hardcoded BPS/price catalogs in routes or UI; no synthetic fallback responses. (Source: Doc 10 §1; AGENTS.md runtime secret discipline.)
INTERFACES.
POST /v1/money/wallets— create or update a wallet binding (declaresadapter_class).GET /v1/money/wallets/:id— read a wallet binding.DELETE /v1/money/wallets/:id— revoke a wallet binding.
INVARIANTS. The platform refuses when adapter_class:payments is unset rather than guessing; no container-level secrets in code; no runtime filesystem reads of .json/.yaml; no hardcoded price catalogs in routes or UI; no synthetic fallback responses. (Source: AGENTS.md runtime secret discipline; Doc 10.)
4. Money Subject Families — Distinct from Lifecycle
PROBLEM — billing events must not interleave with claims lifecycle events. The claims spine and money spine use different envelopes and subject families so that billing middleware does not accidently read claims events and vice versa. (Source: Doc 32 money subject families.)
MECHANISM — money subject families are canonical and distinct. platform.criteria.v1.money.billing, platform.criteria.v1.money.entitlement, platform.criteria.v1.money.settlement, platform.criteria.v1.money.refund. Payloads carry subscription-id, wallet-id, split-profile-id, invoice-id, credit-note-id, and the declared amount/rate. Money events do not carry claim payloads; they carry money-specific fields only. (Source: Doc 32 money subject families.)
INTERFACES.
- Publishers emit money events on their designated subject families.
- Consumers validate against declarative allowlists before dispatching declared handlers.
- The Agent BFF dispatcher resolves capability names against the interface catalogue and fails closed on downstream failure. (Source: Doc 02 gateway capability resolution; Doc 32.)
INVARIANTS. Money events never carry claim payloads; billing middleware never reads claims subjects; the money spine never advances a claim; every money event emits an outbox anchor; every outbox relay failure produces a typed error, never synthetic success. (Source: Doc 32 money spine invariants; operational memory mandates.)
5. Split and Wallet Rule — Configuration Over Code
PROBLEM — split and wallet rules coded per vertical drift over time; no versioning, no auditability. (Source: operational memory monetization gaps.)
MECHANISM — split and wallet rules are declared in declarative planes, versioned and auditable. Profile entries carry profile_id, split_percentage (must sum to 100% across all parties for the invoice), creator_share (named party), effective_from, effective_to, last_modified_by, last_modified_at. Changes flow through a two-person draft → co-sign → land workflow with a full audit trail. Life-safety features are a named clause in the profile: if present and payment is late, features degrade gracefully (e.g. reduced coverage, suspended new enrolments) rather than stop entirely. (Source: Doc 35 playbook monetization backlog; Doc 41 week in review.)
INTERFACES.
POST /v1/money/split-profiles/:id— make a change (draft).POST /v1/money/split-profiles/:id/co-sign— co-sign a draft.POST /v1/money/split-profiles/:id/land— land a co-signed change.GET /v1/money/split-profiles/:id/audit-trail— read full change history.
INVARIANTS. Split percentages must sum to exactly 100% across all parties for the invoice; creator share is a named party; life-safety features degrade gracefully rather than stop when payment is late; changes require two-person approval with full audit trail. (Source: Doc 35; Doc 41.)
6. Life-Safety Feature Degradation
PROBLEM — when payment is late, some verticals stop all service entirely; this is too aggressive and creates fairness disputes. (Source: Doc 36 §0.1; Doc 35 honesty gaps.)
MECHANISM — life-safety features degrade gracefully rather than stop when payment is late. The profile clause life_safety_degrade_strategy is a named enum: reduce_coverage, suspend_new_enrolments, maintain_full_service_with_surcharge, or none. The platform reads the profile at runtime and applies the named strategy; the strategy is a clause in the declarative profile, not code. (Source: Doc 35; operational memory.)
INTERFACES. The money side reads the profile at runtime; no API surface needed beyond what the profile-driven logic already exposes.
INVARIANTS. Life-safety features degrade gracefully rather than stop when payment is late; the strategy is a named clause in the declarative profile, not code; the platform reads the profile at runtime and applies the named strategy. (Source: Doc 35; operational memory.)
7. Split Percentage Sum Discipline
PROBLEM — if split percentages don't sum to 100%, the invoice cannot be settled and the platform refuses rather than guesses. (Source: Doc 35 playbook monetization backlog; Doc 41 week in review.)
MECHANISM — the platform validates that every split profile's percentages sum to exactly 100% before allowing the profile to land. If the sum is not 100%, the profile is refused with a clear error that lists the current sum and the shortfall. The profile cannot land until the percentages are corrected. (Source: Doc 35; Doc 41.)
INTERFACES.
POST /v1/money/split-profiles— validate that percentages sum to 100%; refuse if not.
INVARIANTS. Every split must add up to exactly 100% of the invoice; if the sum is not 100%, the profile is refused with a clear error; the profile cannot land until percentages are corrected. (Source: Doc 35; Doc 41.)
8. Creator Share Visible as Named Party
PROBLEM — the creator's share is hidden in code or represented as an anonymous percentage. (Source: Doc 35; operational memory.)
MECHANISM — the creator's share is a named party, visible in the split profile. The profile field creator_share carries the name of the creator (a person or organization). This is visible in evidence export and proof verification, not merely in code. (Source: Doc 35; Doc 41.)
INTERFACES.
GET /v1/money/split-profiles/:id— readcreator_shareas a named party.
INVARIANTS. Creator share is a named party; visible in evidence export and proof verification; never anonymous or represented as a free percentage. (Source: Doc 35; Doc 41.)
9. Two-Person Approval Workflow (Dual Control MSS)
PROBLEM — sensitive edits need two named people; one drafts, the other co-signs; the change lands only when both have acted and the base hasn't moved since draft started (CAS check). (Source: Doc 35 playbook monetization backlog; Doc 41 week in review; AGENTS.md dual-control mandate.)
MECHANISM. One person drafts a split profile change; the change enters a draft state. A second named person co-signs the draft; the change enters a co-signed state and only lands (transitions to landed) when both have acted and the base configuration has not moved since draft started (checked via CAS = compare-and-swap on the base version). The full audit trail records who drafted and who co-signed and when. (Source: Doc 35; Doc 41; AGENTS.md dual-control mandate.)
INTERFACES.
POST /v1/money/split-profiles/:id/draft— draft a split profile change (one person).POST /v1/money/split-profiles/:id/co-sign— co-sign a drafted change (second person).POST /v1/money/split-profiles/:id/land— land a co-signed change (only when both have acted and base hasn't moved).
INVARIANTS. Changes to a split profile require two-person approval (quorum = met); the base must not have moved since draft started (CAS check); full audit trail records who drafted and who co-signed and when; the change lands only when both have acted and the base hasn't moved. (Source: Doc 35; Doc 41; AGENTS.md mandate 6: dual control MSS.)
10. Kill Bill Production Migration — Ops Gate
PROBLEM — moving real money in the Estate Safe pack requires production keys, a human approver, and the payments ledger; this is an ops-side gated item that cannot be code-deployed. (Source: Doc 35 playbook monetization backlog; FINMON-16.)
MECHANISM. Live money movement is gated until finance keys, a human approver, and the payments ledger are ready; no code change opens the gate; the desk waits on the finance team to provision the production environment. (Source: Doc 35; FINMON-16.)
INTERFACES. None in soft — ops gate only.
INVARIANTS. Live money movement opens only when finance keys + human approval + payments ledger are ready; no code change opens the gate. (Source: Doc 35; FINMON-16.)
11. Terminal Adapters — sikasie / malipo
PROBLEM — the platform must support real terminal adapters for money movement without hardcoding adapter logic in routes or UI. (Source: AGENTS.md commerce guards; Doc 35.)
MECHANISM. Terminal adapters live behind adapter_class:payments; the wallet-binding interface (finmon-wallet.js) refuses when adapter_class:payments is unset; the two named adapter classes are sikasie and malipo; no hardcoded adapter logic in routes or UI; the platform refuses rather than guesses which adapter to use. (Source: Doc 35; AGENTS.md.)
INTERFACES.
POST /v1/money/wallets— declareadapter_class:paymentswith one ofsikasieormalipo.GET /v1/money/wallets/:id— read the adapter class.
INVARIANTS. The platform refuses when adapter_class:payments is unset rather than guessing; the two named adapter classes are sikasie and malipo; no hardcoded adapter logic in routes or UI; the platform refuses rather than guesses which adapter to use. (Source: Doc 35; AGENTS.md.)
12. Currency-Match Settle — Heterogeneous Currencies Do Not Settle Together
PROBLEM — if one invoice is in one currency and another in a different currency, the platform must refuse rather than imagine a match. (Source: FINMON-20, closed-loop rule.)
MECHANISM. Heterogeneous currencies do not settle together; the platform validates currency match before allowing a settle operation; if currencies differ, the platform refuses with a clear error that cites the two currency codes. (Source: FINMON-20, closed-loop rule.)
INTERFACES.
POST /v1/money/settle— validate currency match before settle; refuse if currencies differ.
INVARIANTS. Heterogeneous currencies do not settle together; the platform refuses with a clear error citing the two currency codes if they differ; no implicit conversion or match. (Source: FINMON-20; closed-loop rule.)
Gaps and Honesty Limits
- Production gate FINMON-16 — live money movement through the Estate Safe pack requires production keys, a human approver, and the payments ledger; not yet provisioned; no code change opens the gate (source: Doc 35; FINMON-16).
- Kill Bill production migration — gated on human approval and finance gate; the desk waits on the finance team to provision production keys and the ledger migration procedure; no code change opens the gate (source: Doc 35; FINMON-16).
- Reværonnement with commerce guards (G1–G4) — for citations is covered in
apps/agent-bff/src/commerce-guards.js— skim before touching (source: A-03 billing ops). - Stakeholder brief language — overclaims where capability is still being wired declaratively and no live gate exists (source: Doc 01; operational memory).
- Sikasie/malipo adapter deployment — the two named adapter classes exist but their production deployment and key provisioning are ops-side gated items (source: Doc 35; AGENTS.md).
- Currency-match settle (FINMON-20) — the closed-loop rule that heterogeneous currencies do not settle together is documented but not yet tested in a running plane (source: Doc 35; FINMON-20).
- No live inventory — operational memory is not a verified live inventory of running services; it is the 180-day work catalog (source: this doc's own honesty note).
- Valuation and FX handling — not yet declared in the declarative profile; the platform currently refuses mixed-currency settles but has no FX conversion or valuation engine (source: Doc 35; FINMON-20 unknowns).
- Wallet adapter key management — the
sikasieandmalipoadapter classes require key provisioning that is ops-side gated; no code change provisions adapter keys (source: Doc 35; AGENTS.md). - Dual-control MSS — the two-person approval workflow is documented and coded but has not been exercised in a production-like environment; the CAS (compare-and-swap) check on the base version is implemented but not yet validated end-to-end (source: Doc 35; operational memory).
Interface Summary
| Capability | Core interface | Contract |
|---|---|---|
| Revenue-split profiles | CRUD + audit trail + two-person workflow | Every split must add to 100%; creator share is named; changes need two-person approval |
| Wallet binding | Create/read/delete + adapter_class:payments | Platform refuses when unset; named adapters: sikasie, malipo |
| Money subject families | Publish/consume on distinct subjects | Money events never carry claim payloads; billing middleware never reads claims |
| Life-safety degradation | Profile-driven strategy at runtime | Named enum strategies; gracefully degrade, never stop abruptly |
| Split percentage sum | Validate before land | Must sum to exactly 100%; refuse if not; profile cannot land until corrected |
| Creator share visible | Read from profile | Named party; visible in evidence export and proof verification |
| Two-person approval (MSS) | Draft → co-sign → land workflow | Quorum = met; base must not have moved (CAS); full audit trail |
| Kill Bill production migration | Ops gate only | Finance keys + human approval + payments ledger required; no code change opens gate |
| Currency-match settle | Validate before settle | Heterogeneous currencies refused with clear error citing both codes |
| Terminal adapters | Declare adapter_class | Platform refuses when unset; named: sikasie, malipo; no hardcoded logic in routes/UI |