Skip to content

Latest commit

 

History

History
434 lines (367 loc) · 22.4 KB

CONTRIBUTING.md

File metadata and controls

434 lines (367 loc) · 22.4 KB

Contributing

Registry Stack uses a maintainer-led governance model. Contributions, bug reports, questions, and review notes are welcome. Maintainers review incoming work according to project priorities, release risk, and available review capacity.

Contribution License

Registry Stack uses the Developer Certificate of Origin 1.1 for inbound contributions. By contributing, you certify that you have the right to submit the work under this repository's license and that the contribution can be included in the project.

Add a Signed-off-by trailer to every commit:

git commit -s

The trailer must use your real name or established project identity and an email address you are willing to associate with the contribution:

Signed-off-by: Name <name@example.com>

Pull requests are checked automatically for this trailer.

You may use coding agents or other automation while preparing a change. The person who commits, signs off, and submits the contribution is responsible for reviewing the result, running the relevant checks, and making sure the DCO certification is true.

Maintainer Workflow

  • Maintainers may merge their own pull requests when the change is scoped and the relevant checks have passed.
  • Every change reaches main through a pull request and the merge queue, including release, documentation, CI, and urgent fixes: the main ruleset has no bypass actors and branch protection applies to administrators.
  • CI remains the reference gate for normal pull requests.
  • The merge queue rebases each entry onto main itself before merging, so a pull request that shows BEHIND does not need a manual rebase for that reason alone. Rebase or force-push only to resolve a real conflict or to pick up a fix for red CI that main has since fixed.
  • Required human review should be enabled only when maintainer capacity can satisfy it reliably.

Review Standards

CI feedback budget

Ordinary pull-request and main CI should complete within ten minutes, measured from workflow creation until every selected synchronous check finishes. Include runner scheduling time. Record reruns from the original event as well as the latest attempt; a fast retry does not erase a failed or slow first attempt. Use the longest job and total job minutes to distinguish serial work from runner contention. The maintainers own this budget alongside the CI workflow. Rust test shards run at most six at a time to reduce competition with the longer product integration jobs; all selected shards still execute.

Post-classification fanout is capped at eighteen static matrix slots by making shorter and later-stage jobs wait for the existing Rust policy check. This leaves capacity for the longer product, native client, documentation, and security jobs to start. The Base Registry Engine contracts job takes six of those slots: its PostgreSQL journeys run as three shards and its adopter journeys in a lane of their own, which shortens the longest serial integration job. Their original path selectors remain in force and an explicit !cancelled() status check lets selected jobs run after a Rust policy failure or skip while still honoring workflow cancellation. The same guard requires successful change classification, so a classifier failure cannot release downstream jobs. This controls dependency ordering; GitHub Actions does not guarantee runner scheduling priority.

The required Rust workspace check and the CI result summary aggregate the same raw Rust job results in parallel. Both accept successful and path-skipped jobs and fail for failed or cancelled jobs, so the final summary does not need to wait for the intermediate aggregate runner.

Reporter-only jobs that read dependency results and run one bounded Bash or Python step use ubuntu-slim. Jobs needing actions, services, installs, network access, builds, or tests remain on full runners. Slim jobs run in an unprivileged container and share the plan's standard concurrency limits; GitHub does not guarantee faster scheduling.

Optimize duplicate compilation and execution before adding more blocking jobs. Keep affected security, database/TLS, generated-contract, and basic adopter journeys enforced. Partitioned suites must retain their complete test inventory and feed the same aggregate result. Never meet the budget by hiding a failure, dropping an aggregate dependency, or increasing a timeout.

Repeated fuzz exploration and broad assurance can run asynchronously when an equivalent affected-change regression remains in CI. Before moving a check, name its owning workflow and maintainer, prove the replacement executes successfully, and document which changed inputs bring it back into ordinary CI. Nightly Rust coverage complements the product database gates; its default feature shards do not replace PostgreSQL or TLS execution.

The checks below run wholly or partly after review instead of on the pull request. Each remains a job of RegistryStack CI (.github/workflows/ci.yml), owned by the maintainers, runs in every nightly full sweep, and passed in that workflow's scheduled full sweep before it moved: on 2026-09-24 for platform assurance, the Linux release recipe and docs archives, and on 2026-09-27 for the heavy integration tier and the Casework review examples:

  • Platform fuzz smoke runs on the merge queue whenever the platform crates, their products directory or the root Rust inputs change, including a Cargo.lock change that reaches a platform crate. Platform line coverage runs on the same inputs on main pushes and in the nightly sweep, outside the CI result verdict, and uploads from protected main pushes. No pull-request input brings either back: the platform tests and hygiene checks still run there, and the merge queue holds the committed-corpus fuzz smoke before anything lands. nightly-security.yml additionally explores every declared fuzz target daily, and nightly-rust-coverage.yml reports workspace coverage daily.
  • The heavy integration tier runs on the merge queue, on main pushes, in the nightly sweep and in manual runs, each behind its usual changed-path selector. The release protected-ci checks read the main push run. The tier covers the Base Registry Engine PostgreSQL and immediate-actions lanes (the contracts lane stays on the pull request), the Base Registry Engine WASM executor, Casework and Scheduling PostgreSQL, the Evidence, Base Registry Engine and Casework tutorials, and the Base Registry Engine and Evidence composition. A pull request labeled ci:full runs the tier too; adding the label restarts the run. CI result passes a heavy job that review skipped, and fails one the classifier selected that did not succeed. Before you push a change these suites cover, run the matching local commands from the owning product guide (for example products/breg/scripts/test-postgres.sh or the Casework and Scheduling PostgreSQL suites in AGENTS.md), because the merge queue is where their failure would otherwise first appear.
  • Release Linux native clients, for both architectures, runs only in the nightly sweep and a full manual run. Review and the merge queue prove each binding with the native binding job.
  • Immutable docs archives run on the merge queue only for the archive recipe's own inputs: the docs site package, lockfile and Astro configuration, the archive and link-checking scripts under docs/site/scripts/ and the modules they import, and the docset, archive lock and repository-docs data. A change to ci.yml alone does not rebuild history there; the nightly sweep and a manual full run do.
  • The Casework tutorial job's step that verifies the Base Registry Engine, payment and standalone review examples runs only in the nightly sweep and a full manual run.

Merge-queue checks and main pushes select affected work using the event's exact before/after commits, including every commit in a multi-commit push and both owners of a renamed path. Shared build and CI inputs still select the broad matrix. A Cargo.lock change without them selects the workspace packages that reach a changed locked package, and still selects the broad matrix when the lock cannot be parsed or compared, its format changes, a native, build-helper or git-sourced package changes, or the change reaches half the workspace or more. Missing comparison commits select all gates and refuse to claim archive immutability without the required baseline.

The same RegistryStack CI workflow runs every gate daily at 22:37 UTC, including immutable archives and both Linux client release recipes. Maintainers own failures of this full regression sweep. A manual workflow run defaults to the same full sweep; turning off full compares the chosen ref with main. Periodic archive reconstruction validates current locked bytes, while event runs additionally compare the lock against their actual change baseline.

Change review

Every pull request should make the review path clear:

  • Explain the user-visible or operator-visible impact in the pull request summary.
  • Keep changes scoped to one owning area whenever practical.
  • Call out release, security, migration, compatibility, privacy, or governance concerns in the pull request notes.
  • Run the checks that match the changed files, or state exactly which relevant check was skipped and why.
  • Include tests for major functionality and bug fixes. If a test cannot be added, explain why in the pull request.
  • Treat changes to authentication, authorization, assertion evaluation or signing, audit integrity, release provenance, deployment defaults, or data minimization as security-sensitive. These changes need explicit maintainer review notes, even when the maintainer is also the author.
  • Schema and migration code refuses when it would drop rows; it never drops them silently. A change that would lose persisted state fails with an error that names what would be lost, and the operator decides the next step. Cover the refusal with a test. Every retained domain and operational state table requires row preservation, and the release upgrade rehearsal checks it for the products it rehearses. v0.40.0 does not upgrade its predecessor's state in place: an operator applies it to new databases, as its release notes state, so it carries no v0.39.0 row forward. BReg's fresh installations contain the current caller-scoped replay and verified creator structures directly. No installation step converts or discards earlier rows. BReg's native Record, Dataset, Action, and Proposal replacement is a fresh baseline with no in-place upgrade for retired entity/Request packages or state; native package evolution remains covered by its operator gates. Casework's review-only replacement is another explicit fresh baseline: it refuses retired WorkItem tables without converting or deleting their data. Operators drain or cancel old reviews, retain the old deployment and audit journal for reconciliation, and install the current model in a fresh database. Its current-schema install and backup/restore checks replace the unsupported in-place Casework rehearsal. Evidence's normalized v0.42.0 baseline likewise requires a newly authored evidence.yaml project, target, and complete separate authority. Retired split projects and bundled authority have no in-place conversion; retain the previous deployment and audit, package the current model, and verify a fresh synthetic assertion before cutover. Messaging's and Scheduling's v0.42.0 schemas are fresh baselines as well: each installs its complete schema as one baseline and refuses by name a database whose ledger holds a pre-1.0 version. Operators retain the previous database and audit journal for reconciliation and apply the current package to a fresh database; messages still queued and holds and appointments recorded in the previous database are not carried forward. v0.44.0 adds three more: Casework's and Scheduling's schemas each start again at a fresh version 200 baseline and refuse by name a ledger below version 200, and BReg refuses by name the v0.43.0 attachment verification tables at startup, in bregctl doctor, and in bregctl plan and apply. The operator path is the same: retain the previous database and audit journal, rebuild the BReg package with the current bregctl test and bregctl package, and apply the current package to a fresh database.
  • Before 1.0, a release reads only the state its immediate predecessor wrote, and an operator upgrades one release at a time, except at the explicit fresh baselines. v0.40.0 starts fresh: it does not upgrade state v0.39.0 wrote in place. It refuses a configuration file that keeps a retired key or apiVersion and names the replacement (config.removed-key, config.retired-api-version), refuses a BReg or Messaging package the earlier release built and a Scheduling database it wrote, and is applied to new databases; the BREAKING sections of its release notes give each step. v0.40.0 does not upgrade v0.39.0 state in place; apply to a new database. Except for the BReg, Casework, Evidence, Messaging, and Scheduling fresh baselines described above, from v0.40.0 onward keep everything the previous release could have written to a database, package, configuration file, or local state readable. Do not add code that reads, upgrades, or refuses by name the state of an older release, and remove such code in the release after the one that needed it, with a BREAKING entry in the product changelog. The removed-key and retired-apiVersion tables of the configuration conventions are the one standing exception: CFG-CHANGE-2 in CONFIG-CONVENTIONS.md keeps each refusal until its format's next stable version. FORWARD_PATH_FLOOR in release/scripts/rehearse-upgrade.py names the earliest shared predecessor, v0.43.0, the release before the v0.44.0 Casework, Scheduling, and BReg fresh baselines, and api-stability.mdx states the promise for operators.

Dependency Changes

New or changed release dependencies must follow the Dependency Vetting Policy. Document why the dependency is needed, whether it is runtime or tool-only, license and advisory status, feature selection, transitive impact, and any accepted risk with a review trigger.

Git dependencies require an immutable commit pin, a rationale, and a documented review trigger. external/README.md records the retired Crosswalk input and the review required before any reintroduction.

For new Rust YAML parsing or serialization, use the workspace-pinned serde_norway dependency. Do not introduce a direct serde_yaml dependency without an approved compatibility or security rationale.

Scope

Keep changes focused on the owning area:

  • crates/ for Rust crates and runnable binaries.
  • products/ for product-owned specs, examples, fixtures, and docs.
  • docs/site/ for the public documentation site.
  • release/ for public release manifests, schemas, notes, and tooling.

Private planning, release evidence that is not already public, credentials, hosted deployment details, and internal review notes do not belong in this repository.

Issue Labels

Public GitHub issue labels can help identify scope and priority. Start with:

  • enhancement: product, docs, or release improvements.
  • criticality:p3: lower-risk work that is usually safer for first contributions than release or security-critical changes.

If an issue looks too broad, ask for a smaller slice before opening a pull request.

Before Opening A Pull Request

Run the checks that match the files you changed. The full current gate is in .github/workflows/ci.yml.

Every change proposal or pull request for major new functionality MUST add tests for the new functionality to an automated test suite. The test coverage must exercise the behavior at the lowest practical level, such as Rust unit or integration tests, Python unittest coverage for scripts, Node test coverage for docs or UI scripts, or an existing product-specific automated suite. If a maintainer determines that a change is not major new functionality, the pull request should say so in the testing notes.

Major bug fixes should include the smallest relevant automated test, fixture, or docs check that proves the behavior. If a change is documentation-only, say so in the pull request checks section. If a code change genuinely cannot be tested, explain the reason and the manual or review evidence used instead.

Rust workspace:

cargo metadata --locked --format-version 1
cargo fmt --check
cargo check --locked --workspace --all-targets
cargo clippy --workspace --all-targets -- -D warnings
cargo test --locked --workspace
cargo deny check

Ordinary build and test scripts honor the compiler wrapper configured in Cargo or supplied through RUSTC_WRAPPER. Leave the variable unset to use Cargo's configuration, or set it explicitly to select a wrapper. An explicit RUSTC_WRAPPER="" disables that wrapper; isolated proof scripts may do this intentionally.

Keep one stable CARGO_TARGET_DIR per worktree while iterating, and do not share a writable target directory between worktrees. Reusing that directory avoids rebuilding unchanged artifacts. A compiler cache can help when outputs must be rebuilt, but does not guarantee reuse across worktree paths. Keep active build outputs until their consumers finish.

On macOS, the workspace's FIPS build of AWS-LC is a dynamic library in Cargo's fingerprinted build output. Product gate scripts prepare the exact directory from Cargo's build-script-executed message before they execute a built binary, so they need no manual loader configuration. For a direct development run, prefer cargo run. To execute target/debug/<binary> yourself, source the same helper in that terminal and build through it first:

. scripts/cargo-runtime-library-path.sh
registry_cargo_build "$PWD" --locked -p registry-caseworkctl
./target/debug/caseworkctl --version

Do not disable the workspace fips feature, force a static AWS-LC FIPS build on macOS, or select a dylib by globbing target/*/build: those approaches either weaken the production-equivalent crypto path, fail on macOS, or can select a stale module. A gate binary override such as CASEWORKCTL_BIN or RELAYCTL_BIN must be independently runnable, including any runtime libraries it needs.

The root gate runs the full cargo deny check, advisories included. Open RUSTSEC advisories with no upstream fix are ignored in deny.toml with a scoped rationale and a review trigger; a newly published advisory fails CI until it is fixed or gets its own documented ignore.

Registry Messaging product checks:

products/messaging/scripts/check-checkpoint.sh
products/messaging/scripts/check-contracts.sh
MESSAGING_TEST_DATABASE_URL=<disposable database> cargo test --locked \
  -p registry-messaging --features postgres-test --test postgres_migrate

The Messaging PostgreSQL suites fail, rather than skip, without MESSAGING_TEST_DATABASE_URL; point it at a server whose databases you can discard.

Document Store product checks:

products/documents/scripts/check-checkpoint.sh
products/documents/scripts/check-contracts.sh
DOCUMENTS_TEST_DATABASE_URL=<disposable database> cargo test --locked \
  -p registry-documents --features postgres-test \
  --test postgres_startup --test postgres_operator --test postgres_documents \
  --test postgres_s3 --test postgres_hooks
DOCUMENTS_TEST_DATABASE_URL=<disposable database> cargo test --locked \
  -p registry-documentsctl --features postgres-test --test postgres_cli
cargo build --locked -p registry-render
DOCUMENTS_TEST_DATABASE_URL=<disposable database> \
  REGISTRY_RENDER_BIN="$PWD/target/debug/registry-render" cargo test --locked \
  -p registry-documents --features postgres-test --test postgres_journey

The Document Store PostgreSQL suites fail, rather than skip, without DOCUMENTS_TEST_DATABASE_URL, and postgres_journey also fails without REGISTRY_RENDER_BIN; point the URL at a server whose databases you can discard.

Release source checks:

These checks require Python 3.11 or later.

python3 -m unittest release/scripts/test_registry_release.py
release/scripts/registry-release validate release/manifests/registry-stack-beta-28.yaml
release/scripts/registry-release audit release/manifests/import-map-2026-06-24.yaml
release/scripts/check-release-source-model.sh
python3 -m unittest release/scripts/test_check_release_source_model.py

Docs checks:

cd docs/site
npm ci
npm test
npm run check

If you cannot run a relevant check, say which command was skipped and why.

Repeatable Builds And Generated Outputs

Release candidates are built once from an exact protected-main commit, a pinned Rust builder image, and locked Cargo dependencies in .github/workflows/release-candidate.yml. The candidate records SHA256 hashes, OCI digests, SBOMs, and scan evidence before publication promotes the exact bytes. Independent repeatability exercises are scheduled assurance work, not a duplicate build in every ordinary Beta transaction. Public repeatable-build evidence is recorded in release/REPEATABLE-BUILDS.md.

Site reference pages and data are ignored build outputs. Commit their authored inputs and generators, then run npm test and npm run check under docs/site; both generate the required outputs from a fresh checkout. See the docs contributor workflow. Product-owned generated schemas, the CLI command catalog crates/registry-cli-docs/catalog.json, and checked-in release snapshots still belong in the source change and must reproduce the same bytes when regenerated. Do not introduce generators that depend on wall-clock time, unordered traversal, ambient local paths, network responses, or unlocked dependencies unless the generated output is normalized or pinned so the same source can reproduce it exactly.

Security Reports

Do not open public issues or pull requests for suspected minimum-disclosure failure, auth bypass, audit redaction failure, source connector data leakage, signing key handling bugs, or other vulnerabilities. Follow SECURITY.md.