Skip to content

fix(permissions): pending grant domains — plan/apply with same-run-created group types - #70

Merged
2000game merged 1 commit into
mainfrom
fix/pending-grant-domains-69
Jul 10, 2026
Merged

2000game merged 1 commit into
mainfrom
fix/pending-grant-domains-69

Conversation

@2000game

@2000game 2000game commented Jul 10, 2026 •

Copy link
Copy Markdown
Member

Closes #69

Problem

A permission domain declared by reference — ct.groupTypeRole({ groupType: "struktur", grants: [...] }) — to a group type that is part of the same run's create-set hard-aborted the entire plan:

✗ group_type_role "struktur_roles".domainId: references a resource created in the same run — apply it first, or use a numeric id.

This made fresh-instance rehearsal (#23) and the plan-both-envs CI check (#24) impossible: a valid, prod-no-op config could not even be planned against an empty env.

Fix

Give permission domains the same pending-ref treatment resources already have (#20/#46):

  • Plan — a domain referencing a same-run-created group type is carried as a pending domain (domainId: null, pendingDomain Ref) instead of aborting. Its grants land in diff.toPut, it renders as <group-type:struktur (created this apply)> (consistent with resource pending-ref rendering), and it counts toward hasChanges / exit code 2 / the --json summary. No live grants are fetched for a not-yet-created domain.
  • Apply — permission reconciliation already runs after resource creation (verified: executePlan → then applyPermissionPlan(items, client, state) with post-execute state). The pending domain id is re-resolved from the fresh group type using the existing reresolvePendingValue machinery — not forked. One ct apply converges fully.
  • Hard error preserved for genuinely unresolvable refs (key absent from config, state, AND the live catalog — a true typo): unchanged resolver notFound message.

group_role symmetry (checked & reported)

group_role is deliberately not made symmetric. Its domainId is the (group, role) pairing id, which lives only on GET /groups/{groupId}/roles — re-resolving it needs a live fetch after the group exists, not a post-execute state lookup. So a group_role domain on a same-run-created group still fails fast at groupIdForRole with its own actionable message (unchanged). Documented in docs/permissions.md and locked with a regression test. Deferring it properly is a larger, separate change out of #69's scope (#23 is a group_type_role scenario).

Tests

New tests/permission-pending-domain.test.ts (6 tests) covering the #23 scenario end-to-end with a mocked client:

  • empty state + declared groupType + by-reference grants → plan succeeds (pending block, no throw), renders the marker, counts as a change;
  • build → execute → apply in ONE run creates then grants against the fresh id; second plan is a no-op (convergence);
  • prod-like (type already in state) → concrete domain, unchanged;
  • true typo → resolver hard-errors, unchanged message;
  • group_role same-run group → hard error, unchanged message.

Verification

npm test        → 499 passed | 5 skipped
npm run typecheck → clean
npm run lint    → clean

No live instance contacted.

…eated group types (#69)

A group_type_role domain declared by reference (groupType: "struktur") to a
group type in the same run's create-set no longer hard-aborts the plan. Instead
it plans as a pending domain (domainId: null + pendingDomain Ref, grants in
toPut) rendered `<group-type:x (created this apply)>`, counted in --json /
--detailed-exitcode, and re-resolved against post-execute state at apply time —
reusing the resource pending-ref machinery (reresolvePendingValue). One ct apply
run converges. The hard error remains only for genuinely unresolvable refs.

group_role stays a hard error (its pairing domainId needs a live fetch, not a
state lookup) — documented + regression-tested.

Closes #69
@2000game
2000game merged commit 8604728 into main Jul 10, 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): domain-by-reference to a not-yet-created group type aborts the entire plan — fresh-instance rehearsal impossible

1 participant