feat(permissions): domainId by reference + catalog lifecycle - #57
Merged
Merged
Conversation
group_role domains are now declarable by (group, role) reference: the resolver
maps the pair to its pairing domainId at plan time by matching the role name
against the group's role list (GET /groups/{groupId}/roles). The exact source is
an ASSUMPTION pinned in a unit test + a prominent resolver comment, awaiting a
one-time live check on eqrm-dev; the numeric id: escape hatch remains supported.
Catalog lifecycle: a reserved $meta key records the captured CT version, a
scripted one-command regeneration (npm run regenerate:permission-catalog) rebuilds
catalog.json from the live churchauth getMasterData endpoint, and ct plan now warns
(never fails) on a version mismatch or an unknown-authId live grant — the latter is
kept out of the diff so apply never revokes a right it cannot name.
2000game
force-pushed
the
feat/permissions-ergonomics-25
branch
from
July 9, 2026 11:48
e3c30aa to
61060c1
Compare
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
Two permissions-ergonomics work items from #25.
1.
group_roledomainId by referencect.groupRole({ group: "<key>", role: "<name>", grants: [...] })— a grant is now declarable with zero numeric ids (right name + group key + role name). The resolver maps the (group, role) pair to its numeric pairing domainId at plan time. The numericid:escape hatch remains fully supported (backward compat, mirroring #49's scope escape hatch).ref.group). A same-run, not-yet-created group is a hard error (its pairing id only exists once the group does) — apply the group first or pass a numeric id.group_type_roleby reference (groupType: "<name>") already shipped in feat: portable configs — logical references instead of numeric CT ids (shared resolver) #20 and is unchanged. Note the brief mentioned{ groupType, role }, but the documented/shipped domain semantics are that agroup_type_roledomainId is the group type's own id (role is not part of that domain), so I left itgroupType-only rather than change working semantics.2. Catalog lifecycle
$metakey incatalog.jsonrecords{ capturedFrom, ctVersion, capturedAt, rightCount }.catalog.tssplits it off at load so no consumer (resolveAuthId,ct get permissions-catalog, grant adoption) ever sees it as a right.npm run regenerate:permission-catalog(scripts/regenerate-permission-catalog.ts, run via tsx). Logs in with a login token, calls the legacyPOST /index.php?q=churchauth/ajaxfunc=getMasterData, flattensdata.auth_table[module][right]into the exact existing schema, stamps the instance CT version into$meta, and rewrites the file. Read-only against the instance; no new runtime deps (built-in fetch).ct planwarnings (never failures):$meta.ctVersion→ warn.ct applynever revokes a right it cannot even name. Previous behavior: such a grant had no desired counterpart and landed intoDelete(a silent revoke of an unnameable right) — now excluded every run (idempotent).The semantic question #25 flags — is a
group_roledomainId the role-definition id or the per-(group, role) pairing id? — is settled by the existing code/docs, not by any fixture/HAR in the repo (none exists for it). Pinned assumption, implemented against and locked by a unit test + a prominentASSUMPTION (verify on eqrm-dev)comment at the top ofsrc/resolve/resolver.ts:group_roledomain is keyed by CT's internal (group, role) pairing id — not the group id, not the shared role-definition id.GET /groups/{groupId}/rolesas each row'sidfield, matched by rolename.Neither the endpoint nor the field is confirmed live. If a check shows the pairing id lives elsewhere, flip the two constants (
GROUP_ROLE_ENDPOINT,GROUP_ROLE_PAIRING_FIELD) at the top ofresolver.ts— call sites don't change. Until confirmed, numericid:is the guaranteed-correct path.Testing
npm test→ 380 passed, 4 skipped (52 files).npm run typecheckandnpm run lintclean.New/updated coverage:
$metanever exposed as a right, provenance recorded,KNOWN_AUTH_IDSexposed.Note:
npm run format:checkreports pre-existing repo-wide prettier drift (66 files, incl. many untouched by this PR) — unrelated to this change; not reformatted to avoid noise.Closes #25