refactor(api): extract acting-member and admit-member seams#6648
Open
otavio wants to merge 1 commit into
Open
Conversation
Route the repeated resolve-and-authorize prologue through a single resolveActingMember seam (8 call sites) and the add-and-consume membership write through admitMember (3 intake paths), so each contract lives in one place. Behavior-preserving except that a non-member acting user now yields a uniform NamespaceMemberNotFound, guarded upstream by RequiresPermission. Fixes: #6647
Member
Author
|
/review |
|
Claude finished @otavio's task in 1m 52s —— View job Code Review CompleteReviewed 6 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is. A few things I verified rather than assumed:
Locality and testability both improve here, and the call-site guards (BFLA, owner, invitation-role) were preserved verbatim. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Introduces two deep, unexported seams in the
api/servicespackage and routesthe existing membership-intake code through them, with no change to the public
service interfaces or HTTP contracts. Stacked on top of #6646.
Closes #6647
Why
The membership-intake code had grown two shallow spreads:
check their authority — was hand-copied across 8 service methods. A change to
how the acting member is resolved (or which error a missing member yields) had
to be made in 8 places and re-asserted in 8 tests.
atomically) was hand-rolled in 3 intake paths, with the atomicity rule restated
as a comment in two of them.
The goal is locality (one place to change each concept) and testability, aligned
with the in-flight
refactor/unify-membership-intakework.Changes
resolveActingMember: hides the resolve-and-authorize dance behind onecall returning the namespace and acting member, or the correct typed error.
Asserts authority only when a non-
RoleInvalidrole is passed — the samesentinel convention
UpdateNamespaceMemberalready used. Migrated 8 call sites(add/update/remove/leave member, generate/list/cancel invitation, update API
key), passing the role known at each site and keeping the delicate call-site
guards (BFLA, owner, invitation-role) verbatim.
admitMember: owns the single add-and-consume write (create membership,then delete the invitation when one is supplied). Opens no transaction of its
own — the store's
WithTransactionis not reentrant, so it joins the caller'sambient transaction via the context. Wired into all 3 intake paths
(
AcceptInvite,createInvitedUser,intakeMembershipdirect-add).UserResolvethe three member.go methods did:the actor's ID comes from the authenticated token, so a resolved membership
already proves the account exists.
Behavior change
A non-member acting user now yields a uniform
NamespaceMemberNotFoundacross allmembership endpoints, where the three member.go methods previously returned
UserNotFoundandLeaveNamespacereturnedAuthForbidden. This is stricter andmore consistent, and is guarded upstream by
RequiresPermission.Testing
go test ./services/...andgolangci-lint run ./services/...pass clean. Testsexercise both seams through the public service methods only. Added two
UpdateAPIKeycases covering the empty-role path (an observer renaming a key; anon-member being rejected) to lock in the now-unconditional membership resolution.