Caching, operations and deployment
Cache register
Allowed staleness below is a proposed technical maximum, subject to PRD review where it affects visible behaviour.
| Data and scope | TTL | Invalidation | Allowed staleness and requirement |
|---|---|---|---|
| Hashed static files, public CDN | One year immutable | New content hash | Old file valid for that release; F-12 |
| Public card catalogue, browser memory | One hour | Catalogue version change or refetch | Up to one hour for artwork/name only; F-02 |
| Published rule version, public cache by immutable version | One day | New version gets new key; active pointer never trusted from stale cache | None for version selection; F-05.AC5 |
| Verified business merchant aliases, shared KV | One hour | Versioned key on reviewed update | One hour for readable category only; never authoritative MCC; F-07 |
| Private Home/card/history, TanStack Query in memory | 15 seconds stale time | Invalidate on command, published revision change, focus and account switch | Up to 15 seconds for external intake, with as-of state; corrected facts and pending state update immediately; F-08/F-09 |
| Forwarding tags, session status and deletion markers | No shared cache | Primary D1 transaction | Zero intentional revocation staleness; F-01 |
No personal data in CDN, KV shared caches, localStorage or service-worker response caches. Clear Query state on sign-out and account switch. The cache key includes account identity, exact selection and revision. Private response headers remain no-store even when client memory caches the result. Refresh while visible every 15 seconds only when work is pending; back off when hidden or idle.
KV may serve stale values across locations; therefore it does not control authorization, revocation or budgets. If cache invalidation fails, immutable version keys prevent stale rules from being selected for a new calculation.
Retry and admission controls
Proposed consumer configuration: at most two retries, three total attempts, concurrency two, small batches of five, explicit acknowledgement after durable commit and a one-day ID-only DLQ. Failed messages never retry forever. No raw email bodies in queue payloads. Maximum job age is checked by consumers even if queue retention is longer. A DLQ replay requires an engineer's scoped command, remaining budget and a valid input version.
Outbox dispatch uses leases with expiry. A scheduled application job claims at most 100 rows per run and at most the approved hourly budget. Every job has a semantic deduplication key. A ten-minute reconciliation pass finds missed dispatches. Cleanup and account deletion have a reserved job budget so stopping intake does not stop privacy work.
Application timers and schedules described here are future product infrastructure, not local agent routines. Ravi implements and tests them in the backend deployment.
Failure behaviour
| Failure | User outcome | Recovery |
|---|---|---|
| R2 write fails | Capture not confirmed | No receipt; test SMTP retry or explicit rejection path |
| R2 succeeds, D1 receipt fails | Capture not confirmed | Orphan object expires; bounded sweep deletes it |
| Queue dispatch fails | Setup says pending | Outbox retries with the same intake ID |
| Duplicate delivery | One fact | Tenant unique key and command receipt |
| Parser cannot resolve card/currency | Review item, excluded from affected totals | Explicit user confirmation |
| Unknown MCC | Known amount stays captured; rewards unclear | Later evidence triggers revision |
| Calculation conflicts with a newer correction | Pending or last complete result labelled stale | Discard staged revision, run newest version |
| Quota or kill switch | Capture paused, no claim of complete spending | Preserve history; retry only after operator decision |
| Rule withdrawn | Affected estimate unavailable or pending | Reviewed rule replacement and explainable recompute |
Observability and targets
Proposed, unmeasured acceptance targets: p95 private read under 500 ms from Singapore after warm-up; accepted alert visible or explicitly held within 60 seconds; no projection/fact mismatch across corrections. Measure with real deployed bindings and redacted fixtures. Do not report these as achieved.
Metrics include intake accepted/rejected/held, duplicate candidates, oldest outbox age, queue attempts, DLQ count, stale revision age, CPU, D1 rows scanned/written, R2 bytes, daily AI allowance and deletion deadline breaches. Alerts fire on sustained queue age, any deletion deadline miss, quota reservation failure or usage outside the pilot envelope. Logs use opaque correlation IDs and reason codes only.
Environments and release
Testing and production have distinct D1, R2, queues, secrets, forwarding domains/tags and Google OAuth configuration. Testing uses synthetic or explicitly redacted fixtures, never copied production data.
Ravi owns backend CI/CD, protection and environments; Mira owns frontend CI/CD. Main requires backend-ci and frontend-ci, including Buf compatibility checks. The shared GitHub identity means no required GitHub approval count; Kai records CTO review (Kai): APPROVED and alone merges after all must-fix items close. Main deploys testing only. Production requires Tester sign-off, Kai approval and a release tag. Kai provisions separate scoped CI tokens as environment secrets.
Deploy compatible backend before frontend. Expand schema first. Smoke-test the previous client against the new backend. Roll back application releases without dropping new columns. A bad rule version is withdrawn through a new reviewed version, never edited in place. Restore drills verify reconciliation and deletion replay before production enablement.