refactor(push): simplify internal notification stack

Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
This commit is contained in:
Tom Brow
2026-08-24 20:25:40 -07:00
parent aca13021d6
commit 3b3e536bd2
59 changed files with 488 additions and 2869 deletions

View File

@@ -107,9 +107,6 @@ run_unit_tests() {
run_test_step "buzz-push-gateway tests" \
cargo test -p buzz-push-gateway -- --nocapture
run_test_step "buzz-push-gateway dev App Attest bypass tests" \
cargo test -p buzz-push-gateway --features dev-app-attest-bypass -- --nocapture
# Kubernetes backend provider: pure decision layers driven by a fake
# substrate, no cluster. Mirrors the nextest path in `just test-unit` —
# the two lists must stay in step or the fallback silently covers less.

View File

@@ -1,87 +0,0 @@
#!/usr/bin/env python3
"""Validate the semantic iOS target/configuration table via macOS plutil."""
import json
import subprocess
import sys
from pathlib import Path
PBXPROJ = Path("mobile/ios/Runner.xcodeproj/project.pbxproj")
# This asserts plutil-resolved pbxproj declarations, not object-key uniqueness or
# the final xcconfig-expanded bundle identity.
EXPECTED_ROWS = """\
NotificationService Debug Flutter/Debug.xcconfig $(BUZZ_DEVELOPMENT_TEAM) NotificationService/NotificationService.entitlements $(BUNDLE_IDENTIFIER).NotificationService
NotificationService Profile Flutter/Release.xcconfig $(BUZZ_DEVELOPMENT_TEAM) NotificationService/NotificationService.entitlements $(BUNDLE_IDENTIFIER).NotificationService
NotificationService Release Flutter/Release.xcconfig $(BUZZ_DEVELOPMENT_TEAM) NotificationService/NotificationService.entitlements $(BUNDLE_IDENTIFIER).NotificationService
Runner Debug Flutter/Debug.xcconfig $(BUZZ_DEVELOPMENT_TEAM) Runner/Runner.entitlements $(BUNDLE_IDENTIFIER)
Runner Profile Flutter/Release.xcconfig $(BUZZ_DEVELOPMENT_TEAM) Runner/Runner.entitlements $(BUNDLE_IDENTIFIER)
Runner Release Flutter/Release.xcconfig $(BUZZ_DEVELOPMENT_TEAM) Runner/Runner.entitlements $(BUNDLE_IDENTIFIER)
RunnerTests Debug Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig - - $(BUNDLE_IDENTIFIER).RunnerTests
RunnerTests Profile Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig - - $(BUNDLE_IDENTIFIER).RunnerTests
RunnerTests Release Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig - - $(BUNDLE_IDENTIFIER).RunnerTests
""".splitlines()
def parse_project(path: Path) -> dict:
result = subprocess.run(
["plutil", "-convert", "json", "-o", "-", str(path)],
capture_output=True,
text=True,
)
if result.returncode != 0:
detail = result.stderr.strip() or "no error detail"
raise RuntimeError(f"plutil failed to parse {path}: {detail}")
if not result.stdout:
raise RuntimeError(f"plutil returned no JSON for {path}")
return json.loads(result.stdout)
def semantic_rows(project: dict) -> list[str]:
objects = project["objects"]
root = objects[project["rootObject"]]
rows = []
for target_id in root["targets"]:
target = objects[target_id]
if target.get("isa") != "PBXNativeTarget":
continue
configuration_list = objects[target["buildConfigurationList"]]
for configuration_id in configuration_list["buildConfigurations"]:
configuration = objects[configuration_id]
settings = configuration.get("buildSettings", {})
base_reference = configuration.get("baseConfigurationReference")
base_path = objects.get(base_reference, {}).get("path", "-")
rows.append(
" ".join(
[
target.get("name", "-"),
configuration.get("name", "-"),
base_path,
settings.get("DEVELOPMENT_TEAM", "-"),
settings.get("CODE_SIGN_ENTITLEMENTS", "-"),
settings.get("PRODUCT_BUNDLE_IDENTIFIER", "-"),
]
)
)
return sorted(rows)
def main() -> int:
path = Path(sys.argv[1]) if len(sys.argv) > 1 else PBXPROJ
try:
actual = semantic_rows(parse_project(path))
except (KeyError, TypeError, json.JSONDecodeError, RuntimeError) as error:
print(f"FAIL: {error}", file=sys.stderr)
return 1
if actual != EXPECTED_ROWS:
print("FAIL: unexpected iOS target configuration semantics", file=sys.stderr)
print("expected:", *EXPECTED_ROWS, sep="\n", file=sys.stderr)
print("actual:", *actual, sep="\n", file=sys.stderr)
return 1
print("iOS target configuration semantics match")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -152,8 +152,6 @@ fi
# ── Tracked build files: overrides are debug-only, release stays production ──
debug_xcconfig="$repo_root/mobile/ios/Flutter/Debug.xcconfig"
release_xcconfig="$repo_root/mobile/ios/Flutter/Release.xcconfig"
pbxproj="$repo_root/mobile/ios/Runner.xcodeproj/project.pbxproj"
runner_entitlements="$repo_root/mobile/ios/Runner/Runner.entitlements"
gradle="$repo_root/mobile/android/app/build.gradle.kts"
manifest="$repo_root/mobile/android/app/src/main/AndroidManifest.xml"
plist="$repo_root/mobile/ios/Runner/Info.plist"
@@ -185,348 +183,6 @@ grep -q '^APP_DISPLAY_NAME = Buzz$' "$release_xcconfig" \
&& pass "Release.xcconfig keeps the production display name" \
|| fail "Release.xcconfig must keep APP_DISPLAY_NAME = Buzz"
# These checks assert declarations in the two tracked xcconfigs only. They do
# not prove resolved build settings. The later gitignored includes
# (WorktreeOverrides.xcconfig and AppOverrides.xcconfig) can override these
# declarations and are explicitly outside this tracked-source assertion. The
# value check and declaration census are complementary: xcconfig is last-wins,
# while the census deliberately flags even a harmless duplicate declaration so
# a human reviews the changed declaration surface.
assert_xcconfig_value() {
# $1: file, $2: anchored value regex, $3: pass/failure description
if grep -qE "$2" "$1"; then
pass "$3"
else
fail "$3"
fi
}
assert_xcconfig_declaration_count() {
# $1: file, $2: key, $3: expected count, $4: configuration label
local file="$1" key="$2" expected="$3" label="$4" count
count=$(grep -cE "^[[:space:]]*$key([[:space:]]*\[[^]]*\])*[[:space:]]*=" "$file" || true)
if [[ "$count" -eq "$expected" ]]; then
if [[ "$expected" -eq 0 ]]; then
pass "$label $key has no tracked declaration sites"
elif [[ "$expected" -eq 1 ]]; then
pass "$label $key has one tracked declaration site"
else
pass "$label $key has $count tracked declaration sites"
fi
elif [[ "$expected" -eq 0 ]]; then
fail "$label $key has $count tracked declaration sites; expected zero"
elif [[ "$expected" -eq 1 ]]; then
fail "$label $key has $count tracked declaration sites; expected exactly one"
else
fail "$label $key has $count tracked declaration sites; expected $expected"
fi
}
assert_single_xcconfig_declaration() {
# $1: file, $2: key, $3: configuration label
assert_xcconfig_declaration_count "$1" "$2" 1 "$3"
}
for config in "$debug_xcconfig" "$release_xcconfig"; do
assert_xcconfig_value "$config" \
'^BUZZ_APP_GROUP_IDENTIFIER = group\.\$\(BUNDLE_IDENTIFIER\)$' \
"$(basename "$config") derives the push App Group from the bundle"
assert_xcconfig_value "$config" \
'^BUZZ_KEYCHAIN_ACCESS_GROUP = \$\(BUNDLE_IDENTIFIER\)$' \
"$(basename "$config") derives the push Keychain group from the bundle"
done
assert_xcconfig_value "$debug_xcconfig" \
'^BUZZ_IOS_PUSH_ENVIRONMENT = development$' \
"Debug uses sandbox APNs"
assert_xcconfig_value "$debug_xcconfig" \
'^BUZZ_APP_ATTEST_ENVIRONMENT = development$' \
"Debug uses development App Attest"
assert_xcconfig_value "$release_xcconfig" \
'^BUZZ_IOS_PUSH_ENVIRONMENT = production$' \
"Release uses production APNs"
assert_xcconfig_value "$release_xcconfig" \
'^BUZZ_APP_ATTEST_ENVIRONMENT = production$' \
"Release uses production App Attest"
assert_xcconfig_value "$release_xcconfig" \
'^CODE_SIGN_STYLE = Automatic$' \
"Release code signing style is declared as automatic"
assert_xcconfig_value "$release_xcconfig" \
'^CODE_SIGN_IDENTITY = iPhone Developer$' \
"Release code signing identity is declared as iPhone Developer"
for key in BUNDLE_IDENTIFIER; do
assert_single_xcconfig_declaration "$debug_xcconfig" "$key" "Debug"
assert_single_xcconfig_declaration "$release_xcconfig" "$key" "Release"
done
for key in BUZZ_KEYCHAIN_ACCESS_GROUP BUZZ_IOS_PUSH_ENVIRONMENT BUZZ_APP_ATTEST_ENVIRONMENT BUZZ_APP_GROUP_IDENTIFIER; do
assert_single_xcconfig_declaration "$debug_xcconfig" "$key" "Debug"
assert_single_xcconfig_declaration "$release_xcconfig" "$key" "Release"
done
for key in BUZZ_PUSH_ENABLED BUZZ_CODE_SIGN_ENTITLEMENTS EXCLUDED_SOURCE_FILE_NAMES DART_DEFINES; do
assert_xcconfig_declaration_count "$debug_xcconfig" "$key" 0 "Debug"
assert_xcconfig_declaration_count "$release_xcconfig" "$key" 0 "Release"
done
for key in CODE_SIGN_STYLE CODE_SIGN_IDENTITY; do
assert_xcconfig_declaration_count "$debug_xcconfig" "$key" 0 "Debug"
assert_single_xcconfig_declaration "$release_xcconfig" "$key" "Release"
done
assert_xcconfig_declaration_count \
"$debug_xcconfig" PROVISIONING_PROFILE_SPECIFIER 0 "Debug"
assert_xcconfig_declaration_count \
"$release_xcconfig" PROVISIONING_PROFILE_SPECIFIER 0 "Release"
assert_single_xcconfig_declaration "$debug_xcconfig" APP_DISPLAY_NAME "Debug"
assert_single_xcconfig_declaration "$release_xcconfig" APP_DISPLAY_NAME "Release"
# SWIFT_ACTIVE_COMPILATION_CONDITIONS is checked separately from the closed
# identity census above. This is a tracked-source assertion only; resolved Xcode
# build settings are intentionally outside this Linux-compatible test.
assert_xcconfig_value "$debug_xcconfig" \
'^SWIFT_ACTIVE_COMPILATION_CONDITIONS = \$\(inherited\) DEBUG$' \
"Debug Swift compilation conditions inherit DEBUG"
debug_swift_condition_count=$(grep -cE \
'^[[:space:]]*SWIFT_ACTIVE_COMPILATION_CONDITIONS([[:space:]]*\[[^]]*\])*[[:space:]]*=' \
"$debug_xcconfig" || true)
if [[ "$debug_swift_condition_count" -eq 1 ]]; then
pass "Debug SWIFT_ACTIVE_COMPILATION_CONDITIONS has one tracked declaration site"
else
fail "Debug SWIFT_ACTIVE_COMPILATION_CONDITIONS has $debug_swift_condition_count tracked declaration sites; expected exactly one"
fi
release_swift_condition_count=$(grep -cE \
'^[[:space:]]*SWIFT_ACTIVE_COMPILATION_CONDITIONS([[:space:]]*\[[^]]*\])*[[:space:]]*=' \
"$release_xcconfig" || true)
if [[ "$release_swift_condition_count" -eq 0 ]]; then
pass "Release SWIFT_ACTIVE_COMPILATION_CONDITIONS has no tracked declaration sites"
else
fail "Release SWIFT_ACTIVE_COMPILATION_CONDITIONS has $release_swift_condition_count tracked declaration sites; expected zero"
fi
grep -q '<key>aps-environment</key>' "$runner_entitlements" \
&& pass "Runner entitlements always include APNs support" \
|| fail "Runner entitlements must include aps-environment"
# Split the retired identifiers so the regression test does not match itself.
retired_bundle_id='com.buzz.buzz'"Mobile"
if git -C "$repo_root" grep -q -F "$retired_bundle_id"; then
fail "tracked files must not retain the retired iOS bundle identifier"
else
pass "tracked files do not retain the retired iOS bundle identifier"
fi
grep -q '<key>com.apple.developer.devicecheck.appattest-environment</key>' "$runner_entitlements" \
&& pass "Runner uses the App Attest entitlement key accepted by Apple" \
|| fail "Runner must use com.apple.developer.devicecheck.appattest-environment"
retired_entitlement_key='com.apple.developer.app-attest.'"environment"
if grep -q "$retired_entitlement_key" "$runner_entitlements"; then
fail "Runner must not retain the invalid App Attest entitlement key"
else
pass "Runner omits the invalid App Attest entitlement key"
fi
duplicate_pbx_object_ids=$(awk '
# This bounded source-level smoke check recognizes the current two-tab
# object-key spellings. It is not a general OpenStep uniqueness check:
# measured exclusions include a comment before the key, a presentation
# comment spanning lines, and one-tab indentation (jb_b1/jb_b2/jb_b6).
# The macOS semantic check below owns their resolved build consequences.
function decomment(s, head, tailpart) {
while (match(s, /\/\*/)) {
head = substr(s, 1, RSTART - 1)
tailpart = substr(s, RSTART + 2)
if (!match(tailpart, /\*\//)) return head " "
s = head " " substr(tailpart, RSTART + RLENGTH)
}
return s
}
/^\t\t/ {
line = decomment($0)
if (match(line, /^\t\t"?[[:alnum:]]+"?[[:space:]]*=/)) {
object_id = substr(line, RSTART, RLENGTH)
sub(/^\t\t"?/, "", object_id)
sub(/"?[[:space:]]*=$/, "", object_id)
if (++object_id_count[object_id] == 2) print object_id
}
}
' "$pbxproj" | sort)
if [[ -n "$duplicate_pbx_object_ids" ]]; then
fail "recognized iOS project object identifiers repeat: $(printf '%s\n' "$duplicate_pbx_object_ids" | paste -sd ' ' -)"
else
pass "recognized iOS project object identifiers do not repeat"
fi
signing_map=$(awk '
# PBX comments are separators, not text: strip them before parsing any
# object so a comment cannot hide a duplicate key from the ambiguity count.
function decomment(s, head, tailpart) {
while (match(s, /\/\*/)) {
head = substr(s, 1, RSTART - 1)
tailpart = substr(s, RSTART + 2)
if (!match(tailpart, /\*\//)) { return head " " }
s = head " " substr(tailpart, RSTART + RLENGTH)
}
return s
}
FNR == 1 { pass++ }
# Pass 1 indexes xcconfig paths and follows each PBXNativeTarget to its
# actual configuration-list object. Target names come from object fields,
# not presentation comments.
pass == 1 {
if (/isa[[:space:]]*=[[:space:]]*PBXFileReference/ && /\.xcconfig/) {
declaration = decomment($0)
if (match(declaration, /=[[:space:]]*\{[[:space:]]*isa[[:space:]]*=[[:space:]]*PBXFileReference[[:space:]]*;/)) {
declaration = substr(declaration, RSTART + RLENGTH)
} else {
declaration = ""
}
sub(/\}.*/, "", declaration)
xcconfig_path = "MISSING_PATH"
rest = declaration
path_matches = 0
while (match(rest, /(^|;)[[:space:]]*path[[:space:]]*=[[:space:]]*[^;]+/)) {
candidate = substr(rest, RSTART, RLENGTH)
rest = substr(rest, RSTART + RLENGTH)
sub(/^;?[[:space:]]*path[[:space:]]*=[[:space:]]*/, "", candidate)
gsub(/"/, "", candidate)
sub(/[[:space:]]+$/, "", candidate)
path_matches++
xcconfig_path = candidate
}
# More than one `path =` in one object means a decoy (a quoted value or
# an embedded comment) is shadowing the real key. Never guess which one
# the build uses: fail the row loudly instead.
if (path_matches > 1) xcconfig_path = "AMBIGUOUS_PATH"
xcconfig_paths[$1] = xcconfig_path
}
if (/\/\* Begin PBXNativeTarget section \*\//) {
in_native_targets = 1
next
}
if (/\/\* End PBXNativeTarget section \*\//) {
in_native_targets = 0
next
}
if (!in_native_targets) next
if (/^\t\t[^[:space:]]+ .* = \{$/) {
native_target_id = $1
native_target_name = ""
native_target_list = ""
next
}
if (native_target_id != "" && /^\t\t\tname = /) {
native_target_name = $0
sub(/^.*= */, "", native_target_name)
sub(/;.*/, "", native_target_name)
gsub(/"/, "", native_target_name)
next
}
if (native_target_id != "" && /^\t\t\tbuildConfigurationList = /) {
native_target_list = $3
next
}
if (native_target_id != "" && /^\t\t\};/) {
if (native_target_list != "") {
if (native_target_name == "") native_target_name = "UNNAMED:" native_target_id
if (native_target_list in list_owners) {
list_owners[native_target_list] = "DUPLICATE:" list_owners[native_target_list] "+" native_target_name
} else {
list_owners[native_target_list] = native_target_name
}
}
native_target_id = ""
}
next
}
# Pass 2 maps build-configuration object IDs through only those lists that
# real native targets own. PBXProject and other unowned lists are ignored.
pass == 2 {
if (/\/\* Begin XCConfigurationList section \*\//) {
in_configuration_lists = 1
next
}
if (/\/\* End XCConfigurationList section \*\//) {
in_configuration_lists = 0
next
}
if (!in_configuration_lists) next
if (/^\t\t[^[:space:]]+ .* = \{$/) {
configuration_list_id = $1
configuration_list_owner = configuration_list_id in list_owners ? list_owners[configuration_list_id] : ""
next
}
if (/buildConfigurations = \(/) {
in_list_configurations = 1
next
}
if (in_list_configurations && /\);/) {
in_list_configurations = 0
next
}
if (in_list_configurations && $1 ~ /^[[:alnum:]]+$/ && configuration_list_owner != "") {
if ($1 in targets) targets[$1] = "DUPLICATE:" targets[$1] "+" configuration_list_owner
else targets[$1] = configuration_list_owner
}
next
}
# Pass 3 emits one row for each team-bearing build configuration.
!in_build_configuration && /\/\* (Debug|Release|Profile) \*\/ = \{/ {
in_build_configuration = 1
configuration_id = $1
configuration = $3
base_configuration = "NONE"
team = ""
entitlements = "NONE"
depth = 0
}
in_build_configuration {
if (/baseConfigurationReference =/) {
base_configuration = $3 in xcconfig_paths ? xcconfig_paths[$3] : "UNRESOLVED:" $3
}
if (/DEVELOPMENT_TEAM =/) {
team = $0
sub(/^.*= */, "", team)
sub(/;.*/, "", team)
}
if (/CODE_SIGN_ENTITLEMENTS =/) {
entitlements = $0
sub(/^.*= */, "", entitlements)
sub(/;.*/, "", entitlements)
}
depth += gsub(/\{/, "{") - gsub(/\}/, "}")
if (depth == 0) {
if (team != "") {
target_name = configuration_id in targets ? targets[configuration_id] : "UNMAPPED:" configuration_id
print target_name, configuration, base_configuration, team, entitlements
}
in_build_configuration = 0
}
}
' "$pbxproj" "$pbxproj" "$pbxproj" | sort)
expected_signing_map=$(printf '%s\n' \
'NotificationService Debug Flutter/Debug.xcconfig "$(BUZZ_DEVELOPMENT_TEAM)" NotificationService/NotificationService.entitlements' \
'NotificationService Profile Flutter/Release.xcconfig "$(BUZZ_DEVELOPMENT_TEAM)" NotificationService/NotificationService.entitlements' \
'NotificationService Release Flutter/Release.xcconfig "$(BUZZ_DEVELOPMENT_TEAM)" NotificationService/NotificationService.entitlements' \
'Runner Debug Flutter/Debug.xcconfig "$(BUZZ_DEVELOPMENT_TEAM)" Runner/Runner.entitlements' \
'Runner Profile Flutter/Release.xcconfig "$(BUZZ_DEVELOPMENT_TEAM)" Runner/Runner.entitlements' \
'Runner Release Flutter/Release.xcconfig "$(BUZZ_DEVELOPMENT_TEAM)" Runner/Runner.entitlements')
if [[ "$signing_map" == "$expected_signing_map" ]]; then
pass "Runner and NotificationService signing settings match each build configuration"
else
fail "unexpected iOS signing map: $signing_map"
fi
grep -q '<string>$(APP_DISPLAY_NAME)</string>' "$plist" \
&& pass "Info.plist display name resolves from build settings" \
|| fail "Info.plist CFBundleDisplayName must be \$(APP_DISPLAY_NAME)"