test(release): require retirement atomicity proof

This commit is contained in:
2026-07-22 15:28:04 +02:00
parent c29f1e9977
commit 71e74601cf
2 changed files with 29 additions and 3 deletions

View File

@@ -117,7 +117,12 @@ tools/checks/postgres-integration-check.py \
``` ```
`--reset-schema` drops and recreates the `public` schema before every module `--reset-schema` drops and recreates the `public` schema before every module
set. Use it only against this disposable database. set. Use it only against this disposable database. Before those permutations,
the check runs the Files credential-retirement atomicity proof in random,
test-owned schemas. The proof does not modify `public` and fails the release
gate if its PostgreSQL or full-stack dependencies are unavailable. The
`--skip-retirement-atomicity` option is only for bounded migration/smoke
diagnosis; do not use it for release evidence.
Stop the testbed: Stop the testbed:

View File

@@ -47,6 +47,14 @@ def main() -> int:
) )
parser.add_argument("--skip-migrate", action="store_true", help="Skip govoplan_core.commands.init_db.") parser.add_argument("--skip-migrate", action="store_true", help="Skip govoplan_core.commands.init_db.")
parser.add_argument("--skip-smoke", action="store_true", help="Skip govoplan_core.devserver --smoke.") parser.add_argument("--skip-smoke", action="store_true", help="Skip govoplan_core.devserver --smoke.")
parser.add_argument(
"--skip-retirement-atomicity",
action="store_true",
help=(
"Skip the isolated destructive-retirement atomicity proof. "
"Do not use this option for a release gate."
),
)
parser.add_argument("--python", default=sys.executable, help="Python executable to use. Defaults to the current interpreter.") parser.add_argument("--python", default=sys.executable, help="Python executable to use. Defaults to the current interpreter.")
parser.add_argument("--app-env", default="staging", help="APP_ENV value to pass to subprocesses. Defaults to staging.") parser.add_argument("--app-env", default="staging", help="APP_ENV value to pass to subprocesses. Defaults to staging.")
args = parser.parse_args() args = parser.parse_args()
@@ -59,8 +67,21 @@ def main() -> int:
module_sets = _parse_module_sets(args.module_set) module_sets = _parse_module_sets(args.module_set)
ok = True ok = True
if not args.skip_retirement_atomicity:
print("\n== destructive retirement atomicity ==", flush=True)
retirement_env = _check_env(
database_url=database_url,
modules="tenancy,access,admin,audit,files",
app_env=args.app_env,
)
retirement_env["GOVOPLAN_POSTGRES_DATABASE_URL"] = database_url
retirement_env["GOVOPLAN_REQUIRE_POSTGRES_RETIREMENT_PROOF"] = "1"
ok = _run(
[args.python, "-m", "unittest", "-v", "tests.test_postgres_retirement_atomicity"],
env=retirement_env,
) and ok
for name, modules in module_sets: for name, modules in module_sets:
print(f"\n== {name}: {modules} ==") print(f"\n== {name}: {modules} ==", flush=True)
if args.reset_schema: if args.reset_schema:
_reset_public_schema(database_url) _reset_public_schema(database_url)
env = _check_env(database_url=database_url, modules=modules, app_env=args.app_env) env = _check_env(database_url=database_url, modules=modules, app_env=args.app_env)
@@ -100,7 +121,7 @@ def _check_env(*, database_url: str, modules: str, app_env: str) -> dict[str, st
def _run(command: list[str], *, env: dict[str, str]) -> bool: def _run(command: list[str], *, env: dict[str, str]) -> bool:
print("+ " + " ".join(command)) print("+ " + " ".join(command), flush=True)
result = subprocess.run(command, cwd=ROOT, env=env, check=False) result = subprocess.run(command, cwd=ROOT, env=env, check=False)
if result.returncode != 0: if result.returncode != 0:
print(f"Command failed with exit code {result.returncode}: {' '.join(command)}", file=sys.stderr) print(f"Command failed with exit code {result.returncode}: {' '.join(command)}", file=sys.stderr)