diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd54bdd3..4887580b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,7 +182,7 @@ jobs: ACCOUNTING_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/accounting_test services: postgres: - image: postgres:18.4@sha256:a02db8cac496f15b094798a38254f14d6e00741f709360e5e00bb6668ea31636 + image: postgres:18.6@sha256:4ef4dbc939d61acea57712655ddb4b4ab27419c913f94cca0cd57cb3ea3c2280 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -360,4 +360,4 @@ jobs: uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d with: subject-path: ${{ github.workspace }}/dist/*.whl - sbom-path: ${{ github.workspace }}/dist/sbom.spdx.json + sbom-path: ${{ github.workspace }}/dist/sbom.spdx.json \ No newline at end of file diff --git a/docs/doctoring/POSTGRESQL_RUNTIME_BASELINE.md b/docs/doctoring/POSTGRESQL_RUNTIME_BASELINE.md new file mode 100644 index 00000000..6dc948db --- /dev/null +++ b/docs/doctoring/POSTGRESQL_RUNTIME_BASELINE.md @@ -0,0 +1,41 @@ +# PostgreSQL runtime baseline + +## Current baseline + +Accounting integration and migration regressions run on PostgreSQL **18.6**, the current supported PostgreSQL 18 minor release as of 2026-09-05. PostgreSQL 18.6 was released on 2026-08-13. The PostgreSQL project states that the release fixes 28 security vulnerabilities and more than 110 bugs across the supported branches; PostgreSQL 18.5 was not shipped because of a regression. + +GitHub Actions pins the official multi-platform `postgres:18.6` image by immutable OCI index digest: + +`sha256:4ef4dbc939d61acea57712655ddb4b4ab27419c913f94cca0cd57cb3ea3c2280` + +The tag is kept alongside the digest so reviewers can see the intended minor version; the digest, not the mutable tag, is the execution identity. Accounting tests still target PostgreSQL major-version 18 semantics. This minor update does not alter journal, posting, close, reconciliation, Billing ACL, or accounting-policy authority. + +## Exact-head acceptance + +The exact-head Accounting Foundation job must execute the complete behavior suite, real PostgreSQL regressions, 100% production statement/branch coverage, repository contracts, packaging, SBOM, and reproducibility checks against the pinned 18.6 service before the CI baseline can be treated as GREEN. A Docker Hub digest lookup is provenance for selecting the image, not execution evidence. + +Historical test and ADR statements that explicitly record PostgreSQL 18.4 remain historical evidence when they describe an exact past run. Statements that claim 18.4 is the *current* runtime baseline must be read as superseded by this document and should be corrected when their canonical owner lane next edits them. + +## Existing-cluster upgrade acceptance + +Changing the ephemeral GitHub Actions service image **does not prove an existing database upgraded safely**. PostgreSQL states that an 18.x-to-18.6 upgrade does not require dump/restore, but the 18.6 release notes identify configuration, data, script, and index checks that can require operator action. Release evidence for an existing accounting database therefore keeps the CI image change separate from an operator-controlled cluster upgrade. + +Before upgrading an existing cluster, inventory and retain the following evidence for the target cluster and every replica or failover member that can become authoritative: + +- logical replication slots and non-core logical-decoding plugins. PostgreSQL 18.6 introduces `output_plugin_libraries`; installations that depend on third-party output plugins must explicitly allow them, and `pg_upgrade --check` can reject an incompatible target configuration; +- use of `pgcrypto` PGP encryption with legacy algorithms that OpenSSL can reject. The 18.6 security fix makes unsupported-cipher failures visible; potentially affected ciphertext must be identified and recovered/re-encrypted according to the release notes rather than treated as valid encrypted evidence; +- operational or migration scripts containing `COPY ... FROM STDIN`. Scripts that intentionally exercise a `COPY` command which can fail before copy-in begins must retain a `\.` terminator so following data cannot be interpreted as SQL; +- GIN indexes and table statistics. The release notes describe possibly corrupt `reltuples` after affected parallel GIN index builds; the upgrade runbook must inspect the documented catalog condition and repair affected statistics with the PostgreSQL-recommended `ANALYZE` or equivalent index operation before accepting the cluster; +- `btree_gist` and `ltree` indexes. If present, follow the 18.6 release-note reindex guidance before treating the upgraded database as release-ready. + +The preflight must determine applicability from the live database catalogs and configuration; absence is evidence only when the exact target cluster was queried. Repository source search or a clean CI database is not sufficient proof that a production cluster has no affected extension, index, replication slot, historical ciphertext, or operator script. + +After upgrade, retain the exact server identity (`SHOW server_version` and `SHOW server_version_num`), extension/index/replication-slot postchecks that correspond to the preflight inventory, migration status, application readiness, and the full accounting acceptance suite. A rollback or failover decision must preserve immutable posted facts and reconciliation/close evidence; PostgreSQL runtime recovery is not permission to rewrite accounting history. + +## References + +PostgreSQL Global Development Group. (2026a, August 13). *PostgreSQL 18.6, 17.11, 16.15, 15.19, 14.24 and 19 Beta 3 released*. https://www.postgresql.org/about/news/postgresql-186-1711-1615-1519-1424-and-19-beta-3-released-3365/ + +PostgreSQL Global Development Group. (2026b, August 13). *PostgreSQL 18.6 release notes*. https://www.postgresql.org/docs/18/release-18-6.html + +Docker, Inc. (2026). *Official postgres:18.6 image*. https://hub.docker.com/_/postgres diff --git a/tests/test_postgresql_runtime_baseline.py b/tests/test_postgresql_runtime_baseline.py new file mode 100644 index 00000000..86cc5d97 --- /dev/null +++ b/tests/test_postgresql_runtime_baseline.py @@ -0,0 +1,77 @@ +"""Repository contract for the supported PostgreSQL security baseline.""" + +from __future__ import annotations + +import re +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +CI_WORKFLOW = ROOT / ".github/workflows/ci.yml" +RUNTIME_BASELINE = ROOT / "docs/doctoring/POSTGRESQL_RUNTIME_BASELINE.md" +POSTGRES_IMAGE = ( + "postgres:18.6@sha256:4ef4dbc939d61acea57712655ddb4b4ab27419c913f94cca0cd57cb3ea3c2280" +) +POSTGRES_DIGEST = POSTGRES_IMAGE.partition("@")[2] + + +def _postgres_service_image(workflow: str) -> str: + """Return the direct image field from the Accounting Foundation PostgreSQL service.""" + service_match = re.search( + r"(?ms)^ postgres:\n(?P
(?:(?:^ {8,}\S.*|^\s*)\n?)*)", + workflow, + ) + if service_match is None: + return "" + image_match = re.search( + r"(?m)^ image: (?P