Skip to Content
🚀 Introducing Polygres: Managed cloud for Postgres graph databases. Learn more at polygres.com →
Contributor GuideRelease Gate Checklist

Release Gate Checklist

Use this page as the pre-release validation runbook for contributors and agents. It is intentionally command-oriented: start with the local checks, run the aggregate release gate, then add the environment-specific gates that are disabled by default.

Run commands from the repository root unless a block starts with cd graph. Use disposable databases for every heavy script. Use disposable PostgreSQL clusters for crash, upgrade, and memory-inspection scripts.

Low-Context Agent Rules

Use these rules when an automated or low-context agent runs the checklist:

  • Treat any nonzero command exit as a failed gate unless the release owner explicitly waives it.
  • Do not continue after a failed required gate. Capture the command, exit code, and last useful log lines, then stop.
  • Do not retry a failed gate more than once unless the failure is clearly environmental, such as a transient Docker pull, stale pgrx install, or disposable database name collision.
  • Run RUN_CRASH=1, RUN_TX_DELTA_CRASH=1, crash_recovery.sh, and tx_delta_crash_recovery.sh only through scripts/with_disposable_postgres.sh; the fixture refuses clusters without its per-run sentinel and connected-server identity checks.
  • Run PostgreSQL binary-upgrade checks through run_pg_upgrade_matrix.sh; the lower-level validator refuses directories outside its per-run sentinel.
  • Do not run package-manager install or add commands without the sfw wrapper.
  • Do not mark skipped opt-in gates as passed. Record them as skipped with a reason and owner.

For every gate, record:

command: working directory: start time: end time: exit code: result: pass | fail | skipped | waived log or artifact path: notes:

Pre-Run Sanity Check

Run this first and save the output with release evidence:

pwd git rev-parse --show-toplevel git rev-parse HEAD git status --short command -v cargo command -v cargo-pgrx command -v psql command -v createdb command -v dropdb command -v pg_config command -v pgbench command -v docker command -v cargo-deny pg_config --version psql --version docker --version cargo --version cargo pgrx --version cargo deny --version

If PG_CONFIG is set, also verify it points at the intended PostgreSQL major:

"$PG_CONFIG" --version

If the release uses a non-default PostgreSQL connection, record these environment variables before running heavy tests:

PGHOST= PGPORT= PGUSER= PGDATABASE= PG_VERSION_FEATURE= PG_CONFIG=

The normal PostgreSQL 17 release path should use PG_VERSION_FEATURE=pg17.

Release Owner Summary

Before tagging a release, collect successful output for:

  • docs drift, dependency freshness, formatting, clippy, docs, unit tests, pgrx SQL tests, dependency audit, checked-cast drift, Miri mapped validation, fuzz compile, and projection fuzz seed corpora;
  • package validation, fresh install smoke, SQL metadata audit, SQLSTATE/ACL boundary, backup/restore, lock regressions, concurrency stress, synthetic release fixture, playground query gate, GQL write lifecycle gates, named graph gate, durable projection gates, tx-delta lifecycle, and pgbench sync stress;
  • explicit opt-in gates for Docker smoke, crash recovery, tx-delta crash recovery, PostgreSQL-major matrix, peak RSS, mmap PSS, sanitizer runs, and benchmarks when the release owner requires them.

Do not mark release verification complete when a gate is skipped, interrupted, or run against the wrong PostgreSQL major. Rerun the gate or record an explicit release-owner waiver with the exact command, reason, and available evidence.

Environment Prerequisites

Required for the normal PostgreSQL 17 release path:

  • Rust toolchain for the crate and cargo-pgrx.
  • PostgreSQL server, client tools, and headers for the target major.
  • pg_config for the target major on PATH, or set PG_CONFIG.
  • psql, pg_dump, pg_restore, createdb, dropdb, and related client binaries on PATH.
  • cargo-deny for the dependency policy gate.
  • A pgrx test cluster initialized for the target major.
  • Docker for Docker, package, playground, or matrix gates.
  • pgbench for sync stress gates.
  • Nix for regenerating and validating flake.lock when the dependency freshness gate reports github:nixpkgs, github:rust-overlay, or other flake input updates. On macOS, current upstream Nix no longer supports single-user installs; use the official daemon installer on a release machine where system-level /nix, user, and launchd changes are acceptable.

PostgreSQL 17 is the default release feature:

export PG_VERSION_FEATURE=pg17 export PG_CONFIG=/path/to/postgresql-17/bin/pg_config

Initialize pgrx once per machine and PostgreSQL major:

cd graph cargo pgrx init --pg17 "$PG_CONFIG"

If pgrx reports SQL definition drift after code changes, reinstall against the same PostgreSQL version:

cd graph cargo pgrx install --pg-config "$PG_CONFIG" --features pg17 --no-default-features

Fast Preflight

Run this before long release gates so basic failures are found quickly:

./scripts/check_docs_drift.sh python3 scripts/check_dependency_updates.py cd graph cargo fmt --check cargo clippy --features "pg17 development" --all-targets -- -D warnings cargo doc --features pg17 --no-deps cargo test --features pg17 cargo pgrx test --features "pg17 development" pg17 cargo deny check advisories bans licenses sources

Documentation doctests are part of the public Rust API contract:

cd graph cargo test --doc --features pg17

Documentation And Metadata Checks

Run the aggregate docs drift gate from the repository root:

./scripts/check_docs_drift.sh

It runs:

./scripts/check_doc_references.py ./scripts/check_sql_api_drift.py ./scripts/check_rust_doc_map_drift.py

Use targeted inventories when a failure needs diagnosis:

python3 scripts/check_sql_api_drift.py --list-implemented python3 scripts/check_rust_doc_map_drift.py --list

Run dependency freshness before release metadata freezes:

python3 scripts/check_dependency_updates.py

The dependency checker reports update candidates without rewriting manifests unless an explicit --update ... --yes is passed. Any package-manager command that installs or adds a new dependency must use sfw.

Rust And SQL Test Layers

Use the narrowest layer while iterating, then run the broader release layers.

LayerCommandRequired before release
Formattingcd graph && cargo fmt --checkYes
Clippycd graph && cargo clippy --features "pg17 development" --all-targets -- -D warningsYes
Rust unit testscd graph && cargo test --features pg17Yes
Rust docscd graph && cargo doc --features pg17 --no-depsYes
Rust doctestscd graph && cargo test --doc --features pg17Yes
pgrx SQL testscd graph && cargo pgrx test --features "pg17 development" pg17Yes
Dependency auditcd graph && cargo deny check advisories bans licenses sourcesYes
pgrx focused testcd graph && cargo pgrx test pg17 <test_name>Iteration only
pg_regress setup smokegraph/tests/pg_regress/Packaging-style smoke only

The pgrx SQL test files live in graph/src/pg_tests/ and cover discovery, GQL, Cypher compatibility, traversal, paths, filters, registration/search, sync/config/build behavior, maintenance/admin APIs, named graphs, workflow APIs, validation, and synthetic release SQL shape.

Fuzz Gates

The release gate compiles fuzz binaries with cargo check --bins inside graph/fuzz and runs projection seed corpora:

cd graph (cd fuzz && cargo check --bins) ./fuzz/run_projection_seed_corpora.sh

When a release includes parser, loader, filter, sync, traversal option, or projection format changes, also run the cargo-fuzz compile smoke:

cd graph/fuzz cargo fuzz build

Fuzz targets currently cover:

TargetBoundary
load_graph_filePersisted graph artifact loader
node_ref_stringNode reference parser
traverse_optionsTraversal option parser
sync_propertiesSync JSON property parser
structured_filterStructured filter validation
gql_parserGQL parser frontend
cypher_parseropenCypher compatibility parser frontend
load_projection_manifestDurable projection manifest loader
load_projection_segmentDurable projection segment loader

Aggregate Release Gate

Run the main PostgreSQL 17 release gate from graph/:

cd graph PG_VERSION_FEATURE=pg17 ./tests/heavy/run_release_gate.sh

By default, this gate runs:

  • cargo fmt --check;
  • cargo clippy --features "pg17 development" --all-targets -- -D warnings;
  • cargo doc --features pg17 --no-deps;
  • cargo test --features pg17;
  • cargo pgrx test --features "pg17 development" pg17;
  • cargo deny check advisories bans licenses sources;
  • fuzz binary compile and projection fuzz seed corpora;
  • package_validate.sh;
  • fresh_install_smoke.sh;
  • function_metadata_audit.sh;
  • run_sqlstate_acl_boundary.sh;
  • backup_restore_validate.sh;
  • background_job_lock_regression.sh;
  • build_lock_regression.sh;
  • concurrency_stress.sh;
  • synthetic_release_smoke.sh;
  • playground_release_gate.sh;
  • run_pgbench_sync.sh;
  • gql_create_tx_lifecycle.sh;
  • gql_set_tx_lifecycle.sh;
  • gql_delete_tx_lifecycle.sh;
  • gql_merge_race.sh;
  • gql_relationship_race.sh;
  • named_graphs_heavy_gate.sh;
  • cross_backend_durable_projection.sh;
  • projection_recovery_gate.sh;
  • tx_delta_lifecycle.sh;
  • gql_write_recheck_race.sh.

The default gate does not run Docker smoke, crash recovery, tx-delta crash recovery, full PostgreSQL-major matrix, or peak RSS. Enable those explicitly.

Full Production Gate

Use the repository’s disposable PostgreSQL wrapper for crash gates. It refuses caller-supplied clusters, development databases, and production paths.

For the reproducible RC tier, the repository creates and removes that cluster, retains one log per gate, and records dependency lockfile and artifact hashes:

python3 scripts/run_release.py --tier rc

Use this lower-level aggregate form only for diagnosis; the wrapper still owns the cluster:

cd graph PG_VERSION_FEATURE=pg17 \ DB_PREFIX=pggraph_release \ RUN_DOCKER=1 \ RUN_CRASH=1 \ RUN_TX_DELTA_CRASH=1 \ RUN_FULL_MATRIX=1 \ RUN_RSS=1 \ ../scripts/with_disposable_postgres.sh ./tests/heavy/run_release_gate.sh

For a staged environment that cannot run Docker, playground, pgbench, or crash checks, make skips explicit in the command and record why:

cd graph PG_VERSION_FEATURE=pg17 \ DB_PREFIX=pggraph_release_staged \ RUN_PLAYGROUND=0 \ RUN_PGBENCH=0 \ RUN_DOCKER=0 \ RUN_CRASH=0 \ RUN_TX_DELTA_CRASH=0 \ ./tests/heavy/run_release_gate.sh

Release Gate Toggles

run_release_gate.sh accepts these environment variables:

VariableDefaultMeaning
PG_VERSION_FEATUREpg17Cargo/PostgreSQL feature to test
DB_PREFIXpggraph_releasePrefix for disposable database names
RUN_FULL_MATRIX0Run local PostgreSQL-major matrix first
RUN_PACKAGE1Run package validation
RUN_INSTALL1Run fresh install smoke
RUN_METADATA1Run SQL function metadata audit
RUN_BOUNDARY1Run SQLSTATE/ACL boundary tests
RUN_BACKUP_RESTORE1Run backup/restore validation
RUN_BACKGROUND_LOCK1Run background-job lock regression
RUN_BUILD_LOCK1Run build/vacuum lock regression
RUN_CONCURRENCY1Run mixed concurrency stress
RUN_SYNTHETIC1Run synthetic release fixture
RUN_PLAYGROUND1Run playground query gate
RUN_PGBENCH1Run pgbench sync stress
RUN_GQL_CREATE_TX1Run GQL CREATE transaction lifecycle
RUN_GQL_SET_TX1Run GQL SET transaction lifecycle
RUN_GQL_DELETE_TX1Run GQL DELETE transaction lifecycle
RUN_GQL_MERGE_RACE1Run GQL MERGE race proof
RUN_GQL_WRITE_RECHECK1Run GQL stale-predicate recheck race proof
RUN_TX_DELTA_LIFECYCLE1Run internal tx-delta lifecycle proof
RUN_NAMED_GRAPHS_HEAVY1Run named graph heavy policy gate
RUN_CROSS_BACKEND_DURABLE1Run cross-backend durable projection gate
RUN_PROJECTION_RECOVERY1Run projection recovery gate
RUN_DOCKER0Run Docker smoke
RUN_CRASH0Run crash recovery, requires disposable PGDATA
RUN_TX_DELTA_CRASH0Run tx-delta crash recovery, requires disposable PGDATA
RUN_RSS0Run peak RSS measurement
RUN_BUILD_MEMORY_STRESS0Run baseline, small-batch, rebuild, and low-memory rebuild RSS profiles

Synthetic fixture tuning:

VariableDefault
SYNTHETIC_NODE_COUNT50000
SYNTHETIC_HUB_FANOUT1000
SYNTHETIC_MAX_BUILD_MS60000
SYNTHETIC_MAX_QUERY_MS1000

pgbench sync tuning:

VariableDefault
CLIENTS4
JOBS2
TIME30

Heavy Script Inventory

Run these from graph/. Most accept PG_VERSION_FEATURE=pg17 and a disposable DBNAME. Scripts that kill, upgrade, or inspect PostgreSQL clusters require disposable cluster paths.

ScriptRelease purposeExample
run_release_gate.shAggregate release gatePG_VERSION_FEATURE=pg17 ./tests/heavy/run_release_gate.sh
run_pg_matrix.shLocal PostgreSQL 14 through 18 matrix./tests/heavy/run_pg_matrix.sh
run_pg_matrix_docker.shDocker PostgreSQL-major matrix./tests/heavy/run_pg_matrix_docker.sh
run_gql_write_matrix.shPostgreSQL-major GQL write matrix./tests/heavy/run_gql_write_matrix.sh
package_validate.shValidate pgrx package shape and companion filesPG_VERSION_FEATURE=pg17 ./tests/heavy/package_validate.sh
fresh_install_smoke.shInstall extension and run quick workflowPG_VERSION_FEATURE=pg17 DBNAME=pggraph_install ./tests/heavy/fresh_install_smoke.sh
function_metadata_audit.shAudit SQL volatility, security, cost, rows, and parallel metadataPG_VERSION_FEATURE=pg17 DBNAME=pggraph_metadata ./tests/heavy/function_metadata_audit.sh
run_sqlstate_acl_boundary.shAssert client-visible SQLSTATEs and restricted-role ACL behaviorPG_VERSION_FEATURE=pg17 DBNAME=pggraph_boundary ./tests/heavy/run_sqlstate_acl_boundary.sh
backup_restore_validate.shValidate dump/restore of source tables, graph catalog, sync state, and persisted graph behaviorPG_VERSION_FEATURE=pg17 SOURCE_DB=pggraph_backup_src RESTORE_DB=pggraph_backup_dst ./tests/heavy/backup_restore_validate.sh
background_job_lock_regression.shCheck background job lock and failure-state behaviorPG_VERSION_FEATURE=pg17 DBNAME=pggraph_background_lock ./tests/heavy/background_job_lock_regression.sh
build_lock_regression.shCheck build/vacuum lock conflict behaviorPG_VERSION_FEATURE=pg17 DBNAME=pggraph_build_lock ./tests/heavy/build_lock_regression.sh
concurrency_stress.shExercise build, sync, vacuum, maintenance, and traversal concurrentlyPG_VERSION_FEATURE=pg17 DBNAME=pggraph_concurrency CLIENTS=3 ROUNDS=3 ./tests/heavy/concurrency_stress.sh
synthetic_release_smoke.shDeterministic release fixture for build, persistence, traversal, search, filters, paths, and componentsDBNAME=pggraph_synthetic NODE_COUNT=50000 HUB_FANOUT=1000 ./tests/heavy/synthetic_release_smoke.sh
run_synthetic_release_evidence.shSynthetic smoke plus metadata and persisted artifact inspectionNODE_COUNT=50000 HUB_FANOUT=1000 ./tests/heavy/run_synthetic_release_evidence.sh
playground_release_gate.shValidate every playground sidebar query against stable summariesPGGRAPH_PLAYGROUND_YES=1 ./tests/heavy/playground_release_gate.sh
run_pgbench_sync.shConcurrent DML and trigger-sync stressDBNAME=pggraph_pgbench CLIENTS=4 JOBS=2 TIME=30 ./tests/heavy/run_pgbench_sync.sh
read_latency_under_sync.shMeasure read p50/p95/p99 with no backlog, pending backlog, and concurrent writersDBNAME=pggraph_read_latency NODE_COUNT=10000 SAMPLES=25 ./tests/heavy/read_latency_under_sync.sh
gql_create_tx_lifecycle.shPublic mapped GQL CREATE commit and rollback proofPG_VERSION_FEATURE=pg17 DBNAME=pggraph_gql_create_tx ./tests/heavy/gql_create_tx_lifecycle.sh
gql_set_tx_lifecycle.shPublic mapped GQL property SET rollback and filter-delta proofPG_VERSION_FEATURE=pg17 DBNAME=pggraph_gql_set_tx ./tests/heavy/gql_set_tx_lifecycle.sh
gql_delete_tx_lifecycle.shPublic mapped GQL edge-row DELETE rollback and tombstone proofPG_VERSION_FEATURE=pg17 DBNAME=pggraph_gql_delete_tx ./tests/heavy/gql_delete_tx_lifecycle.sh
gql_merge_race.shConcurrent mapped MERGE same-key convergence proofPG_VERSION_FEATURE=pg17 DBNAME=pggraph_gql_merge_race ./tests/heavy/gql_merge_race.sh
gql_relationship_race.shConcurrent relationship CREATE/DELETE winner, SQLSTATE, and rollback proofPG_VERSION_FEATURE=pg17 DBNAME=pggraph_gql_relationship_race ./tests/heavy/gql_relationship_race.sh
gql_write_recheck_race.shTwo-session stale predicate recheck proof for mapped writesPG_VERSION_FEATURE=pg17 DBNAME=pggraph_gql_write_recheck ./tests/heavy/gql_write_recheck_race.sh
tx_delta_lifecycle.shInternal mutable-overlay transaction lifecycle proofPG_VERSION_FEATURE=pg17 DBNAME=pggraph_tx_delta ./tests/heavy/tx_delta_lifecycle.sh
named_graphs_heavy_gate.shNamed-graph behavior and policy guardrailsPG_VERSION_FEATURE=pg17 DBNAME=pggraph_named_graphs bash ./tests/heavy/named_graphs_heavy_gate.sh
cross_backend_durable_projection.shCross-backend visibility for durable mutable projection statePG_VERSION_FEATURE=pg17 DBNAME=pggraph_cross_backend bash ./tests/heavy/cross_backend_durable_projection.sh
projection_recovery_gate.shDurable projection recovery behaviorPG_VERSION_FEATURE=pg17 DBNAME=pggraph_projection_rec bash ./tests/heavy/projection_recovery_gate.sh
docker_smoke.shBuild root Docker image and run extension workflow in containerIMAGE=pggraph:smoke CONTAINER=pggraph-smoke PG_MAJOR=17 ./tests/heavy/docker_smoke.sh
crash_recovery.shKill and restart disposable postmaster, then verify reload and corrupt artifact rejectionPG_VERSION_FEATURE=pg17 DBNAME=pggraph_crash PGDATA=/path/to/disposable/pgdata ./tests/heavy/crash_recovery.sh
tx_delta_crash_recovery.shKill disposable postmaster with uncommitted overlay and verify it does not persistPG_VERSION_FEATURE=pg17 DBNAME=pggraph_tx_delta_crash PGDATA=/path/to/disposable/pgdata ./tests/heavy/tx_delta_crash_recovery.sh
run_crash_matrix.shRun both destructive crash profiles on PostgreSQL 14–18 through new disposable clustersPG_VERSIONS="14 15 16 17 18" ./tests/heavy/run_crash_matrix.sh
run_pg_upgrade_matrix.shRun adjacent PostgreSQL 14→15 through 17→18 pg_upgrade --check and rebuild validation./tests/heavy/run_pg_upgrade_matrix.sh
pg_upgrade_validate.shValidate one sentinel-owned PostgreSQL upgrade pair; normally called by the matrix wrapper./tests/heavy/run_pg_upgrade_matrix.sh
measure_build_rss.shCapture peak backend RSS during graph buildPG_VERSION_FEATURE=pg17 DBNAME=pggraph_rss NODE_COUNT=200000 MAX_RSS_MB=0 ./tests/heavy/measure_build_rss.sh
build_memory_stress.shRun baseline, small-batch, repeated-rebuild, and low-memory rebuild profilesPG_VERSION_FEATURE=pg17 DB_PREFIX=pggraph_memstress ./tests/heavy/build_memory_stress.sh
measure_mmap_pss.shLinux-only mmap sharing PSS measurement across backendsDBNAME=graph_test BACKENDS=50 ./tests/heavy/measure_mmap_pss.sh
run_memory_sanitizers.shpgrx suite plus optional Rust ASan or disposable PostgreSQL-process Valgrind checksDBNAME=pggraph_pgbench ./tests/heavy/run_memory_sanitizers.sh
run_postgres_process_sanitizer.shRequire zero unsuppressed Valgrind errors across persisted mmap, corruption, callback, guarded-error, and build-job pathsPG_VERSION_FEATURE=pg17 ./tests/heavy/run_postgres_process_sanitizer.sh
run_durable_projection_matrix.shRun cross-backend durable lifecycle, concurrent relation-identity DDL, and publication/writer-lock evidence on PostgreSQL 14-18RUN_RUST_TESTS=0 RUN_PGRX_SQL=0 RUN_GQL_WRITE_MATRIX=0 RUN_DURABLE_PROJECTION_MATRIX=1 ./tests/heavy/run_pg_matrix_docker.sh

pgbench_sync.sql, sqlstate_acl_boundary.sql, and playground_release_gate.py are support files used by the shell gates.

Crash, Upgrade, And Cluster Safety

Crash gates deliberately kill PostgreSQL. Run them only against a disposable cluster, and pass restart options when the instance uses custom socket or port settings:

cd graph PGHOST=/tmp/pggraph-crash \ PGPORT=55437 \ PGDATA=/tmp/pggraph-crash/data \ POSTGRES_CTL=/path/to/pg_ctl \ POSTGRES_OPTS="-p 55437 -k /tmp/pggraph-crash" \ DBNAME=pggraph_release_crash \ ./tests/heavy/crash_recovery.sh

Run the tx-delta crash gate with the same disposable cluster pattern:

cd graph PGHOST=/tmp/pggraph-crash \ PGPORT=55437 \ PGDATA=/tmp/pggraph-crash/data \ POSTGRES_CTL=/path/to/pg_ctl \ POSTGRES_OPTS="-p 55437 -k /tmp/pggraph-crash" \ PG_CONFIG=/path/to/pg_config \ PG_VERSION_FEATURE=pg17 \ DBNAME=pggraph_release_tx_delta_crash \ ./tests/heavy/tx_delta_crash_recovery.sh

Run upgrade validation in the supported-major Docker environment. The wrapper creates both clusters, installs the matching extension binaries, and supplies the required safety sentinel:

cd graph ./tests/heavy/run_pg_upgrade_matrix.sh

Matrix Gates

Run the local PostgreSQL-major matrix when all supported pg_config binaries are available:

cd graph ./tests/heavy/run_pg_matrix.sh

Use Docker when local headers are unavailable:

cd graph ./tests/heavy/run_pg_matrix_docker.sh

The project supports PostgreSQL 14 through 18 in release automation. If a matrix leg is skipped locally, record which major was skipped and where it will be validated.

Performance And Benchmark Evidence

Run Criterion when engine-level traversal, filter, storage, or overlay changes could affect hot paths:

cd graph cargo bench --bench bfs_bench

When comparing against a saved Criterion baseline:

cd graph cargo bench --features "pg17 benchmarks" --bench bfs_bench -- --baseline pre_release

Run SQL-facing benchmark harnesses only when the release owner asks for performance evidence or the change affects SQL-facing query timing:

sandbox/run_benchmarks.sh panama sandbox/run_benchmarks.sh ldbc sandbox/run_benchmarks.sh all --yes

Keep generated report.json files with release evidence. Include the exact command, commit, dirty status, PostgreSQL version, Docker resource settings, machine details, and whether the host was otherwise idle.

Run read-latency evidence when query-time sync semantics or topology-read freshness change:

cd graph DBNAME=pggraph_read_latency \ NODE_COUNT=10000 \ SAMPLES=25 \ CONCURRENT_SAMPLES=25 \ SMALL_BACKLOG=100 \ LARGE_BACKLOG=5000 \ CLIENTS=4 \ JOBS=2 \ TIME=30 \ RATE=100 \ ./tests/heavy/read_latency_under_sync.sh

Keep read-latency-samples.csv and read-latency-summary.csv with milestone evidence.

Package And Publish Workflow Checks

Publishing requires four explicit release-owner steps after all required gates pass: tag, sign, package, and push. Do not start these steps from a dirty worktree or from a commit that has not completed the required release gate.

Required local state:

  • The release commit is checked out and git status --short is empty.
  • graph/Cargo.toml, package metadata, docs, Docker defaults, and release notes identify the intended version.
  • Before tagging, scripts/validate_release.py passes with --version X.Y.Z, the exact full commit SHA, --check-main, and --require-clean.
  • A signing key is configured for Git tags. Use the repository’s required signing backend, such as GPG, SSH signing, or the platform-managed signing flow.
  • Docker is available if preparing Docker images or validating playground packaging.
  • PGXN credentials, GitHub credentials, Docker registry credentials, and any required 2FA/session state are available to the release owner.

Prepare and verify the exact source candidate before any tag or publication:

commit="$(git rev-parse HEAD)" scripts/prepare_release_bundle.py --ref "$commit" --out-dir dist/release-bundle scripts/verify_release_bundle.py dist/release-bundle --commit "$commit"

The bundle contains one PGXN ZIP, SPDX 2.3 SBOM, provenance statement, release-manifest.json, and SHA256SUMS. Repeating preparation from the same commit must produce the same payload digests. Publication consumes these exact verified bytes; it does not rebuild the ZIP.

Before release automation:

./scripts/clean_generated_artifacts.sh --dry-run ./scripts/check_docs_drift.sh python3 scripts/check_dependency_updates.py cd graph PG_VERSION_FEATURE=pg17 ./tests/heavy/package_validate.sh PG_VERSION_FEATURE=pg17 DBNAME=pggraph_install ./tests/heavy/fresh_install_smoke.sh

Create the signed release tag only after the release commit and evidence are final:

git status --short git log -1 --oneline git tag -s vX.Y.Z -m "vX.Y.Z" git tag -v vX.Y.Z

If the project uses SSH tag signing instead of GPG, configure Git before creating the tag:

git config gpg.format ssh git config user.signingkey /path/to/release-signing-key.pub git tag -s vX.Y.Z -m "vX.Y.Z" git -c gpg.ssh.allowedSignersFile=release/ssh-allowed-signers tag -v vX.Y.Z

The checked-in SSH allowed-signers file is the publication trust policy used by local validation and the release workflow. Changes to that file require explicit release-owner review.

Build and validate release packages before pushing the tag:

scripts/validate_release.py --tag vX.Y.Z --check-main \ --require-clean --require-signed-tag cd graph PG_VERSION_FEATURE=pg17 ./tests/heavy/package_validate.sh

Push only after package validation and tag verification pass:

git push origin HEAD git push origin vX.Y.Z

The GitHub Actions release workflow has two manual phases. The intended tag name is supplied to both, but preparation happens before the tag exists:

PhaseRequired state
prepareA full 40-character source commit is supplied; the verified source bundle and Docker manifests are prepared and attested.
publishA signed annotated tag and GitHub Release exist at that exact commit; the prepared run ID is supplied and every published byte or image digest is reused without rebuilding.

Run the prepare phase first and inspect its generated artifacts and attestations before creating the tag. The publish phase must consume prepared artifacts for the exact tag; it must not rebuild from a moving branch or untagged commit.

Homebrew is a separate channel. After the signed release exists, the tap owner updates the formula URL and SHA-256 from the verified source bundle, runs brew audit --strict --online pggraph, brew style pggraph, and brew test pggraph, then verifies the installed PostgreSQL 17 extension reports the released version. Do not describe the formula as available before that handoff completes.

Use this when validating release metadata locally:

scripts/validate_release.py --version X.Y.Z --ref "$(git rev-parse HEAD)" --check-main --require-clean

Evidence Template

Record this for each release candidate:

Release candidate: Commit: Dirty status: Date: Machine/OS: PostgreSQL major: PG_CONFIG: pgrx version: Docker version: Required gates: - docs drift: - dependency freshness: - fmt: - clippy: - cargo doc: - cargo test: - cargo test --doc: - cargo pgrx test: - cargo deny: - fuzz compile: - projection seed corpora: - aggregate release gate: Opt-in gates: - Docker smoke: - crash recovery: - tx-delta crash recovery: - PostgreSQL-major matrix: - RSS/PSS: - sanitizer: - Criterion: - sandbox benchmark: Skipped gates and waivers: - gate: reason: owner: follow-up:
Last updated on