Skip to content

UML Class Diagrams

Focused views of the load-bearing classes; method lists trimmed to the meaningful surface. Source paths under each diagram.

1. Financial domain aggregates

domain/mandate, domain/budget, domain/payment — money is int minor units, quality is int basis points, states are lowercase strings mirrored by the DB CHECKs.

classDiagram
    class Mandate {
        +MissionId id
        +MandateStatus status
        +Money budget
        +int quality_target_bp
        +compile_from(dto) Mandate
        +activate()
        +freeze()
    }
    class MandateStatus {
        <<enumeration>>
        draft
        active
        completed
        frozen
        failed
    }
    class BudgetPool {
        +PoolId id
        +str pool_key
        +Money allocated
        +reserve(amount, key) JournalEntry
        +commit(amount, key) JournalEntry
        +release(amount, key) JournalEntry
        +transfer_from_contingency(amount, key) JournalEntry
    }
    class JournalEntry {
        +JournalReason reason
        +str idempotency_key
        +tuple~JournalLine~ lines
        +assert_balanced()
    }
    class PaymentProposal {
        +ProposalId id
        +ProposalState state
        +Money amount
        +int version
        +transition(to, verdict)
    }
    class ProposalState {
        <<enumeration>>
        proposed
        policy_allowed
        policy_blocked
        policy_escalated
        reserved
        executing
        settled
        failed
        compensated
    }
    class PaymentAttempt {
        +AttemptId id
        +AttemptStatus status
        +str idempotency_key
        +str rail_ref
    }
    class AttemptStatus {
        <<enumeration>>
        pending
        submitted
        reconciling
        settlement_unknown
        confirmed
        failed
    }
    class Credential {
        +CredentialId id
        +CredentialState state
        +str merchant_lock
        +Money limit
    }
    class CredentialState {
        <<enumeration>>
        active
        locked
        used
        retired
        expired
    }
    Mandate --> MandateStatus
    Mandate "1" o-- "1..*" BudgetPool : funds
    BudgetPool ..> JournalEntry : emits
    PaymentProposal --> ProposalState
    PaymentProposal "1" o-- "0..*" PaymentAttempt
    PaymentProposal "1" o-- "0..1" Credential
    PaymentAttempt --> AttemptStatus
    Credential --> CredentialState

2. Policy engine (specification pattern)

domain/policy/engine.py + one file per rule in domain/policy/rules/. Every rule returns pass/fail with a severity; the engine maps results to the verdict — the LLM has no code path here.

classDiagram
    class PolicyEngine {
        +evaluate(proposal, context) Verdict
    }
    class SpendRule {
        <<interface>>
        +evaluate(proposal, context) RuleResult
    }
    class Verdict {
        +Decision decision
        +BlockClass block_class
        +tuple~RuleResult~ results
    }
    class RuleResult {
        +str reason
        +Severity severity
        +bool passed
    }
    class Decision {
        <<enumeration>>
        allow
        block
        escalate
    }
    class Severity {
        <<enumeration>>
        security
        validation
    }
    PolicyEngine o-- SpendRule : catalog (registered in composition)
    PolicyEngine ..> Verdict
    Verdict o-- RuleResult
    SpendRule <|.. MerchantAllowlisted
    SpendRule <|.. WithinEnvelope
    SpendRule <|.. PoolHasFunds
    SpendRule <|.. OutsideMandateScope
    SpendRule <|.. TaskLinked
    SpendRule <|.. NoRecurring
    SpendRule <|.. CardScopeValid
    SpendRule <|.. AgentAuthorityActive
    SpendRule <|.. MandateComplete

3. Rail strategy catalog and card ports (OCP + consumer-driven ISP)

application/payment/rail_strategies.py + application/ports/ + the adapters in infrastructure/rails/. One adapter class may implement several narrow ports; consumers depend only on what they call. Adding a rail = registering a strategy in composition — no use-case body changes.

classDiagram
    class RailStrategy {
        <<interface>>
        +prepare(proposal) RailOperation
        +execute(operation) RailOutcome
        +query_status(operation) SettlementStatus
    }
    class X402Strategy
    class RainCardStrategy
    class LiveRainAuthorizationStrategy
    RailStrategy <|.. X402Strategy
    RailStrategy <|.. RainCardStrategy
    RailStrategy <|.. LiveRainAuthorizationStrategy

    class CardIssuancePort {
        <<interface>>
        +issue_card(request) Card
    }
    class CardLifecyclePort {
        <<interface>>
        +freeze_card(ref, key)
        +retire_card(ref, key)
    }
    class CardTransactionsQueryPort {
        <<interface>>
        +list_transactions(ref, status) list~CardAuth~
    }
    class RainWebhookVerifierPort {
        <<interface>>
        +verify(headers, body) WebhookEvent
    }
    class MockRainCardIssuer {
        six-check decision order
        deterministic uuid5 ids
    }
    class LiveRainAdapter {
        Api-Key client
        conformance-gated transport
    }
    CardIssuancePort <|.. MockRainCardIssuer
    CardLifecyclePort <|.. MockRainCardIssuer
    CardTransactionsQueryPort <|.. MockRainCardIssuer
    RainWebhookVerifierPort <|.. MockRainCardIssuer
    CardIssuancePort <|.. LiveRainAdapter
    CardLifecyclePort <|.. LiveRainAdapter
    CardTransactionsQueryPort <|.. LiveRainAdapter
    RainWebhookVerifierPort <|.. LiveRainAdapter
    X402Strategy ..> PreparedPayloadStore : byte-identical retry
    class PreparedPayloadStore {
        <<interface>>
        +persist(op_id, payload)
        +load(op_id) bytes
    }

4. Agent registry

domain/agents/ — the aggregate holds NO spend authority; grants are separate human-approved records and mission-scoped freezes revoke only the mission grant.

classDiagram
    class AgentDefinition {
        +AgentId id
        +AgentStatus status
        +PromptVersionId active_prompt_version
        +activate(version)
        +freeze()
    }
    class AgentStatus {
        <<enumeration>>
        draft
        evaluating
        active
        frozen
    }
    class PromptVersion {
        +int version
        +str content_hash
        +str boilerplate_hash
        +datetime gated_at
        immutable row
    }
    class SpendGrant {
        +MissionId mission_id (nullable = org level)
        +Money max_per_task
        +Money max_total
        +datetime revoked_at
    }
    class GrantReservation {
        +Money amount
        +ReservationStatus status
        +commit()
        +release()
    }
    AgentDefinition --> AgentStatus
    AgentDefinition "1" o-- "1..*" PromptVersion
    AgentDefinition "1" o-- "0..*" SpendGrant
    SpendGrant "1" o-- "0..*" GrantReservation