perf(server): scope /.profile/access?path= to the requested location only

enumerateAccess always computed the global summary — every project
(EnumerateProjects) and every admin subtree (enumerateAdminSubtrees tree
walk) — and merely appended the path-scoped fields when ?path= was given.
The browse hovercard calls this per folder hovered, so each distinct folder
paid a full global enumeration for data it never reads.

Split the two: a ?path= query now returns ONLY identity + path_verbs/
path_is_admin/path_can_elevate_grant/path_roles and skips the tree walks;
the no-path call still returns the full global view for the profile page.
Verified all path-scoped consumers (browse hovercard, form, tables) read
only path_* fields; the global consumers (elevation, stage, plan-review,
accept-transmittal) all call without ?path=.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-06-01 13:23:22 -05:00
parent 91e6d1ec82
commit 1cf3f3a9b3
2 changed files with 21 additions and 6 deletions

View file

@ -199,8 +199,20 @@ func enumerateAccess(ctx context.Context, decider policy.Decider, cfg config.Con
view := AccessView{ view := AccessView{
Email: p.Email, Email: p.Email,
EmailHeader: cfg.EmailHeader, EmailHeader: cfg.EmailHeader,
IsSuperAdmin: zddc.IsAdmin(cfg.Root, p),
} }
// Path-scoped query: return ONLY the access for THIS location. The
// global summary (every project, every admin subtree) requires tree
// walks that are irrelevant to "what can I do here?" — and the
// hovercard calls this per folder, so paying that cost per hover
// would be wasteful. Callers that want the global view omit ?path=.
if pathQuery != "" {
populatePathScopedAccess(ctx, decider, cfg, p, pathQuery, &view)
return view
}
// Global summary (the profile page).
view.IsSuperAdmin = zddc.IsAdmin(cfg.Root, p)
view.Projects, _ = EnumerateProjects(ctx, decider, cfg, p) view.Projects, _ = EnumerateProjects(ctx, decider, cfg, p)
view.AdminSubtrees = enumerateAdminSubtrees(cfg, p) view.AdminSubtrees = enumerateAdminSubtrees(cfg, p)
view.HasAnyAdminScope = view.IsSuperAdmin || len(view.AdminSubtrees) > 0 view.HasAnyAdminScope = view.IsSuperAdmin || len(view.AdminSubtrees) > 0
@ -216,9 +228,6 @@ func enumerateAccess(ctx context.Context, decider policy.Decider, cfg config.Con
allowed, _ := policy.AllowActionFromChainP(ctx, decider, rootChain, p, "/", policy.ActionCreate) allowed, _ := policy.AllowActionFromChainP(ctx, decider, rootChain, p, "/", policy.ActionCreate)
view.CanCreateProject = allowed view.CanCreateProject = allowed
} }
if pathQuery != "" {
populatePathScopedAccess(ctx, decider, cfg, p, pathQuery, &view)
}
return view return view
} }

View file

@ -540,6 +540,12 @@ acl:
if alice.PathVerbs != "rw" { if alice.PathVerbs != "rw" {
t.Errorf("alice PathVerbs = %q, want rw", alice.PathVerbs) t.Errorf("alice PathVerbs = %q, want rw", alice.PathVerbs)
} }
// A path-scoped query returns ONLY the access for this location — the
// global summary (projects + admin-subtree walks) is omitted.
if len(alice.Projects) != 0 || len(alice.AdminSubtrees) != 0 || alice.CanCreateProject {
t.Errorf("path-scoped response leaked global fields: Projects=%d AdminSubtrees=%d CanCreateProject=%v",
len(alice.Projects), len(alice.AdminSubtrees), alice.CanCreateProject)
}
if alice.PathIsAdmin { if alice.PathIsAdmin {
t.Errorf("alice PathIsAdmin = true, want false") t.Errorf("alice PathIsAdmin = true, want false")
} }