diff --git a/dev/postgres/README.md b/dev/postgres/README.md index c855e3a..fefd5da 100644 --- a/dev/postgres/README.md +++ b/dev/postgres/README.md @@ -117,7 +117,12 @@ tools/checks/postgres-integration-check.py \ ``` `--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: diff --git a/tools/checks/postgres-integration-check.py b/tools/checks/postgres-integration-check.py index 1d7c60e..036f0c3 100644 --- a/tools/checks/postgres-integration-check.py +++ b/tools/checks/postgres-integration-check.py @@ -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-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("--app-env", default="staging", help="APP_ENV value to pass to subprocesses. Defaults to staging.") args = parser.parse_args() @@ -59,8 +67,21 @@ def main() -> int: module_sets = _parse_module_sets(args.module_set) 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: - print(f"\n== {name}: {modules} ==") + print(f"\n== {name}: {modules} ==", flush=True) if args.reset_schema: _reset_public_schema(database_url) 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: - print("+ " + " ".join(command)) + print("+ " + " ".join(command), flush=True) result = subprocess.run(command, cwd=ROOT, env=env, check=False) if result.returncode != 0: print(f"Command failed with exit code {result.returncode}: {' '.join(command)}", file=sys.stderr)