You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A right that is system-authored on one host and user-authored on another cannot be declared no-op on both #114
normalizeActual drops rows with meta.modifiedPid === -1 as "system baseline — invisible to reconciliation" (src/permissions/grants.ts:81). That is the right call for a single host. Across two hosts of the same instance it produces a config that cannot be a clean no-op on both, because the same right can be system-authored on one and user-authored on the other.
Observed on a group_role domain that exists on both our hosts. Three roles of the same group, right churchgroup:view (authId 1101), isInherited: false on both:
Host
meta.modifiedPid
how ct sees it
prod
-1 (system)
invisible
dev
1 (a person)
user-authored, ct's to own
The dev rows are not hand-made — that host is a copy, and the copy stamped a person id onto rows that are system rows upstream.
Why it has no good answer today
Adopt from prod, then plan both:
Config
prod
dev
as ct adopt grants emits it (right omitted, because prod's row is invisible)
✅ no-op
❌ revokes the right — dev ends up less permissive than prod
right declared explicitly
❌ +1 grant on each of the three roles
✅ no-op
Neither is acceptable if "prod plan stays a clean no-op" is the rule, so the group has to be left out of adoption entirely — even though nothing about it is genuinely undeclarable.
Suggestion
Make a declaration satisfiable by a system-baseline row instead of invisible to it: when a declared grant matches a live row with modifiedPid === -1, treat it as already present and plan no PUT. Reconciliation would still never author or revoke a baseline row, so the "never fight the platform" property in the module header holds — the change is only that the tool stops proposing to re-author something the platform already grants.
With that, the second row of the table above becomes ✅ / ✅ and the domain is declarable.
Why it is hard to see coming
isInherited is the field that looks like it answers "is this grant mine to own", and it says false on both hosts. The field that actually decides is meta.modifiedPid, and nothing in plan, coverage or adopt grants surfaces it — the divergence only shows up as an unexplained revoke in the other host's plan, after the config has already been written. Even a warning from adopt grants ("this domain has N system-baseline rows that will not be emitted") would have turned an afternoon of bisecting into a one-liner.
Found on ct-cli 1.7.0 while adopting Local Lead group_role grants in an estate repo.
Activity
fix(adopt): judge bulk adoption on the rows it actually emits (#129 r…
Merge pull request #129 from eqrm/fix/milestone-2-adoption-portability