Push activation: spec enhancements (token variants, updateToken, unified PATCH sync) + full UTS test suite - #513
Draft
paddybyers wants to merge 3 commits into
Draft
Push activation: spec enhancements (token variants, updateToken, unified PATCH sync) + full UTS test suite#513paddybyers wants to merge 3 commits into
paddybyers wants to merge 3 commits into
Conversation
…H sync Extend the push spec to cover behaviour that has shipped in SDKs without spec backing, and to unify the registration sync: - RSH2f-RSH2f3: specify Push#updateToken(token: PushDeviceToken), the API through which the application delivers rotated or additional platform tokens (established by ably-js #2267): validation and activation-required guards (40000), then apply-to-recipient + persist + GotPushDeviceDetails per RSH8g. - PCP3a: specify the apnsDeviceTokens token-slot map on APNs recipients, as shipped by ably-cocoa. Well-known slots: default (equivalent to the legacy deviceToken attribute), location, pushToStart. Slot names are open/extensible; libraries must pass unknown slots through. - RSH8l-RSH8l4: device-side token variants. Variants are recipient-related attributes for RSH8a persistence; any variant change is an RSH8g push- details change whose sync carries the complete recipient; RSH8i verifies per variant. Non-normative note excluding per-activity Live Activity update tokens (broadcast channels make them unnecessary; the reference to RSH1e depends on the AIT-794/live-activity branch). - PDT1-PDT4: the PushDeviceToken type (transportType, token, optional apnsTokenType), plus IDL for it and updateToken. - RSH3a2a3/RSH3d3b: unify the registration sync on PATCH. RSH3d3b becomes the canonical sync (complete recipient + other changed attributes, eg a late-set clientId per RSH8d); the RSH3a2a3 re-activation validation performs the same sync, with the previous full-body PUT retained as a permitted legacy equivalent (as implemented by ably-java/ably-cocoa). - RSH8f: generalised to accept clientId from registration-sync responses, noting the dependency on the endpoint returning the updated DeviceDetails (to be confirmed server-side). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… types) Add portable unit test specs for the push activation half of the RSH spec, previously untested (completion matrix rows were blank): - rest/unit/helpers/mock_push_platform.md: the portable push platform primitives (PushKeyValueStorage getItem/setItem/removeItem, requestToken, PushPlatformConfig, PushDeviceToken incl. apnsTokenType), the standard ably.push.* storage keys and value encodings, and the MockPushStorage / token-provider mocks (dump/seed/fault flags/onOperation with local-array capture, consistent with the transport-mock conventions). Installation is via install_push_platform(), mirroring install_mock(); the injection mechanism is SDK-specific by design. - rest/unit/push/: push_activation_state_machine.md (23 tests — RSH2a/b, full RSH3 transition matrix, both registrar branches, RSH3d2c1 classification, rollbacks), local_device.md (6 — RSH8 attributes and clientId lifecycle), push_activation_persistence.md (6 — RSH3h, RSH8a1 discard, RSH3a2c, RSH8b/c timing), push_activation_event_queue.md (3 — RSH4 queue semantics incl. the spec's worked example, RSH5 ordering), push_update_token.md (12 — RSH2f guards, RSH3d3 PATCH sync, callbacks, races, RSH8l2 token variants), push_device_auth.md (5 — RSH6a/b and the RSH1b/c own-device clauses). - rest/unit/types/push_types.md (7 — PCD, PCP incl. PCP3a, PCS). Tests are black-box (public API + held mock requests; state observed via requests and settled persisted state). Registration syncs follow the unified RSH3a2a3/RSH3d3b PATCH (with the legacy PUT allowance); the token-variant tests anchor to the pending spec extension (RSH2f, RSH8l, PCP3a, PDT1-4). Validated by deriving all 62 tests in ably-js (test/uts, branch feat/uts-push): full UTS unit suite 1428 passing / 73 pending, deviations recorded in ably-js deviations.md. Validation surfaced one spec-test bug, fixed here: the deregistration-rollback tests now inject a non-retriable 4xx instead of a 500, which would trip RSC15 fallback-host retries. Also update docs: completion-status rows for RSH2-RSH8 and push types (+ PDT), README structure/counts, and freshness fixes in writing-test-specs.md and proxy.md (proxy msgpack support per uts-proxy v0.3.0 replaces the stale "SDK doesn't implement msgpack" convention; File Organization now defers to README; push platform mock added to the mock-infrastructure patterns). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the integration and proxy tiers of the push activation UTS suite, and clarify RSH6a following empirical validation: - rest/integration/push_activation.md (7 tests): real-sandbox activation round-trips using storage pre-seeded with an ablyChannel recipient (per RSH3a2c the platform requestToken is never consulted, so no FCM/APNs credentials are needed): registration (POST body, server-issued deviceIdentityToken), device-authenticated subscribeDevice by a fresh client over persisted state (doubles as the RSH6a server-acceptance check, made load-bearing via a push-subscribe-capability key), deactivation, re-activation sync, end-to-end delivery of an admin publish as an __ably_push__ message, registration rejection for an invalid platform, and the updateToken fire-and-forget sync. - rest/integration/proxy/push_activation.md (6 tests): fault injection on the registration endpoints via uts-proxy — RSH3d2c1 status classification (401/40005 classified as Deregistered purely client-side, proven by a direct admin client observing the registration survive the intercepted DELETE), deregistration-failure rollback and retry, registration failure and retry, the RSH4 queue exercised against a delayed registration, and sync-failure recovery via updatedCallback. - specifications/features.md: RSH6a now states the header value is the raw deviceIdentityToken, not base64-encoded, with a non-normative note that the historical base64 encoding (ably-java, ably-cocoa) is tolerated by the service but not conformant. Verified against the sandbox: raw and base64 are both accepted; garbage values are rejected (400, 40005). - Both specs carry a "known server issue" note for the ablyChannel-recipient PATCH bug fixed by ably/realtime#8591: the sync-exercising tests are specified against the fixed behaviour, and derived tests skip with a reason referencing that PR until it deploys. Derived and validated in ably-js (integration 6 passing + 1 pending; proxy 3 passing + 3 pending, deviations recorded) and ably-dart (integration + proxy: 10 passing + 3 pending-deploy skips; fully conformant otherwise). Validation also surfaced two ably-dart implementation bugs (fixed there) and the server-side PATCH bug above. Also: completion-status rows and README structure/counts updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related workstreams in three commits, developed and validated together: a set of push-spec enhancements, and the complete portable (UTS) test suite for the push activation half of the RSH spec — previously untested (the completion-matrix rows for RSH2, RSH3, RSH4–5, RSH6, RSH8 and the push types were blank).
1. Spec enhancements (
specifications/features.md)Push#updateToken(RSH2f–RSH2f3, new)Specifies the API through which the application delivers a rotated or additional platform token — established by ably-js in ably/ably-js#2267 but previously unspecced. Covers the validation guard and activation-required guard (both 40000, no side effects), and the apply-to-recipient + persist +
GotPushDeviceDetails(RSH8g) flow. This is the delivery mechanism for FCMonTokenRefresh, APNs re-registration, and ActivityKit token updates.APNs token slots on the recipient (PCP3a, new)
iOS has multiple flavours of device token — default, location-push, and Live Activity push-to-start. ably-cocoa has already shipped support (
ARTAPNSDeviceTokenTypes, recipient carrying anapnsDeviceTokensmap) with no spec backing — and the nested map doesn't fit PCP3's "map of string key/value pairs" wording. PCP3a codifies the shipped wire shape:apnsDeviceTokens(slot name → token) alongside/instead of the legacydeviceToken(≡apnsDeviceTokens.default); well-known slotsdefault/location/pushToStart, with slot names open/extensible (a token "type" is a registration slot/delivery route, not a capability set — the default token serves alert + background pushes selected per-request viaapns-push-type, while other routes are forcibly distinct tokens; an open namespace absorbs future flavours, e.g. per-attributes-type push-to-start tokens whose granularity Apple doesn't document, without spec surgery).Device-side variant lifecycle (RSH8l–RSH8l4, new)
Variants are
recipient-related attributes for RSH8a persistence; registering/updating any variant is an RSH8g change whose sync carries the complete recipient (RSH8l2); RSH8i verification applies per variant; RSH8l4 (non-normative) records the deliberate exclusion of per-activity Live Activity update tokens — their lifetime is an activity instance, and updates flow over APNs broadcast channels (RSH1e), so only the device-scoped push-to-start token needs registering.PushDeviceTokentype (PDT1–PDT4, new){transportType: "fcm"|"apns"|"web", token, apnsTokenType?}+ IDL for it andupdateToken.Unified PATCH registration sync (RSH3a2a3, RSH3d3b, RSH8f amended)
With RSH8l2 requiring every sync to carry the complete recipient, the payload distinction between the RSH3a2a3 re-activation PUT and the RSH3d3b changed-fields PATCH mostly disappears. RSH3d3b becomes the canonical sync (complete recipient + other changed attributes, e.g. a late-set clientId per RSH8d — resolving a pre-existing ambiguity in the RSH8e flow); RSH3a2a3 performs the same sync, with the full-body PUT retained as a permitted legacy equivalent — ably-java/ably-cocoa remain conformant unchanged, and ably-js (which PATCHes) becomes conformant. RSH8f generalised to accept a clientId from sync responses (verified: the PATCH response returns the full
DeviceDetails).RSH6a clarification
The
X-Ably-DeviceTokenvalue is the raw token, not base64-encoded, with a non-normative note that the historical base64 encoding (ably-java, ably-cocoa) is tolerated by the service but not conformant. Verified empirically against the sandbox: raw and base64 both accepted; garbage rejected (400/40005).2. UTS test suite (10 spec files, 75 tests)
rest/unit/helpers/mock_push_platform.md— the portable push platform primitives (PushKeyValueStoragegetItem/setItem/removeItem+requestToken, matching the ably-js React Native seam from [AIT-850] feat: react native push plugin ably-js#2262), the standardably.push.*storage keys and value encodings,install_push_platform()(consistent with the transport-mock conventions; injection mechanism SDK-specific by design), and theMockPushStoragemock (dump/seed/fault flags/onOperationwith local-array capture).push_activation_state_machine.md, 23), LocalDevice attributes + clientId lifecycle (local_device.md, 6), persistence/rehydration incl. RSH8a1 corruption discard (push_activation_persistence.md, 6), RSH4/RSH5 queue semantics incl. the spec's own worked example (push_activation_event_queue.md, 3), updateToken incl. the RSH8l2 variant tests (push_update_token.md, 12), device auth incl. the RSH1b/RSH1c own-device clauses (push_device_auth.md, 5), and the PCD/PCP/PCS types (types/push_types.md, 7). Black-box style throughout: public API + held mock requests; state observed via requests and settled persisted state.ablyChannelrecipient (rides RSH3a2c, so no FCM/APNs credentials needed), including end-to-end delivery of an admin publish and a device-auth server-acceptance check made load-bearing via a push-subscribe-capability key.Validation
Every test is derived and validated in ably-js (full UTS unit suite: 1428 passing / 73 pending; genuine non-conformances recorded in its
test/uts/deviations.md— 13 push entries) and ably-dart (fully conformant implementation of the extended spec, including the token variants: unit 1274 passing, integration + proxy 10 passing + 3 pending-deploy skips). Validation surfaced and fed back: one spec-test bug (5xx fault injection tripping RSC15 fallback retries — now a non-retriable 4xx), two ably-dart implementation bugs (fixed), several ably-js conformance gaps (recorded as deviations, fix PRs planned), and one server-side bug — the registration-update PATCH rejected any device whose stored recipient wasablyChannel— fixed in ably/realtime#8591; the affected derived tests are skipped pending its sandbox deploy (the specs carry "known server issue" notes).Review points
AIT-794/live-activitybranch — this PR should land after it, or the cross-ref be softened.api-docstrings.mdentries forupdateToken/PushDeviceToken, and dedicated PDT type tests inpush_types.md.🤖 Generated with Claude Code