fix(mobile): verify latest signed owner profiles consistently - #7389
fix(mobile): verify latest signed owner profiles consistently#7389loganj wants to merge 1 commit into
Conversation
🔐 Codex Security Review
Review SummaryOverall Risk: NONE
FindingsNo concrete security, correctness, or reliability findings were identified. Notes
Generated by Codex Security Review | |
|
@buzz-security-review b2253ce |
1 similar comment
|
@buzz-security-review b2253ce |
Independent ownership readiness evidence — 2026-09-07Verified current range:
|
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — REQUEST CHANGES
Reviewed base 3c7f288c60d67df78577b237e27c3dfc8831aaa1 through exact head b2253ce1aa5fd2e05e6a51b67c8edae112364308.
Required fix
[P1] A newer live owner revocation leaves stale owner-derived authority and provenance active.
agentOwnersProvider fetches profile ownership once and caches the resulting map (mobile/lib/shared/mentions/agent_identity_provider.dart:75-88). The live kind-0 path updates userCacheProvider, but only kind 10100 invalidates agentOwnersProvider (mobile/lib/features/channels/channel_detail_page.dart:150-163). Consequently, mention assembly prefers the stale provider owner over the newer cached profile (mobile/lib/features/channels/mentions/mention_candidates.dart:59-64,111-125), while channel lifecycle surfaces continue deriving edit/archive/unarchive/delete capability directly from that stale map (mobile/lib/features/channels/channel_actions_sheet.dart:92-116; mobile/lib/features/channels/channel_details_page.dart:65-102).
A production-seam regression demonstrated the failure: after resolving a valid owned profile through agentOwnersProvider, delivering a newer correctly signed kind-0 profile without the auth tag correctly cleared userCacheProvider.ownerPubkey, but agentOwnersProvider still returned the old owner and mention assembly restored it:
Expected: empty
Actual: {<agent>: <old-owner>}
a newer live signed revocation must invalidate the cached owner snapshot
This can keep showing an agent as managed/owned, preserve owner-only mention eligibility, and retain owner-derived destructive channel controls after the latest signed profile revoked that provenance. Initial latest-event selection is correct, but the live state transition is not.
Author action: make shared owner state follow every newer live kind-0 profile head—including revoked and invalid-latest heads—across all consumers. Add a production-seam regression that drives valid-owned → newer signed revoked/invalid profile and verifies mention provenance and both lifecycle surfaces lose owner-derived capability. Cover reconnect and same-second replacement ordering.
Verification owner: author for fix and regression; this reviewer for a new exact-head review.
Validation
At the reviewed head:
git diff --check 3c7f288...HEAD— pass.just mobile-check— pass; formatting clean and Flutter analyzer reported no issues.just mobile-test/ fullflutter test— pass, 2,077 tests.- Focused crypto/owner/cache/profile/channel suite — pass, 249 tests.
- Causal reviewer regression for the live revocation transition — fail as described above.
- Required Mobile, DCO, and aggregate CI checks — green or intentionally skipped.
The green suite covers initial latest-profile selection, signature validation, and cache ordering, but not the live invalidation contract binding agentOwnersProvider to its authority/UI consumers.
Confidence gap
No installed iOS/Android app was exercised against a live relay. That native observation gap is reviewer/release-owned and is not the reason for requesting changes; the reproduced state-transition defect is author-actionable.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Verdict: REQUEST CHANGES
Reviewed: 3c7f288c60d67df78577b237e27c3dfc8831aaa1..b2253ce1aa5fd2e05e6a51b67c8edae112364308 (exact head b2253ce1aa5fd2e05e6a51b67c8edae112364308)
Risk: high — this changes signed ownership authority used by mention eligibility and channel lifecycle controls.
Blocking finding
[P1] A newer live owner revocation leaves stale owner authority active.
agentOwnersProvider is a one-shot owner snapshot (mobile/lib/shared/mentions/agent_identity_provider.dart:75-88). The live kind-0 path updates userCacheProvider, but the searched production subscription invalidates agentOwnersProvider only for kind 10100, not kind 0 (mobile/lib/features/channels/channel_detail_page.dart:150-163). Consumers then prefer the stale provider owner over the newer cached profile (mobile/lib/features/channels/mentions/mention_candidates.dart:59-64,111-125), while regular-channel archive/delete surfaces derive owner capability from that stale map (mobile/lib/features/channels/channel_actions_sheet.dart:92-116; mobile/lib/features/channels/channel_details_page.dart:65-102).
Both lanes ultimately agree after an independent causal regression: valid owned profile → newer correctly signed kind-0 revocation updates the profile cache, but the owner provider and downstream mention/lifecycle authority retain the old owner. This violates the PR’s “latest … consistently” contract and can preserve owner-derived provenance, mention eligibility, and destructive controls after revocation.
Author action: establish one advancing latest-profile authority source, or invalidate/re-fetch the owner snapshot on every relevant kind-0 head, so stale provider data cannot override a newer cached head. Add a production-seam regression for valid owned → newer signed revoked/invalid profile proving mention provenance and both lifecycle surfaces lose owner-derived capability; include reconnect and same-second ordering.
Verification owner: author for implementation/regression; reviewer for exact-head causal rerun.
Validation
At clean exact head b2253ce1aa5fd2e05e6a51b67c8edae112364308:
git diff --check 3c7f288...HEAD— pass.just mobile-check— pass; formatting clean and analyzer found no issues.just mobile-test/ fullflutter test— pass, 2,077 tests.- Temporary causal reviewer regression — failed as expected, retaining the old owner after a newer live signed revocation; temporary change removed and tree restored clean.
- Exact-head GitHub Mobile, DCO, and aggregate required checks — success.
Manual/native evidence: not run on simulator/device.
Residual risk: native presentation and live-relay reconnect were not independently witnessed; reviewer/tooling owns that confidence gap. It is not the basis for this verdict.
|
Both duplicate ownership-revocation reviews are covered by followup #7530 at a6d7298 (based on #7395), not backported here. Production-subscription regression in Update (2026-09-10) — composed lifecycle-controls regression for review 5157948994's remaining obligation. The narrower both-controls regression obligation now has composed-source evidence at private composed source Source dependencies it runs against (composed private source): ordered Exact commands and receipts (isolated worktree,
Scope: this is an UNSHIPPED regression artifact on the composed private source — not a public CI run, not a claim that unchanged #7389 or #7539 is independently safe, and not a placement of these lines on any public PR (no branch, restack, or private push is proposed; this does not circumvent the 600-line PR cap). No suitable public placement was found for this patch: the checked #7539 candidate Reproducible test source (exact patch; hashes above are local attribution, not public checkouts): Exact unshipped regression patch —
|
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — REQUEST CHANGES remains after reconsidering author response IC_kwDORgXb2s8AAAABTiOU_w on exact head b2253ce1aa5fd2e05e6a51b67c8edae112364308.
The response points to a plausible descendant repair, but this merge object is unchanged and independently unsafe. agentOwnersProvider remains a one-shot profile snapshot (mobile/lib/shared/mentions/agent_identity_provider.dart:75-88); live kind-0 updates advance userCacheProvider without invalidating it (mobile/lib/features/channels/channel_detail_page.dart:150-163). Mention construction prefers the stale owner map (mention_candidates.dart:63,115), while channel lifecycle controls consume it directly (channel_actions_sheet.dart:92-120; channel_details_page.dart:65-106). A newer signed owner revocation can therefore retain managed provenance and owner-derived lifecycle capability.
#7530 is not an enforceable safety prerequisite for this main-based PR. The cited descendant moved during review, was not initially mergeable/green, and repository rules do not atomically couple it to #7389. Clearing this head would permit the known-bad ancestor-first deployment window.
Author action: backport the advancing shared-authority fix and production-seam revoked/invalid-latest regression onto this PR, or replace the topology with one immutable, reviewed integration head that cannot ship #7389 without the fix.
Verification owner: author for coherent composition and green exact-head Mobile; reviewer for causal live-revocation, stale replay/reconnect, provenance, and lifecycle-control reruns.
Exact-head evidence: live base/head rechecked unchanged; git diff --check passed; prior full 2,077-test suite and just mobile-check remain attributable to these unchanged bytes, as does the causal live-revocation failure. Native iOS remains a confidence gap, not this verdict’s basis.
b2253ce to
6f27422
Compare
|
Head-bound response to reviews 5142925936 / 5142956631 / 5157948994 at the new head P1 — newer live owner revocation left stale owner-derived authority active. Fixed at the new head by replacing the one-shot
Production-seam regressions at this exact head (genuinely signed events, real providers): live revocation → empty owners and revoked cached profile; stale snapshot/replay after retirement cannot repopulate ( Exact-head local evidence: full Mobile 2,140 passed, exit 0; analyzer no issues; format 558 files, zero changes; 222 locked package roots verified before/after. Limits: automated-test level only — no native device/simulator journey, no live-relay authorization validation, no native huddle/audio run. Prior checks and the security advisory on superseded |
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — COMMENT
Reviewed exact head 6f274223bbcb855825142fab41b6d7c9f787a5d1 against exact stacked base 46bbe82307c52db6bff33b7bd216704d545ba2d6 (#7588). Both independent review lanes found no material defect in this delta. The previous P1 live-revocation defect is resolved at this head.
Resolution of the prior blocker
Owner authority is no longer a one-shot snapshot. The producer continuously admits live kind-0 evidence through a generation-bound ProfileAdmission (mobile/lib/shared/mentions/agent_identity_provider.dart:77-132), and the shared cache rejects older (created_at,event-id) evidence (mobile/lib/shared/profile/user_cache_provider.dart:201-219). Loading, error, retry/CLOSED, and disconnected states fail closed; authority returns only after replay reaches EOSE. Channel actions/details explicitly reject unavailable owner state (mobile/lib/features/channels/channel_actions_sheet.dart:94-111; mobile/lib/features/channels/channel_details_page.dart:67-82).
The production-seam regression exercises valid owner → live revocation → reauthorization → newer invalid authorization → stale replay/reconnect across lifecycle controls and mention provenance (mobile/test/shared/mentions/agent_identity_provider_test.dart:21-155). Independent mutations removing continuous live admission and permitting stale overwrite both made the regression fail at the expected capability boundary; the restored exact-head control passed.
Validation
At clean exact head 6f274223bbcb855825142fab41b6d7c9f787a5d1:
git diff --check 46bbe823...HEAD— pass; exact delta is 12 mobile files, 571 insertions / 27 deletions.- Six affected/adjacent Flutter test files in one invocation — 285/285 passed:
agent_identity_provider_test,owner_search_generation_test, fullchannel_management_provider_test, fullchannel_detail_page_test,nip_oa_test, andr1_dm_admission_test. - Two independent production-seam mutation checks — expected causal failures; source restored and worktrees clean.
- Live PR head, base, prerequisite head, and merge-base were rechecked immediately before this review.
Merge condition and confidence gaps
This PR is one commit directly atop #7588. Do not approve or merge unless #7588 is independently cleared at exactly 46bbe82307c52db6bff33b7bd216704d545ba2d6 and remains unchanged. At review time #7588 still had CHANGES_REQUESTED and mergeStateStatus=BLOCKED; therefore the prerequisite is not cleared. #7389's Authorize Security Review also fails because that workflow rejects a stacked PR not targeting main; retarget/reconcile after the prerequisite lands.
Repository CI was still running. Mobile remained in progress. Relay/PostgreSQL leaf jobs failed on both this PR and its exact prerequisite while each aggregate Results gate was green; the base-relative #7389 delta does not touch those surfaces, so this review does not attribute them to #7389 without completed logs.
No simulator/device, native huddle/audio, or live-relay authorization journey was run. Those are reviewer/release confidence gaps, not author-actionable defects.
Author action: none for code in this reviewed delta. Preserve the fix and causal regression; retarget/reconcile after #7588 lands, and rerun exact-head gates. Any head or prerequisite change invalidates this review.
Verification owner: reviewer for prerequisite freshness, final CI/security-gate reconciliation, and native/live-relay confidence.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — COMMENT / prerequisite verification pending
Reviewed exact head 6f274223bbcb855825142fab41b6d7c9f787a5d1 against exact base 46bbe82307c52db6bff33b7bd216704d545ba2d6 (#7588).
Both assigned lanes found the prior live-revocation defect resolved in this delta. The one-shot owner snapshot is replaced by generation-bound live profile admission into the ordered shared cache; newer revocation/invalid heads remove owner provenance, stale replay cannot restore it, and loading/error/retry states fail closed. The production-seam regression covers lifecycle controls and mention provenance through authorization, revocation, reauthorization, invalid latest evidence, replay, and reconnect. Two independent mutations of the production admission/order seams caused the intended regression failure.
No author-actionable defect was found in #7389's base-relative delta. Focused affected suites passed 285/285 at the exact clean head; git diff --check passed.
I am not approving yet because the exact prerequisite #7588 head is not cleared: review of 46bbe82307c52db6bff33b7bd216704d545ba2d6 found an unresolved visible-identity signature-validation defect. Approval of this stacked PR is conditional on #7588 being fixed, independently re-reviewed, and this PR remaining based on that cleared exact head. This is a dependency/confidence gate, not additional #7389 author rework. Authorize Security Review also rejects the non-main stacked topology; final gate reconciliation belongs after prerequisite landing/retargeting.
Author action for #7389: none from this review lane; preserve the advancing-authority fix and causal regression. Verification owner: :bot: Jude’s code review agent for prerequisite freshness, retargeted exact-head gates, and final verdict. Native device/live-relay behavior remains unobserved.
46bbe82 to
80c5315
Compare
6f27422 to
9b39d90
Compare
|
Head-bound response to reviews 5186891049 / 5186893632 at the new head The ROOT production delta is unchanged: the immediate production diff at this head is byte-identical to the live-ownership change you reviewed at Root carry, verified rather than asserted: an independent source/evidence review of the exact pair (prerequisite Exact-head local evidence: full local Mobile suite at this exact tree ( Not claimed: no CI/security/merge clearance — head-bound CI had just started at publication. The |
Carry the immediate ROOT ownership readiness, consumer, queued-live and DM/huddle generation corrections on the authenticated profile prerequisite. Preserve the original ROOT production delta and bind its signed fixtures to the corrected parent. Signed-off-by: Logan Johnson <loganj@squareup.com>
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — APPROVE
Reviewed exact head 9b39d90c57ebaefbe9ba62eb5ed8573f8e60320f against exact prerequisite/base 80c531511511e1edb66c3354833ad54d3b46e466 (#7588). Both assigned review lanes independently recommend approval, and #7588 is independently approved unchanged at that exact head. No unresolved author-actionable defect was found.
Result
The prior live-revocation defect remains fixed. Owner authority now follows generation-bound, authenticated, advancing kind-0 evidence and becomes authoritative only after EOSE; acquisition, retry/CLOSED, disconnect, and error states fail closed (mobile/lib/shared/mentions/agent_identity_provider.dart:77-132; mobile/lib/shared/profile/user_cache_provider.dart:12-26,133-154,205-223). Newer revocation or invalid owner evidence removes the owner projection, while stale replay cannot overwrite the ordered head.
Lifecycle actions/details and mention classification use the same availability fence rather than stale retained AsyncValue data (mobile/lib/features/channels/channel_actions_sheet.dart:100-122; mobile/lib/features/channels/channel_details_page.dart:76-108; mobile/lib/features/channels/mentions/mention_candidates_provider.dart:89-111). The production-seam tests exercise authorization, revocation, reauthorization, invalid evidence, unavailable/retry states, reconnect, stale replay, and retired DM/huddle admission through actual consumers (mobile/test/shared/mentions/agent_identity_provider_test.dart:27-157; mobile/test/shared/mentions/r1_dm_admission_test.dart:25-142).
The #7389 production patch is byte-identical to the previously reviewed patch; this head refreshes the prerequisite and rebinds tests to its authenticated ingress contract. The exact old/new production diffs compared equal with SHA-256 a30f6b3f5cb9d93caba091db0132f1ee3c2c0fe0934385d4a80d5610825bde36.
Validation
At clean exact head 9b39d90c57ebaefbe9ba62eb5ed8573f8e60320f:
just mobile-test/ full touched package: 2,152/2,152 passed.just mobile-check: 559 files formatted, zero changed; Flutter analyzer reported no issues.git diff --check: passed.- Independent causal mutations removing live admission and lifecycle availability guards failed the intended production-seam tests, then sources were restored and worktrees rechecked clean.
- Exact live head/base and #7588 approval/head were rechecked immediately before submission.
CI and residual risk
Exact-head Mobile CI remained in progress. Desktop integration jobs fail before tests while pulling removed MinIO images; those failures also concern infrastructure outside this mobile-only delta, while the aggregate relay result is green. CI owns final gate reconciliation.
No native iOS/Android accessibility/device journey or live-relay authorization journey was run. No layout/input component changed; these are reviewer/release confidence gaps, not author rework.
Author action: none.
Verification owner: CI/release owners for remaining exact-head jobs and optional native/live-relay observation. Any head or base change invalidates this approval.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — APPROVED
Reviewed exact head 9b39d90c57ebaefbe9ba62eb5ed8573f8e60320f against exact stacked base 80c531511511e1edb66c3354833ad54d3b46e466 (#7588). The prerequisite was independently approved at that exact SHA; any head or base movement expires this approval.
Both assigned review lanes found no material defect. The base-relative production patch is byte-for-byte unchanged from the previously reviewed delta. It replaces the one-shot owner snapshot with generation-bound live kind-0 admission, denies authority before EOSE and during retry/error/disconnect, rejects retired-generation callbacks and stale replay, and makes lifecycle controls plus mention provenance consume the same availability-fenced authority (mobile/lib/shared/mentions/agent_identity_provider.dart:77-132; mobile/lib/shared/profile/user_cache_provider.dart:12-26,133-154,205-223; mobile/lib/features/channels/channel_actions_sheet.dart:100-122; mobile/lib/features/channels/channel_details_page.dart:76-108; mobile/lib/features/channels/mentions/mention_candidates_provider.dart:89-111).
The prior live-revocation defect remains resolved through authorization → revocation → reauthorization → invalid evidence → reconnect/replay. #7588 now rejects invalid signed envelopes before profile ordering/cache admission, so this delta's owner projection rests on authenticated, deterministic evidence.
Author action: none.
Verification owner: CI for completion of the exact-head Mobile gate; reviewer/release tooling for optional native iOS, VoiceOver, and live-relay observation.
Validation at exact clean head: both independent lanes passed just mobile-check, full Mobile 2,152/2,152, and git diff --check. Production-seam mutations removing live admission and lifecycle availability guards failed the intended owner/lifecycle assertions, then were restored clean. Tests cover real owner, mention, action-sheet/details, DM, huddle, cache-generation, and reconnect consumers.
Confidence gaps: no native simulator/device or live-relay journey was run; this changes state/authority plumbing rather than layout or input structure. GitHub Mobile was still running at verdict time. Red Desktop integration leaf jobs fail while pulling removed MinIO images, are outside this Mobile-only delta, and roll into a green aggregate Results gate; they are not attributed to this PR. The non-main stacked security authorization limitation remains an external topology/gate concern, not author rework.
80c5315 to
ef698b4
Compare
9b39d90 to
24035e2
Compare
|
🤖 |
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — APPROVE
Reviewed exact head 24035e24b6cb80d5d576c57bc27479358a3ad3b2 against exact prerequisite/base ef698b4d6048adcb73785c986e55fe6249ecbada (#7588). Both assigned review lanes independently recommend approval, and #7588 remains independently approved at this exact refreshed head. No unresolved author-actionable defect was found.
Changed-head reconciliation
The complete #7389 base-relative patch is byte-for-byte equal to the previously approved 80c53151…9b39d90c patch (SHA-256 16b492be68e54c4d83e862aedba29dd11a6880ad97accdc28fa188cf84b89d86). The Mobile trees are identical (a660aa4b736b0013d45a4e49c93145626ebe49d9), the stable Mobile patch-id is unchanged (4fbbeb2d3ac1cf965ff835e6aa066eeb88230342), and the refreshed prerequisite differs only in five non-Mobile MinIO/architecture/deploy files.
Result
Owner authority remains generation-bound, authenticated, and ordered by (createdAt, lowest eventId); it becomes authoritative only after EOSE and fails closed during acquisition, disconnect, CLOSED/retry, or error (mobile/lib/shared/profile/user_cache_provider.dart:12-27,133-154,205-223; mobile/lib/shared/mentions/agent_identity_provider.dart:77-133). Newer signed revocation or invalid owner evidence removes authority, stale replay cannot restore it, and newer signed reauthorization can.
Lifecycle controls and mention classification reject unavailable owner state rather than consuming retained stale data (mobile/lib/features/channels/channel_actions_sheet.dart:92-122; mobile/lib/features/channels/channel_details_page.dart:67-108; mobile/lib/features/channels/mentions/mention_candidates_provider.dart:89-111). DM/huddle admission separately requires ready identity, membership, role, and preload sources (mobile/lib/features/channels/channel_detail_page.dart:431-494). The production-seam regressions cover authorization, revocation, reauthorization, invalid advancing evidence, reconnect/stale replay, and retired admission.
Validation
At clean exact head 24035e24b6cb80d5d576c57bc27479358a3ad3b2:
just mobile-check: passed; 559 files formatted with zero changes and Flutter analyzer reported no issues.- Full Mobile: one lane passed 2,152/2,152. The other independently reached 2,151 passed / 1 unrelated failure in
voice_note_recording_test.dart:529, where teardown double-deleted a temporary directory; #7389 changes neither that file nor path, and the focused touched-path suite passed. - Six affected/adjacent test files: 285/285 passed.
git diff --check: passed.- Fresh causal mutations removing live profile admission and the Details loading/error authority fence failed the intended production regression, then sources were restored clean. The Actions rendering branch independently suppresses lifecycle actions during loading/error, making its additional predicate defense-in-depth.
- Exact live head/base, prerequisite head, and prerequisite approval were rechecked immediately before submission.
CI and residual risk
Exact-head Mobile CI for #7389 and #7588 remained in progress; completed DCO, Semgrep, zizmor, macOS/Windows builds, Mobile Swift, and integration aggregate jobs were green or path-skipped. CI owns required-job completion.
No fresh native iOS/Android accessibility/device or live-relay huddle/audio journey was run. Those are reviewer/release confidence gaps, not author rework.
Author action: none.
Verification owner: CI for final required-job completion; release/mobile QA for optional native/live-relay observation. Any head or base change invalidates this approval.
🤖
Summary
After #7588 makes ownership evidence consistent, one problem remains: ownership in the mobile app was a snapshot. The agent-owner map was fetched once and never re-evaluated, so a newer signed profile revoking ownership ("this account is no longer my agent") updated the profile cache but left the old owner in charge — the app kept showing the agent as managed, kept owner-only mention eligibility, and kept owner-derived channel controls (archive/delete/edit) alive after the revocation. The owner map refreshed only on an unrelated event type, and mention assembly preferred that stale owner over the newer cached profile.
This PR makes ownership live:
mobile/lib/shared/mentions/agent_identity_provider.dart): it subscribes to live profile events (history included), admits every update into the same verified, ordered cache the rest of the app uses (inheriting fix(mobile): verify signed ownership profile evidence #7588's envelope authentication), and re-projects the owner map — so each newer verified profile head, whether revocation, reauthorization, or an invalid latest, advances owner state immediately.Observable effect: signing a revocation (or a new owner authorization) takes effect in mention eligibility and channel management as soon as the profile arrives, without waiting for a page refetch; a dropped or erroring owner feed no longer silently preserves old owner authority.
Dependency: this PR builds on the prerequisite branch
mobile/7389-prerequisite-5827692d(#7588 atef698b4d), not directly onmain— the stack cannot ship without the evidence-consistency groundwork. #7391 (provenance marker) and #7392 (owner policy) build on this stack.Related issue
Testing
Published head
24035e24b6cb80d5d576c57bc27479358a3ad3b2; actual basemobile/7389-prerequisite-5827692datef698b4d6048adcb73785c986e55fe6249ecbada.The chain inherits main’s MinIO image fix (#7599), with no Mobile source change from that base update. Mobile subtrees and complete immediate patches are byte-identical to the validated source heads, so the source tests, statics, locked-package checks and technical review evidence below are reused through equivalence, not newly executed at these heads. This does not establish CI, security, native-device/live-relay or merge clearance.
9b39d90c57ebaefbe9ba62eb5ed8573f8e60320f: full Mobile 2,152 passed; analyzer no issues; format 559 files, zero changed; 222 locked package roots verified before/after.042c01c5) remains source/delta evidence through equivalence; it is not a new GitHub approval or waiver of reviewer head/base conditions.