Conversation
Uncertified candidates have been going to the production OTA channel because there was nowhere else to validate them on real hardware against real member data. AN Preview is that place: a separately installable founder-only build that sits beside Production on the same iPhone and talks to the same production server. Isolation is mostly free. No keychain-access-groups entitlement is declared anywhere, so Keychain items land in the default per-bundle-ID access group and RSA keys, site tokens and the push installation id separate automatically despite sharing hardcoded service names. AsyncStorage, cookies, the expo-updates database and @ClientId are all inside the app sandbox. Preview therefore mints its own User API client identity and receives its own key; revoking one leaves the other valid. Three things had to differ explicitly and now do: the URL scheme, because two installed apps claiming adjusternetwork:// is undefined on iOS and the auth callback could reach the wrong app; the auth redirect, which follows it; and the App Group, which is a genuinely shared container. Channel isolation stays structural. The channel is compiled into Expo.plist by the Xcode build phase and sent as the expo-channel-name header, so a binary can request exactly one channel and an unrecognised channel still fails the build. V1 carries neither aps-environment nor associated-domains. Push to Preview is blocked by the server's pinned APNs topic, so Preview registers no device rather than creating undeliverable registrations, and the server AASA lists only the production app id. Both omissions are deliberate, and they keep the Preview App ID to a single capability. Two release gates were made stronger rather than merely updated: the callback scheme check now verifies the resolved per-configuration values instead of one literal, and the iPhone-only check asserts the property of every configuration instead of counting them, so adding a configuration cannot pass by keeping the number the same. Also ports a one-line fix for a real-clock race in rateLimitResilience that was written for the superseded PR #20 and never landed: Date.now() was read twice inside one assertion and could straddle a millisecond. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fk48MTrNBBSZeLvcJmc8SR
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.
Implements the approved Preview lane. Two manual prerequisites remain (Apple portal + one server setting) — this cannot be installed until those are done.
Identity
org.adjusternetwork.apporg.adjusternetwork.app.preview…app.ShareExtension…app.preview.ShareExtensionadjusternetwork://anpreview://adjusternetwork://adjusternetwork.org/auth_redirectanpreview://adjusternetwork.org/auth_redirectgroup.org.adjusternetwork.appgroup.org.adjusternetwork.previewproductionpreviewProof from the actual built binary
Not inference — this is
plutiloutput from the compiled Preview app:xcodebuild -showBuildSettingsconfirms Release still resolvesorg.adjusternetwork.app,adjusternetwork,production, and the original entitlements file.Isolation
Automatic from the bundle ID, because no
keychain-access-groupsentitlement is declared anywhere: Keychain (RSA keys, site tokens, push installation id), AsyncStorage, cookies, expo-updates state,@ClientId, push tokens. Preview mints its own User API client identity and gets its own key — revoking one leaves the other valid.Explicitly separated because they cannot be: URL scheme (two apps claiming one scheme is undefined on iOS — the auth callback could reach the wrong app), auth redirect, app group.
V1 boundaries — deliberate
Preview entitlements carry no
aps-environmentand noassociated-domains. Push is blocked by the server's pinnedTOPIC = "org.adjusternetwork.app"(behind araise), so Preview setspushDelivery: falseand registers no device rather than creating undeliverable registrations. AASA lists only the production app ID. Neither pin is touched. This also keeps the Preview App ID to a single capability (App Groups).Two gates strengthened, not just updated
<string>adjusternetwork</string>became$(AN_URL_SCHEME), so the gate now verifies the resolved per-configuration values — shipping usesadjusternetwork, Preview usesanpreview.TARGETED_DEVICE_FAMILY = 1;occurrences (4). Adding configurations made it 6. Now asserts every occurrence is= 1;, so a future configuration can't slip through by keeping the count.verify:otagained four Preview assertions including that an unknown channel still fails the build.Results
format/lint clean · Jest 799/799 across 97 suites (stable over 3 consecutive runs) ·
verify:ota,verify:ios-auth,verify:release-readiness,verify:backend,validate:otaall PASS · Preview simulator build succeeded.Includes a one-line fix for a real-clock race in
rateLimitResiliencewritten for superseded PR #20 that never landed —Date.now()read twice in one assertion could straddle a millisecond, and it failed once during this work.Not done here
ios/Discourse/PrivacyInfo.xcprivacyandios/Podfile.lockwere regenerated by a localpod installand reverted — unrelated to Preview, and the privacy manifest is governed by its own test.