Skip to content

[i,l,-r] SECOP-952 follow-ups: requester visibility, double-submit guard, org-scoped-role retry (SECOP-1096/1097/1099)#15

Merged
phosphore merged 4 commits into
masterfrom
lorenzo/secop-1096-1099-batch
Jul 10, 2026
Merged

[i,l,-r] SECOP-952 follow-ups: requester visibility, double-submit guard, org-scoped-role retry (SECOP-1096/1097/1099)#15
phosphore merged 4 commits into
masterfrom
lorenzo/secop-1096-1099-batch

Conversation

@phosphore

Copy link
Copy Markdown
Member

Three SECOP-952 follow-ups from the production rollout, one commit each. 2.3.0-wavemm.11; full suite 1042/1042.

SECOP-1099 — requester learns who was auto-notified. When the reviewer set is picked on the requester's behalf (empty picker), they now get a propose-time Slack DM — "sent to what we think are your closest teammates, since you didn't pick anyone specifically: …" — that also teaches the picker (people watch DMs, not the PAM UI). The JOIN_PROPOSED response carries notifiedReviewers + reviewersAutoSelected, rendered as a UI secondary line. ProposeOptions gains reviewersAutoSelected (back-compat ctor kept); mail/debug handlers untouched via a default onOperationProposed overload.

SECOP-1097 — double-submit protection. The broad-role POST is slow, so an impatient second click used to mint a second token + duplicate DM batch (observed live). Frontend now ignores re-entrant submit clicks and disables the button in-flight; backend skips the fan-out when a live registry entry already exists for the same fingerprint (multi-tab / retry defense, fail-open). Async fan-out (the root-cause latency fix) tracked separately in the ticket.

SECOP-1096 — first-join of org-scoped roles. JIT lazily creates the Google group then binds it org-wide before iam.allowedPolicyMemberDomains can resolve it → transient 400 that used to surface as NotAuthenticated (observed: serviceusage-admin). modifyIamPolicy now retries that specific 400 with backoff (2s/4s/8s, budget separate from 412). Narrow, unit-tested predicate; genuine denials still fail.

Not included: SECOP-1098 (availability-aware ranking) — paused on a DI decision + needs live Slack validation; see ticket.

🤖 Generated with Claude Code

When the reviewer set is picked on the requester's behalf (empty picker
selection), the requester now learns who was notified:
- propose-time Slack DM ('sent to what we think are your closest
  teammates, since you didn't pick anyone specifically') that also
  teaches the picker — people watch DMs, not the PAM UI;
- the JOIN_PROPOSED response carries notifiedReviewers +
  reviewersAutoSelected, rendered as a secondary line in the UI.

Plumbing: ProposeOptions gains reviewersAutoSelected (back-compat
constructor kept); AbstractProposalHandler gets an options-aware
onOperationProposed overload defaulting to the legacy hook, so the
mail/debug handlers are untouched.
The elevation POST is slow on broad roles (Cloud Identity teammate
fan-out + synchronous reviewer DM chains), so an impatient second click
used to mint a second proposal token and a duplicate reviewer DM batch
(observed live 2026-07-09: two identical 'Posted 15/15' 25s apart).

- Frontend: ignore re-entrant submit clicks and disable the button
  while the POST is in flight.
- Backend defense-in-depth: SlackProposalHandler skips the fan-out when
  a live registry entry already exists for the same (beneficiary, group,
  recipients) fingerprint — covers multi-tab / post-timeout retries the
  button guard can't. Fail-open on registry read errors.

Not addressed here (bigger change, tracked in SECOP-1097): making the
DM fan-out fully async so the POST returns before Slack delivery.
…SECOP-1096)

First-ever join of an org-scoped role failed with a 400: JIT lazily
creates the Google group, then immediately binds it org-wide, but the
iam.allowedPolicyMemberDomains checker can't resolve the seconds-old
group yet ('not in permitted organization'). The error previously fell
through to NotAuthenticatedException; the user's manual retry succeeded
once propagation cleared (2026-07-09, serviceusage-admin).

modifyIamPolicy now recognises that transient 400 and retries with
backoff (2s/4s/8s, budget separate from the 412 concurrency-control
retries so neither starves the other). A genuine domain-restriction
denial simply exhausts the bounded retries and surfaces as before.
Predicate is deliberately narrow (400 + constraint id / 'not in
permitted organization') and unit-tested.
@phosphore phosphore merged commit 7672629 into master Jul 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant