GloriousFlywheel Longevity Sprint Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Execute an 8-week longevity sprint that hardens the GloriousFlywheel substrate, benchmarks it against real workloads, proves cross-org heterogeneous access from a single on-prem stack, and ships a clear FOSS core + forge adapter adoption story.
Architecture: GloriousFlywheel is a recursive IaC platform: Nix + Bazel + OpenTofu + K8s. The sprint restructures it as 4 layers: (1) FOSS core substrate (cache, runner images, operator tooling, cluster substrate), (2) forge adapters (GitHub ARC primary, GitLab compat, Forgejo proof), (3) optional managed control plane (SaaS layer above FOSS core), (4) compatibility kit (legacy bzlmod overlay consumers). One on-prem honey cluster serves hundreds of repos across orgs and VCS platforms.
Tech Stack: Nix flakes, Bazel (bzlmod), OpenTofu, Kubernetes (RKE2 on honey), GitHub ARC 0.14.0, Attic (Nix binary cache), SvelteKit 5 + Skeleton v4, Vitest, Just, pnpm, GHCR, FlakeHub
Tracker References:
- Initiative: GloriousFlywheel Runner Platform Reset
- GitHub: #210, #211, #212, #213, #215
- Linear: TIN-125, TIN-126, TIN-129, TIN-148, TIN-149
- Sprint plan:
docs/research/gloriousflywheel-longevity-two-month-sprint-plan-2026-04-19.md - Architecture note:
docs/research/gloriousflywheel-saas-foss-adopt-and-mod-future-shape-2026-04-19.md
Canary repos: XoxdWM (tinyland-inc), MassageIthaca (tinyland-inc), cmux (Jesssullivan — cross-org proof)
Parallelization Map
Phase 1 (Weeks 1-2)
├── Task Group A: Architecture & Boundary Docs ──┐
├── Task Group B: Tracker Hygiene & Alignment │ independent
└── Task Group C: Release Baseline ──┘
Phase 2 (Weeks 3-4)
├── Task Group D: Benchmark Harness (depends on A)
├── Task Group E: Substrate Hardening (independent)
└── Task Group F: Canary Enrollment Prep (depends on A)
Phase 3 (Weeks 5-6)
├── Task Group G: Adoption Surfaces (depends on A, D)
└── Task Group H: Cross-Org Canary Proofs (depends on D, F)
Phase 4 (Weeks 7-8)
├── Task Group I: Release & Publication (depends on C, D)
└── Task Group J: Control Plane & Retro (depends on A, G)
Tasks within the same phase and without dependency arrows can be dispatched to parallel subagents.
File Structure
New files (create)
| File | Responsibility |
|---|---|
docs/architecture/platform-layers.md |
Public 4-layer architecture page |
docs/architecture/forge-adapter-matrix.md |
Forge support matrix with caveats |
docs/architecture/enrollment-model.md |
Multi-org enrollment in operator terms |
docs/guides/adoption-quickstart.md |
Adopter-facing “deploy core → enroll forge → choose pool” |
docs/guides/canary-enrollment.md |
How to enroll a repo as canary |
docs/compatibility-kit.md |
Compat kit decision note and scope |
docs/release-baseline.md |
Tag plan, changelog expectations, artifact list |
scripts/benchmark/runner-benchmark.sh |
Benchmark harness: cold start, queue, cache |
scripts/benchmark/parse-results.sh |
Parse benchmark JSON into scorecard |
scripts/benchmark/canary-enrollment-check.sh |
Validate a repo’s enrollment state |
.github/workflows/benchmark.yml |
Benchmark CI workflow (manual dispatch) |
tofu/stacks/arc-runners/examples/minimal/main.tf |
Minimal ARC adoption example |
tofu/stacks/arc-runners/examples/minimal/variables.tf |
Example variables |
tofu/stacks/arc-runners/examples/minimal/README.md |
Example usage doc |
tests/benchmark/validate-scorecard.sh |
Regression: scorecard format validation |
tests/integration/canary-smoke.sh |
Regression: canary repo can reach runners |
docs/research/gloriousflywheel-sprint-retrospective-2026-06.md |
Sprint retro (Phase 4) |
Modified files
| File | Change |
|---|---|
docs/index.md |
Link to platform-layers.md as primary architecture |
docs/roadmap.md |
Update with phase milestones and completion dates |
docs/rfcs.md |
Add sprint-related RFC lanes |
docs/cleanup-program.md |
Link sprint plan tasks to workstreams |
docs/current-state.md |
Update with benchmark results (Phase 2+) |
docs/getting-started-guide.md |
Rewrite around 4-layer model, not overlay |
CONTRIBUTING.md |
Update adoption path language |
CHANGELOG.md |
Add sprint entries as work lands |
.github/workflows/test-arc-runners.yml |
Add timing instrumentation |
.github/workflows/platform-proof.yml |
Add cross-org canary proof jobs |
tofu/stacks/arc-runners/honey.tfvars |
Add canary repo scale sets |
Justfile |
Add benchmark and canary recipes |
Task Group A: Architecture & Boundary Docs (Phase 1)
Task 1: Public Platform Layers Architecture Page
Files:
-
Create:
docs/architecture/platform-layers.md -
Modify:
docs/index.md -
Step 1: Write the platform layers page
---
title: Platform Architecture
order: 1
---
# Platform Architecture
GloriousFlywheel is a self-hosted runner and cache platform organized in four
layers.
## Layer 1: FOSS Core Substrate
The shared infrastructure that works without any managed service dependency.
Components:
- **Attic**: Multi-tenant Nix binary cache backed by S3-compatible storage
- **Bazel remote cache**: Optional build acceleration via remote cache endpoint
- **Runner images**: docker, nix, nix-heavy, future hardware classes (gpu, kvm)
- **Cluster substrate**: Kubernetes + OpenTofu deployment surfaces
- **Dashboard**: SvelteKit operator UX with WebAuthn auth
- **Operator tooling**: Just recipes, health checks, MCP server
This layer deploys on any Kubernetes cluster. The reference deployment runs on
an on-prem RKE2 cluster (`honey`) with Tailscale overlay networking.
## Layer 2: Forge Adapters
Each forge has its own runner registration, scope model, token handling, and
event routing. The shared substrate stays the same; the adapter handles
forge-specific enrollment.
| Forge | Adapter | Scope Model | Status |
| ---------------- | -------------- | ------------------------ | ------------- |
| GitHub | ARC scale sets | repo, org, enterprise | Primary |
| GitLab | GitLab Runner | project, group, instance | Compatibility |
| Forgejo/Codeberg | act_runner | repo, org, account | Proof path |
## Layer 3: Managed Control Plane (Future)
Optional SaaS layer above the FOSS core for fleet enrollment UX, tenant and
pool management, usage reporting, cache and runner observability, policy packs,
and support diagnostics.
Not required for deploying the base platform, bootstrapping a cluster, using
Attic or Bazel cache, or running GitHub ARC on a self-hosted cluster.
## Layer 4: Compatibility Kit
Legacy Bzlmod overlay patterns, `local_path_override` development flows,
downstream merge-and-modify examples, and transitional consumers.
This is not the primary adoption path. See [Adoption Quickstart](../guides/adoption-quickstart.md)
for the recommended onboarding flow.
## Multi-Org Enrollment Model
Runner enrollment is modeled along four dimensions:
1. **Forge scope**: GitHub repo/org/enterprise, GitLab project/group/instance,
Forgejo repo/org/account
2. **Operator tenant**: team, org, enterprise, managed customer
3. **Execution pool**: docker, nix, nix-heavy, gpu, kvm
4. **Cache/state plane**: Attic tenant/cache view, Bazel cache namespace, state
backend authority
## Adopting GloriousFlywheel
1. Deploy the core substrate on your cluster
2. Enroll your forge using the appropriate adapter
3. Choose shared or dedicated runner pools
4. Attach caches (Attic for Nix, Bazel remote for Bazel)
5. Customize runner images, placement, and policy
- Step 2: Update docs/index.md to link the new page
In docs/index.md, replace any reference to overlay-first architecture with:
## Architecture
GloriousFlywheel is organized as [FOSS core substrate + forge adapters](architecture/platform-layers.md).
The shared thing is caches, runner images, operator tooling, and cluster
substrate. The forge-specific thing is enrollment, tokens, scope, and event
routing.
- Step 3: Validate markdown
Run: just check or at minimum:
npx markdownlint-cli2 docs/architecture/platform-layers.md docs/index.md
Expected: No errors
- Step 4: Commit
git add docs/architecture/platform-layers.md docs/index.md
git commit -m "docs(arch): add public 4-layer platform architecture page
Refs: #211, TIN-129"
Completion metric: docs/architecture/platform-layers.md exists, passes markdownlint, and is linked from docs/index.md.
Regression gate: grep -q 'platform-layers' docs/index.md
Task 2: Forge Adapter Matrix
Files:
-
Create:
docs/architecture/forge-adapter-matrix.md -
Modify:
docs/rfcs.md -
Step 1: Write the forge adapter matrix
---
title: Forge Adapter Matrix
order: 2
---
# Forge Adapter Matrix
Snapshot date: 2026-04-19
This document defines which forges GloriousFlywheel supports, at what maturity
level, and with what caveats.
## Matrix
| Forge | Adapter | Runner Registration | Cache Integration | CI Parity | Status |
| ---------------- | ------------------ | ---------------------------------------- | -------------------- | ----------------- | ------------- |
| GitHub | ARC scale sets | GitHub App + org scope | Attic + Bazel remote | Full | Primary |
| GitLab | gitlab-runner Helm | Registration token + group/project scope | Attic (partial) | Validation only | Compatibility |
| Forgejo/Codeberg | act_runner | Instance token + org/repo scope | Attic (planned) | Single proof path | Proof |
## GitHub Adapter (Primary)
- **Registration**: GitHub App installed at org level, ARC controller + scale sets
- **Scale sets**: tinyland-nix, tinyland-docker, tinyland-dind, tinyland-nix-heavy
- **Scope**: org-wide or per-repo via `github_config_url`
- **Cache**: Attic endpoint injected via pod env, Bazel remote cache optional
- **Status**: Production on `honey` cluster
## GitLab Adapter (Compatibility)
- **Registration**: GitLab Runner via Helm, registration token per group/project
- **Scope**: Instance, group, or project
- **Cache**: Attic integration partial — GitLab CI cache mechanism differs
- **Status**: Helm chart exists, not deployed on `honey`. GitLab serves as
OpenTofu state backend only.
- **Caveats**: No active fleet on the live cluster. Compatibility means the
tofu module and Helm config exist and validate, not that a live GitLab runner
fleet runs on `honey`.
## Forgejo/Codeberg Adapter (Proof Path)
- **Registration**: act_runner with instance token
- **Scope**: Account, org, or repo
- **Cache**: Attic planned but not tested
- **Status**: `.forgejo/` workflow directory exists in repo. No live runner
fleet. One honest proof path planned for the longevity sprint.
- **Caveats**: Forgejo Actions is compatible with GitHub Actions workflow
syntax but uses its own runner registration protocol. Runners do not need a
public IP.
## What Is Shared Across Forges
- Attic Nix binary cache (same store, per-tenant views)
- Runner images (docker, nix, nix-heavy)
- Cluster substrate (Kubernetes, OpenTofu modules)
- Operator tooling (dashboard, MCP server, Just recipes)
- Policy model (pool placement, resource limits, scheduling)
## What Is Forge-Specific
- Runner registration protocol and tokens
- Scope model (repo/org/enterprise vs project/group/instance)
- Event routing (webhooks vs long-poll vs polling)
- CI syntax compatibility (Actions YAML vs GitLab CI YAML)
- Step 2: Link from rfcs.md
Add to docs/rfcs.md under “In Effect”:
- forge adapter boundaries are documented in
[Forge Adapter Matrix](architecture/forge-adapter-matrix.md)
- Step 3: Validate and commit
npx markdownlint-cli2 docs/architecture/forge-adapter-matrix.md
git add docs/architecture/forge-adapter-matrix.md docs/rfcs.md
git commit -m "docs(arch): add forge adapter matrix with maturity levels
Refs: #211"
Completion metric: Forge matrix exists with explicit maturity levels and caveats per forge.
Regression gate: grep -q 'Primary' docs/architecture/forge-adapter-matrix.md && grep -q 'Compatibility' docs/architecture/forge-adapter-matrix.md && grep -q 'Proof' docs/architecture/forge-adapter-matrix.md
Task 3: Compatibility Kit Decision Note
Files:
-
Create:
docs/compatibility-kit.md -
Step 1: Write the compatibility kit decision
---
title: Compatibility Kit
order: 8
---
# Compatibility Kit
Snapshot date: 2026-04-19
## Decision
The Bzlmod overlay compatibility kit lives in `Jesssullivan/bzl-cross-repo`,
not in the main GloriousFlywheel repository.
GloriousFlywheel retains the `attic-iac` module name in `MODULE.bazel` for
internal build structure and backward compatibility. It does not publish the
full platform as a Bazel Central Registry module during this sprint.
## What The Kit Covers
- Legacy Bzlmod overlay patterns for downstream consumers
- `local_path_override` development flows
- Downstream merge-and-modify examples
- Transitional consumers such as `tinyland-infra`
## What It Does Not Cover
- Primary onboarding for new adopters (use the adoption quickstart instead)
- Multi-forge enrollment (use forge adapters)
- Cache or runner configuration (use the core substrate docs)
## Why
The overlay-first adoption story created too much conceptual flattening.
It made local overrides and merge mechanics feel more central than the runner,
cache, and cluster truth. The clearer story is: deploy core, enroll forge,
choose pools, attach caches, customize policy.
## Migration Path
Existing overlay consumers should:
1. Continue using `bzl-cross-repo` for Bzlmod composition
2. Migrate configuration to direct OpenTofu variable overrides
3. Treat the compatibility kit as transitional, not permanent
## Related
- [Platform Architecture](architecture/platform-layers.md)
- [Adoption Quickstart](guides/adoption-quickstart.md)
- Step 2: Commit
git add docs/compatibility-kit.md
git commit -m "docs: add compatibility kit decision note
Bzlmod overlay compatibility lives in bzl-cross-repo. GloriousFlywheel
keeps attic-iac module name for internal use only.
Refs: #211"
Completion metric: Decision is documented: compat kit lives in bzl-cross-repo, not in main repo.
Regression gate: grep -q 'bzl-cross-repo' docs/compatibility-kit.md
Task 4: Multi-Org Enrollment Model
Files:
-
Create:
docs/architecture/enrollment-model.md -
Step 1: Write the enrollment model
---
title: Enrollment Model
order: 3
---
# Multi-Org Enrollment Model
Runner enrollment is modeled along four dimensions. This replaces the older
flattened model based on runner labels and overlay language.
## Dimension 1: Forge Scope
Where the runner is registered and what repos can reach it.
| Forge | Scopes |
| ------- | ------------------------------------ |
| GitHub | Repository, Organization, Enterprise |
| GitLab | Project, Group, Instance |
| Forgejo | Repository, Organization, Account |
## Dimension 2: Operator Tenant
Who operates and pays for the runner pool.
| Tenant Type | Description |
| ---------------- | ------------------------------------------- |
| Team | Single team within an org |
| Organization | Full org (e.g., tinyland-inc) |
| Enterprise | Multiple orgs under one billing entity |
| Managed Customer | External customer on the SaaS control plane |
## Dimension 3: Execution Pool
What kind of runner the workload gets.
| Pool | CPU/Mem | Use Case |
| --------- | ------------ | ----------------------------- |
| docker | 2 CPU / 4Gi | General CI, Docker builds |
| nix | 4 CPU / 8Gi | Nix builds, flake checks |
| nix-heavy | 8 CPU / 16Gi | Rust + Nix, heavy compilation |
| gpu | TBD | ML training, CUDA builds |
| kvm | TBD | VM-based testing, kernel work |
## Dimension 4: Cache and State Plane
What cache and state backend the workload uses.
| Component | Scope | Multi-Tenant |
| ------------------ | ---------------------------- | ------------------------- |
| Attic | Per-cache view, global dedup | Yes (tenant isolation) |
| Bazel remote cache | Per-namespace | Yes (namespace isolation) |
| State backend | Per-stack | No (operator-owned) |
## Enrollment Flow
1. Operator deploys core substrate (Task Group E)
2. Operator creates a forge adapter config (GitHub App, GitLab token, etc.)
3. Operator defines scale sets with pool type and resource limits
4. Operator configures cache endpoints in runner pod environment
5. Repos use the runner labels (`tinyland-nix`, etc.) in workflow files
## Cross-Org Enrollment
A single GloriousFlywheel cluster can serve multiple orgs by:
- Installing the GitHub App on each org
- Creating separate scale sets per org (or sharing org-wide sets)
- Using Attic tenant views for cache isolation
- Using Bazel cache namespaces for build isolation
This is the current model on `honey`: tinyland-inc org-wide sets serve all
tinyland-inc repos, and cross-org canaries (e.g., cmux under Jesssullivan)
get dedicated scale sets.
- Step 2: Commit
git add docs/architecture/enrollment-model.md
git commit -m "docs(arch): add multi-org enrollment model
Four dimensions: forge scope, operator tenant, execution pool,
cache/state plane. Replaces flattened label-based model.
Refs: #211, TIN-129"
Completion metric: Enrollment model documents 4 dimensions with concrete tables.
Regression gate: grep -c 'Dimension' docs/architecture/enrollment-model.md returns 4.
Task Group B: Tracker Hygiene (Phase 1)
Task 5: Create Linear Initiative, Milestones, and Phase Issues
Files: None (tracker-only)
- Step 1: Create Linear initiative
Create initiative “GloriousFlywheel Longevity Sprint (Apr–Jun 2026)” with:
-
Status: Active
-
Target date: 2026-06-14
-
Owner: Jess Sullivan
-
Parent: link to “GloriousFlywheel Runner Platform Reset” if possible
-
Description: 8-week sprint. Core substrate hardening, benchmark evidence, cross-org canary proofs, 4-layer architecture.
-
Step 2: Create Linear project with milestones
Create project “Longevity Sprint Execution” with 4 milestones:
| Milestone | Name | Target Date |
|---|---|---|
| 1 | Phase 1: Freeze Architecture | 2026-05-03 |
| 2 | Phase 2: Substrate & Benchmarks | 2026-05-17 |
| 3 | Phase 3: Adoption & Canaries | 2026-05-31 |
| 4 | Phase 4: Release & Retro | 2026-06-14 |
- Step 3: Create phase-mapped Linear issues
Create issues for work not yet in Linear:
| Title | Milestone | Priority | Labels |
|---|---|---|---|
| Write public 4-layer architecture page | Phase 1 | High | docs, foss |
| Write forge adapter matrix | Phase 1 | High | docs, foss |
| Decide and document compatibility kit home | Phase 1 | High | docs |
| Write release baseline doc | Phase 1 | Normal | release |
| Build benchmark harness script | Phase 2 | Urgent | Feature |
| Add timing instrumentation to test-arc-runners | Phase 2 | High | Feature |
| Publish benchmark scorecard | Phase 2 | High | docs |
| Enroll XoxdWM as canary with validation | Phase 3 | High | foss |
| Enroll MassageIthaca as canary | Phase 3 | Normal | foss |
| Prove cmux cross-org enrollment | Phase 3 | High | foss |
| Write adoption quickstart guide | Phase 3 | High | docs, foss |
| Forgejo proof-path: one honest adapter test | Phase 3 | Normal | foss |
| Ship first tagged release with changelog | Phase 4 | High | release |
| Write sprint retrospective | Phase 4 | Normal | docs |
- Step 4: Cross-link existing issues to milestones
Update existing Linear issues with milestone mapping:
-
TIN-125 → Phase 2
-
TIN-126 → Phase 2
-
TIN-148 → Phase 2
-
TIN-129 → Phase 1 + Phase 3
-
TIN-149 → Phase 4
-
Step 5: Create matching GitHub milestone
cd ~/git/GloriousFlywheel
gh api repos/tinyland-inc/GloriousFlywheel/milestones -f title="Longevity Sprint" -f due_on="2026-06-14T00:00:00Z" -f description="8-week longevity sprint: substrate hardening, benchmarks, cross-org canary proofs, 4-layer architecture"
Then assign open issues to it:
for issue in 210 211 212 213 215; do
gh issue edit $issue --milestone "Longevity Sprint"
done
Completion metric: Linear initiative exists with 4 milestones. GitHub milestone exists with #210-215 assigned.
Regression gate: gh api repos/tinyland-inc/GloriousFlywheel/milestones --jq '.[].title' | grep -q 'Longevity'
Task Group C: Release Baseline (Phase 1)
Task 6: Release Baseline Document
Files:
-
Create:
docs/release-baseline.md -
Step 1: Write the release baseline
---
title: Release Baseline
order: 7
---
# Release Baseline
Snapshot date: 2026-04-19
## Tag Convention
Use annotated semantic version tags: `v0.1.0`, `v0.2.0`, etc.
The first tagged release is `v0.1.0` — the longevity sprint baseline.
Tags are created on `main` after PR merge. No pre-release tags during this
sprint.
## Changelog Discipline
`CHANGELOG.md` uses Keep a Changelog format with sections:
Added, Changed, Fixed, Removed, Security.
Every PR that lands on `main` should have a changelog entry. The entry goes
in the `[Unreleased]` section and moves to a versioned section at tag time.
## Published Artifacts
| Artifact | Registry | Cadence |
| ---------------------------- | ---------------------------------------------- | ------------------------------------------ |
| `attic-server` OCI image | GHCR (`ghcr.io/tinyland-inc/attic-server`) | Per release tag |
| `attic-gc` OCI image | GHCR (`ghcr.io/tinyland-inc/attic-gc`) | Per release tag |
| `runner-dashboard` OCI image | GHCR (`ghcr.io/tinyland-inc/runner-dashboard`) | Per release tag |
| Nix flake | FlakeHub | Per release tag (publication, not runtime) |
| OpenTofu modules | Git refs in this repo | Per release tag |
## What Is Not Published
- Bazel Central Registry module (internal-only this sprint)
- npm packages (app is deployed, not published)
- Helm charts (consumed via OpenTofu Helm provider, not standalone)
## Release Checklist
1. All `just check` passes on `main`
2. Benchmark scorecard is current
3. CHANGELOG.md has entries for all merged work
4. Create annotated tag: `git tag -a v0.X.0 -m "Release v0.X.0"`
5. Push tag: `git push origin v0.X.0`
6. GitHub release workflow triggers GHCR image builds
7. FlakeHub publish workflow triggers flake publication
- Step 2: Commit
git add docs/release-baseline.md
git commit -m "docs: add release baseline for longevity sprint
Defines tag convention, changelog discipline, published artifacts,
and release checklist.
Refs: #215, TIN-149"
Completion metric: Release baseline defines tag scheme, changelog format, and artifact list.
Regression gate: grep -q 'v0.1.0' docs/release-baseline.md
Task Group D: Benchmark Harness (Phase 2)
Task 7: Benchmark Script
Files:
-
Create:
scripts/benchmark/runner-benchmark.sh -
Create:
scripts/benchmark/parse-results.sh -
Step 1: Write the benchmark harness
#!/usr/bin/env bash
# scripts/benchmark/runner-benchmark.sh
# Benchmark GloriousFlywheel runners: cold start, queue latency, cache behavior
# Usage: ./scripts/benchmark/runner-benchmark.sh <runner-label> <workload>
# Output: JSON to stdout, human summary to stderr
set -euo pipefail
RUNNER_LABEL="${1:?Usage: runner-benchmark.sh <runner-label> <workload>}"
WORKLOAD="${2:?Workloads: nix-build, docker-build, flake-check, bazel-test}"
RESULTS_DIR="${RESULTS_DIR:-/tmp/gf-benchmark}"
mkdir -p "$RESULTS_DIR"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
RESULT_FILE="$RESULTS_DIR/${RUNNER_LABEL}-${WORKLOAD}-$(date +%s).json"
# Record queue submit time
QUEUE_START=$(date +%s%N)
case "$WORKLOAD" in
nix-build)
# Time a real nix build
BUILD_START=$(date +%s%N)
nix build .#runner-dashboard-image --no-link 2>/dev/null
BUILD_END=$(date +%s%N)
;;
flake-check)
BUILD_START=$(date +%s%N)
nix flake check 2>/dev/null
BUILD_END=$(date +%s%N)
;;
docker-build)
BUILD_START=$(date +%s%N)
docker build -f app/Dockerfile -t benchmark-test . 2>/dev/null
BUILD_END=$(date +%s%N)
;;
bazel-test)
BUILD_START=$(date +%s%N)
bazel test //... 2>/dev/null
BUILD_END=$(date +%s%N)
;;
*)
echo "Unknown workload: $WORKLOAD" >&2
exit 1
;;
esac
QUEUE_END=$(date +%s%N)
# Calculate durations in milliseconds
TOTAL_MS=$(( (QUEUE_END - QUEUE_START) / 1000000 ))
BUILD_MS=$(( (BUILD_END - BUILD_START) / 1000000 ))
OVERHEAD_MS=$(( TOTAL_MS - BUILD_MS ))
# Check cache state
ATTIC_HIT="unknown"
if command -v attic &>/dev/null && [ -n "${ATTIC_SERVER:-}" ]; then
ATTIC_HIT="available"
fi
BAZEL_CACHE_HIT="unknown"
if [ -n "${BAZEL_REMOTE_CACHE:-}" ]; then
BAZEL_CACHE_HIT="available"
fi
# Write JSON result
cat > "$RESULT_FILE" <<ENDJSON
{
"timestamp": "$TIMESTAMP",
"runner_label": "$RUNNER_LABEL",
"workload": "$WORKLOAD",
"total_ms": $TOTAL_MS,
"build_ms": $BUILD_MS,
"overhead_ms": $OVERHEAD_MS,
"attic_cache": "$ATTIC_HIT",
"bazel_cache": "$BAZEL_CACHE_HIT",
"hostname": "$(hostname)",
"nix_store_size_mb": $(du -sm /nix/store 2>/dev/null | cut -f1 || echo 0)
}
ENDJSON
# Human summary to stderr
cat >&2 <<SUMMARY
=== Benchmark: $RUNNER_LABEL / $WORKLOAD ===
Total: ${TOTAL_MS}ms
Build: ${BUILD_MS}ms
Overhead: ${OVERHEAD_MS}ms
Attic: $ATTIC_HIT
Bazel: $BAZEL_CACHE_HIT
Result: $RESULT_FILE
SUMMARY
cat "$RESULT_FILE"
- Step 2: Write the results parser
#!/usr/bin/env bash
# scripts/benchmark/parse-results.sh
# Parse benchmark JSON files into a markdown scorecard
# Usage: ./scripts/benchmark/parse-results.sh <results-dir>
set -euo pipefail
RESULTS_DIR="${1:?Usage: parse-results.sh <results-dir>}"
echo "# Benchmark Scorecard"
echo ""
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""
echo "| Runner | Workload | Total (ms) | Build (ms) | Overhead (ms) | Attic | Bazel Cache |"
echo "|--------|----------|-----------|-----------|--------------|-------|-------------|"
for f in "$RESULTS_DIR"/*.json; do
[ -f "$f" ] || continue
RUNNER=$(jq -r '.runner_label' "$f")
WORKLOAD=$(jq -r '.workload' "$f")
TOTAL=$(jq -r '.total_ms' "$f")
BUILD=$(jq -r '.build_ms' "$f")
OVERHEAD=$(jq -r '.overhead_ms' "$f")
ATTIC=$(jq -r '.attic_cache' "$f")
BAZEL=$(jq -r '.bazel_cache' "$f")
echo "| $RUNNER | $WORKLOAD | $TOTAL | $BUILD | $OVERHEAD | $ATTIC | $BAZEL |"
done
- Step 3: Make scripts executable and validate
chmod +x scripts/benchmark/runner-benchmark.sh scripts/benchmark/parse-results.sh
shellcheck scripts/benchmark/runner-benchmark.sh scripts/benchmark/parse-results.sh
Expected: No errors (or only minor warnings about jq parsing)
- Step 4: Commit
git add scripts/benchmark/
git commit -m "feat(bench): add runner benchmark harness and scorecard parser
Measures cold start, build time, overhead, and cache state per
runner label and workload type. Outputs JSON and markdown scorecard.
Refs: #212"
Completion metric: scripts/benchmark/runner-benchmark.sh is executable and passes shellcheck.
Regression gate: shellcheck scripts/benchmark/runner-benchmark.sh && echo PASS
Task 8: Benchmark CI Workflow
Files:
-
Create:
.github/workflows/benchmark.yml -
Step 1: Write the benchmark workflow
# .github/workflows/benchmark.yml
name: Runner Benchmarks
on:
workflow_dispatch:
inputs:
workload:
description: "Workload to benchmark"
required: true
type: choice
options:
- nix-build
- flake-check
- bazel-test
- all
runner_label:
description: "Runner label to test"
required: true
type: choice
options:
- tinyland-nix
- tinyland-nix-heavy
- tinyland-docker
- ubuntu-latest
jobs:
benchmark:
runs-on: ${{ inputs.runner_label }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Record queue latency
id: queue
run: |
echo "queue_start=$(date +%s%N)" >> "$GITHUB_OUTPUT"
- name: Run benchmark
env:
ATTIC_SERVER: ${{ vars.ATTIC_SERVER || '' }}
BAZEL_REMOTE_CACHE: ${{ vars.BAZEL_REMOTE_CACHE || '' }}
RESULTS_DIR: /tmp/gf-benchmark
run: |
if [ "${{ inputs.workload }}" = "all" ]; then
for wl in nix-build flake-check bazel-test; do
./scripts/benchmark/runner-benchmark.sh "${{ inputs.runner_label }}" "$wl" || true
done
else
./scripts/benchmark/runner-benchmark.sh "${{ inputs.runner_label }}" "${{ inputs.workload }}"
fi
- name: Generate scorecard
run: |
./scripts/benchmark/parse-results.sh /tmp/gf-benchmark > /tmp/gf-benchmark/scorecard.md
cat /tmp/gf-benchmark/scorecard.md >> "$GITHUB_STEP_SUMMARY"
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: benchmark-${{ inputs.runner_label }}-${{ inputs.workload }}-${{ github.run_id }}
path: /tmp/gf-benchmark/
retention-days: 90
- Step 2: Commit
git add .github/workflows/benchmark.yml
git commit -m "ci(bench): add manual benchmark dispatch workflow
Runs benchmark harness on specified runner label and workload.
Generates scorecard as step summary and uploads artifacts.
Refs: #212"
Completion metric: Benchmark workflow exists with manual dispatch and scorecard output.
Regression gate: yq '.jobs.benchmark.steps | length' .github/workflows/benchmark.yml returns at least 4.
Task 9: Add Timing Instrumentation to test-arc-runners
Files:
-
Modify:
.github/workflows/test-arc-runners.yml -
Step 1: Read current workflow
Read .github/workflows/test-arc-runners.yml to understand the existing structure.
- Step 2: Add timing to each job
Add to each job’s first step:
- name: Record start time
id: timing
run: echo "start=$(date +%s%N)" >> "$GITHUB_OUTPUT"
Add to each job’s last step:
- name: Report timing
if: always()
run: |
END=$(date +%s%N)
START=${{ steps.timing.outputs.start }}
DURATION_MS=$(( (END - START) / 1000000 ))
echo "### ${{ github.job }} completed in ${DURATION_MS}ms" >> "$GITHUB_STEP_SUMMARY"
- Step 3: Validate workflow syntax
yq '.' .github/workflows/test-arc-runners.yml > /dev/null
Expected: No errors
- Step 4: Commit
git add .github/workflows/test-arc-runners.yml
git commit -m "ci(runners): add timing instrumentation to soak tests
Each job now reports duration in step summary for benchmark tracking.
Refs: #212"
Completion metric: Each job in test-arc-runners.yml has start/end timing.
Regression gate: grep -c 'Record start time' .github/workflows/test-arc-runners.yml matches job count.
Task Group E: Substrate Hardening (Phase 2)
Task 10: Workspace Hygiene Script
Files:
-
Create:
scripts/runner-workspace-cleanup.sh -
Modify:
Justfile -
Step 1: Write the cleanup script
#!/usr/bin/env bash
# scripts/runner-workspace-cleanup.sh
# Recover from stale runner workspace checkouts
# Usage: runner-workspace-cleanup.sh [--dry-run]
set -euo pipefail
DRY_RUN="${1:-}"
WORKSPACE_ROOT="${RUNNER_WORKSPACE:-/home/runner}"
STALE_THRESHOLD_HOURS="${STALE_THRESHOLD_HOURS:-6}"
echo "=== Runner Workspace Cleanup ==="
echo "Root: $WORKSPACE_ROOT"
echo "Stale threshold: ${STALE_THRESHOLD_HOURS}h"
CLEANED=0
ERRORS=0
# Find directories older than threshold
while IFS= read -r -d '' dir; do
AGE_HOURS=$(( ($(date +%s) - $(stat -c %Y "$dir" 2>/dev/null || stat -f %m "$dir" 2>/dev/null || echo 0)) / 3600 ))
if [ "$AGE_HOURS" -gt "$STALE_THRESHOLD_HOURS" ]; then
echo "STALE: $dir (${AGE_HOURS}h old)"
if [ "$DRY_RUN" != "--dry-run" ]; then
if rm -rf "$dir" 2>/dev/null; then
CLEANED=$((CLEANED + 1))
else
ERRORS=$((ERRORS + 1))
echo "ERROR: failed to remove $dir" >&2
fi
else
echo " (dry run, would remove)"
fi
fi
done < <(find "$WORKSPACE_ROOT" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
echo "=== Summary: cleaned=$CLEANED errors=$ERRORS ==="
exit $ERRORS
- Step 2: Add Justfile recipe
Add to Justfile:
# Runner workspace cleanup (dry-run by default)
runner-cleanup *ARGS:
./scripts/runner-workspace-cleanup.sh {{ ARGS }}
- Step 3: Make executable and validate
chmod +x scripts/runner-workspace-cleanup.sh
shellcheck scripts/runner-workspace-cleanup.sh
- Step 4: Commit
git add scripts/runner-workspace-cleanup.sh Justfile
git commit -m "feat(runners): add workspace hygiene and stale checkout recovery
Cleans runner workspaces older than configurable threshold.
Supports --dry-run for safe inspection.
Refs: #213"
Completion metric: Cleanup script exists, passes shellcheck, has dry-run mode.
Regression gate: shellcheck scripts/runner-workspace-cleanup.sh && echo PASS
Task 11: Canary Enrollment Check Script
Files:
-
Create:
scripts/benchmark/canary-enrollment-check.sh -
Step 1: Write the enrollment validation script
#!/usr/bin/env bash
# scripts/benchmark/canary-enrollment-check.sh
# Validate that a canary repo can reach GloriousFlywheel runners
# Usage: canary-enrollment-check.sh <owner/repo> <runner-label>
set -euo pipefail
REPO="${1:?Usage: canary-enrollment-check.sh <owner/repo> <runner-label>}"
RUNNER_LABEL="${2:-tinyland-nix}"
echo "=== Canary Enrollment Check ==="
echo "Repo: $REPO"
echo "Runner: $RUNNER_LABEL"
# Check 1: Repo exists and is accessible
if ! gh repo view "$REPO" --json name -q '.name' &>/dev/null; then
echo "FAIL: Cannot access repo $REPO"
exit 1
fi
echo "PASS: Repo accessible"
# Check 2: Runner label is available (check recent workflow runs)
RECENT_RUNS=$(gh api "repos/$REPO/actions/runs?per_page=5" --jq '.workflow_runs | length' 2>/dev/null || echo 0)
echo "INFO: Recent workflow runs: $RECENT_RUNS"
# Check 3: Self-hosted runners are visible to the repo
RUNNER_COUNT=$(gh api "repos/$REPO/actions/runners?per_page=100" --jq '.total_count' 2>/dev/null || echo "N/A (may need org-level check)")
echo "INFO: Repo-visible runners: $RUNNER_COUNT"
# Check 4: Org-level runner groups (if applicable)
ORG=$(echo "$REPO" | cut -d/ -f1)
if gh api "orgs/$ORG/actions/runner-groups" --jq '.total_count' &>/dev/null 2>&1; then
ORG_GROUPS=$(gh api "orgs/$ORG/actions/runner-groups" --jq '.total_count')
echo "INFO: Org runner groups: $ORG_GROUPS"
fi
echo "=== Enrollment check complete ==="
- Step 2: Commit
chmod +x scripts/benchmark/canary-enrollment-check.sh
git add scripts/benchmark/canary-enrollment-check.sh
git commit -m "feat(canary): add enrollment validation script
Checks repo access, runner visibility, and org runner groups.
Refs: #210, #212"
Completion metric: Script validates canary enrollment state for a given repo.
Regression gate: shellcheck scripts/benchmark/canary-enrollment-check.sh && echo PASS
Task Group F: Canary Enrollment Prep (Phase 2-3)
Task 12: Add Canary Scale Sets to ARC Config
Files:
-
Modify:
tofu/stacks/arc-runners/honey.tfvars -
Step 1: Read current honey.tfvars to understand format
Read tofu/stacks/arc-runners/honey.tfvars and tofu/stacks/arc-runners/variables.tf to find the extra_runner_sets variable structure.
- Step 2: Add cross-org canary scale sets
Add to honey.tfvars in the extra_runner_sets map:
# Cross-org canary: Jesssullivan/cmux (proves multi-org enrollment)
# This scale set is scoped to the Jesssullivan GitHub user, not tinyland-inc
cmux-nix = {
github_config_url = "https://github.com/Jesssullivan"
runner_group = "default"
min_runners = 0
max_runners = 2
cpu_request = "2"
cpu_limit = "4"
memory_request = "4Gi"
memory_limit = "8Gi"
runner_image = "" # uses default
storage_class = ""
node_selector = {}
labels = ["cmux-nix"]
}
- Step 3: Validate tofu format
cd tofu/stacks/arc-runners && tofu fmt -check && tofu validate
Expected: No errors
- Step 4: Plan to verify no destructive changes
just tofu-plan arc-runners
Review the plan output to ensure only additive changes.
- Step 5: Commit
git add tofu/stacks/arc-runners/honey.tfvars
git commit -m "feat(runners): add cross-org canary scale set for cmux
Proves multi-org enrollment: Jesssullivan user-scoped runner pool
alongside tinyland-inc org-scoped pools on the same cluster.
Refs: #210, #212, TIN-129"
Completion metric: Canary scale set added, tofu validate passes, tofu plan shows additive only.
Regression gate: cd tofu/stacks/arc-runners && tofu validate
Task Group G: Adoption Surfaces (Phase 3)
Task 13: Adoption Quickstart Guide
Files:
-
Create:
docs/guides/adoption-quickstart.md -
Modify:
docs/getting-started-guide.md -
Step 1: Write the adoption quickstart
---
title: Adoption Quickstart
order: 1
---
# Adoption Quickstart
Deploy GloriousFlywheel on your cluster and enroll your first repo in 4 steps.
## Prerequisites
- A Kubernetes cluster (RKE2, EKS, GKE, k3s, etc.)
- `kubectl` configured for your cluster
- OpenTofu >= 1.6.0
- A GitHub App (for GitHub adapter) or runner registration token (GitLab/Forgejo)
## Step 1: Deploy Core Substrate
Clone the repo and deploy the ARC controller:
git clone https://github.com/tinyland-inc/GloriousFlywheel.git
cd GloriousFlywheel
# Copy and edit the example config
cp config/organization.example.yaml config/organization.yaml
# Edit with your cluster details
# Deploy the ARC controller (one per cluster)
just tofu-init arc-runners
just tofu-plan arc-runners
just tofu-apply arc-runners
## Step 2: Enroll Your Forge
### GitHub (Primary)
1. Create a GitHub App at your org level
2. Install it on the repos or org you want to serve
3. Set the app credentials in your `.env`:
GITHUB_APP_ID=...
GITHUB_APP_INSTALLATION_ID=...
GITHUB_APP_PRIVATE_KEY_PATH=...
4. Configure `honey.tfvars` (or your cluster's tfvars) with your org URL:
github_config_url = "https://github.com/your-org"
### GitLab (Compatibility)
See [Forge Adapter Matrix](../architecture/forge-adapter-matrix.md#gitlab-adapter-compatibility)
### Forgejo/Codeberg (Proof Path)
See [Forge Adapter Matrix](../architecture/forge-adapter-matrix.md#forgejocodeberg-adapter-proof-path)
## Step 3: Choose Runner Pools
Default pools:
| Label | CPU/Mem | Use Case |
| -------------------- | ------- | ---------- |
| `your-org-docker` | 2/4Gi | General CI |
| `your-org-nix` | 4/8Gi | Nix builds |
| `your-org-nix-heavy` | 8/16Gi | Rust + Nix |
Configure in your tfvars `extra_runner_sets` map.
## Step 4: Attach Caches
### Attic (Nix binary cache)
just tofu-init attic
just tofu-plan attic
just tofu-apply attic
Runner pods automatically receive `ATTIC_SERVER` and `ATTIC_TOKEN` env vars.
### Bazel Remote Cache (optional)
Set `BAZEL_REMOTE_CACHE` in runner pod environment via tfvars.
## Step 5: Use In Workflows
In your repo's `.github/workflows/*.yml`:
jobs:
build:
runs-on: your-org-nix # or your-org-docker, your-org-nix-heavy
steps:
- uses: actions/checkout@v4
- run: nix build .#default
## Validating Enrollment
Run the enrollment check:
./scripts/benchmark/canary-enrollment-check.sh your-org/your-repo your-org-nix
## Measuring Performance
Run the benchmark harness:
./scripts/benchmark/runner-benchmark.sh your-org-nix nix-build
- Step 2: Update getting-started-guide.md
Add a prominent link at the top of docs/getting-started-guide.md:
> **New adopter?** Start with the [Adoption Quickstart](guides/adoption-quickstart.md).
> This guide covers internal development workflow.
- Step 3: Commit
git add docs/guides/adoption-quickstart.md docs/getting-started-guide.md
git commit -m "docs(guides): add adoption quickstart for new adopters
Deploy core → enroll forge → choose pools → attach caches → use in
workflows. Links to enrollment check and benchmark scripts.
Refs: #210, #211"
Completion metric: Quickstart covers all 5 steps. Getting-started links to it.
Regression gate: grep -q 'Adoption Quickstart' docs/getting-started-guide.md
Task 14: Minimal ARC Example
Files:
-
Create:
tofu/stacks/arc-runners/examples/minimal/main.tf -
Create:
tofu/stacks/arc-runners/examples/minimal/variables.tf -
Create:
tofu/stacks/arc-runners/examples/minimal/README.md -
Step 1: Create the example directory
mkdir -p tofu/stacks/arc-runners/examples/minimal
- Step 2: Write minimal main.tf
# tofu/stacks/arc-runners/examples/minimal/main.tf
# Minimal GloriousFlywheel ARC runner deployment example.
# Deploy this on any Kubernetes cluster to get GitHub Actions runners.
terraform {
required_version = ">= 1.6.0"
}
module "arc_controller" {
source = "../../../modules/arc-controller"
namespace = var.arc_system_namespace
controller_version = var.arc_controller_version
}
module "nix_runner" {
source = "../../../modules/arc-runner"
depends_on = [module.arc_controller]
namespace = var.arc_runner_namespace
github_config_url = var.github_config_url
github_app_id = var.github_app_id
github_app_installation_id = var.github_app_installation_id
github_app_private_key = var.github_app_private_key
runner_scale_set_name = "${var.org_prefix}-nix"
min_runners = 0
max_runners = 4
cpu_request = "4"
cpu_limit = "4"
memory_request = "8Gi"
memory_limit = "8Gi"
}
- Step 3: Write variables.tf
# tofu/stacks/arc-runners/examples/minimal/variables.tf
variable "github_config_url" {
description = "GitHub org or repo URL for runner registration"
type = string
}
variable "github_app_id" {
description = "GitHub App ID"
type = string
sensitive = true
}
variable "github_app_installation_id" {
description = "GitHub App Installation ID"
type = string
sensitive = true
}
variable "github_app_private_key" {
description = "GitHub App private key (PEM)"
type = string
sensitive = true
}
variable "org_prefix" {
description = "Prefix for runner scale set names (e.g., 'myorg')"
type = string
default = "gf"
}
variable "arc_system_namespace" {
description = "Namespace for ARC controller"
type = string
default = "arc-systems"
}
variable "arc_runner_namespace" {
description = "Namespace for runner pods"
type = string
default = "arc-runners"
}
variable "arc_controller_version" {
description = "ARC Helm chart version"
type = string
default = "0.14.0"
}
- Step 4: Write README
# Minimal ARC Runner Example
Deploy GitHub Actions self-hosted runners on any Kubernetes cluster.
## Usage
cp terraform.tfvars.example terraform.tfvars
# Edit with your GitHub App credentials
tofu init
tofu plan
tofu apply
## What This Deploys
- ARC controller (one per cluster)
- One `nix` runner scale set (0-4 runners, 4 CPU / 8Gi each)
## Customizing
- Add more runner sets by duplicating the `nix_runner` module block
- Change resource limits in the module parameters
- See the full stack at `tofu/stacks/arc-runners/` for all options
- Step 5: Validate and commit
cd tofu/stacks/arc-runners/examples/minimal && tofu fmt -check
cd ~/git/GloriousFlywheel
git add tofu/stacks/arc-runners/examples/
git commit -m "feat(tofu): add minimal ARC runner deployment example
Self-contained example for new adopters. Deploys ARC controller
plus one nix runner scale set on any Kubernetes cluster.
Refs: #210"
Completion metric: Example validates with tofu fmt -check. README explains usage.
Regression gate: cd tofu/stacks/arc-runners/examples/minimal && tofu fmt -check
Task Group H: Cross-Org Canary Proofs (Phase 3)
Task 15: Platform Proof Workflow for Cross-Org
Files:
-
Modify:
.github/workflows/platform-proof.yml -
Step 1: Read current platform-proof.yml
Read the full workflow to understand its structure.
- Step 2: Add cross-org proof job
Add a new job that validates cross-org runner access:
cross-org-proof:
name: Cross-org runner proof
runs-on: tinyland-nix
if: github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- name: Verify runner identity
run: |
echo "Runner: $(hostname)"
echo "Org: ${GITHUB_REPOSITORY_OWNER}"
echo "Repo: ${GITHUB_REPOSITORY}"
- name: Verify cache endpoints
run: |
echo "Attic: ${ATTIC_SERVER:-not set}"
echo "Bazel: ${BAZEL_REMOTE_CACHE:-not set}"
[ -n "${ATTIC_SERVER:-}" ] && echo "PASS: Attic reachable" || echo "WARN: No Attic"
- name: Run enrollment check
run: |
./scripts/benchmark/canary-enrollment-check.sh "$GITHUB_REPOSITORY" tinyland-nix
- Step 3: Commit
git add .github/workflows/platform-proof.yml
git commit -m "ci(proof): add cross-org runner proof job
Validates runner identity, cache endpoints, and enrollment state
from inside a running workflow. Manual dispatch only.
Refs: #212, TIN-129"
Completion metric: Platform proof workflow includes cross-org validation job.
Regression gate: yq '.jobs.cross-org-proof' .github/workflows/platform-proof.yml | grep -q 'runs-on'
Task Group I: Release & Publication (Phase 4)
Task 16: First Tagged Release
Files:
-
Modify:
CHANGELOG.md -
Step 1: Read current CHANGELOG.md structure
Read the first 50 lines of CHANGELOG.md to understand its format.
- Step 2: Add v0.1.0 release section
Add below the [Unreleased] section:
## [0.1.0] - 2026-06-14
### Added
- 4-layer platform architecture: FOSS core, forge adapters, control plane, compat kit
- Forge adapter matrix with maturity levels (GitHub primary, GitLab compat, Forgejo proof)
- Multi-org enrollment model (4 dimensions: forge scope, tenant, pool, cache plane)
- Benchmark harness and scorecard for runner and cache performance
- Cross-org canary proofs (XoxdWM, MassageIthaca, cmux)
- Adoption quickstart guide
- Minimal ARC runner example for new adopters
- Release baseline with tag plan and changelog discipline
- Runner workspace hygiene and stale checkout recovery
- Canary enrollment validation script
### Changed
- Getting started guide updated to reference adoption quickstart
- Public docs reframed around 4-layer architecture
- Bzlmod/overlay language moved to compatibility kit scope
- FlakeHub classified as publication-only, not runtime
### Fixed
- Attic endpoint truth reconciled across docs and runtime
- test-arc-runners timing instrumentation added
- Step 3: Commit
git add CHANGELOG.md
git commit -m "docs: prepare CHANGELOG for v0.1.0 longevity sprint release
Refs: #215, TIN-149"
- Step 4: Tag (after all Phase 4 work merges)
git tag -a v0.1.0 -m "v0.1.0: Longevity sprint baseline
4-layer architecture, benchmark harness, cross-org canary proofs,
adoption quickstart, release baseline."
git push origin v0.1.0
Completion metric: CHANGELOG has v0.1.0 section. Tag exists after sprint completion.
Regression gate: grep -q '0.1.0' CHANGELOG.md
Task Group J: Control Plane & Retrospective (Phase 4)
Task 17: Control Plane Roadmap Note
Files:
-
Create:
docs/architecture/control-plane-roadmap.md -
Step 1: Write the control plane roadmap
---
title: Control Plane Roadmap
order: 5
---
# Control Plane Roadmap
The managed control plane is a future SaaS layer above the FOSS core substrate.
## What It Adds
- Fleet enrollment UX (add/remove repos, orgs, runner pools via web UI)
- Tenant and pool management (multi-org billing, resource quotas)
- Usage reporting and cost visibility (runner-minutes, cache hit rates, trend)
- Cache and runner observability (dashboards, alerts, diagnostics)
- Policy packs (scheduling rules, placement constraints, approval gates)
- Hosted control-plane APIs (REST/gRPC for enterprise fleet management)
## What It Does Not Replace
The control plane does not replace the FOSS core for:
- Deploying the base runner and cache platform
- Bootstrapping a cluster
- Using Attic or Bazel cache
- Running GitHub ARC on a self-hosted cluster
Adopters can run the full FOSS core without the managed control plane.
## Current State
The existing runner-dashboard app (SvelteKit 5 + WebAuthn) provides:
- Runner listing and status
- Cache health monitoring
- GitOps configuration view
This is the seed for the control plane. The next evolution adds:
- Multi-tenant views (tenant sees only their pools and usage)
- Enrollment workflows (guided forge adapter setup)
- Observability dashboards (Prometheus metrics, Attic cache analytics)
## Implementation Approach
Build on the existing dashboard rather than starting a separate service.
The dashboard already has:
- WebAuthn auth
- Role-based access
- PostgreSQL backend
- REST API (12 endpoints via MCP server)
Add tenant awareness, enrollment flows, and observability views as
dashboard features, not separate microservices.
## Timeline
Not in scope for the current longevity sprint (Apr-Jun 2026).
Target: design phase in Q3 2026, first tenant features in Q4 2026.
- Step 2: Commit
git add docs/architecture/control-plane-roadmap.md
git commit -m "docs(arch): add control plane roadmap
SaaS layer above FOSS core: fleet UX, tenant management,
observability, policy packs. Not required for base platform.
Refs: #211, TIN-129"
Completion metric: Control plane roadmap exists, clearly separates SaaS from FOSS core.
Regression gate: grep -q 'does not replace' docs/architecture/control-plane-roadmap.md
Task 18: Sprint Retrospective Template
Files:
-
Create:
docs/research/gloriousflywheel-sprint-retrospective-2026-06.md -
Step 1: Write the retrospective template
---
title: GloriousFlywheel Longevity Sprint Retrospective
---
# GloriousFlywheel Longevity Sprint Retrospective
Sprint: 2026-04-19 to 2026-06-14
## What Is Now True
- [ ] FOSS core is clear: cache, runners, operator tooling, local-first deploy
- [ ] GitHub ARC is primary and measured
- [ ] Attic and Bazel cache are working acceleration layers with measurements
- [ ] Downstream adoption moved beyond proof-of-life into repeatable canary kit
- [ ] Compatibility story is explicit and narrow
- [ ] SaaS control plane is defined above FOSS core, not tangled into bootstrap
## Benchmark Evidence
| Metric | Target | Actual | Status |
| -------------------- | ------ | ------ | ------ |
| Cold start (nix) | < 60s | TBD | |
| Cold start (docker) | < 30s | TBD | |
| Queue latency | < 15s | TBD | |
| Nix cache hit rate | > 80% | TBD | |
| Bazel cache hit rate | > 70% | TBD | |
## Canary Results
| Repo | Org | Forge | Enrolled | Benchmark Run | Status |
| ------------- | ------------ | ------ | -------- | ------------- | ------ |
| XoxdWM | tinyland-inc | GitHub | [ ] | [ ] | |
| MassageIthaca | tinyland-inc | GitHub | [ ] | [ ] | |
| cmux | Jesssullivan | GitHub | [ ] | [ ] | |
## What Remains Compatibility-Only
- Bzlmod overlay kit (lives in bzl-cross-repo)
- GitLab runner fleet (Helm chart validates, no live fleet)
- Forgejo runner fleet (one proof path, not production)
## What Moves Next
- Control plane tenant features (Q3 2026)
- Broader canary rollout (scheduling-kit, acuity-middleware, lab)
- SOPS/age secrets migration
- MCP server wiring into claude code
- Step 2: Commit
git add docs/research/gloriousflywheel-sprint-retrospective-2026-06.md
git commit -m "docs: add sprint retrospective template
Scorecard for longevity sprint exit criteria and benchmark evidence.
Refs: #212"
Completion metric: Retrospective template exists with checkboxes for all exit criteria.
Regression gate: grep -c '\[ \]' docs/research/gloriousflywheel-sprint-retrospective-2026-06.md returns at least 10.
Validation Summary
Per-Phase Regression Gates
Phase 1 exit (all must pass):
test -f docs/architecture/platform-layers.md
test -f docs/architecture/forge-adapter-matrix.md
test -f docs/architecture/enrollment-model.md
test -f docs/compatibility-kit.md
test -f docs/release-baseline.md
grep -q 'platform-layers' docs/index.md
grep -q 'bzl-cross-repo' docs/compatibility-kit.md
Phase 2 exit (all must pass):
shellcheck scripts/benchmark/runner-benchmark.sh
shellcheck scripts/benchmark/parse-results.sh
shellcheck scripts/benchmark/canary-enrollment-check.sh
shellcheck scripts/runner-workspace-cleanup.sh
test -f .github/workflows/benchmark.yml
grep -c 'Record start time' .github/workflows/test-arc-runners.yml
Phase 3 exit (all must pass):
test -f docs/guides/adoption-quickstart.md
test -d tofu/stacks/arc-runners/examples/minimal
grep -q 'Adoption Quickstart' docs/getting-started-guide.md
yq '.jobs.cross-org-proof' .github/workflows/platform-proof.yml
Phase 4 exit (all must pass):
grep -q '0.1.0' CHANGELOG.md
test -f docs/architecture/control-plane-roadmap.md
test -f docs/research/gloriousflywheel-sprint-retrospective-2026-06.md
Full Sprint Validation
# Run all regression gates
just check # existing validation
# Plus the phase gates above