Skip to content

fix(permissions): resolve grant scope by catalog scopeField; numeric scope escape hatch - #54

Merged
2000game merged 1 commit into
mainfrom
fix/grant-scope-dimension-49
Jul 9, 2026
Merged

2000game merged 1 commit into
mainfrom
fix/grant-scope-dimension-49

Conversation

@2000game

@2000game 2000game commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

ct adopt grants group_type_role 9 against real prod data (issue #49) returned 27 live grants, 10 of which are scoped churchdb rights (churchdb:view comments, churchdb:security level view own data, churchdb:security level edit own data) scoped to dataIds 1, 2, 3, 5. src/permissions/adopt.ts assumed every scoped dataId is a group (findByTypeId(state, "group", id)) and told you to ct adopt group <id> — but GET /groups/{1,2,3,5} 404s, because those dataIds aren't groups: the rights' catalog scopeField is cc_securitylevel / cdb_comment_viewer, a different ChurchTools dimension. The resulting partial adoption block couldn't express those grants, so applying it would revoke live grants — blocking plan-to-no-op on a real instance. There was also no way to declare a known non-group dataId in the DSL at all (scope only accepted logical group keys).

This PR implements all three requirements from the issue:

  1. Scope resolution now follows the right's actual scopeField. Only rights whose scopeField is the group dimension ("cdb_gruppe") are resolved/round-tripped as logical group refs (src/permissions/adopt.ts). Every other scope dimension passes through numerically.
  2. Numeric scope escape hatch in the DSL (src/permissions/scope.ts, src/permissions/types.ts, src/config/context.ts): a scope array entry may now be a raw positive-integer number alongside logical group keys — validated at eval time, resolved with no state lookup, and (correctly) never retains a scopeKey for re-resolution at apply time, since its dataId is already final.
  3. ct adopt grants emits the numeric form and names the scope dimension for any scoped right whose scopeField isn't the group dimension — always as an active, paste-ready grant line, never a ct adopt group #N hint for a dataId that was never a group. The existing ct adopt group <id> hint is preserved unchanged for genuinely unmanaged group-scoped dataIds.

Test plan

  • Unit tests added first (TDD), covering:
    • resolveScope numeric entries (pass-through, sorting, validation of non-positive/non-integer values) — tests/permission-scope.test.ts
    • desiredTuples numeric scope entries (no scopeKey retained, mixable with logical keys) — tests/permission-plan.test.ts
    • DSL validation accepting (string | number)[] scope arrays and rejecting invalid entries — tests/context.test.ts
    • emitAdoptedGrants: non-group scope dimensions emit the numeric form as an active grant (never a WARNING or ct adopt group hint), a regression test locking the unchanged group-dimension behavior, and a full round-trip repro of the issue's exact scenario (churchdb:security level view/edit own data scoped to dataIds 1,2,3,5) asserting diffGrants produces zero toPut and zero toDelete — tests/permission-adopt.test.ts
  • npm test — 354 passed, 4 skipped (0 failures)
  • npm run typecheck — clean
  • npm run lint — clean
  • npm run build — sanity build succeeds

Never hits a live instance or requires credentials — all fixtures are inline RawPermission[] rows.

Closes #49

…scope escape hatch (#49)

`ct adopt grants` assumed every scoped dataId was a group and pointed at
`ct adopt group <id>`, but scoped rights whose catalog scopeField isn't
"cdb_gruppe" (e.g. churchdb's security-level / comment-viewer rights) scope
by a different dimension entirely — GET /groups/{id} 404s for them, so a
partial adoption block would revoke live grants on apply.

- src/permissions/scope.ts: resolveScope now accepts a raw numeric scope
  entry alongside logical group keys (the escape hatch), passed straight
  through with no state lookup and no re-resolution at apply time.
- src/permissions/types.ts, src/config/context.ts: Grant.scope widened to
  (string | number)[], with DSL-level validation of each entry.
- src/permissions/plan.ts: desiredTuples only retains scopeKey for
  logical-key resolutions, never for numeric ones.
- src/permissions/adopt.ts: grant emission now branches on the right's
  actual scopeField — only "cdb_gruppe"-scoped rights are round-tripped as
  managed-group refs (unchanged behavior, including the `ct adopt group`
  hint for unmanaged dataIds); every other scope dimension emits the
  numeric form directly as an active, paste-ready grant.
- docs/permissions.md, src/permissions/README.md, examples/permissions.config.ts:
  document the numeric scope escape hatch and the adopt behavior split.

Closes #49
@2000game
2000game merged commit 0d26995 into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(permissions): ct adopt grants assumes every scoped dataId is a group

1 participant