# Dispatch specification ## 1. Purpose Dispatch is a private notification relay. A trusted computer calls a Linux server, the server delivers an Apple Push Notification (APNs), and the iOS app displays the notification title, message, and originating machine. The repository contains: - `server/`: Swift 6.3 Linux HTTP service. - `client/`: small Swift command-line wrapper around the HTTP API. - `app/`: SwiftUI app targeting iOS 26 or newer. ## 2. Initial scope The first usable version supports: 1. An operator provisions a separate bearer token for every sending machine. 2. The iOS app registers its APNs device token with the server using a separate enrollment secret. 3. An authenticated machine submits a title and body. 4. The server derives the displayed machine name from the bearer token record, never from an untrusted request field, and sends the notification to all active registered app installations. 5. The app displays the ordinary iOS notification and keeps a small in-memory list of notifications received while it is open. Delivery acknowledgements, multiple users, a web administration UI, silent remote commands, and end-to-end encryption are outside the initial scope. ## 3. Security model Version 1 uses high-entropy per-machine bearer tokens over HTTPS. Public-key signed requests would add canonicalization, clock-skew, nonce, and key-management complexity without protecting against a compromised client more effectively for this use case. Tokens are easy to generate, rotate, and revoke independently. - Generate at least 32 random bytes for each token. - Store only a SHA-256 digest of tokens in persistent storage (planned before production deployment); the initial implementation reads token-to-machine mappings from environment configuration. - Terminate TLS at the service or a trusted reverse proxy. Plain HTTP is only for loopback development. - Use constant-time digest comparison. - Never log bearer tokens, enrollment secrets, or APNs credentials. - Apply request-size and rate limits before exposing the service publicly. - APNs payloads are not end-to-end encrypted. Do not send secrets in a notification. ## 4. Configuration The server reads: - `DISPATCH_HOST` (default `127.0.0.1`) - `DISPATCH_PORT` (default `8080`) - `DISPATCH_MACHINE_TOKENS`: comma-separated `machine=token` entries - `DISPATCH_ENROLLMENT_TOKEN`: secret accepted only by the device registration route - `DISPATCH_APNS_MODE`: `log` initially; `live` is reserved for APNs integration - `DISPATCH_APNS_TOPIC`: iOS bundle identifier - `DISPATCH_APNS_TEAM_ID`, `DISPATCH_APNS_KEY_ID`, `DISPATCH_APNS_PRIVATE_KEY_PATH` The initial in-memory device registry is deliberately development-only. Persistent storage and live APNs delivery are the next server milestone. ## 5. HTTP API All request and response bodies are UTF-8 JSON. Unknown JSON fields are ignored. Errors have the shape `{"error":{"code":"...","message":"..."}}`. ### `GET /health` Unauthenticated liveness endpoint. Returns `200` and `{"status":"ok"}`. ### `POST /v1/devices` Registers or refreshes an app installation. Authentication: `Authorization: Bearer `. Request: ```json { "deviceToken": "hexadecimal APNs token", "environment": "development" } ``` `environment` is `development` for an Xcode-installed app and `production` for TestFlight. Returns `201` for a new token or `200` for a refresh. ### `DELETE /v1/devices/{deviceToken}` Uses enrollment authentication and disables that installation. Returns `204`. ### `POST /v1/notifications` Authentication: a configured per-machine bearer token. Request: ```json {"title":"Build complete","body":"dispatch/main passed"} ``` Constraints: title 1–100 characters; body 1–1000 characters. The server adds the authenticated machine name. A successful response is `202`: ```json {"id":"UUID","acceptedDevices":1} ``` The APNs payload is conceptually: ```json { "aps": { "alert": {"title":"Build complete","subtitle":"workstation","body":"dispatch/main passed"}, "sound":"default" }, "notificationId":"UUID", "machine":"workstation" } ``` `202` means accepted for delivery, not displayed. APNs and iOS do not guarantee delivery. ## 6. iOS app - Deployment target: iOS 26.0. - Requests visible-notification authorization in response to an explicit button. - Registers with APNs and uploads the resulting token to the configured server. - Displays title, body, and authenticated machine name. - Handles foreground notifications via `UNUserNotificationCenterDelegate`. - Does not use silent/background pushes or execute arbitrary commands. The server URL and enrollment token are development settings in the initial app. They must move to managed configuration or an enrollment flow before wider use. ## 7. Reliability and operations - Notification submission has a server-generated id for observability. - Clients may retry transport failures. Future idempotency-key support will make retries safe against duplicate notifications. - The server will remove device tokens when APNs reports them invalid. - Health checks do not disclose configuration or registered-device counts. ## 8. Acceptance criteria for the first milestone - The server builds and its route/authentication tests pass on Linux. - An unknown or missing machine token receives `401`. - A valid machine token produces a notification carrying its configured machine name. - Invalid titles, bodies, environments, and APNs token formats receive `400`. - The client can submit a notification with one command. - The app can request permission, register, upload its device token, and render a foreground notification.