Skip to main content

Stage 0: workspace quality gate

Stage 0 is Loom's pattern for catching breakage early. This workflow runs locally to validate your workspace before changes reach CI or other consumers. It produces deterministic, pointer-first artifacts so you can jump directly to the failing unit instead of scrolling through logs.

loom run --local --workflow .loom/workflow.yml

This page walks through the actual Stage 0 workflow used in the Loom repository itself.

Prerequisites

RequirementWhy
Loom installedRuns the workflow via loom run --local. See CLI → run.
Docker availableThe workflow builds and runs Docker images. The Docker daemon must be reachable.
Linux targetLocal execution targets linux in the current release. On macOS or Windows, run inside a Linux environment (VM, container, or CI runner).

The workflow

This is the canonical .loom/workflow.yml from the Loom repository:

version: v1

stages:
- deps
- ci

default:
cache:
- name: pnpm
key:
prefix: loom-cache
files:
- pnpm-lock.yaml
paths:
- .pnpm-store
policy: pull-push
when: always
- name: go
key:
prefix: loom-cache
files:
- go.work
- go.work.sum
- apps/**/go.sum
- libs/**/go.sum
paths:
- .go
policy: pull-push
when: always
variables:
PNPM_STORE_DIR: .pnpm-store
GOPATH: .go
GOCACHE: $GOPATH/cache
GOMODCACHE: $GOPATH/pkg/mod
target: linux

build-nix-image:
stage: deps
target: linux
image:
name: loom:nix-local
build:
context: .
dockerfile: nix/Dockerfile
cache: []
script:
- echo "built by loom"

test-services:
stage: deps
target: linux
cache: []
image: postgres:16
secrets:
POSTGRES_PASSWORD:
ref: keepass://beepbeepgo/loom-platform/loom#services/test/db:password
file: false
services:
- name: postgres:16
variables:
POSTGRES_DB: testdb
POSTGRES_USER: runner
POSTGRES_HOST_AUTH_METHOD: trust
script:
- >-
i=0; until pg_isready -h postgres -U runner -d testdb;
do i=$((i+1)); [ "$i" -ge 30 ] && echo "postgres not ready after 30s" && exit 1; sleep 1; done
- psql -h postgres -U runner -d testdb -c "SELECT 1;"

install-deps:
stage: ci
image: loom:nix-local
variables:
PNPM_STORE_DIR: .pnpm-store
script:
- pnpm i --frozen-lockfile
- pnpm nx run-many -t go-deps-verify

check-task:
stage: ci
image: loom:nix-local
needs:
- install-deps
variables:
PNPM_STORE_DIR: .pnpm-store
cache:
- name: pnpm
policy: pull
- name: go
policy: pull
script:
- pnpm i --frozen-lockfile
- task check

check-pnpm:
stage: ci
image: loom:nix-local
needs:
- install-deps
variables:
PNPM_STORE_DIR: .pnpm-store
cache:
- name: pnpm
policy: pull
- name: go
policy: pull
script:
- pnpm i --frozen-lockfile
- pnpm nx run loom-platform:check

What each job does

Stage: deps

JobPurpose
build-nix-imageBuilds the loom:nix-local Docker image containing the workspace's deterministic Nix toolchain. Uses the image.build mapping form to build the image before execution. Disables cache (cache: []) since image builds don't benefit from workspace caching.
test-servicesValidates sidecar service infrastructure by running a Postgres health check. Uses services to start a Postgres container alongside the job, and secrets to resolve the database password from a KeePass vault at runtime.

Stage: ci

JobPurpose
install-depsInstalls pnpm dependencies and verifies Go module checksums. Runs in the Nix toolchain image. Populates the shared cache that downstream jobs pull from.
check-taskRuns task check (the Taskfile quality gate). Depends on install-deps via needs. Pulls cache in read-only mode (policy: pull) to avoid redundant saves.
check-pnpmRuns the Nx check target for the workspace. Same dependency and cache strategy as check-task.

Features demonstrated

This workflow uses most of Loom's v1 feature set:

FeatureHow it's usedLearn more
Stagesdeps runs first, then ciSyntax → stages
Default configurationdefault sets shared cache, variables, and target for all jobsSyntax → default
Named multi-cacheTwo caches (pnpm, go) with independent keys and pathsCache
Cache policydeps jobs use pull-push; ci jobs use pull onlySyntax → cache:policy
Glob patterns in cache keysapps/**/go.sum and libs/**/go.sum match Go modules anywhere in the treeSyntax → cache:key:files
Image buildbuild-nix-image uses the image.build mapping to build before runningDocker provider
Servicestest-services runs Postgres as a sidecar containerSyntax → services
Secretstest-services resolves a password from KeePass at runtimeSecrets
DAG dependenciescheck-task and check-pnpm use needs to depend on install-depsSyntax → needs
Variable referencesGOCACHE: $GOPATH/cache references another variableVariables
Provider routingbuild-nix-image with no image runs on host; others use DockerProviders

Caching strategy

The workflow is designed so repeat runs are significantly faster than cold runs:

CacheKey derived fromPaths cachedStrategy
pnpmpnpm-lock.yaml.pnpm-storeDependency changes invalidate. deps and install-deps populate; ci jobs pull only.
gogo.work, go.work.sum, **/go.sum.goGo module changes invalidate. Same populate/pull pattern.

The ci stage jobs set policy: pull to avoid redundant cache saves — the cache was already populated by install-deps. This reduces I/O on parallel jobs.

If you change inputs that should invalidate the cache (install flags, toolchain versions, additional build outputs), update cache.key.files accordingly.

Why this pattern matters

Stage 0 catches breakage before it propagates. Compared to ad-hoc local scripts:

Ad-hoc scriptsStage 0 with Loom
Different on every developer's machineRepeatable workflow checked into the repo
Failures produce raw console outputFailures produce structured artifacts (receipts, manifests, events)
Debugging means scrolling through logsDebugging means following pointers to the exact failing step
Cache strategy is manual or nonexistentCache strategy is declarative and reproducible

Diagnosing failures

When a Stage 0 run fails, follow pointers — not logs:

  1. Pipeline summary — check pipeline/summary.json for overall status and exit code.
  2. Pipeline manifest — check pipeline/manifest.json to identify which job failed.
  3. Job manifest — check jobs/<job_id>/manifest.json for the failing step or system section.
  4. Failing events — open the events.jsonl file pointed to by the manifest for the specific failure details.

All artifacts are under .loom/.runtime/logs/<run_id>/.

For the full triage process, see:

Running the gate

Validate the workflow structure:

loom check

Run the full Stage 0 pipeline:

loom run --local --workflow .loom/workflow.yml

Planned

  • Remote runners — run the same workflow on remote infrastructure without bespoke wrappers.
  • Richer structured events — more granular step-level and system-section events for deeper diagnostics.

Next steps