Contributing to Workcell
August 5, 2026 · View on GitHub
Workcell changes should preserve the runtime boundary first and developer ergonomics second.
Ground rules
- keep
runtime/,policy/,adapters/,verify/, andworkflows/in sync when a change touches shared contracts - do not widen trust silently
- document lower-assurance paths instead of implying parity
- sign every commit
- use feature branches and pull requests; do not push directly to
main - use ASD-STE100 Simplified Technical English Issue 9 for public prose
See docs/documentation-language.md for the project language rules.
First-time setup
Use the bootstrap helper:
./scripts/bootstrap-dev.sh
That script installs the common local toolchain, configures .githooks as the
repo hook path, and leaves you ready to run the local gates.
Prerequisites
Local development expects:
gitgodockershellcheckshfmtyamllintcodespellactionlintzizmorjq- Node.js and npm (the bootstrap helper enforces the repository-locked markdownlint runtime requirement)
cargo,rustfmt, andclippy
On macOS with Homebrew:
brew install go node shellcheck shfmt yamllint codespell actionlint zizmor jq
brew install --cask docker
rustup-init # installs cargo, rustfmt, clippy
For the real VM boundary path:
brew install colima
Commit signing
Every commit on main must be signed. Set up GPG or SSH signing before your
first contribution:
git config --global commit.gpgsign true
git config --global user.signingkey <your-key>
See GitHub's docs on signing commits for setup details.
Recommended workflow
-
Create a feature branch from
main. -
Bootstrap once if you have not already:
./scripts/bootstrap-dev.sh -
Make the change.
-
Run the fast local gate:
./scripts/dev-quick-check.sh -
Create a signed commit. Use Risk-Aware Commit Notation.
-
Before publication, run the full local gate:
./scripts/pre-merge.sh --profile pr-parity -
Publish a draft PR against
mainwith the repository wrapper:./scripts/repo-publish-pr.sh \ --branch feature-name \ --title "PR title" \ --body "Explain the change and its evidence." \ --commit-message "^D Describe the change (validation passes; user-visible documentation)" -
Post the standalone PR comment
@codex review. -
Fix or disposition each finding.
-
Resolve each review thread.
-
Repeat the Codex review after each push.
-
Require a clean Codex marker for the current head.
-
Mark the PR ready only after all required checks and reviews pass.
-
Check comments and review threads again immediately before merge.
The pre-commit hook blocks unrelated commits when stable provider pin bumps are
pending and points you at ./scripts/publish-provider-bump-pr.sh.
Good first contributions
Useful starter changes tend to be:
- quickstart, README, or manpage consistency fixes
- validation coverage for already-documented behavior
- scenario-gap closure that does not change the trust model
- adapter documentation and control-plane clarity improvements
If a change touches the boundary or policy model, read docs/invariants.md and docs/threat-model.md first.
Use GitHub Discussions for usage questions, open-ended design exploration, and operator workflow conversations. Use GitHub issues for confirmed bugs and concrete feature requests.
Commit messages
Use Risk-Aware Commit Notation:
<risk><intention> <description> (risk reason; case reason)
Risk symbols are ., ^, !, and @. They mean safe, validated, risky,
and broken.
Intention letters are F, B, R, and D. They mean feature, bug fix,
refactor, and documentation. Use an uppercase letter for a user-visible change.
Example:
^D Update release status (validation passes; user-visible documentation)
See the commit skill for the complete rules.
Validation levels
Fast local gate
./scripts/dev-quick-check.sh is the normal edit loop. It covers:
- shell lint and format checks (
shellcheck,shfmt) - Dockerfile lint via
hadolint - Go formatting (
gofmt -l),go vet ./..., andgo test ./... - Rust fmt, clippy, and tests inside
runtime/container/rust/ - Dead-code check (
scripts/check-dead-code.sh) - Public repo hygiene check (
scripts/check-public-repo-hygiene.sh) - Requirements coverage and operator-contract verification
For fuller repo validation without the entire pre-merge stack, use:
./scripts/build-and-test.sh
./scripts/build-and-test.sh --docker
The default path is host-native. --docker reruns repo validation inside the
validator container from a disposable snapshot of the current worktree.
Full local gate
./scripts/pre-merge.sh is the normal local parity entrypoint. It supports
three explicit profiles:
repo-core: repo-required deterministic checkspr-parity(default): the mirrored local subset of requiredmain-based PR workflows plus parity evidence generation for publicationrelease-preflight:pr-parityplus the extra mirrored release-facing hygiene lanes
The default pr-parity profile covers the shared mirrored workflow bodies:
- workflow lint and workflow-lane manifest verification
- PR shape
- validator-backed shared validate job
- docs parity
- container smoke
- runtime-image reproducibility on locally supported platforms
release-preflight adds the mirrored pin-hygiene lane. repo-core keeps the
smaller deterministic subset for repo-owned contract work.
Helpful flags:
./scripts/pre-merge.sh --allow-dirty
./scripts/pre-merge.sh --profile repo-core
./scripts/pre-merge.sh --profile release-preflight
./scripts/pre-merge.sh --profile repo-core --skip-repro
./scripts/pre-merge.sh --profile release-preflight --skip-release-bundle
./scripts/pre-merge.sh --rebuild-validator
--skip-repro is diagnostic-only for the non-publication profiles;
publication-grade pr-parity rejects it so emitted evidence cannot certify a
selected but unexecuted reproducibility lane.
For main-based PRs, ./scripts/repo-publish-pr.sh consumes the fresh
pr-parity evidence emitted by ./scripts/pre-merge.sh and refuses to publish
if that evidence does not match the tree being sent for review.
Pull requests
A good PR should:
- explain what changed and why
- call out any runtime or trust assumptions the change depends on
- note any lower-assurance modes introduced or widened
- update docs in the same change when behavior changes
- update CHANGELOG.md for user-visible changes
If you touch the boundary or policy model, link the relevant invariant or threat-model section in the PR description.
Security-sensitive issues
Do not open a public issue for:
- sandbox escapes
- secret exposure
- signing or provenance bypasses
- unexpected trust widening
Use the process in SECURITY.md.
Adding or changing adapters
Adapters should stay thin. A new or changed adapter should:
- map into the provider's native control plane
- avoid treating provider config as the primary boundary
- ship invariant checks with the adapter change
- update the provider matrix and adapter-control-plane docs
See workflows/adapter-porting.md for the porting checklist and docs/extending-adapters.md for worked examples (adding a credential type, extending an adapter) annotated with the invariants each step touches.
Package naming
For new internal Go packages, name by role rather than suffix:
canonicalpath, secretfile, transcript, injection. Avoid the
*util, *helper, *manager, *handler suffixes that the
Go style guide
flags as opaque — they hide what the package is for and become magnets
for unrelated helpers over time.
The repo carries some pre-existing *util packages (pathutil,
metadatautil, runtimeutil, colimautil) and corresponding binary
names (cmd/workcell-hostutil, cmd/workcell-runtimeutil,
cmd/workcell-citools, cmd/workcell-colimautil). These are not
retargeted for rename in place — the binary names appear in
policy/host-support-matrix.tsv, audit logs, install paths, and PR
review surfaces. The rule applies to new packages only.