Contributor Onboarding
On this page
This page is the starting point for new contributors. It does not duplicate the existing developer documentation; it indexes it in the order you should read it.
TL;DR
-
Get the devstack running: DevStack Guide.
-
Read CONTRIBUTING.adoc (issue → branch → MR → close).
-
Pick a
~good-first-issuefrom the issue tracker. -
Open an MR following the developer guide; the pre-push hook gates fmt + clippy + tests.
-
Get one approval from a
CODEOWNERSreviewer; auto-merge.
That’s the whole loop.
Prerequisites
| Tool | Why |
|---|---|
Rust 1.97.0 (pinned by |
Workspace edition is 2024; all 66 workspace members compile against |
Docker + Compose v2 |
Devstack runs in containers: Postgres, RabbitMQ, Keycloak, Garage S3, the CRAIG services, and the web BFF |
|
Issue/MR/epic operations. Authenticate via |
Node.js 18+ |
Antora docs build; Playwright E2E suite |
|
|
DevStack Guide has the full environment setup walkthrough including Keycloak realm bootstrap and seeded test users.
What to read, in order
-
Why CRAIG? — system context: this is a CCWIS (Comprehensive Child Welfare Information System) for the State of Georgia. Knowing the user (caseworker, supervisor, foster parent, partner agency) is more important than knowing the architecture.
-
CONTRIBUTING.adoc — non-negotiable workflow: how issues are filed, how MRs flow through the pre-push hook, when to update plan status, how to close issues post-merge. Authoritative for both human and AI agent contributors.
-
Developer Guide — environment, project structure, how-to guides for adding endpoints, services, and tests.
-
Architecture — the system shape: the REST API services, the BFF, the message broker topology (ADR-003), the rules-engine integration (ADR-006), the shared per-service
reqwest::Client(ADR-014), and the fail-closed encryption posture (ADR-020). -
The
coding-conventionsstandard — Rust idioms enforced in CI:#[forbid(unsafe_code)],clippy -D warnings,tokioruntime,sqlxfor SQL,axumfor HTTP. -
Testing Reference (CRAIG) — what the test pyramid looks like, what
cargo nextest run --workspaceruns, when to use Playwright E2E vs. Rust integration vs. unit. -
Glossary — domain vocabulary (CCWIS, AFCARS, NCANDS, ICWA, CICC, IV-E). Read this before your first MR — getting the names right matters in this domain.
That’s enough to make a competent first MR. Everything else is reference material to consult as needed.
Picking your first issue
Issues labeled ~good-first-issue are scoped to:
-
Single-file or single-module changes
-
No cross-service coordination required
-
Acceptance criteria fully spelled out
-
Test coverage path clear (existing test scaffolding can be extended)
Browse the good-first-issue queue. If you don’t see one that fits, file a chore: issue describing what you’d like to work on and ask a maintainer to label it before starting.
When you pick one up:
-
Assign yourself before starting work (one assignee per issue — coordinate if it’s already taken).
-
Branch off
main:feature/short-description. -
Implement; the MR Completion Protocol in CONTRIBUTING.adoc is the ground truth for the rest.
Where to ask questions
-
Architecture questions: file an issue labeled
~questionor open a draft MR withDraft:prefix and ask in the description. -
Design decisions: read the ADR index first; if the decision isn’t recorded, propose one — see Writing ADRs.
-
Workflow questions: CONTRIBUTING.adoc is authoritative; it mirrors the
gitlab-workflowstandard for the CLI/agent perspective. -
Domain questions (CCWIS terminology, federal compliance, jurisdiction-specific rules): Glossary + Federal Requirements cover most. Rule Set Patterns and Domain Knowledge explains the JDM-ruleset pattern.
Common gotchas
These have tripped up contributors more than once. Read Known Issues & Lessons Learned for the full list; the ones below are the headline items.
-
Linux:
host.docker.internalneeds an/etc/hostsentry. Docker Desktop (macOS/Windows) provides the name automatically; the Docker daemon on Linux does not. The devstack’s browser-facing URLs (Keycloak’s issuer, craig-web’s redirect URI) use it so one config serves host browsers and Playwright-in-Docker alike — without127.0.0.1 host.docker.internalin/etc/hosts, host-side logins and CLI calls fail with could-not-resolve errors. Containers are unaffected (compose maps the name viahost-gatewaywhere a container needs it). One-time fix:echo '127.0.0.1 host.docker.internal' | sudo tee -a /etc/hosts. See also the UFW note in Local Development — that is the other Linux networking gotcha (DNS resolves, TCP blocked). -
Pre-push hook is mandatory.
git config core.hooksPath .githooksis the most common skipped step. It runs fmt, clippy, tests, and the 8-question reflection checklist before the push lands. Don’t bypass with--no-verify; if a hook fails, fix the underlying issue. -
Migrations are additive only. Adding a column or a table is fine; renaming or dropping requires a two-step deprecation. Multiple MRs touching the same service must use non-colliding migration timestamps — check the existing
services/<name>/migrations/dir before stamping yours. -
Plan status tracking is per-step, not end-of-session. Sessions can end abruptly. Update the
== Statustable after each step lands; that is what lets the next agent or human pick up without guessing. -
CLAUDE.mdstays lean — no per-plan history rows. When a plan lands, we add its name once to the Completed-Plans list and nothing more. The archived plan bodies underplans/archive/plusCHANGELOG.adocare the historical record; duplicating that history (per-MR SHAs, phase-status tables, implementation blow-by-blow) intoCLAUDE.mdadds no information and crowds out the rules that must actually be read. Never append a detailed status row at archive time. -
The wildcard audit subscriber is real. Per ADR-003, every
.event.topic publishes to a wildcard queue captured by craig-security’s audit subscriber. New events automatically flow through; you don’t need to wire up an audit consumer for every event you publish. -
claims.subis a UUID, not a username. API handlers storeclaims.subfor actor identity; the web UI usespreferred_usernamefor display. Don’t confuse the two.
What we optimise for
Maintainability and code quality come first. When you have a choice between a quick fix that leaves the logic in the wrong place and a proper refactor that puts it in the right module, we take the refactor — even when there is no user-visible behaviour change. Clean module boundaries, clear names, and honest structure are worth an MR on their own.
Two consequences for how you scope work:
-
No fig-leaf seams. Don’t introduce a new function or module that merely looks like a boundary while the real logic stays tangled in the wrong place. Do the actual extraction.
-
Don’t scope-down to hit an estimate. If the proper refactor is larger than an optimistic estimate suggested, that’s a signal the estimate was low — not a reason to ship the smaller, worse version. When a task sits between a scope-limited MR and a larger proper-refactor MR, raise both options and pick the one that leaves the code more maintainable.
Service ownership
The CODEOWNERS file at the repo root maps each path to its owner(s). For the current single-maintainer phase, every path is owned by @bitskrieg. When the team grows, see Service Ownership for the escalation model.
After your first MR
Once your first MR merges, you’ll have learned more than this page can teach. From here:
-
Browse the completed plans archive for examples of multi-step features.
-
Pick up a
~P3-lowissue without the~good-first-issuelabel. -
If you see a recurring pattern that isn’t documented, propose an ADR.
Welcome to CRAIG.