LastVet Integration — Implementation Plan¶
Objective¶
Ship the Onset → LastVet sync described in ONSET_TO_LASTVET_SYNC_CONTRACT.md as the first (and only v1) patient-sharing path. This is the actual rollout plan. The earlier doc with this filename described a generic provider-network rollout that is no longer the plan — see ADR-002.
Scope summary¶
- Onset side: iOS connect/revoke UI, OAuth dance, encrypted token storage, sync scheduler, audit table, error/reconnect surfacing.
- LastVet side: OAuth server endpoints,
oauth_clients+oauth_authorization_grantstables,/me/syncsource-derivation, two new consent categories, provider-portal "Supplements & Protocols" view, "Connected apps" view in LastVet iOS. - Out of scope: civilian sharing, third-party-provider access to Onset, FHIR projection, snapshot/PDF share, second aggregator.
Phases¶
Phase 0 — Design freeze¶
Owner: Engineering lead.
Inputs: ADR-001, ADR-002, sync contract, LastVet API changes doc, connect/revoke doc, audit doc.
Exit criteria:
- All five docs reviewed and signed off by Onset engineering and LastVet engineering.
- LastVet legal/compliance sign-off that adding 'onset' as a source and adding supplements / protocol_adherence as consent categories does not require updates to LastVet's existing BAAs or partner agreements. (Likely no — Onset is patient-controlled outbound, not a covered third party.)
- iOS engineering confirms ASWebAuthSession + Keychain plan is implementable on iOS 17+.
- Threat model walkthrough: token theft, replay, refresh-token rotation drift, dual-revoke race conditions.
Phase 1 — LastVet side: OAuth server + /me/sync admit 'onset'¶
Owner: LastVet API team.
Work:
- Migration 020_oauth_clients_and_grants.sql.
- New last.vet-api/src/oauth/ module: token mint, verify, rotate, revoke. Reuse provider-session.service.ts as template.
- New routes: GET /api/v1/oauth/authorize, POST /api/v1/oauth/token, DELETE /api/v1/oauth/token. Authorize page can be a tiny dedicated Next.js route on last.vet-web, or a server-rendered page from the API directly — call to be made by LastVet team.
- Auth middleware: JWT path + existing header path coexist.
- /me/sync derives source from authenticated principal.
- Add supplements and protocol_adherence to CONSENT_CATEGORIES.
- Seed oauth_clients row for onset-ios (sandbox + production rows distinct).
Exit criteria:
- Postman collection runs end-to-end: authorize → exchange code → POST /me/sync with source=onset → row appears in health_record_items with source='onset' → revoke → next sync 401.
- Existing LastVet iOS regression suite passes unchanged (header auth still works).
- Sandbox environment available with onset-ios test client credentials.
Phase 2 — Onset side: iOS connect + initial sync¶
Owner: Onset iOS team.
Work:
- iOS Settings → Connected health records section, gated to veterans.
- ASWebAuthSession integration to LastVet's authorize endpoint.
- iOS Keychain storage for tokens, scoped WhenUnlockedThisDeviceOnly.
- Onset Supabase: patient_aggregator_connections table + RLS policies (patient can only see their own row).
- Onset Supabase: data_share_events table + RLS + append-only triggers.
- LastVetSyncService Swift module: build payload from legacy UserProtocolItem when protocolEngineV2 is off, or from StoredProtocol DTO items when on (same source_key shape), plus ProtocolCompletion + LabResult, POST to /me/sync, log to data_share_events.
- Initial sync on connect: full snapshot + last-90-day adherence.
- Error surfacing: reconnect_required state, banner copy.
Exit criteria:
- A test veteran on Onset can tap Connect, complete the OAuth flow against LastVet sandbox, and see their protocol items appear in LastVet sandbox's health_record_items.
- Disconnect from Onset works: token revoked at LastVet, no further syncs.
- Disconnect from LastVet sandbox works: Onset surfaces reconnect_required within one sync cycle.
- Onset audit table has a paired sync_attempted + sync_completed row per attempt.
Phase 3 — Background sync + provider-side surface¶
Owner: Onset iOS + LastVet provider-portal team.
Work:
- BGAppRefreshTask scheduled for 22:00 local; foreground catchup if last_synced_at > 24h.
- LastVet provider portal "Supplements & Protocols" view, gated by consent.
- LastVet iOS "Connected apps" surface.
- Onset Settings activity list, reading from data_share_events (last 90 days).
Exit criteria:
- A test provider on provider.last.vet sandbox sees the connected veteran's Onset protocol and adherence sparkline.
- Background sync runs reliably overnight; verified by a 7-day soak with N=10 internal testers.
- Activity list renders correctly across success, failure, and revoke cases.
Phase 4 — Closed pilot¶
Owner: Product + LastVet account management. Work: - Recruit 10–25 dual-app veterans (Onset + LastVet) who have a connected provider on LastVet. - Daily monitoring: success rate, p95 latency, denial events, support tickets. - Bi-weekly product reviews with at least three pilot veterans and one pilot provider.
Exit criteria: - Sync success rate ≥ 99% over a rolling 7-day window. - Connection success rate (initiated → connected) ≥ 95%. - Zero confirmed cases of revoke-not-honored (either direction). - No critical security findings. - At least three pilot providers have viewed Onset-sourced data and confirmed it's useful in their workflow.
Phase 5 — General availability for all veterans¶
Owner: Product.
Work:
- Remove pilot gate; LastVet card visible for all veterans on Onset.
- In-app announcement to existing veteran users.
- Migration playbook: how to migrate the seeded onset-ios client to a long-term key rotation schedule (RS256 + JWKS).
Exit criteria: none stricter than Phase 4 KPIs sustained for 30 days.
KPI targets¶
| KPI | Target |
|---|---|
| OAuth dance success rate | ≥ 95% |
| Sync success rate (7-day rolling) | ≥ 99% |
p95 /me/sync latency |
≤ 1.5s for ≤ 50-item payloads |
| Background sync reliability (synced within 25h of prior) | ≥ 98% |
| Revoke effective time (either direction) | ≤ 5 minutes |
100% of sync attempts logged in data_share_events |
yes |
Risk register¶
- OAuth implementation correctness. First time LastVet runs an OAuth server. Mitigation: HS256 with a single client until a second appears; lean on the existing
provider-session.service.tspatterns; explicit threat-model review in Phase 0; keep tokens short-lived. - Refresh token leakage on stolen device. Mitigation:
WhenUnlockedThisDeviceOnlykeychain attribute; refresh tokens rotate; explicit "Disconnect on lost device" instruction in support docs (the veteran can revoke from inside LastVet on a different device). - Patient confusion about three sharing relationships. A veteran on Onset can have (a) a practitioner via clinic, (b) a connected LastVet, (c) providers connected through LastVet. Mitigation: copy split — clinic surface uses "practitioner", LastVet surface uses "providers", and we never mix the words. Settings sections are visually separated.
- LastVet API outages cause silent data loss. Mitigation: failed syncs retry on next schedule, and the activity list shows the failure to the patient. We do not auto-purge unsynced data; it stays in Onset and re-syncs when LastVet is reachable.
- Schema drift between Onset and LastVet payload shape. Mitigation: pin the contract version in
X-Onset-Client-Version; LastVet reads it and applies a per-version validator. Version bump = explicit doc change + paired client/server release. - Veteran self-identification is wrong. Someone marks themselves a veteran in Onset who isn't, then connects to LastVet. LastVet's own auth is the source of truth — they can't successfully connect if LastVet rejects their identity. Mitigation: rely on LastVet's auth; Onset's
isVeteranflag is only used to decide whether to show the card. - Background sync battery / network impact. Mitigation: BGAppRefreshTask with reasonable cadence, batched payloads, exponential backoff on failures.
Backward compatibility¶
- Existing practitioner→patient adherence push (
shared_adherenceon Onset's clinic side) keeps running. The LastVet sync is an additional data path, not a replacement. - Patients who never connect to LastVet see no behavior change.
- LastVet's existing iOS app continues to work via its
Phase 1 bootstrap authheaders; the OAuth path is new and parallel.
What this plan replaces¶
The earlier doc at this path described a multi-provider OAuth/SMART-FHIR rollout. That's no longer the plan — see ADR-002. This file is now the actual rollout plan for the LastVet integration.