Wave A: identity + permissions (docs/features/01) #7
No reviewers
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub!7
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/identity"
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?
Wave A of the collaboration layer (tracks #3):
internal/identityend to end perdocs/features/01_identity_permissions.md§§2–10.What lands
internal/identity: orgs/members/teams CRUD (CAS loops, bounded ≤5 then 409; no locks, no sidecars),access.jsonrole resolution per P6 with version-stamped conditional-GET LRU, invitations (Create-only, delete-on-transition, inbox index), visibility +require_readgate (anonymous-denied reads get a real 401 +WWW-Authenticate: Bearer).server.ChainAPI(featureHandle(w,r) boolfronts the core mux); Seam 3team:/role:expansion at load time incl. newpolicy.ActorExpander(protect bypass lists expand on a copy — the source doc is never mutated); Seam 5access-bootstrapop (per-repo materialize; unionopStartso listed extension ops start); Seam 7walhub access get|put.Env.Access/GroupExpander+ Dispatch read hook (AuthRead repo routes) + dry-run expansion warnings; serverReadGate, api-seam principal injection (invalid tokens now 401 instead of anonymous-treated; anonymous unchanged), git/LFS/bundle read-path gates./:org/settings, profile/members/teams/invitations), repo Access tab (4th settings tab, CAS version footer, 409 reload hint), SDKusers/orgs/access/invitessubmodules + typedefs; dark+light via existing Tailwind vocabulary.Verification
internal/identity: 98.5% statements,-raceclean, table-driven httptest per handler.make covergreen (api 95.4%, server 95.2%, policy 98.6%); fullgo test -short ./...green; 129/129node --test./, repo settings + Access save interaction, org page,/setup, dark + light — zero console errors.Deviations / notes (for review)
SynthesizeDefaultomits the owner-admin binding when the owner namespace is not an email (else the materialized object would fail its own validation); org repos are covered by org-owner resolution, host flags still apply.AcceptInvitevalidates invite roles fail-closed (a craftedsuperrole can no longer be absorbed by an existing binding).GET …/membersandGET /api/v1/orgs/{org}/invitations(owner). No separate/:org/teams/:slugroute — team membership edits inline in the Org page.role:references over missing repos synthesize silently to empty (the legacy default resolves); malformed references warn. Empty match expansion denies fail-closed.router.go,index.jsx,Repos.jsx) contain only my hunks in this PR; your uncommitted hunks there stay in the worktree — expect a small rebase touch onRepos.jsx(old-file tail vs your rewrite).Do NOT merge per Wave plan — Wave B/C build on these seams.
PR #7 review (Wave A identity,
feat/identity) — round 1Reviewed
3e622b5+ review-fix commit6e67618(pushed) againstdocs/features/01_identity_permissions.md, P1–P9,13_concurrency.md,14_extensibility.md. Verified: full diff file-by-file;gofmtclean;go vetclean;go test -race ./internal/identity/...pass;coverage 98.1% (≥95% gate);
api/policy/server -racepass;go build ./...clean;node --test web/test/unit/*.test.js129/129 pass.No new third-party imports (
go.moduntouched); core↔feature seamdirection holds (
api/serverdepend on identity only via theReadAccess/policy.Expander/ExtraRoutesinterfaces).Fixed and pushed in
6e67618(all in PR files, your other worktree changes untouched)internal/identity/orgs.goDeleteTeam — the per-repo binding stripwas a single-shot
PutUpdatethat swallowed 412 (!IsPreconditionFailed→ skip), leaving stale
team:bindings behind under concurrent adminedits and violating the §3 "sequential CAS per affected repo" rule.
Now a bounded
casUpdateloop: transient 412s heal, persistent contentionsurfaces an honest 409. New
TestDeleteTeamHealsTransientConflictprovesthe heal;
edge_test.goupdated to assert the 409 contract.ListRepoInvites/ListOrgInvitesnow take the team-list?n=convention (default 100,max 1000); handlers parse it via
pageSize().DeleteOrghalf-delete on LIST failure — it deletedorg.json/members.jsonfirst and swallowed the team-list error, leaking teams.Now lists teams before deleting anything: failure aborts with the org
intact (
mop_test.goupdated).GetTeamevicts its version-stamped cache entry on NotFound so adeleted team leaves no stale roster entry behind (was only a hygiene
leak — the NotFound path already bypassed the cache — but evicting is
clearly right).
Open findings (non-blocking; recommend follow-ups, not rework here)
http_invites.gorouteInvites):spec §8 says
GET /invitations/{id}?token=is "token OR subject match",but the handler rejects anonymous callers before the token is checked —
and
findInviteneeds the caller's inbox, so a token-only lookup isstructurally impossible without a global index. Authed flow works
(recipient logs in → subject match). Suggest a one-line doc note in 01 §7.
DELETE /invitations/{id}is invitee-decline only; spec §8says "invitee (decline) or issuer (cancel)". Issuer cancel exists on the
scoped org/repo admin endpoints, so nothing is unachievable — same doc-note
suggestion.
endpoints[]omits the identity routes (14.12 says additiveendpoints MUST be listed).
discoveryEndpoints()derives from the coreroute table and the identity surface fronts it, so there is no seam to
contribute entries today. The SDK calls fixed paths (never gates on
discovery) and all 129 JS tests pass, so this is cosmetic — suggest a
follow-up (e.g.
Env.ExtraEndpointsmerged by the discovery handler).Spot-checks that passed: resolution order P6 verbatim (incl. org-owner→admin,
host flag step 3, anon public-read);
require_read401+Bearer realm="walgit"on git/LFS/API lanes with the §8.4 in-band exception preserved;
CAS loops bounded at 5 → 409; bucket-only state (LRUs version-stamped,
staleness ≤ 1 request);
[]-never-null / plain-text errors / RFC3339;both lanes in
Handler.Handle; dark: variants on new UI; E2 evidencereproduces the claimed shape (1 conditional access GET + 1 per referenced
team, 0 bodies warm — matches the code).
Tests re-run after the fix commit: identity
-racepass, 98.1% cover;api/policy/server-racepass; build clean; JS 129/129.