feat(user/v1): add UserProvisioningService.ResolveOrCreateByEmail (NEW-40 decoupling) #6
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/user-provisioning-rpc"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Contract for the NEW-40 user-identity decoupling (.planning/28 in im2be-mono). social-login will call user-service at OAuth login to resolve-or-create the canonical user by email, returning the canonical UUID — making user-service the sole users-table writer (today both user-service + social-login INSERT directly: the R1 two-writer anti-pattern + NEW-13 duplicate-row race).
First of the decoupling PRs: (1) this contract; (2) user-service impl (gRPC + V10 password-nullable migration + provisioning) consuming the vendored copy; (3) social-login gRPC client + stop direct-write + the new mTLS hop; (4) docs/ADR retiring NEW-40.
The canonical-user-provisioning gRPC contract for the NEW-40 user-identity decoupling (.planning/28): social-login-service will call user-service at OAuth login to resolve — or, on first sign-in, create — the canonical user by provider-verified email, returning the canonical UUID. This makes user-service the SOLE writer of the users table (today both user-service and social-login INSERT rows directly — the R1 two-writer anti-pattern). New file user/v1/user_provisioning.proto: UserProvisioningService.ResolveOrCreateByEmail (idempotent; creates verified + password-less OAuth users, fires the standard init fan-out once, returns shared.v1.UserId + created flag). AuthProvider enum {GOOGLE, APPLE}. Reuses shared.v1.UserId. Additive (buf BACKWARD-safe); separate service from UserProfileService so its SPIFFE ACL (social-login → provision) is distinct from the identity → profile ACL. buf lint + build clean; import-direction guard OK.Show previous round
hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-protobuf)
Round 1 — head
0d4fe1cface2, basemain, triggeropenedTL;DR: NEEDS_WORK — 1 major + 1 minor + 1 info findings kept this round.
Summary
Arbiter reconciled 2 (A) + 3 (B) → 3 findings.
CI status (head
0d4fe1cface2)Overall: ✗ failure
3 checks: 3 pending
Findings (3)
[MAJOR] ResolveOrCreateByEmailRequest missing provider_subject field — email-only lookup enables account-takeover and permanent identity split
user/v1/user_provisioning.proto:71
Agreed finding (A + B independently).
ResolveOrCreateByEmailRequestcarriesemail(field 1) andprovider(field 4) but has no field for the OAuth provider's stable, per-app subject identifier — thesubclaim from the OIDC ID token. This creates two concrete, irreversible data problems:Account takeover via recycled email (A). Google retires and eventually recycles email addresses. A new Google user who inherits a recycled address resolves to the previous owner's canonical UUID on first sign-in — silently inheriting their data (family membership, diary, subscription tier). The
subclaim is per-account and not recycled.Apple private-relay rotation permanently splits identity (B). Sign in with Apple may supply a per-app relay address (
xyz@privaterelay.appleid.com). If the user revokes and re-grants consent, Apple rotates that relay address. The next sign-in finds no email match, creates a second canonical user with a new UUID, and the original account is orphaned. The only stable Apple identifier across revoke+re-grant is thesubclaim.Adding
provider_subjectlater is wire-safe (new field number), but any sign-in before the field is introduced writes a NULLprovider_subjectcolumn — making retroactive provider-side account linking impossible without an Apple/Google API migration script.Concrete fix — add after field 4 in
ResolveOrCreateByEmailRequest:The semantics block at line 47 should extend the fail-closed list:
Blank provider_subject → INVALID_ARGUMENT.[MINOR] Consent-version fields have dual under-specification: no blank-string validation contract and no stated create-only vs upsert semantics
user/v1/user_provisioning.proto:87
Agreed finding (A + B — complementary aspects).
accepted_privacy_policy_version(field 5, line 89) andaccepted_terms_version(field 6, line 91) have two related but distinct specification gaps:Gap 1 — Blank-string validation (A). The semantics block at line 47 enumerates fail-closed cases (
blank email,UNSPECIFIED provider) but is silent about blank consent-version strings. Proto3 zero-value strings ("") are valid on the wire and indistinguishable from an absent field. If user-service persists empty strings without rejecting them, the consent audit trail (required for GDPR Article 7 / App Store Review) is silently broken for any caller that omits these fields.Gap 2 — Create-only vs upsert on the resolve path (B). Line 37-38 states "No mutation: an existing user's profile / verification / password … is untouched." Lines 87-88 state "Persisted on the canonical user record." These are in direct tension. A returning user who accepted a newer ToS at the OAuth consent screen would either have their consent record silently un-updated (if create-only) or trigger an unintended mutation (if upsert). Neither interpretation is stated.
Fix — pick one semantics and document both aspects in the line-87 comment block:
If create-only + blank is rejected on create:
If upsert is intended, remove "No mutation" generality at line 38 and note the carve-out explicitly.
[INFO]
AuthProviderenum is package-scoped but consumed by only one message — deferred hygieneuser/v1/user_provisioning.proto:59
Unique-to-B, verified.
enum AuthProvideris defined at theuser.v1package scope (line 59) but is consumed exclusively byResolveOrCreateByEmailRequest.provider(line 85). The existing codebase pattern for shared/reusable enums is package-scope placement; for single-consumer enums, nesting inside the owning message reduces namespace surface and makes the dependency obvious.This has zero wire impact (proto field numbers govern the wire, not nesting), and
buf lintdoes not enforce nesting. No change is needed before merge — the right time to address it is the plannedshared/v1extraction referenced inbuf.yaml(TODO PR-OPAQUE-4-FOLLOWUP-shared-proto-extraction), or when a second service needs a provider enum and promotion is warranted. Raised as a tracking note.Verdict
NEEDS_WORK
hib-pr-reviewer • round 1 • 3 findings (1M/1m/1i) • 2026-06-02T08:22:55.023Z → 2026-06-02T08:25:23.337Z • posted-as: pr-reviewer-bot • model: auto • [bookkeeping fallback]
Show previous round
hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-protobuf)
Round 2 — head
da77a713082f, basemain, triggersynchronizeTL;DR: NEEDS_WORK — kept 2 findings: 1 agreed minor (field-number source ordering) + 1 unique-to-B major (verified: backfill path indistinguishable in response, security audit gap).
Summary
Arbitration — Round 2 (
user/v1/user_provisioning.proto)Memora context
No prior memories found for this repo or PR (both queries returned empty). Treating this as first-persisted entry; Memora write attempted but MCP tag-validation permissions blocked all create paths — logged for ops.
File verification
Read the full file at
/var/lib/pr-reviewer/repos/affinity-intelligence-rework/im2be-protobuf/1479/user/v1/user_provisioning.proto(126 lines).Finding 1 — field ordering (agreed A+B, verified ✅)
Lines 83–111 confirm the declaration sequence:
email=1(83),first_name=2(87),last_name=3(90),provider=4(93),provider_subject=7(102),accepted_privacy_policy_version=5(108),accepted_terms_version=6(111). Source-order of field numbers is 1, 2, 3, 4, 7, 5, 6. Both reviewers agree; finding is grounded. Kept as minor. B's concrete fix (reorder theprovider_subjectblock to after field 6, keeping the wire number 7) is the cleanest option and is incorporated in the body.Finding 2 —
provider_linkedmissing from response (unique-to-B, verified ✅)Lines 115–125 confirm
ResolveOrCreateByEmailResponsehas onlyuser_id=1andcreated=2. The service comment at lines 41–46 explicitly describes three distinct outcomes, of which two — normal returning sign-in and new-provider backfill — both returncreated=falsewith no distinguishing signal. B's claim is grounded in the file text. The security consequence (audit gap, no step-up hook, email-recycling vector at the protocol layer) is coherent and not rebutted by A. Kept as major.Outcome
Kept 2 findings: 1 agreed minor (field ordering) + 1 unique-to-B major (verified,
provider_linkedabsent). Verdict: NEEDS_WORK.Blast Radius
Single new proto file, but it defines the public gRPC contract between two services (social-login-service and user-service). Once generated code is committed and both services implement against this contract, any response-shape change requires coordinated updates to both callers and servers. The
provider_linkedfix must land before either service implementation is finalised.BLAST_SCORE: 5/10
Risk Indicators
ResolveOrCreateByEmail,provider_subject backfill pathCI status (head
da77a713082f)Overall: ✗ failure
3 checks: 3 pending
Findings (2)
[MINOR]
provider_subject = 7declared out of field-number order in a brand-new file — safe to fix nowuser/v1/user_provisioning.proto:102
The source declaration sequence in
ResolveOrCreateByEmailRequestis 1, 2, 3, 4, 7, 5, 6:provider_subject = 7(line 102) appears physically betweenprovider = 4(line 93) andaccepted_privacy_policy_version = 5(line 108). Proto3 encoding is valid — field numbers need only be unique, not ordered — but the non-sequential layout creates real maintenance friction:buf lintwithFIELD_NUMBERS_ORDER) will flag this.Because this is a brand-new file from
/dev/nullwith zero existing wire-format consumers or generated-code dependents, there is no backward-compatibility cost to correcting this now.Recommended fix — reorder source to match wire numbers (no field-number changes, no semantic change):
Declaration order and numeric order become identical (1 → 7 top-to-bottom).
[MAJOR]
ResolveOrCreateByEmailResponsecarries no signal for the OAuth provider-backfill path — new linkage events are invisible to social-loginuser/v1/user_provisioning.proto:124
The service comment (lines 37–46) documents three distinct outcomes for
ResolveOrCreateByEmail:createdreturned(provider, provider_subject)— no mutationfalsetrue(provider, provider_subject)miss, email match → link establishedfalseCases 1 and 3 are indistinguishable to social-login via the current response. This is a security-contract gap, not merely a logging inconvenience:
provider_linked=true, social-login cannot gate the JWT on "confirm via registered email" or similar step-up controls.(provider, provider_subject)lookup misses, email matches the original owner,provider_subjectis silently backfilled, and the attacker receives the original owner's UUID — withcreated=false, indistinguishable from a normal login. The planning doc §9 is referenced but the proto gives social-login nothing to act on.Concrete fix — add one boolean field (pure additive, zero wire-format impact on existing fields):
This is additive-only: existing consumers reading only
user_idandcreatedare unaffected.Verdict
NEEDS_WORK
hib-pr-reviewer • round 2 • 2 findings (1M/1m) • 2026-06-02T08:33:54.422Z → 2026-06-02T08:36:11.107Z • posted-as: pr-reviewer-bot • model: auto
Show previous round
hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-protobuf)
Round 3 — head
4d352904aad2, basemain, triggersynchronizeTL;DR: NEEDS_WORK — kept 2 findings (1 major agreed A+B, 1 minor unique-to-A verified); no findings dropped.
Summary
Arbitration Round 3 — im2be-protobuf#6
Memora persistence: Tag allowlist blocked
pr-review-run; fallbackmemory_absorblacked permission. Run history could not be persisted — logged for ops.Reconciliation actions:
SHOULD/MAYvsMUSTlanguage on theprovider_linkedsecurity-sensitive path. Verified at file lines 129–130:"social-login SHOULD emit a security event and MAY require step-up verification". The comment itself labels this the recycled-email takeover vector boundary; SHOULD/MAY leaves a conformant implementation free to silently issue a JWT. KEPT at major severity.created+provider_linkedresponse design at lines 121–135, which permits the wire-valid but semantically invalid state{created:true, provider_linked:true}(explicitly excluded only in a comment at line 132). Verified: both fields exist as plainboolfields with no proto-level enforcement of the mutual-exclusion. TheRESULT_KINDenum fix is well-grounded and consistent with the file's ownAUTH_PROVIDER_UNSPECIFIED=0fail-closed pattern. KEPT at minor severity.Prior-round closure confirmed (R1 + R2 findings all resolved or deferred, no re-raising).
Blast Radius
Change is a single new proto file (+136/-0) touching only the user-service provisioning surface. The
provider_linkedfield establishes a security-sensitive contract that social-login-service must implement correctly, so the blast radius extends to any OAuth login flow consuming this RPC. The file does not touch shared types or existing wire contracts.BLAST_SCORE: 3/10
Risk Indicators
ResolveOrCreateByEmail,provider_linkedCI status (head
4d352904aad2)Overall: ✗ failure
3 checks: 3 pending
Findings (2)
[MAJOR]
provider_linkedcontract uses SHOULD/MAY where the recycled-email takeover boundary demands MUSTuser/v1/user_provisioning.proto:129
Lines 129–130 (verified):
The comment itself identifies
provider_linked=trueas the mechanism that "bounds the recycled-email takeover vector" (line 131). RFC 2119 SHOULD permits a conformant implementation to omit the security event entirely; MAY makes step-up fully optional. A social-login implementation that silently issues a JWT onprovider_linked=trueis therefore contract-conformant today, directly undermining the stated security guarantee.Contrast line 56–57 in the same file, which correctly uses MUST for the fault path:
"The caller (social-login) MUST fail the OAuth login closed (HTTP 503)".Required fix:
SHOULD emit a security event→MUST emit a security event— there is no valid reason to skip observability on an account-linking event.MAY require step-up verification→SHOULD require step-up verification(orMUST, per product policy) — raises the obligation out of "fully optional" while acknowledging that linking policy may legitimately vary by provider.[MINOR]
ResolveOrCreateByEmailResponsetwo-boolean design admits a wire-valid but semantically invalid combined stateuser/v1/user_provisioning.proto:115
Lines 121–135 define two
boolfields:bool created = 2;(line 124)bool provider_linked = 3;(line 135)Line 132 documents a mutual-exclusion:
"Always false when created=true". Proto3 cannot enforce this; a server bug returning{created:true, provider_linked:true}is wire-valid and will silently deserialize in every consumer without error.Valid combinations per the comment:
createdprovider_linkedRecommended fix — replace with a
ResultKindenum, consistent with the file's existingUNSPECIFIED=0fail-closed pattern (AUTH_PROVIDER_UNSPECIFIED=0at line 71):Field 3 would be tombstoned:
reserved 3; reserved "provider_linked";. Since this file is brand-new (+136/-0) and has not shipped, there is no wire-compat concern.Verdict
NEEDS_WORK
hib-pr-reviewer • round 3 • 2 findings (1M/1m) • 2026-06-02T08:41:12.451Z → 2026-06-02T08:42:53.652Z • posted-as: pr-reviewer-bot • model: auto
R3 NEEDS_WORK (1 major + 1 minor): - [MAJOR] provider_linked's RFC-2119 SHOULD/MAY let a conformant caller skip the security event that bounds the recycled-email takeover vector. Folded into the new ResultKind enum: RESOLVED_NEW_PROVIDER_LINK now mandates "MUST emit a security event" + "SHOULD require step-up verification" — a caller may NOT silently issue a JWT on a provider-link. - [MINOR] the {created, provider_linked} two-boolean response admitted a wire-valid but semantically invalid created+linked combination. Replaced with ResultKind {UNSPECIFIED(fail-closed), RESOLVED_EXISTING, RESOLVED_NEW_PROVIDER_LINK, CREATED} — mutually exclusive by construction. Old fields tombstoned (reserved 3 + names). buf lint + build clean; vendored user-service copy re-synced byte-identical.Show previous round
hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-protobuf)
Round 4 — head
21087b88862e, basemain, triggersynchronizeTL;DR: NEEDS_WORK — kept all 3 B findings (1 major SHOULD→MUST security gap on recycled-email boundary, 2 minor doc/reserved issues); A returned empty JSON so all were unique-to-B but each verified against HEAD.
Summary
Memora persistence: Tag allowlist rejected all attempts; memory was not persisted (permissions not granted). Memora step noted as failed.
Reconciliation: Reviewer A returned 0 formal findings (parse-truncated JSON, prose-only salvage). Reviewer B returned 3 findings. All 3 required independent verification against the file at HEAD.
B-1 (major, line 129) — SHOULD→MUST for step-up on RESOLVED_NEW_PROVIDER_LINK: Verified. Line 129 literally reads
SHOULD require step-up verification. This has been an open finding since R3 with no change. The closing prohibition (a conformant caller may NOT silently issue a JWT) does not mandate step-up — it only forbids silent issuance. A caller can read SHOULD, skip step-up, emit the required security event, and still issue a JWT without violating any MUST. This is a meaningful gap on a recycled-email takeover boundary. Kept.B-2 (minor, line 97) — stale
(created=true)reference: Verified. Line 97 readsRequired on the create path (created=true). The fieldcreatedis reserved (line 152); it no longer exists in the response message. Both A (prose) and B independently flagged this. Should referenceresult_kind=RESULT_KIND_CREATEDinstead. Kept.B-3 (minor, line 151) —
reservedblock may not coverprovider_linked's original field number: Verified. Lines 151–152 showreserved 3;andreserved "created", "provider_linked";. Only field number 3 is numerically reserved. If the draftprovider_linkedwas= 4(natural consecutive numbering aftercreated = 3), field number 4 is unprotected and any client built against the never-shipped draft would silently decode a future= 4field as a boolean. Unique-to-B but grounded. Kept.Disposition: 1 major + 2 minor → NEEDS_WORK.
Blast Radius
The diff is a single new proto file (+153/-0). It introduces a new public gRPC service surface (UserProvisioningService) that is the sole writer of canonical user identity across the system, invoked by social-login-service at every OAuth login. The blast radius is moderate: the service boundary is narrow (one RPC, one caller via SPIFFE ACL), but the identity data it writes underlies downstream fan-out services (member, halo, subscription, diary, flow-coach) and the RESOLVED_NEW_PROVIDER_LINK path directly bounds the recycled-email account-takeover vector.
BLAST_SCORE: 4/10
Risk Indicators
ResolveOrCreateByEmail,RESULT_KIND_RESOLVED_NEW_PROVIDER_LINK,AuthProvider,provider_subjectCI status (head
21087b88862e)Overall: ✗ failure
3 checks: 3 pending
Findings (3)
[MAJOR] SHOULD step-up verification on RESOLVED_NEW_PROVIDER_LINK must be upgraded to MUST
user/v1/user_provisioning.proto:129
Line 129 still reads:
SHOULD require step-up verification (e.g. confirm via the registered email) before issuing a JWT. This has been an open finding since Round 3 and is unchanged. The closing sentencea conformant caller may NOT silently issue a JWTonly forbids silent issuance; it does not mandate step-up. A compliant caller can therefore read SHOULD, consciously skip step-up, emit the required security event, and still issue a JWT — all without violating any MUST. On the recycled-email takeover boundary (RESULT_KIND_RESOLVED_NEW_PROVIDER_LINK) that is an exploitable gap. Fix: changeSHOULD require→MUST require; or, if step-up is intentionally deployment-controlled, add a server-driven signal (e.g.bool step_up_requiredin the response) so the server enforces the policy rather than delegating the decision to the caller's reading of a SHOULD.[MINOR] Stale
(created=true)reference in consent-field commentuser/v1/user_provisioning.proto:97
Line 97 reads
Required on the create path (created=true). The boolean fieldcreatedhas been removed and is now reserved at line 152 — it no longer exists in the response message. A reader of this comment will search for acreatedfield, find only a reservation, and be confused about the validation rule. Fix: replace(created=true)with(result_kind=RESULT_KIND_CREATED)to reference the live enum value. The identical stale reference should be checked inaccepted_terms_versionat line 102 (which defers toaccepted_privacy_policy_versionby cross-reference).[MINOR] Reserved block covers only field number 3 —
provider_linked's original field number may be unprotecteduser/v1/user_provisioning.proto:151
Lines 151–152:
Field number 3 is protected by number, and both names are name-reserved. However, if the draft response also had
bool provider_linked = 4;(the natural consecutive number aftercreated = 3), then field number 4 is not numerically reserved. Any client binary built against the never-shipped draft would silently decode a future= 4field as a boolean. Fix: confirm the field number the draftprovider_linkedcarried; if it was= 4, extend the reservation toreserved 3, 4;.Verdict
NEEDS_WORK
hib-pr-reviewer • round 4 • 3 findings (1M/2m) • 2026-06-02T08:57:14.369Z → 2026-06-02T08:59:06.066Z • posted-as: pr-reviewer-bot • model: auto
Show previous round
hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-protobuf)
Round 5 — head
3a64d05459ac, basemain, triggersynchronizeTL;DR: CONDITIONAL_APPROVE — kept 2 findings (1 agreed minor wire-type comment error, 1 verified info abbreviated enum names); no blocking issues; all rounds 1–4 blocking/major findings confirmed resolved.
Summary
Round 5 Arbitration — 2 findings kept, 0 dropped
Memora persistence note:
memory_createandmemory_absorbfailed with tag-allowlist errors not attributable to my inputs; run history could not be persisted.Verification performed
user/v1/user_provisioning.protoat HEAD (158 lines confirmed).// \created` was field 2 (now reused by result_kind, a different wire type);— the phrase "a different wire type" is present and factually wrong. Bothboolandenum` encode as proto varint (wire type 0). AGREED A+B → kept [minor].result_kind=RESOLVED_EXISTING,result_kind=RESOLVED_NEW_PROVIDER_LINK, andresult_kind=CREATED— all missing theRESULT_KIND_prefix required by the actual enum definitions at lines 125, 134, 137. Generated identifiers in every proto target language carry the full prefix. UNIQUE-TO-B, verified → kept [info].All prior blocking/major findings (rounds 1–4) were correctly marked resolved by both reviewers and are not re-raised.
Blast Radius
The diff adds a single new proto file (
user/v1/user_provisioning.proto) with one new service, two new messages, and two new enums. The blast radius is bounded to consumers of the newUserProvisioningService.ResolveOrCreateByEmailRPC — currently only social-login-service per the SPIFFE ACL. No existing types or field numbers are mutated.BLAST_SCORE: 3/10
Risk Indicators
ResolveOrCreateByEmail,RESULT_KIND_RESOLVED_NEW_PROVIDER_LINKCI status (head
3a64d05459ac)Overall: ✗ failure
3 checks: 3 pending
Findings (2)
[MINOR] "a different wire type" is factually wrong — bool and enum are both proto wire type 0 (varint)
user/v1/user_provisioning.proto:153
Line 153 reads:
boolandenum/int32are both encoded as proto wire type 0 (varint). There is no wire-type difference. The phrase implies a harder serialisation break than exists and could mislead a future maintainer into introducing an unnecessary migration or compatibility shim. In reality, oldcreated=true(varint 1) decodes asRESULT_KIND_RESOLVED_EXISTING; oldcreated=false(varint 0) decodes asRESULT_KIND_UNSPECIFIED— both safe outcomes for an unreleased contract. The correct justification for safe field-number reuse is already in the same sentence: "this contract is unreleased — no shipped consumers."Suggested fix:
[INFO] Service-level docstring uses abbreviated ResultKind names that do not match generated identifiers
user/v1/user_provisioning.proto:40
Lines 40, 44, and 51 reference enum values as bare
RESOLVED_EXISTING,RESOLVED_NEW_PROVIDER_LINK, andCREATED. The actual generated identifiers in every protobuf target language areRESULT_KIND_RESOLVED_EXISTING,RESULT_KIND_RESOLVED_NEW_PROVIDER_LINK, andRESULT_KIND_CREATED(defined at lines 125, 134, and 137 respectively).An implementer reading the RPC-level docstring to understand what values to switch on will see names that do not compile if copied directly into generated-code switch statements. Apply the full prefixed names at all three call-sites:
Verdict
CONDITIONAL_APPROVE
hib-pr-reviewer • round 5 • 2 findings (1m/1i) • 2026-06-02T09:05:12.454Z → 2026-06-02T09:06:47.596Z • posted-as: pr-reviewer-bot • model: auto
hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-protobuf)
Round 6 — head
26b1c36a7844, basemain, triggersynchronizeTL;DR: APPROVE — all 11 prior findings closed; wire-type comment corrected, full ResultKind identifiers in docstring, MUST-step-up enforcement present; no new issues found.
Summary
[quorum-converged] A=0 = B=0. ## Round 6 Review — All Prior Findings Closed
All 11 previously-open findings have been addressed in this revision:
provider_subjectfieldprovider_subject = 7at line 113provider_subject = 7out of field-number orderRESULT_KIND_RESOLVED_NEW_PROVIDER_LINKcovers thisResultKindenum(created=true)stale reference in consent commentresult_kind=RESULT_KIND_CREATEDreserved 3+reserved "created", "provider_linked"RESULT_KIND_*identifiers used throughoutAuthProviderenum package-scoped but single-consumerThe file is consistent with the repo option conventions (Java-only, no
go_package, matchinguser_profile.proto). The field-number history comment at lines 153–158 is technically accurate:created=true(bool, varint 1) decoding asRESULT_KIND_RESOLVED_EXISTING(enum value 1) is a moot hypothetical given the explicitly-stated "no shipped consumers" precondition. No new actionable findings.Blast Radius
Single new proto file adding a provisioning service to the user.v1 package. Surface is restricted to social-login-service via SPIFFE/Envoy RBAC, so blast radius is bounded to that service boundary. The ResultKind enum and request message constitute a new exported gRPC contract but have no shipped consumers yet.
BLAST_SCORE: 5/10
Risk Indicators
UserProvisioningService.ResolveOrCreateByEmail,RESULT_KIND_RESOLVED_NEW_PROVIDER_LINKCI status (head
26b1c36a7844)Overall: ✗ failure
3 checks: 3 pending
Related PRs
Findings
No new findings this round.
Quorum converged on empty findings (A + B both returned 0).
Verdict
APPROVE
hib-pr-reviewer • round 6 • 0 findings • 2026-06-02T09:09:12.013Z → 2026-06-02T09:12:25.099Z • posted-as: pr-reviewer-bot • model: auto