App/server hardening follow-ups (loadReport currency, reconnect debounce, token->Keychain, idempotency namespacing) #6

Closed
opened 2026-06-29 16:11:49 -07:00 by agent-int · 1 comment
Collaborator

Design context: docs/design/server-split.md + tracking issue seltzerhq/pecun#18 (the code lives here in nic/commons).

Accumulated app/server hardening follow-ups (from the P3b/P4 reviews + the P5/P6 session), none blocking:

  • App loadReport: thread report currency + priorNetWorth through the gRPC path (currently dropped).
  • Live-push reconnect: resubscribe from the last seq (not 0) + debounce reconnects (avoid replaying
    the whole change_log per entry on a flap).
  • Move the gRPC bearer token to the Keychain (currently env/UserDefaults).
  • Namespace idempotency keys by principal (cross-principal content_hash collision is first-writer-wins
    today -- a documented decision; revisit).
  • Plaintext non-loopback guard for the gRPC client endpoint.
Design context: `docs/design/server-split.md` + tracking issue **seltzerhq/pecun#18** (the code lives here in nic/commons). Accumulated app/server hardening follow-ups (from the P3b/P4 reviews + the P5/P6 session), none blocking: - [ ] App `loadReport`: thread report currency + priorNetWorth through the gRPC path (currently dropped). - [ ] Live-push reconnect: resubscribe from the last seq (not 0) + debounce reconnects (avoid replaying the whole change_log per entry on a flap). - [ ] Move the gRPC bearer token to the Keychain (currently env/UserDefaults). - [ ] Namespace idempotency keys by principal (cross-principal content_hash collision is first-writer-wins today -- a documented decision; revisit). - [ ] Plaintext non-loopback guard for the gRPC client endpoint.
Author
Collaborator

PR #12 (merged) landed four of the five follow-ups:

  • App loadReport: report currency + priorNetWorth now threaded through the gRPC path (added currency + prior_net_worth to GetReportRequest).
  • Live-push reconnect: resubscribes from the last seq (not 0) + debounces reconnects with capped exponential backoff.
  • gRPC bearer token moved to the Keychain (migrates the legacy UserDefaults value once; env var still wins for dev).
  • Namespace idempotency keys by principal -- deferred, needs a human decision.
  • Plaintext non-loopback guard for the gRPC client endpoint (GRPCEndpoint.isLoopback).

Leaving this issue open for the idempotency-namespacing item. It's a deliberate, documented semantic (first-writer-wins on a shared content_hash), flagged in WriterExecutor.postTransaction: it only matters once clients choose their own idempotency keys -- today keys are server-generated per proposal -- so changing money-path dedup behavior shouldn't happen unilaterally. Decision needed: namespace the hash by principal now, or keep as-is until clients pick keys.

Full gate was green at merge: 188 SwiftPM tests + the Xcode app oracle.

PR #12 (merged) landed four of the five follow-ups: - [x] App `loadReport`: report currency + priorNetWorth now threaded through the gRPC path (added `currency` + `prior_net_worth` to `GetReportRequest`). - [x] Live-push reconnect: resubscribes from the last seq (not 0) + debounces reconnects with capped exponential backoff. - [x] gRPC bearer token moved to the Keychain (migrates the legacy UserDefaults value once; env var still wins for dev). - [ ] Namespace idempotency keys by principal -- **deferred, needs a human decision.** - [x] Plaintext non-loopback guard for the gRPC client endpoint (`GRPCEndpoint.isLoopback`). Leaving this issue open for the idempotency-namespacing item. It's a deliberate, documented semantic (first-writer-wins on a shared `content_hash`), flagged in `WriterExecutor.postTransaction`: it only matters once clients choose their own idempotency keys -- today keys are server-generated per proposal -- so changing money-path dedup behavior shouldn't happen unilaterally. Decision needed: namespace the hash by principal now, or keep as-is until clients pick keys. Full gate was green at merge: 188 SwiftPM tests + the Xcode app oracle.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
nic/commons#6
No description provided.