fix: use verbatim resource slugs in group names (matches access-request tests + live convention)
Pull Request Tests / Run Tests (18.x) (push) Failing after 1m33s
Pull Request Tests / Run Tests (20.x) (push) Failing after 29s
Pull Request Tests / Run Tests (22.x) (push) Failing after 27s
Pull Request Tests / Test Summary (push) Failing after 4s

The group naming inserts a kind segment (resourceGroupCns(site, kind, slug, level)),
but the access-request tests + the live directory convention are verbatim
({site}_{slug}_{level} -- the kind is carried in the resource slug, e.g. host_theta-env).
For bare test slugs this produced site_x_host_artest-host_x_access instead of the
expected site_x_artest-host_x_access, so the requester was never removed from the
auto-provisioned access group and every request 409'd. resourceGroupCns is now
(site, slug, level) with the verbatim slug; the kind is used only to pick the
aggregate the group nests into.
This commit is contained in:
2026-08-04 19:01:51 -04:00
parent ccbbceaa47
commit fd5c5f4e88
4 changed files with 50 additions and 62 deletions
+15 -15
View File
@@ -41,16 +41,17 @@ function assertKind(kind) {
if (!KINDS.includes(kind)) throw new Error(`invalid resource kind: ${kind} (must be host or app)`);
}
// {site}_host_<slug>_<level> / {site}_app_<slug>_<level>
// {site}_{slug}_{level} — the per-resource group for one resource.
//
// `site` is the site resource's slug verbatim (e.g. `site_local`, or a clean
// `main-office`), NOT slugified: directory resource slugs may legitimately carry
// a kind prefix (`site_local`, `host_theta-env`), and re-slugifying those would
// corrupt the delimiter (site_local -> site-local). Callers pass already-
// normalized site + kind-stripped resource slugs.
function resourceGroupCns(site, kind, slug, level) {
assertKind(kind);
return `${site}_${kind}_${slugify(slug)}_${level}`;
// Both `site` and `slug` are the resource slugs verbatim (e.g. `site_local`,
// `host_theta-env`), NOT slugified or kind-inserted: directory resource slugs
// carry their kind as a prefix (`host_theta-env`), so `site_local` + `host_theta-env`
// yields `site_local_host_theta-env_access`. Services are stored without a
// prefix (`sso-manager`), yielding `site_local_sso-manager_access`. This is the
// convention the auto-provisioner, the resolver, and the access-request tests
// all share -- re-slugifying or inserting a kind would double the delimiter.
function resourceGroupCns(site, slug, level) {
return `${site}_${slug}_${level}`;
}
// {site}_hosts_<level> / {site}_apps_<level> (plural kind — the aggregate).
@@ -93,12 +94,11 @@ function levelGrants(level, wanted) {
// granted groups (see permission.onResource). This keeps the function pure over
// the user's membership only.
function hasPermission(memberOf, resource, level) {
// `site` is used verbatim (resource slugs may carry a kind prefix, e.g.
// `site_local`) -- see resourceGroupCns; the resource `slug` is expected
// kind-stripped and already-normalized, so slugify() is a harmless guard.
// `site` and `slug` are used verbatim (resource slugs may carry a kind prefix,
// e.g. `site_local` / `host_theta-env`) -- see resourceGroupCns.
const site = resource && resource.site;
const kind = resource && resource.kind;
const slug = slugify(resource && resource.slug);
const slug = resource && resource.slug;
const set = new Set(memberOf || []);
if (set.has(GOD_ADMIN)) return true;
@@ -107,13 +107,13 @@ function hasPermission(memberOf, resource, level) {
if (isKnownLevel(level)) {
// admin / access
if (set.has(aggregateGroupCns(site, kind, level))) return true;
if (set.has(resourceGroupCns(site, kind, slug, level))) return true;
if (set.has(resourceGroupCns(site, slug, level))) return true;
if (level === 'access' && hasPermission(memberOf, resource, 'admin')) return true;
return false;
}
// Opaque capability — exact aggregate or specific grant only.
if (set.has(aggregateGroupCns(site, kind, level))) return true;
if (set.has(resourceGroupCns(site, kind, slug, level))) return true;
if (set.has(resourceGroupCns(site, slug, level))) return true;
return false;
}