GitHub-hosted CI
LeapView runs continuous integration on standard, ephemeral GitHub-hosted runners. GitHub owns runner provisioning, isolation, queueing, logs, permissions, cancellation, and cleanup. The repository owns the pinned toolchain setup, bounded remote caches, Taskfile commands, and validation tiers that define a valid change.
Every job starts from a clean Ubuntu image and loses its local filesystem when it completes. Correctness therefore cannot depend on an old checkout, Docker daemon, image, volume, or tool cache. This prevents accumulated runner state from making a later run fail or pass.
Trust boundary
Internal, external, and Dependabot pull requests use the same GitHub-hosted execution path.
The pull-request workflow grants only contents: read; it does not expose deployment or
registry credentials. Fork code never executes on a Flid-managed host.
GitHub Actions caches contain only reproducible dependency downloads, compiler outputs, browser binaries, Terraform providers, and BuildKit layers. They never contain credentials, runtime databases, Docker state, or qualification evidence. Cache restoration is a performance optimization: deleting every cache must still produce the same result.
Execution contract
Four Taskfile targets define the validation tiers:
task ci
task ci:pr
task ci:full
task ci:nightly
task ci is the local alias for the fast pull-request contract. task ci:pr prepares shared
inputs, runs the bounded Go and frontend lanes, and checks generated artifacts. task ci:full
adds desktop tests, static and selected race analysis, route QA, and deployment validation.
task ci:nightly adds dependency and vulnerability scans. task ci:local remains a
compatibility alias for the full current-machine contract.
GitHub Actions distributes those same Taskfile units across clean runners. Pull requests first run the existing ciplan dependency classifier against the tested
candidate, then run selected APIGen, Go package/application, frontend, docs/site, PostgreSQL,
dbt, and spatial validation lanes concurrently. Unknown and cross-cutting inputs, manual
dispatch, the ci:full label, and deterministic audit PRs run the full PR tier. The repository validation jobs execute task ci:prepare; the independent APIGen module
does not need generated or embedded application assets and skips that preparation. The merge queue
adds bounded overlap through task ci:full:extras:hosted; local and nightly full validation retain
the sequential task ci:full:extras contract, and the daily schedule also runs
task ci:nightly:extras. Local
composition remains available through the tier targets; the workflow does not duplicate individual
test commands or introduce a runner-specific container wrapper.
PostgreSQL conformance topology
The source-inventoried PostgreSQL lane uses the same runner locally and on GitHub. It runs up
to four Go packages concurrently. Each package gets one disposable, pinned PostgreSQL 18
server with its data on tmpfs; the testcontainers PostgreSQL module already disables fsync
for these containers. The wrapper passes a server URL and a per-container token to its test
binary; the harness verifies the token before it can create objects, and the wrapper
terminates the server after the package exits. The lane fails closed if PostgreSQL is unavailable.
Each test fixture creates a separate database, which is dropped along with its roles after the test. Tests within a package run serially because PostgreSQL roles are cluster-wide and qualification tests depend on their exact production names. Parallelism is across packages, not across tests sharing one server. TLS-specific and direct-container qualifications retain their own containers. The lane does not substitute PGlite or skip migrations; changing those behaviors would need separate conformance evidence.
Frontend validation has five isolated shards: core, reports, chat, data, and site.
Each hosted shard runs task ci:lane:frontend:shard SHARD=<name> on its own runner with a
180-second watchdog and at most one retry for a timeout, never for an assertion failure.
This bounds each independent suite without treating the cumulative runtime of five healthy
suites as a hang. Every selected shard must succeed for CI gate to pass. The gate requires successful
planning, checks candidate/run/attempt provenance, and rejects missing lane results or a
frontend matrix differing from the versioned plan. Unselected jobs must report skipped. Local task ci runs the
same bounded shards sequentially to avoid browser and bundler contention on a shared machine.
Toolchain and caches
.github/actions/setup-ci installs pinned Go, Node.js, Bun, and Task versions. Jobs opt
into the pinned Terraform and Playwright installations only when their validation requires
them. The action is shared by pull-request, merge, nightly, and production qualification jobs
so a toolchain change has one reviewable source.
The setup action uses separate GitHub Actions cache entries for:
- Go modules and compiler outputs, scoped by validation workload, runner platform, installed Go version, module manifests and pinned tool definitions;
- Bun downloads, keyed by the root and desktop lockfiles;
- the pinned Playwright Chromium build;
- Terraform providers, keyed by the deployment lockfiles.
Go validation uses the go-validation-v1 cache namespace and the stable workflow
job ID as its workload scope. Application, package and frontend
validation jobs reuse their respective entries across PR, merge and nightly runs;
full validation shares its scope between merge and nightly runs. Frontend matrix shards share their preparation scope. APIGen, security and
native packaging cannot win another validation workload's cache key. Native
packaging retains its separate setup-go cache.
The validation key covers root and nested go.mod/go.sum files, Taskfile.yml
and the shared setup action, including pinned SQLC and toolchain definitions.
There is no fallback to the old shared Go cache or another workload. Each successful
job may publish its own scope; a cache hit never skips preparation or validation.
Default-branch nightly runs can populate the scopes for subsequent candidates,
subject to GitHub's branch access rules. More scopes consume more cache storage;
eviction must remain a performance concern only.
Production and public-site image builds export BuildKit layers to independently scoped
GitHub Actions caches. LeapView does not cache node_modules, /var/lib/docker, mutable
worktrees, or application data. Exact keys are immutable, restore prefixes may seed a new
dependency set, and the package manager or build tool always validates restored content. The
main artifact workflow populates the default-branch Bun download cache so new pull requests do
not inherit an empty cache entry from image qualification.
Each job owns its checkout's installed dependencies. node:deps uses Task's run: once
mode so parallel and nested dependency paths await a single bun install --frozen-lockfile
in that Task invocation. A later invocation verifies the tree again; it does not trust a
cached executable marker. Download caches can be shared, but installed trees are never
shared between runners. Run independent local Task processes in separate worktrees rather
than allowing multiple installers to write the same checkout concurrently.
The repository currently works within GitHub's default 10 GB cache allowance. The intended operating limit is 50 GB or more so the independent Go, Bun, browser, Terraform, and BuildKit caches do not evict one another. Hitting the lower limit may reduce cache hits but cannot change validation behavior.
Workflow tiers
The pull-request workflow runs selected validation lanes on independent four-vCPU runners
and reports the stable required CI gate check. Docs/site changes use a dedicated contract
lane so embedded Go documentation tests remain covered when backend lanes are unselected. This prevents browser
and Go test contention and shortens wall-clock feedback without increasing per-job machine size.
For a native GitHub pull-request stack, only the top pull request runs those validation lanes.
Lower layers report a successful CI gate with a summary that validation is deferred to the
stack tip. The workflow listens for the stacked action. CI and security concurrency are scoped
to each PR ref: a new revision cancels obsolete runs for that PR while other stack layers finish
their own required checks. A stack-wide key can cancel lower-layer planning and aggregate gates
when the tip starts, leaving the stack unable to enter the merge queue. Standalone pull requests
use the selected plan; manual dispatch runs the complete PR tier.
Stack planning compares the cumulative candidate against its merge base with the fetched
default branch, including lower-layer changes. For stacks targeting another branch this
is a conservative broader diff. Label changes also trigger planning.
The main-branch ruleset must require GitHub's merge queue. A deferred lower-layer gate is feedback, not authorization to merge directly. The queue validates the exact candidate selected for main, whether that candidate is the complete stack or a contiguous prefix, by running the Go and frontend lanes plus the full extras. Merge-validation concurrency is scoped to the candidate ref: a rebuilt candidate cancels its obsolete run without making distinct queue candidates cancel one another. Full merge validation starts alongside the base lanes on a separate runner and prepares its own inputs. The always-running CI gate still requires every lane to succeed and the native desktop proof to succeed for the exact merge candidate. Local full validation remains sequential to avoid contention on a shared machine.
CI / Nightly validation also runs security scans in parallel. Post-merge artifact CI builds and pushes the
production image using a BuildKit cache, then qualifies its immutable digest on a second clean runner.
Splitting production build and qualification prevents build layers from consuming the local disk needed by the qualification journey. The digest passed between jobs is the only product identity boundary; qualification never falls back to a mutable tag or source build.
Operations
There is no persistent CI VM to patch, prune, or recover. Monitor queue time, cache hit rate, cache eviction, job duration, and GitHub service health. A cache outage or eviction should make a job slower, not fail it. If a clean GitHub runner lacks enough per-job CPU, memory, or disk, split independent work into jobs before introducing persistent infrastructure.