Skip to content

feat(mobile): push notifications MVP - #6269

Draft
brow wants to merge 24 commits into
mainfrom
codex/ios-push-internal
Draft

feat(mobile): push notifications MVP#6269
brow wants to merge 24 commits into
mainfrom
codex/ios-push-internal

Conversation

@brow

@brow brow commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This PR implements MVP, iOS-only, NIP-PL-compliant push notifications.

A relay with BUZZ_PUSH_ENABLED will send a push notification for any message that appears in the in-app Notifications tab.

Enablement and configuration

  • Bundle identifiers and push certificates

Enrollment flow

The first time the client first connects to a relay with BUZZ_PUSH_ENABLED:

sequenceDiagram
    autonumber
    participant App as Buzz iOS app
    participant iOS
    participant Relay as Buzz relay
    participant Attest as Apple App Attest
    participant Gateway as Push gateway

    App->>Relay: Fetch NIP-11 push capability
    Relay-->>App: Push profile, current relay public key, and limits

    par
        App->>iOS: Request notification permission
        iOS-->>App: Permission result
    and
        App->>iOS: Register for remote notifications
        iOS-->>App: Device token
    end

    App->>Gateway: Request installation challenge
    Gateway-->>App: Single-use challenge
    App->>Attest: Attest installation transcript
    Attest-->>App: Attestation proof
    App->>Gateway: Enroll device token and proof
    Gateway-->>App: Installation handle

    App->>Gateway: Request delegation challenge
    Gateway-->>App: Single-use challenge
    App->>Attest: Assert relay-key delegation
    Attest-->>App: Assertion
    App->>Gateway: Create delegation
    Gateway-->>App: Opaque endpoint grant

    App->>Relay: Publish encrypted push lease and filters
    Relay-->>App: Lease acknowledged
Loading

Push-time flow

relay → push gateway → APNs -> NSE -> Notification Center

Privacy characteristics

Known limitations

The APNs wake payload is intentionally constant and opaque: it contains no originating community or message identifier, in keeping with the implemented NIP-PL privacy design.

The Notification Service Extension must therefore reconnect to the relay and resolve eligible messages after each wake. Around overlapping wakes, timing boundaries, or resolution windows, notification presentation may occasionally omit an expected message or display a message more than once.

This best-effort behavior is deliberately accepted for the current implementation and will be measured during the internal rollout to determine whether the user experience is acceptable before any broader deployment; the implementation does not claim exactly-once presentation.

Summary

Add the relay-controlled iOS push notification MVP. This refreshes the end-to-end work from #2744 onto current main, retains only the best-effort delivery behavior needed from #3347, and includes exact-message notification routing from #6159. Lossless catch-up remains deferred. The earlier drafts remain unchanged for comparison.

Rollout authority lives on the relay. BUZZ_PUSH_ENABLED defaults to false; only explicitly enabled relay deployments advertise a valid push capability and participate in push delivery.

What changed

  • Add a closed, server-owned application profile registry for the canonical push.buzz.xyz gateway. A verified App Attest application ID selects the APNs topic, certificate identity, connection pool, and environment. The client cannot select an APNs topic.
  • Keep certificate-based APNs transport and a constant, opaque wake-up payload. The gateway receives no community, channel, conversation, message, title, or body metadata.
  • Add BUZZ_PUSH_ENABLED, defaulting to false, as the relay deployment gate. NIP-11 advertisement, lease acceptance, matching, workers, and gateway delivery all require the gate.
  • Bundle the push bridge and Notification Service Extension in every iOS build. The client uses the current authenticated relay's strictly validated NIP-11 nip-pl descriptor as its sole activation signal.
  • Fail closed before any permission, APNs registration, or enrollment side effect when the relay capability is absent, malformed, or unreachable. After a valid capability, display authorization and APNs registration start independently: denial or authorization-request failure does not gate device-token enrollment or lease publication. Enrollment also revalidates the descriptor before using it.
  • Limit the MVP to message kinds 9, 40002, 45001, and 45003, using the Notifications-tab message eligibility rules.
  • Resolve notifications in the extension by fetching from the relay with NIP-98 authorization, then validating the Nostr event ID and signature before rendering plaintext event content.
  • Route notification taps to the exact resolved and signature-verified message. Channel routing remains an observable last-resort fallback during manual validation.
  • On community removal, tombstone the scoped relay lease and remove that community's extension snapshot and key state. Do not revoke a gateway delegation that another community on the same relay identity may still use.
  • Add gateway, relay, database, Flutter, Swift, Xcode-project, Helm, and rollout/latency coverage.

Rollout and rollback

Rollout remains internal-only:

  1. Configure the canonical gateway with the closed dogfood profile and its certificate identity.
  2. Set BUZZ_PUSH_ENABLED=true only on selected internal relay deployments.
  3. Distribute a correctly signed internal iOS build. Once authenticated to an enabled relay, the client validates the advertised capability, then independently requests display authorization and registers with APNs. APNs token acquisition, gateway enrollment, and lease publication continue even if display authorization is denied or fails.

Every signed iOS artifact now includes the app push entitlements and embedded Notification Service Extension. The private release configuration must sign the application and extension with their separate provisioning profiles and map both bundle identifiers during export.

To roll back, set BUZZ_PUSH_ENABLED=false on relays. They stop advertising push, accepting leases, matching events, running workers, and sending deliveries. Clients connected to a relay without a valid capability remain inactive.

Server metrics cover enrollment, delegation, lease acceptance, match and delivery outcomes, APNs status, queue age, and delivery latency. NSE resolution outcomes and exact-message versus channel-fallback taps are manually counted during dogfood validation until privacy-preserving client telemetry is designed.

Validation

  • Repository pre-push checks, including formatting, analysis, file-size gates, Rust tests, the full 1,759-test Flutter suite, and desktop-native checks
  • Focused Flutter push, authentication, community lifecycle, channel, and presentation-cache tests
  • BuzzPushKit package tests: 85 passed
  • Runner iOS Simulator tests, including push response routing and Communication Notifications presentation
  • Unsigned iOS Simulator build with the app and embedded Notification Service Extension
  • Xcode-project semantic validation and relay/gateway Helm contract tests

The prior development-stack hardware demo proved real sandbox APNs delivery and visibly fetched, signature-verified notification content on an iPhone 12 mini using the normal mobile development identity. Exact-message routing is covered by the refreshed implementation and tests. Signed dogfood-artifact validation, including a manual notification tap into the intended message, is the next release-validation step after the change reaches main and an immutable internal candidate reaches Mobile Releases/Comp Portal.

Provisioning note

This change adds the aps-environment, App Attest, application-group, keychain-access-group, and Communication Notifications requirements. Before the first internal candidate, the private release configuration must provide the application and Notification Service Extension identifiers, capabilities, and separate provisioning profiles. Local dogfood signing is intentionally not a merge blocker for this source PR.

Signed-off-by: Tom Brow <tomb@squareup.com>
@brow
brow force-pushed the codex/ios-push-internal branch from c7a089a to d69b89c Compare August 18, 2026 23:25
@brow brow changed the title Add internal iOS push notification MVP feat(mobile): push notifications MVP Aug 19, 2026
brow added 3 commits August 20, 2026 13:48
Signed-off-by: Tom Brow <tomb@block.xyz>
…ifications-pr

* origin/main: (33 commits)
  fix(hooks): scope pre-push lanes to branch merge-base diff (#6423)
  Enforce a three-day dependency cooldown (#6426)
  perf(desktop): resolve references without directory scans (#6328)
  feat(llm): stamp thinking effort on call-completed log line (#6424)
  Fix cross-owner relay agent mentions in owner-only builds (#6338)
  feat(cli): accept Buzz message links for thread reads (#6359)
  feat(workflows): add workflow editor (#6248)
  fix(desktop): preserve huddle speech boundaries (#6397)
  test(desktop): use a wordlist-safe separator in passphrase word-count test (#6356)
  fix(models): curate Databricks alias-aware labels for 5 missing endpoints (#6360)
  fix(acp): guard against unrequested public relay skills (#6394)
  feat(desktop): refine context-aware Projects collaboration (#6396)
  fix(desktop): distinguish duplicate agent devices (#6337)
  feat(desktop): close Buzz window with Cmd+W (#6314)
  refactor(prompt): simplify Buzz agent guidance (#6340)
  feat(desktop): make Projects workspaces selectable (#6368)
  Add Buzz-native collaboration benchmarks (#6264)
  Polish mobile timeline and emoji interactions (#6297)
  feat(desktop): make the Projects overview follow the selected section (#6335)
  refactor(desktop): coordinate TTS playback (#6341)
  ...

Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
@lezdoors

Copy link
Copy Markdown

Independent validation on 2026-08-20:

  • swift test --package-path mobile/ios/BuzzPushKit: 54 tests passed.
  • Focused Flutter push/auth/community/deep-link suite: 57 tests passed.
  • scripts/test-ios-pbxproj-semantics.py and scripts/test-mobile-worktree-overrides.sh: passed.
  • Push-enabled unsigned simulator build succeeded and embedded PlugIns/NotificationService.appex.
  • git diff --check origin/main...8e7f5dc: clean; current origin/main is an ancestor of the PR head.

Operational blockers before dogfood E2E:

  1. POST https://push.buzz.xyz/v1/installations/challenges currently returns HTTP 503.
  2. The available Mac has no Apple code-signing identity; the private signed dogfood pipeline/profile owners must produce the physical-device artifact.
  3. PR remains draft; available GitHub identity lacks MarkPullRequestReadyForReview permission.

No merge, deployment, credential change, or TestFlight release was performed.

@lezdoors

Copy link
Copy Markdown

Owner handoff for the validated blockers:

  • @brow: please mark this PR ready and request @block/buzz-oss-team review when you consider the implementation ready. The available lezdoors identity cannot change draft state.
  • @tlongwell-block: after merge, the bb-public rollout must publish/attest the updated gateway image, release the chart, and restore POST /v1/installations/challenges from the current HTTP 503 using private environment values and secret-manager mounts.

Please keep APNs/App Attest credentials out of GitHub comments, manifests, logs, and Buzz.

brow and others added 20 commits August 21, 2026 21:09
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
…ifications-pr

* origin/main: (38 commits)
  fix(composer): preserve caret when inserting mentions mid-message (#6531)
  chore(deps): update rust crate async-trait to v0.1.92 (#6094)
  chore(deps): update dependency sonner to v2.0.8 (#6093)
  chore(deps): update rust crate http-body-util to v0.1.4 (#5452)
  chore(deps): update rust crate http to v1.4.2 (#5451)
  chore(deps): update rust crate futures-util to v0.3.33 (#5448)
  chore(deps): update rust crate futures to v0.3.33 (#5445)
  chore(deps): update dependency @tauri-apps/api to v2.11.1 (#5444)
  chore(deps): update ubuntu:24.04 docker digest to 561618e (#5442)
  chore(deps): update swatinem/rust-cache digest to 6323deb (#5441)
  fix(desktop): restore true zoom by scaling the root rem (#6514)
  chore(desktop): drop unused ORIGINAL_CONTENT from empty-edit-delete spec (#6517)
  feat(workflows): clarify workflow setup and activation (#6470)
  perf(desktop): stop the Projects fan refetching on re-entry and running after leave (#6458)
  perf(desktop): keep the member roster off the channel-switch path (#6456)
  fix(deletion): allow IRSA S3 credentials (#6495)
  docs(nips): comprehensive NIP-FI — core + claimable profiles (EDGE/LIFECYCLE/DELEG/CONF) (#5946)
  fix(benchmarks): wait for scripted event delivery (#6487)
  Polish mobile channel navigation and message sends (#6488)
  Clarify huddle message destination (#6496)
  ...

Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
…ifications-pr

* origin/main:
  fix(desktop): emit singular `mention` feed category so alerts route correctly (#6665)
  fix(mobile): recover stale and shuffled messages (#6691)
  feat(mobile): browse and join open channels (#6243)
  show mention counts in channel notifications (#6696)
  fix(desktop): hide selection formatting tray on composer right-click (#6683)
  fix(desktop): stabilize members dialog scrolling (#6670)
  fix(desktop): keep member runtime status off the UI thread (#6445)
  perf(desktop): persist channel heads, collapse thread reads and reply sends (#6572)
  Downgrade desktop Huddles to audio protocol v2 (#6610)
  Polish Huddle participant interactions (#6312)
  Downgrade mobile Huddles to audio protocol v2 (#6558)
  perf(desktop): make the Projects surface render-cheap (#6460)
  refactor(acp): clarify agent prompt sections (#6501)
  Add mobile Huddles voice MVP (#6056)
  feat(desktop-messages): keep agents addressed across messages (#6315)
  fix(desktop): remove Buzz entity link previews (#6512)

Signed-off-by: Tom Brow <tomb@block.xyz>

# Conflicts:
#	mobile/lib/features/channels/channels_provider.dart
#	mobile/lib/shared/auth/auth_provider.dart
#	mobile/lib/shared/community/community_provider.dart
#	mobile/test/shared/auth/auth_provider_test.dart
#	mobile/test/shared/community/community_provider_test.dart
#	scripts/mobile-worktree-overrides.sh
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
…ifications-pr

* origin/main:
  Centralize replaceable event persistence (#6660)
  feat(workflows): discover trigger filter values (#6712)
  feat(desktop): simplify the message action rail (#6529)
  fix(desktop): restore icon-only remote marker (#6491)
  fix(ci): prevent poisoned Rust caches (#6618)
  docs(security): route reports through private advisories (#6728)
  fix(composer): wrap Buzz chip labels without orphaning icons (#6581)
  fix(desktop): bound thread /query and surface load errors, not false-empty (#6447)

Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants