Design for review · 06 / 11

Proposed architecture. Pilot products, privacy policy and cost-risk acceptance remain open.

RPC contract

Package proposal: optima.v1. These signatures are design sketches. Ravi owns the real proto; Kai and Mira review it before client mocks or implementation.

Shared value shapes

Money { currency: ISO4217, minor_units: int64 }
Evidence { kind: supplied | inferred | user_asserted | unknown,
           source_id, captured_at, confidence? }
Period { kind, local_start, local_end_exclusive, timezone, card_id? }
SnapshotMeta { ledger_version, calculation_revision,
               state: ready | pending | unavailable, completeness, revised_at }
Allowance { rule_id, period, native_unit, used, limit?,
            status: available | exhausted | conditional | unavailable,
            spend_equivalent?, reasons[] }
PurchasePortions { covered_minor, no_bonus_minor, unclear_minor }

Native units distinguish SGD, bonus points, cashback minor units and miles. JSON int64 values remain strings on the wire; clients must not lose precision by converting arbitrary int64 to JavaScript Number. Unsupported enum variants render an unavailable state, not a success label.

Reads use Connect GET

All following RPCs are unary and annotated idempotency_level = NO_SIDE_EFFECTS. Configure the generated Connect client to use GET. Authenticate every request and mark personal responses private/no-store. Do not put forwarding secrets or merchant text in GET requests. Session validation does not slide expiry during these reads; renewal is an explicit POST.

RPC Request Response
GetSession empty Signed-in account summary or unauthenticated
GetSetup empty Cards, masked forwarding destination, test status
ListCardProducts issuer, cursor Verified/support-status catalogue; max 50
GetHome reporting_month One snapshot with cards, split, rewards, review count
GetCard owned_card_id, period Identity, allowances, gates and summary
ListTransactions selection, cursor, revision Max 50 rows, next cursor, metadata
GetTransaction transaction_id, revision? Fact, current correction, portions, provenance and explanations
ListReviewItems cursor Max 50 held or unclear items and reasons
GetHistory filter, six_period_window Six chart rows and matching table; earlier-window cursor
GetPeriod exact period key, revision? Period totals, credits, card summaries and transaction cursor
GetRevision revision_id Explainable changed inputs and affected periods

The protocol defines GET for methods marked as having no side effects. Authentication and cache policy remain application responsibilities. Connect protocol.

Commands use POST

RPC Input and concurrency Result
CreateCard product, last_four, statement day, command_id Owned card and version
UpdateCardSettings dated settings, expected_version, command_id New settings version and pending recalculation
UpdatePriority ordered owned-card IDs, expected_version, command_id Saved order
CorrectTransaction transaction_id, typed patch, expected_version, command_id Corrected fact, pending snapshot and job status
ResolveCapture held capture ID, explicit card/amount/currency choice, expected_version Confirmed fact or kept-excluded state
RecordCreditCorrection amount, currency, optional original purchase, reason, command_id Credit fact, optional link, pending calculation
RevealForwardingDestination recent-auth proof Destination, no-store; never included in analytics
RotateForwardingDestination recent auth, expected_version, command_id New token; old token revoked atomically
RenewSession / SignOut CSRF protection New expiry / revoked session
RequestAccountDeletion recent auth, explicit confirmation, command_id Access revoked and opaque deletion request ID

Store command result and request digest under (account_id, command_id) in the same transaction as the change. Reusing a key with a different body fails. Retain command receipts for a proposed 48-hour retry window; make that window part of the client contract. Identity comes from the server session, never an account ID in the request.

Errors and response limits

  • unauthenticated: sign in again, preserve local form entries without shared storage.
  • not_found: missing or not owned; avoid revealing another account's record existence.
  • invalid_argument: field errors, including invalid period or ambiguous amount.
  • aborted: record version conflict; show latest value before retry.
  • resource_exhausted: quota reached, retry time and reason; no automatic busy loop.
  • unavailable: safe retry for reads, command retry with the same command ID.
  • failed_precondition: unsupported product/operator, expired revision cursor or deletion state.

Proposed limits: 64 KiB command bodies, 256 KiB responses, 50 transaction rows, six chart periods per request. History remains reachable through pagination; limits do not delete older history. Cursors are opaque, signed and bound to account, selection and revision.

Compatibility and rollout

Add fields and enum variants; do not reuse tags or remove fields. Reserve retired tags and names. New behaviour uses a new RPC or package version. CI runs buf lint and buf breaking against main and the last production schema. Backend deploys first. Additive database expansion precedes compatible backend and frontend deployment; contraction requires all callers migrated and a separately reviewed release.