# ThemeFactory ARC execution

ThemeFactory uses project-owned GitHub Actions plus two repo-scoped ARC scale sets on the shared K3s cluster.

- `themefactory-ci`: unprivileged/default K3s runtime, no service-account token, planning/static/aggregation only. The cluster does not currently expose a `gvisor` RuntimeClass, so the live manifest does not claim one.
- `themefactory-runtime`: internal-only exceptional DIND pool for disposable WordPress/MariaDB/Chromium fixtures. Fork PRs are rejected before this runner is selected.

Both use shared scale-set labels. Workflows never name `debian1`, `debian2`, or `debian3`; Kubernetes places ephemeral runner pods across available buildbox capacity. `maxRunners: 3` bounds heavy runtime concurrency to cluster capacity.

The checked-in values reference `themefactory-arc-github-config`; credential bytes are never committed. `install.sh` copies the existing cluster GitHub config secret into each trust namespace and installs the pinned scale-set chart. The runner pods themselves receive no reusable Git publication credential and have `automountServiceAccountToken: false`.

The runner image is built from `ci/arc/runner-image/Dockerfile`, preloaded on every eligible K3s node, and used with `imagePullPolicy: Never` because the available GitHub token is read-only for packages. It contains the stable verification toolchain (Python/pytest, PHP CLI, Node, Docker CLI, curl, rsync, zip/unzip). This avoids repeated package setup in every ephemeral job.

Live proof is not satisfied by rendering these manifests. Completion requires a real GitHub workflow scheduled to each scale-set label, observation of ephemeral runner creation/removal, and inspection showing the intended trust/privilege/service-account properties.

## Runtime disk budget

`themefactory-runtime` explicitly requests ephemeral storage for the fresh DIND image graph and runner workspace: DIND requests 8Gi (20Gi limit) and the runner requests 2Gi (8Gi limit). This makes Kubernetes scheduling/eviction account for the real disk cost instead of treating runtime pods as zero-request disk consumers. Eligible nodes must keep enough free nodefs/imagefs headroom above Kubelet eviction thresholds.

The custom runner archive is also persisted through K3s's air-gap import path (`/var/lib/rancher/k3s/agent/images/`) on every eligible node while the pod still uses `imagePullPolicy: Never`. The archive SHA/config digest are the durable runner identity; a missing local image must fail closed rather than silently substituting registry bytes.
