fix(mobile): make non-member invitations explicit; stopping halts publication but cannot retract accepted invitations - #7390
Conversation
🔐 Codex Security Review
|
d545460 to
9b00041
Compare
|
@buzz-security-review 9b00041 |
Independent invitation readiness evidence — 2026-09-07Verified current range:
|
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent reviewed exact head 9b000415f0644010f0f5606a971ac8eb92d63bae against base 3c7f288c60d67df78577b237e27c3dfc8831aaa1.
Blocking finding
[P1] Cancel or a refused later invite can silently leave earlier invitees added without sending the message.
_addMentionedNonMembers now submits one kind:9000 membership write per identity, sequentially (mobile/lib/features/channels/compose_bar/helpers.dart:452-489). ensureCurrent() runs only before and after each awaited write (:471-477), so it can prevent later work but cannot retract a membership the relay already committed. The Cancel control remains available during this operation (mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:993-1009). Consequently, if invitee A is accepted and the user cancels while B is pending—or B is refused—the message is not sent, but A remains a durable channel member.
The production-widget regression currently codifies that partial side effect: it observes one membership event before Cancel and still expects one afterward while asserting sends == 0 (mobile/test/features/channels/compose_bar_test/send_lifecycle_tests.dart:150-177). This contradicts the PR contract that cancellation/refusal “adds nobody” and creates an invisible audience/authorization change.
Author action: make multi-invite preparation atomic with respect to cancellation and refusal, or stop presenting/claiming cancellation once durable membership changes may begin and explicitly surface partial completion plus safe recovery. Add production-bound regressions asserting zero accepted membership additions after a successful Cancel and after a refused batch. Do not blindly compensate by removing A: it may have independently become a legitimate member.
Verification owner: author for code and regressions; reviewer to rerun the production race and perform native iOS observation at the replacement exact head.
Evidence
- Two independent review lanes traced the same production race and reached REQUEST CHANGES.
- Focused production-widget cancellation test passed at exact head, confirming the checked-in oracle retains one membership mutation.
just mobile-checkpassed at exact head: formatting unchanged and Flutter analysis clean.- Required GitHub Mobile check succeeded for exact-head run
34137483375; the author reports fulljust ciand 2,084 mobile tests passed. git diff --check 3c7f288...HEADpassed in a clean exact-head worktree.
Confidence gaps
Native iOS dialog focus/VoiceOver, narrow/theme/keyboard behavior, and live-relay recovery were not observed. These are reviewer/tooling-owned follow-up after remediation, not additional author rework and not the basis for this verdict.
|
Pin/status refresh (2026-09-10). The head, approval, and classification-status statements in this response were accurate when written but are stale; they are retained below as historical fact, followed by the current verified mapping. This refresh updates pins/status only — it is not a response to, or clearance of, any later current-head review, and adds no new CI or merge claim. The irreversible-prefix correction lives in direct child #7527, historically at exact head The correction records each successful kind:9000 acknowledgement inside ChannelActions before its post-write community fence, independently of permission to continue. Recovery identifies the original community/channel and asks the sender to check the remaining draft; it no longer promises complete draft/media retention after leaving, Stop remaining stays truthful, and incomplete irreversible effects are reported. No rollback or atomic relay transaction is claimed. Production socket regressions cover accepted-OK then scope change, Stop, media revisit, and later refusal; moving accounting below the fence fails the scope test; media revisit verifies the selected attachment disappears while source text remains. Historically, at Technical coverage and standalone landing safety are separate questions, and review 5158131513 is correct on the second: this head
|
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent reconsidered exact unchanged head 9b000415f0644010f0f5606a971ac8eb92d63bae against base 3c7f288c60d67df78577b237e27c3dfc8831aaa1, including author response IC_kwDORgXb2s8AAAABTh-kYg and dependent follow-up #7527.
Verdict: changes still required
The response relocates the corrective work; it does not make #7390 independently merge-safe. At this head, _addMentionedNonMembers can durably submit an accepted prefix of sequential kind:9000 membership writes (mobile/lib/features/channels/compose_bar/helpers.dart:452-489) while the active UI still offers Cancel (mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:993-1009). The checked-in regression explicitly permits one retained membership mutation with zero message sends (mobile/test/features/channels/compose_bar_test/send_lifecycle_tests.dart:150-177).
That runtime behavior still contradicts this PR’s title/body contract that invitations are “cancellable” and that cancellation or refusal “adds nobody.” Merging #7390 alone can therefore silently retain an audience/authorization change after the apparent cancellation of the operation.
#7527 is stacked directly on this exact head and appears to have the right corrective shape: disclose persistence, replace Cancel with Stop remaining once writes begin, count acknowledged invitations, and surface retained membership plus draft recovery. But #7527 remains open and unmerged. Its current ancestry means ordinary sequential landing necessarily puts #7390’s unsafe state on the default branch first; neither #7390’s code nor a prose cross-link mechanically prevents that state from landing or releasing. A follow-up promise is not a runtime dependency. Clearance remains blocked until the corrective delta is part of the actual merge candidate.
Author action: put #7527’s disclosure/recovery delta into a replacement #7390 integration head (merge/cherry-pick or equivalent), and update #7390’s title/body to state the irreversible-prefix semantics. Alternatively, provide a maintainer-controlled, mechanically enforceable combined landing that guarantees no default-branch or releasable state contains #7390 without the independently approved #7527 delta. Request review on the resulting immutable combined head.
Verification owner: author/maintainer for the combined integration topology and truthful PR contract; reviewer for exact-head transition regressions and landing enforcement. Native iOS focus/VoiceOver and live-relay recovery remain reviewer/tooling-owned confidence gaps, not additional author defects.
No broad test rerun was needed for this same-head response reconsideration. Fresh GitHub inspection confirmed #7390 remained at the pinned head/base and #7527 remained open, unmerged, and stacked directly on it when this verdict was prepared.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — REQUEST CHANGES remains
Reviewed: unchanged exact head 9b000415f0644010f0f5606a971ac8eb92d63bae against base 3c7f288c60d67df78577b237e27c3dfc8831aaa1.
Reconsideration trigger: author response IC_kwDORgXb2s8AAAABTh-kYg at 2026-09-09T17:01:14Z, #7390 (comment).
Both review lanes reconsidered the prior blocker. The response relocates the corrective work to dependent PR #7527; it does not make this unchanged head independently merge-safe.
Blocking defect
#7390 still ships sequential durable kind:9000 invitation writes while presenting Cancel and claiming cancellation/refusal “adds nobody.” An earlier membership can commit before the user cancels or a later invitation is refused; publication and remaining invitations then stop, but that audience/authorization change remains. The checked-in production-widget regression still accepts one committed membership with zero sends (mobile/test/features/channels/compose_bar_test/send_lifecycle_tests.dart:150-177).
#7527 is stacked directly on this head and appears to change the contract in the correct direction—disclosure before Invite, Stop remaining, acknowledged-count reporting, retained-membership warning, and draft recovery—but it is open and absent from #7390. A later comment or ordinary dependent PR is not a runtime or landing guarantee; sequential landing necessarily exposes #7390’s unsafe state first.
Author action: create one replacement #7390 head containing #7527’s disclosure/recovery delta and update the PR contract to describe irreversible accepted-prefix semantics, then request exact-head review. An alternative must mechanically guarantee that no releasable/default-branch state contains #7390 without #7527; a prose dependency is insufficient.
Verification owner: author/maintainer for combined-head topology and truthful contract; reviewer for exact combined-head state-transition regressions. Native iOS focus/VoiceOver and live-relay recovery remain reviewer/tooling confidence gaps, not additional author work.
At final refresh, #7390 remained open at 9b000415...; #7527 remained open at 4bb3be24... with base exactly 9b000415.... No head drift. The qualifying response is now processed; unchanged repeat comments do not clear this verdict.
9b00041 to
1f571bd
Compare
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent reviewed exact head 1f571bd3d74eedee2a0c2dbbdf0a402698c31398 against base b4f266dae1f28982f6c43df4d1b9ffa8f3e54d50.
[P1] Do not present irreversible invitation writes as cancellable
After media upload, _addMentionedNonMembers submits sequential kind:9000 membership writes before message publication, with generation checks only around each awaited write (mobile/lib/features/channels/compose_bar/helpers.dart:452-488; mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:650-687). The progress UI continues to expose Cancel throughout that phase and only increments uploadGeneration (mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:1057-1073). Once invitee A has been accepted, cancelling while B is pending—or encountering B’s refusal—can stop the remaining invitations and message publication but cannot safely retract A’s membership.
The production-widget regression demonstrates the mismatch: it exposes Cancel after the first membership operation begins, then expects zero sends while one membership event remains (mobile/test/features/channels/compose_bar_test/send_lifecycle_tests.dart:141-181). The consent dialog also does not warn that accepted invitations survive a later stop or failure (mobile/lib/features/channels/compose_bar/helpers.dart:341-361). The PR body now acknowledges the irreversible prefix, but the runtime still describes it as ordinary cancellation and gives no retained-membership disclosure. That is a user-trust and channel-access defect.
PR #7527 appears to contain the right-shaped remedy, but it remains open and is stacked on this head; those protections are absent from this merge candidate. A serial landing note is not runtime behavior.
Author action: integrate the truthful irreversible-prefix contract into the merge candidate: disclose before Invite that accepted invitations survive stop/failure; change the active action to Stop remaining once invitation writes begin; account for accepted acknowledgements before post-write scope fences; and report retained membership plus precise draft/attachment recovery. Add production-bound regressions for cancellation, later refusal, and scope switch after an accepted acknowledgement. An equally safe alternative is to remove or disable the cancellation affordance before durable membership writes begin.
Verification owner: author/maintainer for the combined head and landing topology; reviewer for exact-head source/race verification and production-widget/native transition behavior.
Validation
Two independent review lanes traced the same exact-head state transition and found the same defect.
just mobile-checkpassed at the pinned head: formatting clean and Flutter analysis clean.- Full
just mobile-testpassed at the pinned head: 2,131 tests. - A focused rerun of
flutter test test/features/channels/compose_bar_test.dart --plain-name 'cancel cannot finish an old membership batch'passed 1/1, confirming the checked-in oracle retains one membership event with no send. git diff --check b4f266dae1f28982f6c43df4d1b9ffa8f3e54d50...HEADpassed in a clean pinned worktree; the exact-head Mobile/Clients GitHub gates are green.- Authenticated reviewer is
jedwards27; live PR author isloganj, so this is not self-review.
Desktop jobs failed during runner package setup with Google Chrome repository Hash Sum mismatch; final path aggregates and the changed Mobile scope are green. This is a gate/tooling confidence gap, not additional author action for this defect. Native iOS focus/VoiceOver/narrow-layout behavior and live-relay recovery were not observed; those remain reviewer/tooling-owned residual risks.
1f571bd to
069c8b7
Compare
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — changes requested
Reviewed exact head 069c8b71a4b43edc3dae946e14d2523fc8a16d29 against base 17c3b83c60e106902565d3abd879b7456ade9fa9.
[P1] “Cancel” can leave an undisclosed, irreversible membership prefix
_addMentionedNonMembers publishes one durable kind:9000 membership event per identity, sequentially (mobile/lib/features/channels/compose_bar/helpers.dart:452-488). Once an event is accepted, the client cannot roll it back. But while that batch is running, the progress UI still offers Cancel, whose handler only cancels the upload and increments the local generation (mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:1057-1073; mobile/lib/features/channels/compose_bar/upload_progress_pill.dart:162-181). The confirmation dialog does not disclose that accepted invitations survive a later stop or failure (helpers.dart:341-361).
A concrete path is: invite A and B with media; A's membership event is accepted; the user taps Cancel while B is pending, or B is refused. Message publication stops, but A retains channel access, and the UI does not report that audience/access change. The production-widget regression currently codifies this outcome by expecting zero message sends while retaining one membership event after Cancel (mobile/test/features/channels/compose_bar_test/send_lifecycle_tests.dart:141-181). The focused test passes, so it demonstrates the defect rather than preventing it.
Author action: make this merge candidate truthful about the irreversible prefix. Before inviting, disclose that accepted invitations can survive later stop/failure; once membership writes begin, label the action Stop remaining rather than Cancel; account for accepted acknowledgements before post-write scope fences; and report retained membership plus precise draft/attachment recovery. Add production-bound regressions for stop after accepted A, later refusal, and scope switch after an accepted acknowledgement. A safe alternative is to remove or disable cancellation before durable invitation writes begin. The corrective behavior being developed separately in #7527 is not runtime protection in this PR.
Verification owner: author/maintainer for the integrated code, user contract, and regressions; reviewer for exact-head race/widget verification and native transition observation.
Validation and confidence gaps
- PASS: review preflight,
git diff --check, clean exact-head worktrees. - PASS:
just mobile-check/ analyzer. - PASS: focused production-widget cancellation test, 1/1, confirming one retained membership event with no message publication.
- PASS: exact-head GitHub Clients / Mobile aggregate, DCO, Semgrep, and zizmor.
- The full local
just mobile-testrun had three failures outside this PR's changed files (activity_provider_test,settings_profile_header_test,channel_sort_manager_test), while exact-head Mobile CI is green. This is a baseline/flakiness confidence gap, not additional author action and not the basis for this review. - Native iOS VoiceOver/focus/narrow-layout and live-relay recovery were not exercised; these remain reviewer/tooling-owned confidence gaps.
Both independent systems/integration and product/UI traces reproduced the same access-boundary defect. The current head’s mobile tree is byte-identical to the previously blocked revision; the changed-head delta came from the updated base and does not repair this behavior.
069c8b7 to
d080ecc
Compare
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — changes requested
Reviewed exact replacement head d080eccb12c50a79c6d345e295b5beaae22c7ed9 against exact base 1fe1193a29744cf7bd26e2a138db047169ecad7c.
[P1] “Cancel” can retain an undisclosed channel-access change
The composer emits one durable kind:9000 invitation per identity, sequentially (mobile/lib/features/channels/compose_bar/helpers.dart:452-489). Once a relay accepts an invitation, the later currentness check can stop subsequent work but cannot retract that membership. During the batch, the media progress surface still offers Cancel; its handler only cancels the upload token and advances local uploadGeneration (mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:1057-1073). The confirmation dialog does not disclose that accepted invitations survive a later stop or failure (helpers.dart:341-361).
Concrete path: invite A and B with media; A's membership event is accepted; the user taps Cancel while B is pending, or B is refused. Message publication and remaining work stop, but A retains channel access with no retained-membership disclosure. The production-widget regression codifies this state: its cancellation row expects zero sends while retaining one membership event; the partial-refusal row retains two attempted membership events with zero sends (mobile/test/features/channels/compose_bar_test/send_lifecycle_tests.dart:86-185). The focused test passes, confirming the checked-in runtime/oracle mismatch rather than preventing it.
The exact stack is #7393 df61d54c → #7394 2fd0d0bc → #7531 1fe1193a → #7390 d080eccb → #7527 84f61093. Corrective disclosure and recovery behavior exists only in the open descendant #7527, not this independently landable merge candidate. The current composer and test bytes are unchanged from the previously blocked head; the replacement-head delta does not repair the behavior.
Author action: make this merge candidate truthful at runtime. Before Invite, disclose that accepted invitations can survive a later stop/failure; once durable writes begin, change Cancel to Stop remaining (or remove/disable cancellation); account for accepted acknowledgements even when a post-write scope fence fires; and report retained membership plus precise draft/attachment recovery after stop, refusal, or scope change. Add production-bound regressions for accepted A → stop, accepted A → later refusal, and accepted acknowledgement → scope change. Do not blindly remove A because it may independently have become a legitimate member. Update the title/body away from ordinary “cancellable” semantics unless the combined runtime contract lands here.
Verification owner: author/maintainer for the integrated code, runtime copy, recovery behavior, regressions, and safe landing topology; reviewer for exact-head race/widget reruns and native transition observation.
Exact-head evidence and confidence gaps
- PASS: official preflight; base/merge-base exactly
1fe1193a…; own range four commits, six Mobile files,+416/-50; DCO trailers present; clean worktree. - PASS:
git diff --check. - PASS:
just mobile-check; 557 files format-clean and analyzer clean. - PASS: full
just mobile-test; 2,131 tests at the exact clean head. - PASS: focused production-widget cancellation row, 1/1, demonstrating one retained membership write with zero message sends.
- PASS: exact-head Mobile/Clients, DCO, Semgrep, and zizmor checks. Some unrelated Desktop-domain jobs remained in progress at review time; they are not the basis for this author-actionable defect.
- Native iOS VoiceOver/focus, narrow text layout, and live-relay recovery were not run. These are reviewer/tooling-owned confidence gaps, not additional author defects.
Both independent systems/integration and product/UI reviews reproduced the same access-boundary defect on this exact replacement head.
d080ecc to
5f8744a
Compare
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — changes requested
Reviewed exact head 5f8744a3fb571b6dd9a6d3853ae254694ab9eeea against exact base 612a131b24a3a57e2b365de87dc98007e165a17d.
[P1] “Cancel” still hides an irreversible channel-access change
The new generation/source fences correctly stop future invitation work, and mutation testing confirms that mechanism. They do not change the already-committed prefix: invitations remain sequential durable writes, one identity per awaited addMembers, followed by a currentness check (mobile/lib/features/channels/compose_bar/helpers.dart:452-488). Once A's kind:9000 event is accepted, a later cancellation, revisit, refusal, or scope fence cannot retract A.
The media progress surface remains visible during membership preparation and still labels its action Cancel; tapping it cancels the upload token and increments uploadGeneration (mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:647-688,1056-1073; mobile/lib/features/channels/compose_bar/upload_progress_pill.dart:162-181). The confirmation dialog does not disclose that accepted invitations survive a later stop or failure (helpers.dart:341-361). A concrete path remains: invite A and B with media; A is accepted; tap Cancel while B is pending, or B is refused. Message publication and remaining invitations stop, but A retains channel access with no retained-membership report.
The checked-in production-widget regression demonstrates this contract rather than making it truthful: after the first membership event starts, it taps Cancel, expects zero message sends, and retains one membership event; the partial-refusal row likewise stops publication after attempted membership writes (mobile/test/features/channels/compose_bar_test/send_lifecycle_tests.dart:87-183).
Author action: disclose before Invite that accepted invitations remain if sending is stopped or fails; once membership writes begin, relabel the action Stop remaining (or remove/disable it); count accepted acknowledgements before any post-write fence; and report retained membership plus precise draft/attachment recovery after stop, refusal, or scope change. Add production-bound regressions for accepted A → stop, accepted A → later refusal, and accepted A → scope switch. Do not blindly compensate by removing A because it may independently have become a legitimate member.
Verification owner: author/maintainer for runtime copy, acknowledgement accounting, recovery behavior, regressions, and standalone-safe landing topology; reviewer for exact-head race/widget reruns and native transition observation.
Reconciled evidence
The systems lane found no additional contract, ownership, concurrency, persistence, or security defect and causally verified the new cancellation fence: removing only the generation fence caused a second membership event; restoring exact head passed. The product/UI lane correctly distinguishes that future-work cancellation from the unresolved user-facing promise about the accepted prefix. These findings are compatible: the fence works, but the UI still misstates the irreversible result.
- PASS: exact base/head preflight, merge-base, DCO, clean worktrees, and
git diff --check. - PASS:
dart formatover 557 files andflutter analyze. - PASS: full Mobile suite, 2,131 tests, at the exact clean head.
- PASS: focused production-widget cancellation test, 1/1; mutation test establishes causal coverage of the generation fence.
- DCO, Semgrep, zizmor, and completed exact-head checks were green. Some exact-head GitHub jobs were still running at the final lane polls; they are not the basis for this defect.
- Native iOS VoiceOver/focus, narrow/dynamic-type layout, and live-relay recovery were not exercised. Those are reviewer/tooling-owned confidence gaps, not additional author defects.
Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
5f8744a to
c93d15e
Compare
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Reviewed: 893c668e1475997142c27dc661def869f56a848a..c93d15ebc48f48aa96e3a6eed157bb1b18967d0d (exact live head c93d15ebc48f48aa96e3a6eed157bb1b18967d0d)
Risk: high — cancellation intersects durable channel-membership writes, so a misleading stop contract can leave a user with channel access even though no message was published.
Blocking finding — [P1] “Cancel” hides an irreversible accepted invitation prefix
Invitation writes run sequentially, one awaited addMembers operation per identity, with currentness checked around each await (mobile/lib/features/channels/compose_bar/helpers.dart:452-489). While that work runs, the production progress action still says Cancel, but its handler only cancels the upload token and increments uploadGeneration (mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:1064-1073).
That fence correctly stops later work, but it cannot retract an already accepted membership write. In the A+B case, A can be relay-accepted and then Cancel, a scope switch, or B's refusal can prevent B and the message while A silently retains channel access. The consent dialog does not disclose this irreversible-prefix behavior, and the stopped/error path does not report retained membership or recovery state. The checked-in production-widget regression actually demonstrates the mismatch: zero message publications coexist with one retained membership event after Cancel (mobile/test/features/channels/compose_bar/send_lifecycle_tests.dart:87-183). A mutation run removing only the upload-generation fence caused that test to emit two membership events instead of one; restoring the exact-head bytes passed. The mechanism proves stop remaining, not cancel accepted invitations.
Author action: before Invite, disclose that accepted invitations survive stop/failure; once membership writes begin, label the action Stop remaining (or disable/remove it); track accepted acknowledgements before post-write fences; report retained membership and exact draft/attachment recovery after stop, refusal, and scope switch. Add production-bound regressions for accepted A → stop, accepted A → later refusal, and accepted A → scope switch. Do not automatically remove A because membership may independently have become legitimate. Update the PR's “cancellable” contract unless the truthful combined runtime lands here.
Verification owner: author/maintainer for the runtime contract, accepted-acknowledgement accounting/recovery, regressions, and standalone-safe landing topology; reviewer for exact-head widget/mutation replay and native observation.
Validation at matching head
- PASS: live GitHub head/local
HEAD/merge-base pinned; clean detached worktree;git diff --check. - PASS:
just mobile-check— 557 files format-clean; analyzer clean. - PASS: focused production-widget test
cancel cannot finish an old membership batch(1/1); targeted mutation failed causally, then exact-head restoration passed. - PASS: repository-policy review of
AGENTS.md,VISION.md, andTESTING.md; no added productionunwrap/expect, no undocumented new public API, and all four commits carry DCO sign-off. - PASS at final check: Mobile/Clients Mobile, DCO, Semgrep, zizmor, macOS build, Windows build, and available Desktop integration/smoke jobs. Some Desktop rollup jobs remained pending; the blocking product defect does not depend on them.
Confidence gaps — not additional author defects
Native iOS VoiceOver/focus/dynamic-type behavior and live-relay recovery were not exercised. Reviewer/tooling owns those checks. No additional systems, concurrency, persistence, security, or product/UI defect was found in the six-file PR range.
— :bot: Jude’s code review agent
|
'Cancellable' contract corrected — factual metadata response to review 5163507302 at head Your clause — "Update the PR's 'cancellable' contract unless the truthful combined runtime lands here" — is answered on the first branch. The truthful combined runtime (pre-Invite disclosure, Stop remaining labeling, accepted-acknowledgement accounting, and stop-path recovery reporting) has not landed in this merge unit, so the title and the Summary lead-in no longer describe the choice as "cancellable". This is a factual correction of the contract text to match what the public head actually does — no runtime change, no semantic adoption, no review clearance or approval transfer, and no merge authorization is claimed by it; the standalone-safe vs. lineage landing decision remains the maintainer's. Precisely, at this head, the stop affordance:
The title now reads "make non-member invitations explicit; stopping halts publication but cannot retract accepted invitations", and the Summary lead-in and Testing bullet now state the two cancellation regressions' actual outcomes (upload-phase stop: no membership events at all; mid-batch stop: publication and later writes halted, one accepted invitation retained). No new CI or whole-repository claim is made by this edit. |
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES (same-head reconsideration)
Reviewed: 893c668e1475997142c27dc661def869f56a848a..c93d15ebc48f48aa96e3a6eed157bb1b18967d0d (exact live head c93d15ebc48f48aa96e3a6eed157bb1b18967d0d), reconsidering author response 5615065403.
The corrected title, Summary, and Testing section now accurately describe the non-atomic relay behavior. That resolves the metadata portion of the prior action, but not the in-product access/recovery defect.
Blocking finding — [P1] the runtime still presents a cancellable operation while silently retaining completed access grants
The consent dialog explicitly authorizes invitations, so an accepted kind-9000 write is not an unauthorized grant by itself (mobile/lib/features/channels/compose_bar/helpers.dart:341-361). That is the strongest case for treating the remainder as a follow-up.
It does not clear the standalone runtime, however. The dialog never tells the user that a multi-recipient Invite is a sequence of independently durable access grants. Once the sequence begins, the only visible action remains Cancel (mobile/lib/features/channels/compose_bar/upload_progress_pill.dart:163-181), but that handler only cancels the upload token and advances the generation (mobile/lib/features/channels/compose_bar/compose_bar_widget.dart:1064-1073). Invitations are individually awaited durable writes (mobile/lib/features/channels/compose_bar/helpers.dart:452-489). The production-widget regression proves the resulting hidden partial state: after Cancel, one membership write remains accepted, zero message is published, and the draft returns without reporting who retained channel access (mobile/test/features/channels/compose_bar_test/send_lifecycle_tests.dart:87-183).
Because channel membership is the access gate, truthful GitHub metadata cannot substitute for disclosure and recovery in the workflow where the user makes and then stops that decision. The user authorized an invitation batch in the context of a send; the UI subsequently says Cancel, publishes nothing, and gives no indication that only a prefix of the access-changing operation survived. That is a concrete product defect, not merely missing native observation.
Author action: before Invite, disclose that already accepted invitations survive stop/failure; once membership writes may have begun, relabel Cancel to Stop remaining (or remove/disable it); account for accepted acknowledgements before every post-write fence; visibly report retained membership and recoverable draft/attachment state after stop, later refusal, and scope switch. Add production-bound regressions for those three accepted-prefix transitions. Do not automatically remove accepted members because concurrent legitimate membership may exist. The open stacked #7527 contains the intended runtime work and is based on this exact head; maintainers may land the pair atomically rather than duplicate it here, but #7390 is not standalone-safe.
Verification owner: author/maintainer for the runtime disclosure, phase-correct action label, ACK accounting/reporting, recovery semantics, tests, and landing topology. Reviewer owns exact-head widget/mutation replay. Native iOS VoiceOver/focus/dynamic type and live-relay recovery remain reviewer/tooling confidence gaps; they are not additional blockers.
Exact-head evidence
- Live PR remained open and non-draft at base/head
893c668e1475997142c27dc661def869f56a848a/c93d15ebc48f48aa96e3a6eed157bb1b18967d0dduring final integration. - Current-head CI is terminal green for Mobile, Clients/Mobile, Desktop integration/smoke/build lanes, DCO, Semgrep, and zizmor; path-gated skips were inspected. Green CI does not exercise the missing in-product disclosure/recovery contract.
- Prior exact-head checks remain applicable because response 5615065403 changed PR metadata only: clean worktree,
git diff --check,just mobile-check, focused production-widget regression, and causal generation-fence mutation. - The assigned systems lane found the accepted prefix authorized and recommended APPROVE; the product/access lane found the undisclosed partial retained-access state blocking. I resolve that disagreement in favor of REQUEST CHANGES because explicit initial consent establishes authorization, but does not make the later Cancel label or silent partial recovery state truthful at an access-control boundary.
— :bot: Jude’s code review agent
:bot: Jude’s code review agent — reconsideration result: REQUEST CHANGES remainsReconsidered: exact unchanged head The corrected title, Summary, and Testing text now truthfully describe the branch, but they do not resolve the user-facing access defect. GitHub metadata is not shown where the irreversible decision occurs. The mobile dialog still offers Invite without disclosing that a multi-recipient invitation is a sequence of independently durable access grants ( The team considered the contrary systems interpretation that explicit Invite makes each accepted membership durable by definition. That is insufficient: the UI presents one combined operation and later says Cancel, yet silently leaves a partial access-grant prefix when that operation stops. The user is neither warned before commitment nor told afterward who retained access. Author action: before Invite, disclose that accepted invitations survive stop/failure; once membership writes may have begun, label the action Stop remaining (or remove/disable it); track accepted acknowledgements before post-write fences; visibly report retained membership and draft/attachment recovery after stop, later refusal, and scope switch. Add production-bound regressions for those accepted-prefix transitions. Do not automatically remove accepted members, because concurrent legitimate access may exist. Verification owner: author/maintainer for runtime disclosure, phase-correct labeling, accepted-acknowledgement accounting/reporting, recovery semantics, and regressions; reviewer for exact-head widget/mutation replay. Native iOS VoiceOver/focus/dynamic type and live-relay recovery remain reviewer/tooling confidence gaps, not additional author defects. Current exact-head required CI is terminal/green where applicable; cancelled entries are superseded by successful current runs. The live PR remained open, non-draft, and unchanged immediately before publication. |
🤖
Summary
Mentioning a person who wasn't in the channel already prompted you to invite them or send anyway — but mentioning an agent outside the channel silently added it as a channel bot and sent, changing your audience without asking. A refused invitation demoted the mentions to plain references and sent anyway, and an interrupted media upload could still leave membership changes behind. This PR makes both people and agents an explicit choice; stopping halts pending invitations and publication but cannot retract an invitation the relay has already accepted:
Related issue
main; now deliberately based on feat(mobile): read fresh evidence for every selected mention #7531, including fix(mobile): revalidate exact agent intent before publication #7394 publication guards. See the serial sequence below.Testing
1f571bd3d:just mobile-checkand fullflutter testpass (2,131 tests). The earlier exact-head evidence comment applies only to its historical head, not this rebased candidate; no new whole-repositoryjust cipass claimed.To see it: mention a person or agent outside the channel and send — choose Invite, Send without inviting, or dismiss; each outcome matches the bullets above.
Screenshots
Flutter production-widget test renders — not native-device screenshots or acceptance captures.
Capture provenance
Rendered by the Flutter widget engine in a
flutter testrun (production widgets, production theme; no device or simulator). Before: the historical base3c7f288c60d67df78577b237e27c3dfc8831aaa1. After: the historical head9b000415f0644010f0f5606a971ac8eb92d63bae.Authorized serial integration carry (2026-09-09)
Current HEAD
1f571bd3d74eedee2a0c2dbbdf0a402698c31398; own PR range 466 changed lines, including tests. Existing PR retained; no duplicate feature patch or merge. Declared integration sequence: #7393 → #7394 → #7531 → #7390 → #7527 → forthcoming classification consumer. This is an integration order, not a claim that the historical root features inherently depended on each other.Final carried HEAD:
just mobile-checkpasses; fullflutter test: 2131 passed. These supersede prior standalone counts for this head. Existing widget-render screenshot attachments remain historical UI evidence, not a new composed runtime run. Repository-wide just-ci and final persisted classification workflow are not claimed green by these package checks.Composition retains the #7394 authorization/cancellation guard and combines invitation visit/upload fences with it, retains explicit consent and source-draft listener/recovery ownership. #7527 retains successful ACK accounting before ChannelActions scope fences. Saved-isAgent classification/restart repair is still forthcoming; this carry alone does not resolve #7387.
Mechanical docs-carry note — 2026-09-10
Head mechanically carried to
d080eccb12c50a79c6d345e295b5beaae22c7ed9(corrected parent #75311fe1193a2974…+ this PR's original own commits, zero conflicts, original messages/authors/DCO preserved). The carry is docs-only — the #7531 review-5162334206///correction onmobile/lib/shared/mentions/selected_mention_authorization.dart(comment-stripped file equality;mobile/testbytes identical to069c8b71a4b43edc3dae946e14d2523fc8a16d29); earlier "Head069c8b71a4b43edc3dae946e14d2523fc8a16d29"/current-HEAD mappings above are dated receipts for their pre-carry heads, including their CI links. Own churn is unchanged at 466 including tests (inherited docs are not counted as own); no executable delta. Normal CI on the carried head: https://github.com/block/buzz/actions/runs/34436597921.Workflows helper test carry — 2026-09-10
Head rebased to
5f8744a3fb571b6dd9a6d3853ae254694ab9eeea(same four commits, authors and messages preserved, on corrected parent #7531612a131b24a3a57e2b365de87dc98007e165a17d); no conflicts, no merge commits. Own range vs the corrected parent is unchanged at 416 additions + 50 deletions = 466 lines including tests; the only old-head→new-head content delta is the carried #7531 Desktop E2E test repair indesktop/tests/e2e/workflows.spec.ts(+15/−3) anddesktop/tests/e2e/workflow-local-controls.spec.ts(+7/−2) — mobile production, mobile tests, and API docs are byte-identical old→new. Current-head review 5162786862 predates this head movement and requires re-validation on the new head per its own terms. Normal CI on the new head is running; no green claim is made here.Workflow regression test carry — 2026-09-10
Head rebased to
c93d15ebc48f48aa96e3a6eed157bb1b18967d0d(same four commits, authors, messages, and DCO preserved, on parent #7531893c668e1475997142c27dc661def869f56a848a); no conflicts, no merge commits. Own range vs the new parent is unchanged at 416 additions + 50 deletions = 466 lines including tests; the only old-head→new-head content delta is the carried #7531 test-only +24/−0 regression coverage indesktop/tests/e2e/workflows.spec.ts— mobile production, mobile tests, and API docs are byte-identical old→new. Current-head review 5163162911 predates this head movement and is not resolved or invalidated-as-addressed by this test-only carry. Normal CI on the new head: https://github.com/block/buzz/actions/runs/34444700430 (no green claim is made here).