Route Device push tokens through Secrets

Status summary: implementation and local verification are complete. Device-owned ciphertext and its crypto module are replaced by a Secret path, and Expo delivery uses JSON-body substitution. Existing enrollments deliberately do not migrate and must re-register; preview and trace verification remain.

Outcome#

New Device enrollments store Expo push tokens as write-only Secret material pinned to Expo. Device state retains only the Secret path, and notification delivery sends the token through the Secret cell without exposing plaintext to project code.

Checklist#

  • Store each newly enrolled Expo token at a stable device-specific Secret path pinned to https://exp.host. Enrollment creates or updates /secrets/devices/<deviceId>/expo-push-token through the Secret Durable Object.
  • Replace Device token ciphertext state/events with the Secret path; do not add migration or legacy compatibility code. The contract has one Secret-backed representation; the obsolete crypto module and its test are deleted.
  • Send Expo requests through Secret egress using the opted-in JSON template added by the parent PR. The Expo adapter emits an exact getSecret(...) value and Device delegates the request to that Secret cell.
  • Preserve enrollment authentication, token rotation, revocation, retry/receipt behavior, and safe public projections. Credential writes are serialized and carry the exact Secret revision so stale invalid-token results cannot clear a newer rotation.
  • Treat project access as the Device authorization boundary; retain ownerId only as provenance. The RPC target establishes project access; Device mutation methods no longer add an enrolling-owner ACL.
  • Cover enrollment, rotation, revocation, invalid-token handling, and non-disclosure with focused and real-worker tests. 53 focused specs pass; the 9-test real-worker egress/device run proves public enrollment, rotation, request append, and journal non-disclosure.
  • Verify typecheck, lint, formatting, tests, preview behavior, and coherent traces without unexplained errors. Typecheck, lint, format, focused unit tests, and local real-worker tests pass; preview evidence remains.

Breaking-change policy#

  • Devices enrolled before this change may become unusable and must enroll again.
  • No processor migration, dual-read, compatibility event, or fallback decryption path is permitted.
  • The new state/event contract should be simpler because only the post-change representation exists.

Stack#

  • Base branch: device-push-secret-egress
  • Base PR: #2145
  • #2145 itself stacks on mobile PR #2143.

Implementation log#

  • 2026-07-20: split from the generic JSON-body Secrets capability at the user's request. Existing project state is explicitly disposable, so migration code would add complexity without product value.
  • 2026-07-20: implemented the single Secret-backed state/event contract, removed Device crypto entirely, retained revision-safe invalidation, and passed focused unit plus local real-worker verification.

Was this page helpful?