Enrollment Model
The enrollment model defines how repositories, teams, and organizations join the GloriousFlywheel runner and cache platform. It replaces the earlier flattened model that relied on runner labels and overlay language with a structured four-dimension approach.
An enrollment is fully described by its position in four dimensions: forge scope, operator tenant, execution pool, and cache/state plane.
This document describes platform design intent. The current proof contract is stricter: nominal label visibility, template consumption, or historical exceptions do not by themselves establish real runner authority.
Control-plane enrollment scope is not workload taxonomy. Per-owner or per-installation visibility constraints must be solved underneath the shared capability-class contract and must not be normalized into repo-shaped runner labels.
Shared capability classes are the product. Repo-shaped sets are debt. If temporary control-plane plumbing still exists, it should stay explicitly bounded and out of the product contract.
Owner-specific deployment facts belong in implementation overlays. The core repo should expose reusable modules, shared lane names, and cache contracts; the overlay should carry GitHub App install media, tfvars, backend settings, and any private registration anchors needed for one owner or tenant.
Dimension 1: Forge Scope
Each forge defines its own hierarchy of scopes at which runners can be registered. The forge adapter (Layer 2 of the platform architecture) maps these scopes into ARC scale sets or GitLab runner registrations.
| Forge | Scope Levels |
|---|---|
| GitHub | Repository, Organization, Enterprise |
| GitLab | Project, Group, Instance |
| Forgejo | Repository, Organization, Account |
Organization-scoped registration is the default for GitHub. It installs a single GitHub App and can expose shared scale sets broadly across an org.
Important GitHub boundary:
- GitHub exposes self-hosted runners at repository, organization, or enterprise scope
- there is no GitHub personal-account-wide runner-group surface equivalent to organization scope
- Jess-owned personal repos therefore still require repo-level control-plane anchors today when they cannot be moved under an org-scoped shared lane
That is the infrastructure default, not a promise that every repo is already a clean current authority consumer. Current reporting should still separate:
- declared runner intent in workflow YAML
- accessible runner inventory
- real default-branch authority
- repo-scoped escape hatches or stale authority claims
Dimension 2: Operator Tenant
The operator tenant identifies who owns and pays for the runner capacity. Tenant boundaries control resource quotas, secret isolation, and billing attribution.
| Tenant | Description |
|---|---|
| Team | Single team within an org, scoped namespace |
| Organization | Full org enrollment, shared runner pool |
| Enterprise | Multiple orgs under one enterprise billing account |
| Managed Customer | External customer whose runners are operator-managed |
Each tenant maps to one or more Kubernetes namespaces. Secrets, RBAC, and network policies are scoped to the tenant namespace boundary.
Dimension 3: Execution Pool
Execution pools define the resource profile and intended workload class for a set of runners. Each pool corresponds to an ARC AutoScalingRunnerSet (or GitLab runner deployment) with specific resource limits.
| Capability Class | CPU / Mem | Use Case |
|---|---|---|
tinyland-docker |
2 CPU / 4Gi | General CI |
tinyland-nix |
4 CPU / 8Gi | Nix builds |
tinyland-nix-heavy |
8 CPU / 64Gi | Rust + Nix heavy compilation |
tinyland-nix-gpu |
bounded | GPU and WebGPU smoke |
tinyland-nix-kvm |
bounded | VM-based testing |
tinyland-dind |
bounded | Container builds |
Workflows select a capability class via the runs-on label (GitHub) or tags
field (GitLab/Forgejo). Shared capability labels are the intended steady-state
contract. If a repo cannot currently use one, that is enrollment debt to fix
underneath the label contract rather than a reason to mint a repo-shaped label.
Current boundary:
- label shape is only a routing convention
- shared-label adjacency does not prove accessible capacity, reachable cache surfaces, or default-branch authority for a given repo
Dimension 4: Cache and State Plane
Each enrolled entity participates in one or more cache and state planes. Multi-tenancy determines whether data is shared or isolated across tenant boundaries.
| Plane | Isolation | Multi-Tenant | Notes |
|---|---|---|---|
| Attic | Per-cache view | Yes | Global dedup across tenants |
| Bazel remote cache | Per-namespace | Yes | Namespace-scoped cache entries |
| State backend | Per-stack | No | Operator-owned, not shared |
Attic uses cache views to give each tenant a logical partition while deduplicating store paths at the storage layer. Bazel remote cache isolates by Kubernetes namespace. The OpenTofu state backend is always operator-owned and never shared across tenants.
Enrollment Flow
-
Deploy substrate — Stand up the Kubernetes cluster, install ARC controller, deploy Attic and Bazel remote cache. This is Layer 1 of the platform architecture.
-
Create forge adapter — Install the GitHub App (or register GitLab/ Forgejo runners) at the chosen scope level. Configure webhook routing and token exchange.
-
Bind the implementation overlay — Put owner-specific GitHub App installation IDs, private keys, tfvars, backend config, and cache namespace choices in the owner or tenant overlay. Do not put personal-account or repo-scoped anchors in the reusable core product stack.
-
Define scale sets — Create AutoScalingRunnerSets for each execution pool the tenant needs. Set resource limits, runner image, and warm-pool schedule.
-
Configure cache plane — Provision an Attic cache view and Bazel remote cache namespace for the tenant. Distribute cache tokens to the runner pods via Kubernetes secrets.
-
Repos use shared labels — Repository workflows reference the shared capability label in
runs-on. Shared org lanes can work without per-repo registration. Repository-level registration or installation quirks are control-plane plumbing, not a user-facing runner taxonomy.For GitHub personal-account repos, remember that there is no org-style personal runner group scope. If a personal repo cannot reach the shared lane today, the compliant exits are an owner-boundary change, an org or enterprise shared-runner surface, or an explicit blocked state. A repo-scoped ARC anchor may be unavoidable plumbing for compatibility, but it is not a shared pooled-substrate proof and must not become a repo-shaped workload label.
Current boundary:
- “can use labels” is substrate capability, not a blanket readiness claim
- real enrollment still depends on reachable self-hosted infrastructure, workflow-owned bootstrap where required, and a proved default-branch path
Cross-Org Enrollment
A single GloriousFlywheel cluster can serve multiple GitHub organizations (or GitLab groups, Forgejo accounts) simultaneously. The mechanism:
-
Implementation overlay per owner/tenant: Each owner keeps its GitHub App installation media, tfvars, backend settings, and private topology values out of the reusable core repo. Multiple overlays may still target the same cluster and cache substrate when isolation boundaries are explicit.
-
GitHub App per org: Each org installs its own instance of the GloriousFlywheel GitHub App. The ARC controller routes jobs to the correct scale set based on the installation ID.
-
Separate or shared scale sets: Orgs can share execution pools (cost efficiency) or run dedicated scale sets (isolation). The choice is a tenant-level decision configured in the OpenTofu stack.
-
Attic tenant views: Each org gets its own Attic cache view. Store paths are deduplicated globally, so cross-org builds of the same derivation hit cache without leaking private paths.
-
Bazel namespaces: Each org’s Bazel remote cache lives in its own Kubernetes namespace, enforcing hard isolation at the storage layer.
Example Rollout Shape
A bounded rollout should still converge on shared capability classes rather than repo-shaped lanes.
Examples:
- shared baseline capability classes such as
tinyland-nix,tinyland-docker, andtinyland-dind - additive capability classes only when there is a real runtime or hardware
difference, such as
tinyland-nix-heavyortinyland-nix-kvm
If a downstream repo cannot currently reach one of those shared lanes because of GitHub App installation scope, owner boundary, or secret wiring, that repo is blocked on enrollment debt. It is not evidence that repo-shaped runner labels are the correct architecture.
Current boundary:
- org-wide GitHub enrollment is substrate capability, not blanket readiness
- repo readiness still depends on accessible runner inventory, explicit proof, and lane-specific contract truth
- nominal shared-label configuration alone is not enough to count a repo as a real authority consumer
- hosted runners are an emergency/operator escape hatch, not the normal authority path
- Chapel, Nix, and Bazel-heavy workloads remain core target consumers, not special-case exceptions