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