Development environment
Requirements:
- Elixir 1.15+ (CI uses 1.17.3)
- OTP 26+ (CI uses OTP 27.0)
- PostgreSQL 14+ (PostgreSQL 16 recommended; matches CI and
docker-compose.yml)
Setup
Clone the repository.
Install dependencies:
mix deps.getStart PostgreSQL — no manual
createdbrequired: the test helper createsthreadline_testwhen missing.docker compose up -dWait until Postgres is healthy (
docker compose ps).Port 5432 already in use (e.g. Homebrew PostgreSQL): Compose maps the container to host port
5433by default (THREADLINE_DB_PORTindocker-compose.yml). Point Mix at it:DB_PORT=5433 mix ci.allRun the full local gate (same steps CI runs, modulo Postgres). The project sets
preferred_envs: ["ci.all": :test]inmix.exs, so the whole chain (format, credo, compile strict, tests, Threadline trigger coverage, doc contract tests) runs in the test environment and picks upconfig/test.exs.MIX_ENV=test mix ci.allmix ci.allis equivalent when invoked withoutMIX_ENVbecause ofpreferred_envs.With the alternate Compose port:
DB_PORT=5433 mix ci.all.
Running tests
mix verify.test # format of CI: full suite (needs PostgreSQL)
mix test test/path.exs # single file
Integration tests use a real database and triggers; they are not excluded from mix test.
Environment: DB_HOST defaults to localhost; DB_PORT defaults to 5432 (see config/test.exs). Override if Postgres listens on another port (e.g. DB_PORT=5433 with the default docker-compose.yml mapping).
CI parity and act
GitHub Actions workflow: .github/workflows/ci.yml. Live runs (branch main): https://github.com/szTheory/threadline/actions?query=branch%3Amain — Stable job keys (do not rename; used by docs, act, and branch protection):
| Job key | Purpose |
|---|---|
verify-format | mix verify.format |
verify-credo | mix verify.credo |
verify-compile-no-optional | mix verify.compile_no_optional (compile without optional deps; gates against missing Phoenix/LiveView) |
verify-test | compile --warnings-as-errors + mix verify.test (Postgres service) |
verify-pgbouncer-topology | Postgres + PgBouncer (POOL_MODE=transaction) — priv/ci/topology_bootstrap.exs on direct Postgres, then mix verify.topology + mix verify.threadline on the pooler port |
verify-docs | MIX_ENV=dev — mix docs (ExDoc + extras) |
verify-hex-package | mix hex.build + assert tarball contains lib/ |
verify-release-shape | bin/verify-release-shape — @version / dated CHANGELOG for release versions |
Hex publish runs from .github/workflows/release.yml (canonical) using the HEX_API_KEY repository secret — see Hex publish (maintainers) below. Legacy tag-only fallback: .github/workflows/hex-publish.yml.
For running the test job locally with nektos/act, see scripts/ci/README.md.
PgBouncer topology CI parity
docker-compose.yml includes pgbouncer (transaction mode) on host port 6432 by default (THREADLINE_PGBOUNCER_PORT), alongside Postgres on 5433 (THREADLINE_DB_PORT).
docker compose up -dand wait until both services are healthy.Bootstrap migrations + topology fixture on direct Postgres (DDL does not go through PgBouncer):
MIX_ENV=test DB_HOST=localhost DB_PORT=5433 THREADLINE_TOPOLOGY_BOOTSTRAP=1 mix run priv/ci/topology_bootstrap.exsRun topology tests +
verify.threadlinethrough the pooler:MIX_ENV=test DB_HOST=localhost DB_PORT=6432 THREADLINE_PGBOUNCER_TOPOLOGY=1 mix verify.topology MIX_ENV=test DB_HOST=localhost DB_PORT=6432 THREADLINE_PGBOUNCER_TOPOLOGY=1 mix verify.threadline
mix verify.topology requires THREADLINE_PGBOUNCER_TOPOLOGY=1 so it cannot accidentally pass against direct Postgres only.
Host STG evidence (integrators)
Host staging / pooler parity (requirements STG-01–STG-03) is integrator-owned attestation: detailed topology, logs, and runbooks live in your repo or docs under your control. Threadline maintainers do not operate your staging stack.
To contribute a short in-repo index (tables, links, redacted excerpts) that helps other operators, use a fork and open a pull request against this repository. Maintainers merge for modesty of claims, redaction, and link hygiene only — not to vouch for third-party environments.
Fill the canonical scaffolds in guides/adoption-pilot-backlog.md: search for STG-HOST-TOPOLOGY-TEMPLATE (fixed-field topology narrative) and STG-AUDITED-PATH-RUBRIC (HTTP + job paths with OK / Issue / N/A / Not run and evidence pointers). Long-form evidence stays in integrator-controlled artifacts; the PR updates the small, reviewable surface in main.
Submitting a Pull Request
- Fork the repository and create a branch from
main. - Make your changes and run the full gate:
mix ci.all(requires PostgreSQL — see Setup above). - Open a pull request against
main. Describe what changed and why. - All CI checks on the PR must pass (including
verify-docs,verify-hex-package, andverify-release-shapewhen present onmain).
Branch protection (maintainers)
In GitHub repository settings, require these checks on main (names match the workflow name: fields or job summaries as shown in the PR UI):
- Check formatting (
verify-format) - Run Credo (strict) (
verify-credo) - Run test suite (
verify-test) - PgBouncer transaction topology (
verify-pgbouncer-topology) - Build ExDoc (dev) (
verify-docs) - Hex package tarball (
verify-hex-package) - Release metadata (version / changelog) (
verify-release-shape)
Exact labels depend on GitHub’s UI; map them to the job keys above.
Hex publish (maintainers)
Canonical path: .github/workflows/release.yml — Release Please on main (0.6.1+) or workflow_dispatch bootstrap/recovery (e.g. first v0.6.0 cut).
The release workflow:
- Resolves the release ref (Release Please tag or dispatch inputs).
- Waits for green
ci.ymlon the release SHA (gate-ci-green). - Runs
mix verify.release, thenmix hex.publish --yes(idempotent if version already on Hex). - Polls Hex.pm until the version is indexed.
- Opens a distribution sync PR (
bin/post-publish-distribution-sync) that flips the adoption-pilot Hex row to OK, trims evaluating-guide lag prose, and writes.planning/phases/122-release-distribution-truth/122-VERIFICATION.md.
Secrets: HEX_API_KEY (required). RELEASE_PLEASE_TOKEN (optional fine-grained PAT — recommended for Release Please PRs and distribution sync PRs).
Bootstrap v0.6.0 (one-shot)
After Wave 1 distribution doc work is on main and CI is green:
- Actions → Release → Run workflow
- Inputs:
tag=v0.6.0,release_version=0.6.0 - Merge the automated distribution sync PR when
mix verify.doc_contractpasses on that PR
The workflow creates tag v0.6.0 on green main HEAD if the tag does not exist yet.
Ongoing releases (0.6.1+)
- Merge conventional commits to
main— Release Please opens/updates a Release PR (release-please-config.json, manifest.release-please-manifest.json). - Merge the Release PR when CI is green — Release Please tags, then the same publish + distribution sync chain runs.
Recovery / dry-run
workflow_dispatch inputs:
| Input | Purpose |
|---|---|
tag | Existing or to-be-created vX.Y.Z |
release_version | Must match @version in mix.exs at that ref |
dry_run | mix hex.publish --dry-run --yes only |
skip_distribution_sync | Publish without opening the doc sync PR |
Legacy fallback: pushing tag v*.*.* still triggers .github/workflows/hex-publish.yml (no CI gate, no doc sync).
Local manual runbook (optional): mix hex.publish --dry-run / mix hex.publish with mix hex.user auth instead of CI.
Post-publish distribution proof for adopters: adoption-pilot Distribution preflight OK row in guides/adoption-pilot-backlog.md plus .planning/phases/122-release-distribution-truth/122-VERIFICATION.md.
Maintainer manual checklist (release)
Use when preparing or debugging a release (no secrets in logs):
- Clean tree:
git status --porcelainempty (local preflight only). - Run
mix verify.release. - Run
DB_PORT=5433 mix ci.all(ormix ci.all) with Postgres up. - Ensure
mainCI is green on the commit to release. - Release workflow: dispatch
release.ymlor merge Release Please PR — do not rely on manualmix hex.infocopy-paste; the workflow polls Hex.pm and opens the distribution sync PR. - Merge the distribution sync PR after doc contracts pass.