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
Remove all repository team associations and undeclared direct grants, including outside collaborators. Preserve team hierarchy and membership, order user upserts before cleanup, and verify snapshots before and after application.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a8107bce-2500-45f8-b046-3f50092d9fbd
Copy file name to clipboardExpand all lines: README.md
+17-11Lines changed: 17 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,7 +88,7 @@ Entitlements configs can contain metadata which the plugin will use to make furt
88
88
89
89
### GitHub repositories
90
90
91
-
The `github_repository` backend manages **direct, user-only repository grants for active, non-owner organization members**. It does not create or delete repositories. Load `entitlements/backend/github_repository` in your plugin loader and add this entry under `groups`:
91
+
The `github_repository` backend enforces **individual-only repository grants**. Role files define the desired direct grants; undeclared direct user grants (including outside collaborators and explicit owner grants) and **all repository team grants** are removed when `remove` is enabled. New grants are restricted to active, non-owner organization members. It does not create or delete repositories. Load `entitlements/backend/github_repository` in your plugin loader and add this entry under `groups`:
Group references, filters, and expiration are evaluated by the normal Entitlements rules engine. People must resolve through the configured people data source, with their `uid` equal to their GitHub login. As with other backends, the underlying username rule omits people absent from that data source; validate such references in your configuration CI. `ignore_not_found` applies to evaluated people missing active GitHub organization membership, not to missing repositories or API failures.
132
+
Group references, filters, and expiration are evaluated by the normal Entitlements rules engine. Group references expand to individual users, never GitHub team grants. People must resolve through the configured people data source, with their `uid` equal to their GitHub login. As with other backends, the underlying username rule omits people absent from that data source; validate such references in your configuration CI. `ignore_not_found` applies to evaluated people missing active GitHub organization membership, not to missing repositories or API failures.
133
133
134
134
YAML and Ruby role files are also supported when enabled in `allowed_types`, e.g. `[txt, yaml, rb]`. If omitted, all three formats are allowed. Ruby files use the standard Entitlements Ruby rule-class convention (repository directory and role filename determine the class); enable them only for trusted configuration authors. `allowed_methods` constrains declarative rules, not arbitrary Ruby code.
135
135
@@ -143,46 +143,52 @@ YAML and Ruby role files are also supported when enabled in `allowed_types`, e.g
143
143
144
144
Custom roles are not supported. Unsupported direct roles or incomplete API responses abort reconciliation rather than falling back to effective permissions. A user cannot occur in multiple roles, even with different capitalization. Comparisons are case-insensitive; difference logs preserve login capitalization and show old and new roles. Duplicate role files, unsupported extensions, symlinks, nested directories, and unexpected files (including README and hidden files) are rejected. Keep documentation outside the managed root.
145
145
146
-
**A missing role file means no desired direct members for that role.** An empty repository directory therefore requests removal of all managed direct grants if `remove` is enabled. To keep an explicitly empty role file, use the standard `metadata_no_conditions_ok = true` text directive. **Deleting the entire repository directory opts that repository out without cleanup**; existing access is untouched. The configured root must still exist. Git does not track empty directories, so keep an explicit empty role file when intending to remove every managed grant.
146
+
**A missing role file means no desired direct members for that role.** An empty repository directory therefore requests removal of all managed direct user and team grants if `remove` is enabled. To keep an explicitly empty role file, use the standard `metadata_no_conditions_ok = true` text directive. **Deleting the entire repository directory opts that repository out without cleanup**; existing access is untouched. The configured root must still exist. Git does not track empty directories, so keep an explicit empty role file when intending to remove every managed grant.
147
147
148
148
#### Ownership boundary and feature flags
149
149
150
-
Only a `Repository` permission source is used to determine a current direct role, even when a team or organization gives the person a higher effective permission. Team, organization, enterprise-team, and owner grants are not managed. Outside collaborators and organization owners are excluded entirely; desired owners and non-members fail validation by default. With `ignore_not_found: true`, they are skipped with a warning. This backend does not invite people into the organization or manage pending organization invitations.
150
+
Only a `Repository` permission source determines a current direct user role, even when a team or organization gives the person higher effective access. Direct grants are read regardless of organization membership. Desired owners and non-members still fail validation by default; with `ignore_not_found: true`, they are ignored with a warning. This backend does not invite people into the organization or manage pending organization invitations.
151
+
152
+
Every managed repository also opts into removal of all organization-team repository associations, including empty teams. There is no team manifest or team allowlist. Team membership, hierarchy, and access to other repositories remain unchanged. Parent associations are removed before child associations; the backend re-reads the team list before each removal because inherited child access may disappear with its parent. Diffs report one removal per observed team, not a collaborator deletion for each team member. Exact team roles are unnecessary because no team grant is desired.
153
+
154
+
Organization base permissions, organization-owner privileges, and public/internal repository visibility are outside repository-grant management and remain unchanged. The diff explicitly notes this boundary: removing a grant does not necessarily remove all of a person's effective access. Public repositories remain publicly readable. Encountering an `EnterpriseTeam` permission source aborts reconciliation because this backend cannot remove that association; it does not silently claim convergence.
151
155
152
156
`ignore` is an array of logins removed from both sides of the diff, case-insensitively. Ignored users' grants are never mutated. Ignoring a user does not bypass schema/response validation when reading the repository. No fallback to effective permissions is performed.
153
157
154
-
`features` defaults to `[add, update, remove]`. `add` permits new direct grants, `update` permits role changes, and `remove` permits deleting direct grants absent from desired state. Disabled operations are suppressed in both actions and displayed state. `features: []` performs reads and validation but produces no changes. To inspect the full proposed diff without applying it, use Entitlements' no-op mode with all features enabled.
158
+
`features` defaults to `[add, update, remove]`. `add` permits new direct grants, `update` permits role changes, and `remove` permits deleting undeclared direct users and all repository team associations. Disabled operations are suppressed in both actions and displayed state. If removals are disabled while teams remain, the backend warns that individual-only grants are not enforced. `features: []` performs reads and validation but produces no changes. Feature restrictions and ignored users are policy exceptions; use all features and an empty ignore list for authoritative enforcement. To inspect the full proposed diff without applying it, use Entitlements' no-op mode with all features enabled.
155
159
156
-
Role changes issue one `PUT`, never a remove followed by an add. All upserts for a repository precede removals. Removing a direct grant does not remove inherited access; it also has GitHub's documented side effects on forks and other resources. Review the [collaborator API documentation](https://docs.github.com/en/rest/collaborators/collaborators) before enabling removal. GitHub may reject a direct role below organization base permissions.
160
+
Role changes issue one `PUT`, never a remove followed by an add. All desired user upserts for a repository precede user and team removals. Removing grants has GitHub's documented side effects on forks and other resources. Review the [collaborator API documentation](https://docs.github.com/en/rest/collaborators/collaborators) before enabling removal. GitHub may reject a direct role below organization base permissions.
| Organization | **Members: read** | Active organization members, owners, and repository teams |
167
171
168
172
These REST requirements are listed in [GitHub's App permission reference](https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps). Repository reads require access to `collaborators(affiliation: DIRECT)`, `permissionSources`, and each direct source's `roleName`; see the [GraphQL schema](https://docs.github.com/en/graphql/reference/repos). **Live installation-token access to these fields has not been validated by the unit suite.** Before deploying, verify it using the actual App installation and GitHub/GHES version. If those fields are unavailable, do not enable mutations or substitute effective REST permissions.
169
173
170
174
A live GitHub.com probe with a classic OAuth token confirmed that `permissionSources` and `roleName` require the `admin:org` scope; `repo` plus `read:org` was rejected. This is a classic-token scope requirement, not evidence that an App installation token has access. Validate the App separately rather than broadening an operator's token automatically.
171
175
172
176
The backend deliberately does not request the unused effective `permission` field. A live CI calculation with an `admin:org`-only token showed that field requires an additional `public_repo` scope. Exact direct roles come from `permissionSources.roleName`, so requesting effective permissions would add an unnecessary credential requirement.
173
177
174
-
In a designated disposable repository, give an active organization member a direct role and a different, higher inherited role. Verify the direct source reports the lower role, then add/change/remove the direct grant using the installation token. Expect `204` for organization-member adds, updates, and removals. `201` indicates an invitation rather than active access; the backend warns and does not cache it as a completed grant. Confirm inherited and outside access remain unchanged and a subsequent run has no diff. Do not run this check against production accounts or repositories.
178
+
In a designated disposable repository, give an active organization member a direct role and a different, higher team role. Include an undeclared outside collaborator and parent/child teams, including an empty team. Verify reads using the actual installation token, then reconcile individual-only access. Expect `204` for user upserts/removals and team association removals. `201` indicates an invitation rather than active access; the backend warns and post-apply verification must not mistake it for convergence. Confirm team memberships and other repositories remain unchanged, undeclared user/team grants disappear, and a subsequent run has no diff. Do not run this check against production accounts or repositories.
175
179
176
180
#### API usage, failure behavior, and rollout
177
181
178
-
Repository reads use one GraphQL request per page of up to 100 direct collaborators, at least one per managed repository. Pagination follows `hasNextPage` and rejects missing/repeated cursors. Snapshots are cached in memory for the service's lifetime and invalidated after successful or partial applies. There is no persistent repository cache or `entitlements-caches` integration.
182
+
Repository reads use one GraphQL request per page of up to 100 direct collaborators, plus paginated REST repository-team reads (100 per page). GraphQL pagination follows `hasNextPage` and rejects missing/repeated cursors; team reads use Octokit's automatic Link pagination. Snapshots include team identities and hierarchy independently of user membership. Snapshots are cached in memory for the service's lifetime and invalidated after successful or partial applies. There is no persistent repository cache or `entitlements-caches` integration.
183
+
184
+
Before application, a fresh snapshot must match the calculated existing state (excluding ignored users), otherwise the backend aborts and requires recalculation. After application, another fresh snapshot must match the feature-controlled target state. Residual grants or partial failures are explicit errors, never successful-looking convergence. These checks detect drift but are not an atomic transaction with GitHub; concurrent administrators can still change access during a run.
179
185
180
186
Organization membership uses the shared per-run cache (paginated REST reads for `admin` and `member`, 100 users per page); predictive membership is refreshed before authorizing repository grants. Each added or changed grant requires one REST `PUT`, and each removed grant one `DELETE`, excluding retries. Octokit's existing middleware retries server errors on idempotent mutations; authorization, validation, and abuse/rate-limit responses abort without application-level retries. GraphQL uses the shared bounded retry transport. A partial failure stops application, leaves already-applied grants in place, and invalidates the snapshot. Re-run after resolving the failure; changes are not rolled back automatically.
181
187
182
188
1. Complete the disposable-repository App validation above.
183
189
2. Start with a small set of repository directories and no-op mode; compare direct roles with repository settings. `features: []` is also safe for read/validation checks but suppresses the diff.
184
190
3. Enable only `add` and `update`, then confirm successive runs converge.
4. Review ignored accounts, organization-level access, outside collaborators, and all repository team associations before enabling `remove`. Existing team-based write/admin access will be removed even when team members are declared individually.
186
192
5. Expand gradually while measuring GraphQL cost, REST rate usage, runtime, and failure rates. Consider persistent caching only if measurements justify it.
187
193
188
194
Production convergence and API budgets require this live rollout; mocked tests do not establish them.
0 commit comments