field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

README.md (27532B)


      1 # Tera iOS App
      2 
      3 Tera is a public iOS 18 app for discovering and publishing local farm
      4 updates, asks, in-person events, and food listings over Nostr. The product has
      5 exactly two bottom tabs: Today for discovery and Add for authored operations.
      6 
      7 The current public release is `0.1.0-alpha`.
      8 
      9 Tera owns the application's Rust validation, runtime transitions, durable
     10 authored operations and application FFI as well as the native iOS host. Shared
     11 domain types, signing protocols, transport and storage mechanics remain in the
     12 public foundation packages. The target behavior keeps cached Today and local
     13 Add usable independently of network readiness; the native host presents the Rust contracts and supplies
     14 Apple platform capabilities.
     15 
     16 Application Rust packages belong under `core/crates` in the single root Cargo
     17 workspace. Installed native artifacts use the owned `tera_ffi` and matching
     18 `tera_bindgen` packages, with shared packages at their exact foundation pins.
     19 The five creation families, Today/Add tabs, installed identity and persisted
     20 operation formats remain compatible with the original app.
     21 
     22 Today refresh starts from the newest relay window without an age cutoff or a
     23 latest-event watermark. Each search requests the same seven supported kinds
     24 with at most eight pages of 500 observations. Saved-feed pagination reads local
     25 posts; the separate **Search older posts** action explicitly continues remote
     26 discovery while saved posts remain usable. Continuations belong to the active
     27 network, account and store generation. Incomplete relay responses remain visible
     28 through the search, including when a later page completes. A saturated timestamp
     29 can leave gaps among same-time posts, so advancing to older posts does not prove
     30 complete relay history. Refresh starts a new search from the newest window.
     31 
     32 Publication keeps the original captured form, signed bytes when available, and
     33 frozen relay evidence after the delivery window or shared attempt cap is
     34 exhausted. Authentication, quota and malformed-event refusals require attention
     35 instead of an automatic retry. Explicit continuation respects shared claims and
     36 retry-after limits plus bounded exponential backoff from one to sixty seconds,
     37 with restart-stable jitter. Rust rechecks the time window before signing and
     38 delivery; the native host presents its decision and offers status refresh.
     39 Existing late-fact reconciliation remains local and never also sends a retry.
     40 No deadline extension, new signature, destination amendment or retry worker is
     41 implied by recovering a saved submission.
     42 
     43 `TeraFFI/producer.toml` separately governs the owned Tera FFI producer.
     44 After staging its source inputs, `make ffi-source-write ffi-source-check`
     45 captures and checks the exact source tree, foundation lock, target, features and
     46 toolchains under extbuild output. Select a supported target with `FFI_TARGET`.
     47 This is local source evidence. `make ffi-bootstrap` installs the matching
     48 native artifacts and records their exact source tree, foundation and hashes.
     49 
     50 `make ffi-candidate-build ffi-candidate-check` builds the owned device,
     51 simulator and host libraries, generates matching Swift and API outputs, and
     52 verifies the staged XCFramework and provenance. Candidates remain under
     53 extbuild output; this command does not install them into the native app.
     54 
     55 ## Requirements
     56 
     57 - macOS with Xcode and an iOS 18-or-newer simulator
     58 - XcodeGen
     59 - Rust `1.97.1-aarch64-apple-darwin` with `llvm-tools` and the iOS device and simulator targets
     60 - SwiftFormat and the locked Python verifier installed by bootstrap
     61 - JDK 21 on Apple silicon for the generated Kotlin binding smoke
     62 - `cargo-extbuild` configured for the checkout
     63 
     64 Physical-device development additionally requires one exact paired, connected,
     65 unlocked iPhone with Developer Mode enabled, an Apple development team, and a
     66 generated xcconfig below the extbuild-owned DerivedData root. The governed
     67 parent workspace supplies those machine inputs. The standalone script refuses
     68 name-only destinations, unsigned builds, non-Debug physical builds, and
     69 xcconfig files outside the managed output root:
     70 
     71 ```sh
     72 TERA_IOS_PHYSICAL_AUTOMATION=1 \
     73 TERA_IOS_DEVELOPMENT_TEAM=ABCDEFGHIJ \
     74 cargo extbuild run -- scripts/xcode.sh physical-app-build \
     75   id=00000000-0000000000000000 \
     76   "$XCODE_DERIVED_DATA/radroots-ios-device/config/device.xcconfig"
     77 ```
     78 
     79 The values above are placeholders. Device identities, teams, endpoints, and
     80 certificate material are never checked into this public repository.
     81 
     82 ## Bootstrap and verify
     83 
     84 The first bootstrap requires network access. It resolves exact foundation
     85 dependencies, builds the owned UniFFI XCFramework and Swift
     86 bindings, bootstraps the pinned Kotlin smoke dependencies, resolves exact Swift
     87 package revisions, and generates the Xcode project:
     88 
     89 ```sh
     90 cargo extbuild doctor
     91 make bootstrap
     92 ```
     93 
     94 After bootstrap, the complete package and Xcode build/test lane uses the
     95 resolved revisions without automatic dependency updates:
     96 
     97 ```sh
     98 make verify
     99 ```
    100 
    101 The complete lane includes the repository-owned Swift formatting and lint
    102 policy and the pinned Linux x86_64 shared-Rust runner. Run those focused checks
    103 independently with `make swift-quality` and `make linux-shared-rust`; both keep
    104 their build output under extbuild.
    105 
    106 `make kotlin-smoke` generates Kotlin from the exact installed producer's native
    107 host library and executes its scope, error, cancellation, subscription, shutdown,
    108 and media-ownership tests through JNA. The pinned Gradle wrapper, dependency locks
    109 and checksum verification are owned by this repository; generated Kotlin, native
    110 test results and Gradle output remain under extbuild. Verification uses offline
    111 dependency resolution after `make kotlin-smoke-bootstrap` or `make bootstrap`.
    112 This test-only JVM harness is part of `make verify` and does not qualify Android
    113 UI or a published release.
    114 
    115 `make swift-quality` also applies the exact checked SwiftLint complexity
    116 baseline and the repository-owned Swift/Python maintainability ratchet. New
    117 Swift files are capped at 600 physical lines, new Python files at 800, and new
    118 Python functions at complexity 10. Existing larger files and functions are a
    119 closed, non-growing inventory; the newly separated user-message classifier
    120 and package verification modules must remain below the new-file limits. Run
    121 `make maintainability-check` for the narrow size and Python-complexity gate.
    122 
    123 Use `SIMULATOR_NAME="Device Name" make verify` when the default simulator is
    124 not installed. `make clean` removes the external native candidate cache while
    125 preserving Cargo incremental output, the current installation, source and locks.
    126 
    127 The focused local-social scenario starts bounded loopback Nostr and Blossom
    128 fixtures, then exercises the real app stores and installed Rust FFI on one
    129 exact simulator. The test saves an unverified photo draft, relaunches, retries
    130 from the durable draft against the enabled fixture, publishes all five product
    131 types, and proves Today and the outbox survive another relaunch:
    132 
    133 ```sh
    134 TERA_IOS_UI_TEST_RUN_ID=local-social-example \
    135 cargo extbuild run -- scripts/xcode.sh local-social-ui-test \
    136   'platform=iOS Simulator,id=SIMULATOR-UDID' \
    137   local-social-example
    138 ```
    139 
    140 This harness is Debug-only, binds only explicit loopback ports, records bounded
    141 protocol evidence under the extbuild results root, and cannot become a
    142 production endpoint fallback. Its typed isolated-loopback mode is the only
    143 mode permitted to use automated user presence or test secret policy. Public
    144 and physical qualification retain normal Apple user presence, remain optional,
    145 and are not claimed by the deterministic simulator lane.
    146 
    147 The app and XCUITest runner admit the simulator endpoints through closed typed
    148 policies. The fixture creates both servers through one observable loopback
    149 connection factory, rejects and counts every non-loopback peer, and records
    150 accepted and rejected socket counters in its bounded evidence snapshot.
    151 
    152 The loopback Blossom fixture admits uploads only with the exact signed BUD-11
    153 HTTP authorization produced by the installed Rust runtime: kind `24242`,
    154 bounded non-empty human content, one upload action, one exact SHA-256, one
    155 lowercase domain-only server scope, and one canonical expiration whose
    156 lifetime is at most 300 seconds. Event ID and signature verification precede
    157 admission, and the relay explicitly rejects authorization events. The shared
    158 mutation corpus contains only field-mutation instructions; it persists no
    159 private material or signed authorization event.
    160 
    161 Passing `accessibility` as the final launcher argument runs Apple's
    162 accessibility audit over every progressively disclosed Add composition at the
    163 largest accessibility text size with Reduce Motion enabled. The corresponding
    164 fixture verifier requires zero publication and upload effects:
    165 
    166 ```sh
    167 TERA_IOS_UI_TEST_RUN_ID=local-social-accessibility \
    168 cargo extbuild run -- scripts/xcode.sh local-social-ui-test \
    169   'platform=iOS Simulator,id=SIMULATOR-UDID' \
    170   local-social-accessibility \
    171   accessibility
    172 ```
    173 
    174 Element-bound clipping findings remain fatal. The harness tolerates only
    175 Xcode's elementless clipping diagnostics and narrowly identified contrast
    176 false positives for disabled controls, system-chrome overlap, and the
    177 black-on-white Submit button.
    178 
    179 Passing `persona` runs the strict five-persona, 15-attempt deterministic
    180 local-social matrix serially. Each persona receives a fresh run-scoped native
    181 identity and isolated durable store while one bounded loopback Nostr relay and
    182 Blossom service record exact event, media, retry, and subscription evidence:
    183 
    184 ```sh
    185 TERA_IOS_UI_TEST_RUN_ID=local-social-persona-run-001 \
    186 cargo extbuild run -- scripts/xcode.sh local-social-ui-test \
    187   'platform=iOS Simulator,id=SIMULATOR-UDID' \
    188   local-social-persona-run-001 \
    189   persona
    190 ```
    191 
    192 The persona fixture and historical v1 result remain strict, deny-unknown
    193 contracts. Each completed attempt now also emits one bounded canonical
    194 `persona-attempt-evidence.v1` xcresult attachment bound to the exact XCUITest
    195 target and identifier, test action and configuration, source commit and tree,
    196 app-build digest, simulator, run, persona, attempt, endpoint policy, and visible
    197 UI outcome. The attachment never retains a raw secret, signed authorization
    198 event, or raw event content. Validation, retry, relaunch, retention, Today,
    199 connection, subscription, event, upload, and retrieval fields come from the
    200 executed UI path and monotonic fixture-snapshot deltas. The v2 result is
    201 reconstructed only from the exact 15 measured attachments and is cross-checked
    202 against the final fixture totals; a non-loopback attempt fails the run.
    203 
    204 The standalone fixture and result verifier runs under the exact Python 3.14.7
    205 and `jsonschema` 4.26.0 environment locked in
    206 `scripts/persona-verifier/uv.lock`. `make bootstrap` installs that exact lock
    207 through extbuild once; qualification and package checks then use only
    208 `--offline --frozen` resolution. Every JSON input is read with a
    209 maximum-plus-one bound before decoding, schema files pass Draft 2020-12
    210 meta-validation, semantic fixtures must agree with their schemas, exported
    211 attachment inventory is exact, and result-bundle hashing uses a
    212 domain-separated length-framed preimage with bounded paths, entries, files,
    213 and aggregate bytes.
    214 
    215 The test uses ordinary visible controls, native-generated signing identities,
    216 real app stores, and generated Rust FFI. This is deterministic non-human
    217 conformance evidence. It does not claim human usability, demographic or
    218 population validity, observed VoiceOver-user experience, release readiness,
    219 or production qualification.
    220 
    221 ## Package surface
    222 
    223 `Package.swift` publishes the `TeraApp` library used by the generated Xcode
    224 application wrapper. It pins AppleKit by exact HTTPS Git revision and consumes
    225 the locally bootstrapped `TeraFFI.xcframework`. Ordinary Xcode compilation
    226 never writes repository source: a read-only preflight rejects missing or stale
    227 FFI artifacts and directs the developer to run `make bootstrap`.
    228 
    229 The Rust source lock, generated bindings, XCFramework hashes, provenance, Swift
    230 package locks, privacy manifests, and public API snapshots are checked as part
    231 of the release lane.
    232 
    233 `make package-contract-check` evaluates the Swift package manifest and parses
    234 the TOML, plist, JSON, xcconfig, project-package, and lock inputs as structured,
    235 bounded data. It also runs the locked fixture and verifier unit suites.
    236 
    237 For focused UI iteration, `make ui-test UI_TEST_SELECTOR=TeraAccessibilityUITests/testFormAskAtLargestTextWithReduceMotionAndLocalSave`
    238 selects an existing owned class and method through the same artifact, package,
    239 project and output-routing checks. An omitted selector runs the full UI target.
    240 Unknown classes, methods, malformed selectors and extra arguments fail before
    241 Xcode starts. Focused runs do not replace complete required qualification.
    242 
    243 The package check evaluates Cargo's workspace graph and rejects members or local
    244 dependencies outside this standalone repository, including implicit sibling
    245 checkouts. The default Rust lane selects `tera_core`, `tera_ffi`, and
    246 `tera_bindgen`; `tera_wasm` remains non-default. The resolved graph must use
    247 the exact shared foundation lock, activate the FFI mobile-social profile, and
    248 contain no retired source-lock shim or old mobile producer. Human specifications
    249 and execution evidence remain outside the capsule and are never required by
    250 these checks.
    251 Comments, examples, unreachable source, and arbitrary matching text cannot
    252 satisfy a behavior-bearing package assertion; application behavior is proven
    253 by the compiled Swift and simulator test lanes.
    254 Legacy identifiers are governed by `test-fixtures/legacy-identifiers.v1.json`.
    255 Each exact name/path/count refers to an owner, reason, reader, and removal
    256 condition. Unknown names, expanded or stale exceptions, old app declarations,
    257 and changed display names fail the package check. Shared Apple/foundation APIs,
    258 installed identities, persisted namespaces, and existing compatibility readers
    259 retain their names. Generated FFI names are checked through owned generation
    260 and API freshness; dependency caches and generated binaries are not source
    261 exceptions. The policy is part of the native producer's declared input tree.
    262 
    263 The contract also binds the exact Ruff development tool, both maintainability
    264 baselines, and their executable verifier, so local lint behavior cannot drift
    265 with an ambient Python installation.
    266 
    267 The unsigned release-evidence lane also regenerates a deterministic CycloneDX
    268 SBOM from the locked Rust and Swift dependency graphs and binds it to the
    269 checked-in locks, API snapshots, privacy inputs, Xcode project, XCFramework
    270 provenance, and exact public repository identity:
    271 
    272 ```sh
    273 make release-evidence-write
    274 make release-preflight
    275 ```
    276 
    277 Run the write target only when an owned release input changes. The preflight
    278 is read-only and rejects stale generated evidence. Signing, tagging,
    279 publication, and deployment remain separate operations.
    280 
    281 Revision preparation retains a request identity and immutable replacement form
    282 before signing or delivery. Retrying an uncertain preparation returns the same
    283 replacement and retraction graph; changed input requires a new intentional
    284 request. Regular posts retain the original source event and a separate ordered
    285 retraction child. Current local card overlays also carry the exact source draft
    286 key, so revision editing can recover the retained form from either a legacy
    287 draft or a scoped submission. Missing forms and mismatched signed sources fail
    288 closed; a displayed operation ID is never treated as proof of source ownership.
    289 
    290 Regular revision delivery retains the replacement's exact relay policy on its
    291 linked retraction child. Each deletion destination requires replacement evidence
    292 from that same destination; another relay's success or aggregate completion does
    293 not grant permission. Retries reuse the original signed child and full destination
    294 list, withholding ineligible and already-satisfied destinations. Cancelling a
    295 partial revision stops both pending branches and retains observed effects across
    296 reopen. Historical revision children without an exact parent link remain held;
    297 their signed bytes and operation identities are never rewritten or rebound to a
    298 guessed replacement. This restriction also holds historical independent deletions
    299 using the reserved revision reason. Other independent retractions remain separate.
    300 
    301 Saved revisions expose their original event, replacement and linked retraction
    302 in Drafts & outbox. Selected details reconstruct per-relay observations and local
    303 stop state from durable records, including uncertain acknowledgments and partial
    304 effects after restart. Resume and stop actions are checked against current Rust
    305 policy; inspecting or acting on a revision does not replace composer editing.
    306 Meeting an Any relay policy does not imply completion at every saved destination,
    307 and even a fully accepted retraction remains a request, not proof of erasure.
    308 
    309 Food Availability remains a public announcement. Shared constructors validate
    310 exact decimal strings, matching quantity/price units, currency shape and
    311 active/sold status. A three-letter currency is not a verified financial asset;
    312 bags and bunches carry no inferred conversion. The native form preserves partial
    313 draft text. A new Submit or revision capture translates complete locale decimal entries into exact
    314 canonical strings before capturing the saved revision, without floating-point
    315 conversion. Canonical dots remain valid after a locale change; grouping, mixed
    316 separators, signs, exponents and incomplete entries are not guessed. Translation
    317 does not alter an existing submission or retry. A new intentional announcement
    318 retains its own identifier and operation even when its visible details match.
    319 
    320 Retraction requires the exact signed original in the current account's store.
    321 Rust verifies its signature, author, event kind, address and product card identity
    322 before capture and again before signing, local admission and delivery. A native
    323 card or an older saved signature alone does not authorize those effects. Missing
    324 source evidence holds publication while retained receipts and cancellation remain
    325 available. Already retracted originals remain eligible evidence for repeated
    326 requests; their visibility does not grant or remove authorship.
    327 
    328 Addressable publication keeps its full kind, author and identifier coordinate.
    329 Local requests acquire durable ownership before signing or publication, and a
    330 superseded request cannot regain that ownership by retrying after restart.
    331 If the known current revision changes, publication is held while the captured
    332 form, signed bytes and relay evidence remain available. A new request must be
    333 reviewed against the current revision. Addressable replacement does not create
    334 a broad deletion request.
    335 
    336 Equal-second replacements use the shared Nostr event-ID tie-break rule. Tera
    337 preserves the captured event timestamp and never advances it artificially to
    338 win a replacement. A timestamp ahead of the current clock, or one that loses
    339 to the known revision, holds publication for review. A forward clock jump
    340 does not extend the saved delivery window. Review the device clock and current
    341 revision before choosing another intentional request. Civil calendar dates
    342 remain unchanged. This local policy cannot establish globally accurate time
    343 or prevent an unseen remote replacement.
    344 
    345 Publication stage labels report saved local, signing, media and relay facts. Completion means the operation met its saved relay policy; it does not mean every relay accepted it or will retain it permanently. Mixed legacy settlement counts remain visible alongside the current stage, including pending, unknown, exhausted, failed and stopped work. Stopping local work preserves remote evidence, and retraction is a deletion request rather than proof of erasure. Submit and continuation controls follow the saved typed retry decision; status checks remain available without authorizing new effects.
    346 
    347 Storage startup retains the existing identity-scoped database names. An
    348 incomplete pair requires recovery and is never replaced with an empty member.
    349 The shared SQLite owner checks both existing schemas and the expected source
    350 generation before writable setup; unsupported versions or incompatible state
    351 remain typed failures with original evidence retained. Protected-data absence
    352 also fails before opening storage. Startup does not rename directories, import
    353 arbitrary paths, repair permissions or silently replace identity custody.
    354 
    355 Interrupted calendar rebuilds reconcile through the existing storage owner's
    356 durable records, retaining legacy bytes and publication identities. Lazy
    357 prepared-photo recovery validates an existing destination before accepting it;
    358 conflicts require recovery without overwriting either copy. A verified legacy
    359 copy is installed only after confirming destination absence, and the old copy
    360 remains available across repeated restarts. Unpublished partial staging is not
    361 treated as a completed photo or discarded by migration.
    362 
    363 Local application backup uses the exact shared database owner's capture,
    364 verification and finalization capability. An explicit backup-enabled startup
    365 prepares the account backup root through the native file owner; ordinary startup
    366 does not create it. The caller closes its previous session before selecting this
    367 startup mode. Backup admission requires an idle runtime, waits for earlier owner
    368 writes to settle (including canceled commands), and excludes mutations until the
    369 attempt finishes. It binds the account, source generation, request ID,
    370 format version, database digests and sizes, and all required authored-media
    371 digests and byte counts. Native media leases are immutable and survive removal
    372 of the original prepared file. They live in the protected account backup tree;
    373 directory custody metadata is applied before immutable file creation. Missing or
    374 conflicting media refuses completion.
    375 
    376 The application manifest is bounded to 16 MiB, each retained media member to
    377 64 MiB, and the reference inventory to the existing 65,536-reference traversal
    378 budget. The caller supplies a positive aggregate byte limit. These are processing
    379 bounds, not a disk-space reservation. Candidate and complete manifests use
    380 create-only durable installation. Retrying an exact persisted candidate verifies
    381 its original snapshot and leases, even if live state has since changed. An
    382 interrupted attempt retains incomplete owner staging and media leases; absence
    383 of a complete manifest is never success. Backups stay local, excluded from OS
    384 backup, with host file protection; this adapter neither exports Keychain secrets
    385 nor restores or republishes historical operations. Existing conservative media
    386 cleanup retains the backup tree. Physical power-loss behavior is not inferred
    387 from simulator interruption tests.
    388 
    389 Cold restore is a separate explicit host operation against a completed,
    390 account-bound local backup. It requires exclusive native file maintenance, no
    391 live application process users, available protected data and a known-empty OS
    392 transfer inventory. The canonical database owner verifies and installs both
    393 members, closes and reopens them; the native file owner restores only verified
    394 immutable media. There is no arbitrary-path restore or secret export.
    395 
    396 A bounded external guard is installed before database replacement. Ordinary
    397 startup refuses that guard; guarded startup requires a matching durable restore
    398 barrier. Interrupted or ambiguous installation retains all evidence and requires
    399 explicit repair with supported software. Never delete the guard, replace a
    400 missing member with an empty database, or use an older unsupported application
    401 to bypass recovery. Existing binaries do not retroactively acquire this guard.
    402 Guarded native startup loads saved transport preferences without adopting
    403 bootstrap overrides or stopping original operations. Review waits for canceled
    404 owner writes to finish before it reads the complete inventory.
    405 
    406 Restored signing, upload authorization and delivery remain held until the user
    407 checks original destinations, reviews the complete current operation inventory,
    408 and separately confirms resume in Drafts & outbox. Each destination check is
    409 bounded to two pages of 64 events and verifies actual event identity. Offline or
    410 partial results cannot authorize resume; a completed query that did not observe
    411 an event is not proof that it was never accepted. The inventory admits at most
    412 4,096 records and 4,096 pending destinations within a 64 MiB payload budget.
    413 Changes invalidate the prior review. Resume preserves original operation IDs,
    414 signed bytes and outcomes; it does not itself sign, upload or deliver anything.
    415 
    416 Storage pressure is reported separately from native transfer receipt capacity.
    417 A failed save can have committed before its acknowledgement was lost; reconcile
    418 the original composer or operation rather than creating a replacement. Settings
    419 accept cache budgets from 16 MiB through 2 GiB and 1 through 10,000 artifacts.
    420 Inbound cache admission also enforces the same upper bounds, including decoded
    421 policies; smaller internal budgets remain valid for constrained callers.
    422 
    423 Settings offers explicit cached-photo cleanup for the selected context. Each
    424 action invalidates at most 64 least-recently-used cache entries and reports
    425 remaining entries and retained file candidates, without claiming bytes freed.
    426 Physical deletion requires complete bounded cross-context ownership proof under
    427 the existing file/projection fence. Unknown owners or unsettled writes retain
    428 files. Draft staging, pending operations, transfer receipts and backup media are
    429 not cleanup targets. Invalidation must persist first: if the store is completely
    430 full, free device space before trying again. Cache cleanup cannot resolve the
    431 independent bounded transfer-receipt envelope limit.
    432 
    433 
    434 Public posts and profile updates are linked to the public Nostr key and may be
    435 retained by relays and other people. Only deliberately included location text
    436 (such as a public venue or address) is posted; camera/library images have
    437 sensitive metadata removed before staging. The app does not automatically
    438 publish device location and has no tracking or automatic telemetry endpoint.
    439 Its privacy manifest covers public names, identifiers, deliberately supplied
    440 addresses, photos and other posted content for application functionality.
    441 
    442 File metadata is accessed only for owned storage. The generic file owner
    443 checks available capacity locally before a write and reports the existing
    444 insufficient-space outcome. This check is conservative and does not reserve
    445 space or replace actual write/quota error handling. Capacity values stay local.
    446 
    447 Diagnostics exports contain bounded status codes and counts; prepare and review
    448 one before explicitly sharing it. [Radroots Support](mailto:support@radroots.org)
    449 is the canonical contact for support, privacy and removal inquiries. Settings
    450 opens the system email handler only after an explicit choice; no report or attachment
    451 is sent automatically. Provisioning/monitoring and operational reporting drills
    452 remain distribution prerequisites, not engineering-test outcomes.
    453 
    454 Removing a local signing key does not erase public copies. Review and stage any
    455 desired signed deletion requests before removing the key. Requests and relay
    456 acknowledgements cannot prove erasure by every recipient. The implemented local
    457 removal flow preserves its existing source and user-presence checks; it does not
    458 claim a decentralized-account policy exemption or App Store approval.