Skip to content

Latest commit

 

History

History
142 lines (105 loc) · 5.82 KB

CONTRIBUTING.md

File metadata and controls

142 lines (105 loc) · 5.82 KB

Contributing to Open Foundry

Thanks for considering a contribution. This guide covers the setup, the conventions that are easy to miss, and how to get a change reviewed quickly.

Getting set up

Prerequisites: Node.js >= 24, pnpm 9.15+, Docker with Compose v2, and Go 1.25+ (only if you build packages/cel-evaluator outside Docker).

pnpm install
pnpm run build
pnpm run test          # unit tests, no Docker required

Integration tests need the stack running:

cd deploy && cp .env.example .env && cd ..
docker compose -f deploy/docker-compose.yaml -f deploy/docker-compose.test.yaml up -d --wait
pnpm run test:integration

The docker-compose.test.yaml override mounts a fixtures pack and sets SEED_TENANT=default so seeded reference data is readable through the API.

The enforcement E2E suites

Three suites exercise behaviour the default stack cannot show, because dev mode runs allow-all stubs. Each owns the Docker lifecycle — it tears the stack down and reboots it in a non-default mode on the same ports — so run them one at a time, never alongside the standard suite:

SECURITY_E2E=1       pnpm --filter @openfoundry/integration-tests exec vitest run security-enforcement
CAPABILITY_E2E=1     pnpm --filter @openfoundry/integration-tests exec vitest run capability-gating
CONSENT_VOCAB_E2E=1  pnpm --filter @openfoundry/integration-tests exec vitest run open-consent-vocab

They leave the stack down; bring the dev stack back up before running anything else. CI runs all three in a dedicated enforcement-e2e job.

Conventions worth knowing

These are the ones reviewers actually raise.

Fail closed. Security-relevant configuration is opt-in, never defaulted to a permissive value. If an environment variable is unset, the platform should refuse or deny rather than pick a convenient default. For example OIDC_DEFAULT_TENANT is passed through unset so a token without a tenant claim is rejected, rather than defaulting every claimless token into a shared tenant. A change that makes something permissive by default will be asked to invert.

Blank env vars are unset. Compose and Helm pass unset knobs through as empty strings, so process.env.X ?? 'default' does not catch them. Use process.env.X?.trim() || 'default' when a fallback is intended.

Domain pack permission overrides replace the whole type block. A pack's permissions/*.fga does not merge relation-by-relation, so an override must re-declare every relation the runtime checks — chiefly viewer on each object type and can_<verb> for each action. Boot validation enforces this: a missing viewer stops production startup. See docs/external-domain-packs.md.

Don't hardcode counts in docs. Test counts and line counts decay silently. Describe coverage qualitatively.

No NHS-specific defaults in platform code. Healthcare behaviour is opt-in through domain packs, capabilities, and deployment policy env vars. Platform defaults stay generic.

Making a change

  1. Branch from main.
  2. Keep commits focused; the body should explain why, not restate the diff.
  3. Add or update tests. A bug fix should include a test that fails without it — say so in the PR, since it is the strongest signal a fix is real.
  4. Update docs in the same PR when behaviour or configuration changes.

Before pushing:

pnpm run build && pnpm run typecheck && pnpm run test

typecheck covers test files that build excludes, so run both.

Commit messages

Conventional-commit style: fix(api):, feat(odl):, docs(packs):, test(integration):, chore(deps):. Please don't add Co-Authored-By or other attribution trailers.

Cutting a release (maintainers)

  1. Choose the next version using SemVer. Before 1.0, breaking changes require a minor release; compatible fixes and dependency maintenance use a patch.

  2. In one chore(release): X.Y.Z commit, move the relevant changelog entries out of Unreleased and align the version in package.json, the chart version/appVersion, the default chart image tag, and the install example in deploy/README.md. Rebuild and regenerate the checked-in API contracts:

    pnpm run build
    pnpm --filter @openfoundry/api spec:all
  3. Land that commit on main, then verify the full CI workflow succeeded for its exact SHA. Do not infer this from the latest run:

    sha=$(git rev-parse HEAD)
    gh run list --workflow CI --commit "$sha" --branch main --event push \
      --json databaseId,headSha,status,conclusion
  4. Create an annotated vX.Y.Z tag on that verified commit and push only the tag. The release workflow repeats the exact-SHA CI check before publishing.

The workflow refuses tags whose commit is not on main or whose tag, package, chart, image default, changelog, and install example disagree. It then builds the release assets from the checked-in version, verifies that the generated OpenAPI and AsyncAPI contracts advertise that version, creates a draft, attests the artifacts, and makes the release visible only after provenance succeeds.

Reporting bugs

Open an issue using the template. The three fields that most often decide whether a report is actionable are NODE_ENV, the loaded DOMAIN_PACKS, and the gateway boot log — dev mode disables enforcement entirely, so a behaviour that looks like a bug in one mode is often expected in the other.

For security vulnerabilities, do not open a public issue — see SECURITY.md.

Review

Maintainers aim to respond within a week. Expect questions about fail-closed behaviour and test coverage on anything touching auth, authorisation, or consent; that scrutiny is the point of the project, not a reflection on the contribution.

By contributing you agree your work is licensed under Apache 2.0.