Project
Contributing
Development setup, the PR bar and the review checklist.
CONTRIBUTING.mdOn this page
Thanks for your interest in SnowOps Labs - the flight simulator for platform engineers. This project thrives on community-contributed scenarios, platform modules, documentation, and code. This guide explains how to contribute and what to expect.
New here? The highest-leverage place to start is content - a new scenario or incident. Start with Your First Scenario and look for issues labelled good first issue.
Code of Conduct
By participating you agree to our Code of Conduct.
Sign-off (DCO)
SnowOps Labs uses the Developer Certificate of Origin - a lightweight,
no-paperwork way to certify you wrote (or have the right to submit) your
contribution. Just add a Signed-off-by line to each commit by committing with
-s:
git commit -s -m "your message"The DCO check in CI verifies every commit in a PR is signed off. That's the only agreement required, no CLA, no bot account, no click-through.
What you can contribute
| Type | Where it lives | Review owner |
|---|---|---|
| Scenarios | scenarios/ |
scenario maintainers |
| Platform modules | platform/<category>/<provider>/ |
platform maintainers |
| Incidents / learning / challenges | incidents/, learn/, challenges/ |
scenario maintainers |
| Documentation | docs/ |
docs maintainers |
| Engine / CLI / SDK | src/cmd/, src/internal/, src/pkg/, src/engine/ |
lead maintainer (see GOVERNANCE.md) |
Changes to the engine, the public SDK (pkg/), or the scenario schema require an
RFC first - a short markdown PR under docs/rfcs/ that a maintainer approves
before implementation. This keeps architectural direction coherent.
Development setup
End users clone the latest release (--branch stable) and download a matching
labctl with ./install.sh - see the README.
Contributors clone main and build labctl from source so they can test
their changes. You need Go 1.25+ and Node 22+. The steps are identical
on macOS, Linux, and Windows/WSL2: on Windows run them inside your WSL2
distro, in your Linux home, never native PowerShell.
Distro packages are often older than that (Ubuntu 24.04 ships Go 1.22 and an older Node). None of the fixes need sudo:
- Go: with Go 1.21 or later installed,
export GOTOOLCHAIN=autoand the build downloads the Go versionsrc/go.modasks for. - Node: unpack the Node 22 tarball from nodejs.org under your home and put
its
bin/first onPATH(or use a version manager such as nvm). - bats (for
make test-shell):npm install -g batswith that Node.
git clone https://github.com/sagar2395/snowopslabs.git
cd snowopslabs
# 1. Build the CLI, UI embedded (no committed binary). The Go module lives under
# src/; the root make targets delegate there and the binary lands at bin/labctl.
make cli-build
# 2. Run the gates. All four test layers are mandatory - see docs/TESTING.md.
make test # Go unit + shell (bats), race detector, coverage gates, test-check
make test-ui # vitest component tests
make test-e2e # playwright journeys
make lint # gofmt, golangci-lint, gosec, govulncheck, shellcheck,
# shfmt, the portability gate, and TypeScript strict
# 3. Bring up a local cluster + platform to test changes end to end. Use the CLI
# you just built - it drives the same loop end users run:
bin/labctl init # setup-tools + create cluster + install platform
bin/labctl scenario up observability-sreOther targets:
make cli-install # build labctl, install it over the one install.sh put in ~/.local/bin, and record this clone as the lab
make ui-dev # serve src/ui/dist live without rebuilding Go (LABCTL_UI_DIR)
make docs-check # the docs agree with the code and the website's manifest
make fmt # gofmt the treelabctl finds the lab in this order: --project-dir; the clone around the
working directory (it walks up to the directory with scenarios/ and
runtimes/); SNOWOPS_LAB_DIR; and the clone install.sh or make cli-install last
recorded in ~/.snowops/lab-dir. Inside your checkout your edits take effect without
reinstalling. Lab state (active scenarios, history, progress, snapshots) lives
in ~/.snowops/state/<cluster>/, outside every clone, so all of them see the
same cluster. main carries a -dev LAB_VERSION, and a release labctl
warns when pointed at it.
Repository layout
# User-facing content at the root - what a learner's clone runs
scenarios/ incidents/ declarative content
learn/ challenges/
platform/<cat>/<prov>/ install.sh / uninstall.sh / status.sh / values.yaml
runtimes/<profile>/ k3d | kind | incluster (+ runtimes/_lib shared helpers)
apps/<name>/ sample workloads
bootstrap/ tool installation
config/ versions.env, footprints.yaml, .env.example
docs/ PRODUCT, ROADMAP, TESTING, architecture/, adr/, runbooks/
install.sh installs the labctl release that matches LAB_VERSION
LAB_VERSION the release this checkout belongs to (-dev on main)
scripts/ footprint measurement, docs checks, release
# The labctl Go module - self-contained under src/
src/cmd/labctl/ entrypoint
src/internal/ implementation (cli, httpapi, service, run, store, …)
src/pkg/ public SDK: checks, scenario types, extension seam
src/ui/ React SPA, embedded into the binary
src/engine/ src/services/ app build/deploy scripts and shared services (shipped)
src/test/shell/ bats suites with kubectl/helm stubbedScenario and fault footprints
config/footprints.yaml holds the measured memory of the baseline lab, each
Helm release scenarios install, platform components and apps; a scenario's own
extra goes in its requirements.memory. The capacity check that decides
whether a scenario fits is built on them, so re-measure when you add or change
a scenario:
labctl init # a fresh lab, nothing active
scripts/measure-footprints.sh <scenario> # prints what the scenario addsReleasing
A release ships the binary and the content together; see RELEASING.md.
New to the repo? Work through R00 - Environment & Build once; it verifies your setup and shows you each gate biting.
Before you start: the traps
Every one of these was learned by something failing silently on a real cluster, and each is an afternoon you do not have to lose:
- Helm:
upgradeanduninstallboth ignore CRDs; most of a StatefulSet spec is immutable: R05. - Metrics, logs and traces: empty-selector fallbacks, Tempo's real port, Promtail relabelling, k6's units: R13.
- Writing a scenario: values ownership, chart pinning, checks that teach: scenario schema.
docs/AGENT-CONTEXT.md is the index: it says which single document answers which task, and lists every invariant in one place. It is written for AI agents, but it is the fastest orientation for a human too.
The golden rules (please read before a code/content PR)
- Cross-platform. Must run on macOS (Apple Silicon + Intel) and modern
Linux. No GNU-only shell flags (
grep -P,sed -iwithout a backup suffix,readlink -f,date -d), and no cgo - it breaks cross-compilation.make lint-shellenforces this automatically. - Go orchestrates, scripts do the work. Don't move shell/helm/kubectl logic into Go.
- Declarative content. Scenarios, faults, learning paths and checks are YAML + scripts, never hardcoded in Go.
- Idempotent everything.
helm upgrade --install,kubectl apply, safe re-activation. Interrupting an operation and re-running it must converge. - Every operation is cancellable and durable. Anything that shells out goes
through
internal/runwith a context, a timeout and a lock key. Never callexec.Command(...).Run()directly. - Every change ships its tests, and a bug fix starts with a failing test.
Go unit (table-driven, hermetic, ≥80%, cancellation covered), bats for
scripts with logic, contract tests for endpoints and commands, Vitest and
Playwright for UI. docs/TESTING.md says what each kind
of change needs. CI blocks a change that brings no test or leaves more than
15% of its lines untested; a change that needs none says why in a
Tests-Not-Needed: <reason>commit trailer. - Docs and runbook in the same PR. Update the relevant
docs/, add or update the runbook, and write an ADR if you made a notable decision. Documentation is the source of truth here - the code is expected to match it, andmake docs-checkgates the links and the pages the website publishes. - Comments follow the comment rule in CLAUDE.md: what the code does and why it is needed, never the story behind a change.
Changing Go code? docs/GO-CONVENTIONS.md covers the everyday choices: which layer owns the code, how to handle contexts and errors, the handler and command patterns, and the lint set. It ends with a pre-PR checklist.
Pull-request workflow
fork → branch → make your change → run lint + tests → commit with -s (DCO) →
open a PR → CI green → CODEOWNERS review → maintainer merge- Keep PRs focused and small where possible.
- Use Conventional Commits (
feat:,fix:,docs:,ci:,chore:). - Fill out the PR template (what/why/how-tested).
- CI must pass every gate: Go build/vet/test on macOS and Linux with the race detector, the per-package coverage gate, the changed-lines gate, test-check, golangci-lint, gosec, govulncheck, a fuzz smoke run, bats, shellcheck, the portability gate, TypeScript strict, vitest, Playwright, the license scan, and content validation. None of them are advisory.
- A maintainer or domain code owner reviews; the lead maintainer holds final merge authority on engine/SDK/schema changes (see GOVERNANCE.md).
Reporting bugs / requesting features
Use the issue templates. For security vulnerabilities, do not open a public issue - follow SECURITY.md.
Licensing of contributions
All contributions are licensed under Apache-2.0 (the same license as
the project); your Signed-off-by line certifies you have the right to submit
them under it (see DCO.md). New source files should carry an SPDX
header:
// SPDX-License-Identifier: Apache-2.0Thank you for helping build the open platform-engineering simulator. ✈️