From 9f27a823940a91a64f8727d3750aa0d519a1f170 Mon Sep 17 00:00:00 2001 From: ashutosh Date: Sun, 19 Jul 2026 17:12:12 +0530 Subject: [PATCH 1/2] fix: recover immutable release tag workflow --- .github/workflows/release.yml | 46 ++++++++++++++++++---- IMPLEMENTATION_PLAN.md | 58 +++++++++++++++++++++++++--- RELEASING.md | 8 ++++ tests/unit/test_release_readiness.py | 15 +++++++ 4 files changed, 113 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e57185..b8efd9c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,12 +3,21 @@ name: Publish release on: push: tags: ["v*"] + workflow_dispatch: + inputs: + tag: + description: Existing verified release tag to recover + required: true + type: string permissions: contents: read +env: + RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} + concurrency: - group: release-${{ github.ref }} + group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} cancel-in-progress: false jobs: @@ -21,9 +30,20 @@ jobs: id-token: write attestations: write steps: + - name: Validate release request + run: | + test -n "$RELEASE_TAG" + if [[ ! "$RELEASE_TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)((a|b|rc)(0|[1-9][0-9]*))?$ ]]; then + echo "release tag is not canonical: $RELEASE_TAG" >&2 + exit 1 + fi + if test "$GITHUB_EVENT_NAME" = "workflow_dispatch"; then + test "$GITHUB_REF" = "refs/heads/main" + fi - name: Check out signed tag uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: + ref: refs/tags/${{ env.RELEASE_TAG }} fetch-depth: 0 - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -37,13 +57,17 @@ jobs: - name: Verify tag identity and signature env: GH_TOKEN: ${{ github.token }} - TAG: ${{ github.ref_name }} + TAG: ${{ env.RELEASE_TAG }} run: | - git fetch origin main:refs/remotes/origin/main --no-tags + git fetch --force --no-tags origin \ + "refs/heads/main:refs/remotes/origin/main" \ + "refs/tags/${TAG}:refs/tags/${TAG}" python scripts/verify_release_tag.py "$TAG" object_type=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" --jq '.object.type') object_sha=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" --jq '.object.sha') test "$object_type" = "tag" + remote_target=$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/${object_sha}" --jq '.object.sha') + test "$remote_target" = "$(git rev-parse "${TAG}^{}")" test "$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/${object_sha}" --jq '.verification.verified')" = "true" - name: Sync locked environment run: | @@ -59,7 +83,7 @@ jobs: uv run pytest -q uv build uv run twine check dist/* - uv run python scripts/verify_distribution.py --tag "${GITHUB_REF_NAME}" + uv run python scripts/verify_distribution.py --tag "${RELEASE_TAG}" uv run python scripts/verify_pipx_install.py dist/dploydb-*.whl uv run python scripts/verify_clean_linux.py --wheel dist/dploydb-*.whl - name: Create checksums @@ -95,6 +119,8 @@ jobs: steps: - name: Check out release documentation uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: refs/tags/${{ env.RELEASE_TAG }} - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: @@ -112,7 +138,7 @@ jobs: - name: Create or refresh draft release env: GH_TOKEN: ${{ github.token }} - TAG: ${{ github.ref_name }} + TAG: ${{ env.RELEASE_TAG }} run: | version=${TAG#v} python scripts/release_notes.py "$version" --output release-notes.md @@ -136,6 +162,8 @@ jobs: steps: - name: Check out verification scripts uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: refs/tags/${{ env.RELEASE_TAG }} - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: @@ -153,7 +181,7 @@ jobs: skip-existing: true - name: Verify TestPyPI metadata, hashes, and installation env: - VERSION: ${{ github.ref_name }} + VERSION: ${{ env.RELEASE_TAG }} run: | version=${VERSION#v} python scripts/verify_registry.py "$version" --base-url https://test.pypi.org --dist dist @@ -193,6 +221,8 @@ jobs: steps: - name: Check out verification scripts uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: refs/tags/${{ env.RELEASE_TAG }} - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: @@ -204,7 +234,7 @@ jobs: path: dist - name: Verify PyPI metadata, hashes, and pipx installation env: - VERSION: ${{ github.ref_name }} + VERSION: ${{ env.RELEASE_TAG }} run: | version=${VERSION#v} python scripts/verify_registry.py "$version" --dist dist @@ -222,5 +252,5 @@ jobs: - name: Publish verified draft env: GH_TOKEN: ${{ github.token }} - TAG: ${{ github.ref_name }} + TAG: ${{ env.RELEASE_TAG }} run: gh release edit "$TAG" --draft=false --prerelease=true diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index a605b4d..bc041df 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -2410,12 +2410,58 @@ GitHub preparation evidence observed on 2026-07-19: enforcement. Protection includes administrators, requires linear history and resolved review conversations, and prohibits force-pushes and deletion. -Status: `LOCAL GATE COMPLETE; PUBLICATION PENDING`. No signed `v0.1.0` tag -exists. The dedicated SSH signing key, matching TestPyPI/PyPI pending Trusted -Publishers, release PR merge, registry uploads, and GitHub prerelease still -require their ordered external gates. Registry URLs, public hashes, signed-tag -evidence, and GitHub prerelease evidence must be appended here after those -gates pass. The release must not be called published before that verification. +#### Alpha 0.1.0 immutable-tag workflow recovery + +Planned on 2026-07-19 after failed publication run +[`29685288229`](https://github.com/recursiveway/dployDB/actions/runs/29685288229): + +- **Observed safe state:** annotated SSH-signed tag `v0.1.0` targets reviewed + `main` commit `bd7c0ed71afeecddc435a809c12eadeec35fef77`; local verification, + the release-tag helper, GitHub verification, and main CI all pass. The failed + workflow stopped at tag identity verification before building, attesting, + drafting a release, or reaching either registry environment. +- **Root cause:** `actions/checkout` materialized the pushed annotated tag as a + peeled local commit ref. The verifier correctly rejects lightweight tags but + inspected that checkout-created ref before the workflow explicitly fetched + the remote annotated tag object. +- **Recovery boundary:** do not move, delete, or recreate `v0.1.0`. Add a + protected `workflow_dispatch` recovery input that accepts only the canonical + tag, explicitly fetches its annotated object, and checks out that immutable + tagged source for every job that reads repository files. Normal `v*` pushes + remain the primary release trigger. +- **Security boundary:** remote GitHub API evidence must still prove that the + ref is an annotated tag with a verified signature and a target contained in + `origin/main`. Manual recovery must retain the same protected `testpypi` then + `pypi` approvals, least-privilege job permissions, exact artifact reuse, + checksums, provenance, public install verification, and prerelease finalizer. +- **Acceptance gate:** focused workflow/tag tests, actionlint, Ruff, format, + strict mypy for the verifier, the complete safety suite, and a protected PR + must pass. Dispatch `tag=v0.1.0` only from the merged `main` workflow and do + not call the release published until both registries and GitHub verify it. + +Local recovery evidence observed on 2026-07-19: + +- `git verify-tag v0.1.0` accepted the Ed25519 signature with fingerprint + `SHA256:CzffVvqABshVVWQq15jFeSP3MJ4odvbSJgF8P9vwDGY`; the helper proved + the tag matches package version `0.1.0` and is contained in `origin/main`. + GitHub reported `verified=true`, `reason=valid`, and target commit + `bd7c0ed71afeecddc435a809c12eadeec35fef77`. +- Pinned actionlint `v1.7.7` accepted the recovery workflow. A disposable Git + repository reproduced the runner's peeled commit ref, then proved the new + explicit fetch restores object type `tag` with the unchanged `bd7c0ed` + target. The disposable repository was removed afterward. +- Focused workflow, metadata, and SQLite safety tests passed (`29 passed`). + Ruff check and format check passed for all `98` Python files; strict mypy + passed for `34` package modules, `9` demo modules, and the tag verifier. +- `uv lock --check` retained all `65` resolved packages; the ignored editable + environment was refreshed from the unchanged lockfile. The complete suite + passed with unrestricted Docker and loopback access (`589 passed in + 172.75s`). `git diff --check` passed. + +Status: `LOCAL RECOVERY GATE COMPLETE; PROTECTED PR PENDING`. The immutable tag +has not moved. Registry URLs, public hashes, Trusted Publisher results, and +GitHub prerelease evidence must be appended here after the merged recovery run +passes. The release must not be called published before that verification. --- diff --git a/RELEASING.md b/RELEASING.md index 1a39e13..19a6293 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -66,6 +66,14 @@ different bytes. Keep the GitHub release as a draft, diagnose the failure, and rerun the immutable tag workflow when safe. PyPI files cannot be replaced or rolled back; a bad public release must be yanked and superseded by a new patch. +If a tag-triggered run fails before artifacts are built because the runner did +not materialize the annotated tag object, preserve the verified tag. Fix the +workflow through a protected pull request, merge it to `main`, and manually +dispatch `Publish release` from `main` with the existing canonical tag. The +recovery path explicitly fetches that remote tag object and checks out the +immutable tagged source; it must not be used to move or recreate a tag. The +normal protected TestPyPI and PyPI approvals still apply. + ## One-time repository setup - Make the repository public, enable Issues, private vulnerability reporting, diff --git a/tests/unit/test_release_readiness.py b/tests/unit/test_release_readiness.py index c414858..e1891ba 100644 --- a/tests/unit/test_release_readiness.py +++ b/tests/unit/test_release_readiness.py @@ -110,6 +110,21 @@ def test_workflows_pin_actions_and_limit_publish_permissions() -> None: assert "--draft=false --prerelease=true" in release +def test_release_workflow_recovers_an_existing_immutable_tag_from_main() -> None: + release = WORKFLOWS[1].read_text(encoding="utf-8") + + assert "workflow_dispatch:" in release + assert "Existing verified release tag to recover" in release + assert "release tag is not canonical" in release + assert 'test "$GITHUB_REF" = "refs/heads/main"' in release + assert '"refs/tags/${TAG}:refs/tags/${TAG}"' in release + assert 'test "$object_type" = "tag"' in release + assert 'test "$remote_target" = "$(git rev-parse "${TAG}^{}")"' in release + assert ".verification.verified" in release + assert release.count("ref: refs/tags/${{ env.RELEASE_TAG }}") == 4 + assert "${{ github.ref_name }}" not in release + + def _write_wheel(path: Path, version: str) -> None: with zipfile.ZipFile(path, "w") as archive: archive.writestr( From 4651c00afbda9be80f4610dc6eb732e26f85bb00 Mon Sep 17 00:00:00 2001 From: ashutosh Date: Sun, 19 Jul 2026 19:15:49 +0530 Subject: [PATCH 2/2] fix: stop treating PWD as a secret --- CHANGELOG.md | 13 +++- IMPLEMENTATION_PLAN.md | 78 ++++++++++++++++++++- README.md | 6 +- docs/first-run.md | 2 +- dploydb/redaction.py | 5 +- pyproject.toml | 2 +- scripts/verify_clean_linux.py | 7 ++ tests/integration/test_deploy_end_to_end.py | 12 +++- tests/test_redaction.py | 8 ++- tests/unit/test_deploy.py | 31 ++++++++ tests/unit/test_milestone8_docs.py | 2 +- tests/unit/test_release_readiness.py | 8 +-- uv.lock | 2 +- 13 files changed, 159 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc9c69..a4b8341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ All notable changes to DployDB are documented here. The project follows ## [Unreleased] +## [0.1.1] - 2026-07-19 + +### Fixed + +- Treat the standard uppercase Linux `PWD` environment variable as a working + directory rather than a password field, preventing exact-value redaction from + invalidating absolute release-manifest paths during deployment. +- Exercise the installed clean-Linux deployment with `PWD` equal to the project + root and require the active release to retain an absolute operation-log path. + ## [0.1.0] - 2026-07-19 ### Added @@ -32,5 +42,6 @@ All notable changes to DployDB are documented here. The project follows - During `0.x`, breaking public-contract changes may occur only in minor releases and will include explicit migration guidance. -[Unreleased]: https://github.com/recursiveway/dployDB/compare/v0.1.0...HEAD +[Unreleased]: https://github.com/recursiveway/dployDB/compare/v0.1.1...HEAD +[0.1.1]: https://github.com/recursiveway/dployDB/releases/tag/v0.1.1 [0.1.0]: https://github.com/recursiveway/dployDB/releases/tag/v0.1.0 diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index bc041df..b5c4ccd 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -77,15 +77,91 @@ The product must be useful after the hackathon. Do not build fake progress scree (`COMPLETE` on 2026-07-19). Slices 8A through 8C, the complete regression suite, installed-wheel audit, and clean-Linux README-only gate passed; Milestone 9 is the next allowed work. -- **Current release-readiness slice:** DployDB 0.1.0 Alpha publication +- **Current release-readiness slice:** DployDB 0.1.1 Alpha correction (`LOCAL GATE COMPLETE; PUBLICATION PENDING` on 2026-07-19). This bounded post-Milestone-8 slice owns the Apache-2.0 license, public package metadata, distribution-content boundary, community/release policies, release verification, and least-privilege GitHub/TestPyPI/PyPI workflows. It does not change deployment behavior, durable state, rollback rules, or any public CLI/JSON contract. +- **Completed corrective slice:** DployDB 0.1.1 Linux `PWD` redaction regression + (`COMPLETE` on 2026-07-19). A real installed-CLI run proved that the + standard `PWD` environment variable was misclassified as a password, causing + its absolute working-directory value to be registered as a secret and later + redacted out of typed release-manifest paths. The reported failure occurred + after verified backup and successful rehearsal, before production mutation. - **Dependency workflow:** Use uv for project dependencies and development commands. Support and verify `pipx install .` as the isolated end-user installation path. - **Repository outcome:** Every existing `.gitignore` rule remains, including `IMPLEMENTATION_PLAN.md`; `demo/.state/` was added for generated demo databases. +#### DployDB 0.1.1 `PWD` redaction corrective slice + +Planned on 2026-07-19: + +- **Owned modules:** `dploydb/redaction.py`, the focused redaction/subprocess/ + deployment regression tests, `scripts/verify_clean_linux.py`, and the bounded + 0.1.1 package-version/changelog/install-documentation metadata. Release state + models and transition semantics remain unchanged. +- **Detection boundary:** exempt exactly the standard uppercase environment key + `PWD` from secret-value registration while retaining `pwd`, `--pwd`, and + prefixed password-key detection. Text containing an explicit password-style + assignment remains redacted independently of exact-value registration. +- **Durable-state boundary:** prove a normal `PWD` equal to the project root is + never registered as a secret and therefore cannot rewrite absolute operation + log or application database paths during release persistence. +- **Focused gate:** execute a real bounded subprocess with `PWD` equal to the + temporary configured project root, then complete the deployment coordinator + to `active` and reread its strict release manifest. +- **Installed end-to-end gate:** run the built wheel in the clean Linux + Docker-in-Docker README flow with explicit `PWD=/workspace`; require + `{"ok": true, "outcome": "active"}`, valid absolute release paths, healthy + v2, and clean Docker resources. +- **Completion gate:** focused tests, full `pytest`, Ruff check and format, + mypy for `dploydb` and `demo`, distribution build/audit, installed-CLI gate, + and real Docker deployment must all pass before this slice is complete. + +Corrective-slice acceptance evidence observed on 2026-07-19: + +- The exact uppercase `PWD` key is now classified as a standard non-secret + environment key while `pwd`, `DATABASE_PWD`, and `--pwd` remain sensitive. + Explicit password-style assignment text is still covered by the independent + text-redaction patterns. +- Focused redaction and deployment regression command passed (`30 passed, 27 + deselected`). The regression executed a real bounded subprocess with `PWD` + equal to the configured project root, completed the coordinator to `active`, + reread the strict release manifest, and proved its operation-log and previous/ + new database-directory paths remained absolute beneath that root. +- The focused real-Docker golden path passed (`1 passed in 5.16s`) with the CLI + process working directory and explicit `PWD` both equal to the directory + containing the database, configuration, backups, and state. The deployment + activated v2 and the persisted release log path remained absolute. +- Final `.venv/bin/python -m pytest -q` passed all `594` tests in `178.52s` + against the version-bumped 0.1.1 source, including real Docker deployment, + rollback, restore, crash recovery, retention, and secret-redaction gates. +- `.venv/bin/ruff check .` and `.venv/bin/ruff format --check .` passed for all + `98` Python files. Strict mypy passed for all `34` package modules and all `9` + demo modules. Console and module version commands both reported `dploydb + 0.1.1`; `git diff --check` passed. +- `uv lock --check`, `uv sync --locked`, and the final locked-environment check + passed with `65` resolved and `58` checked packages. `uv build` produced + `dist/dploydb-0.1.1-py3-none-any.whl` and `dist/dploydb-0.1.1.tar.gz`; Twine + accepted both. +- `scripts/verify_distribution.py --tag v0.1.1` passed. The wheel contains `40` + files with SHA-256 + `2522dffc6880b58f428686b86ddbb63029860262241caa5f9fa9b3a785230dc7`; the + allowlisted source distribution contains `131` files with SHA-256 + `202137807f95ca6c0b4a195ba94d1ce35bf116afab60e581da65c32d0e7b8e63`. +- The isolated pipx audit installed the exact 0.1.1 wheel and passed every + required CLI, JSON, version, and uninstall check. +- `scripts/verify_clean_linux.py --wheel + dist/dploydb-0.1.1-py3-none-any.whl` passed inside privileged + Docker-in-Docker Linux with Python 3.12.13 and Docker 29.1.5. It explicitly + supplied `PWD=/workspace`, installed the wheel, returned `outcome: active`, + verified v2 and an absolute `/workspace/...` operation-log path, cleaned all + containers/networks, and proved uninstall preserved all `14` database, + backup, release, and event files. +- No release-state model, transition, backup, migration, cutover, rollback, or + recovery semantics changed. The correction is confined to the shared secret + key classifier, its regression gates, and the 0.1.1 release metadata/docs. + #### Milestone 0A acceptance evidence Observed on 2026-07-18: diff --git a/README.md b/README.md index 77d07d6..95893fd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ DployDB is being built as a deployment-safety tool for applications that use one SQLite database on one Linux server. > [!WARNING] -> DployDB 0.1.0 is Alpha software. Its safety gates are real and extensively +> DployDB 0.1.1 is Alpha software. Its safety gates are real and extensively > tested, but public interfaces may still change in documented `0.x` minor > releases. Read the [supported limits](docs/limitations.md), keep independent > backups, and prove the complete failure/restore flow on a non-production host @@ -29,7 +29,7 @@ docker compose version git clone https://github.com/recursiveway/dployDB.git cd dployDB -pipx install dploydb==0.1.0 +pipx install dploydb==0.1.1 dploydb --no-color version ``` @@ -157,7 +157,7 @@ off-server backup, and protected retention are implemented. Install the published Alpha CLI: ```bash -pipx install dploydb==0.1.0 +pipx install dploydb==0.1.1 dploydb version ``` diff --git a/docs/first-run.md b/docs/first-run.md index b4b359a..e580dd3 100644 --- a/docs/first-run.md +++ b/docs/first-run.md @@ -25,7 +25,7 @@ docker compose version Install the exact published Alpha version into an isolated environment: ```bash -pipx install dploydb==0.1.0 +pipx install dploydb==0.1.1 dploydb --no-color version ``` diff --git a/dploydb/redaction.py b/dploydb/redaction.py index b4a85ce..3fd294f 100644 --- a/dploydb/redaction.py +++ b/dploydb/redaction.py @@ -127,7 +127,10 @@ def is_sensitive_key(key: str) -> bool: """Return whether a mapping key conventionally contains a secret value.""" - candidate = key.strip().strip("\"'").removeprefix("--") + normalized = key.strip().strip("\"'") + if normalized == "PWD": + return False + candidate = normalized.removeprefix("--") return _SENSITIVE_KEY.fullmatch(candidate) is not None diff --git a/pyproject.toml b/pyproject.toml index 96a6c30..bde9ed8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "dploydb" -version = "0.1.0" +version = "0.1.1" description = "Deployment safety for SQLite applications" readme = "README.md" requires-python = ">=3.12" diff --git a/scripts/verify_clean_linux.py b/scripts/verify_clean_linux.py index 37e5988..9ea7b8f 100644 --- a/scripts/verify_clean_linux.py +++ b/scripts/verify_clean_linux.py @@ -198,6 +198,7 @@ def _demo_environment() -> dict[str, str]: "DPLOYDB_DEMO_UID": "0", "DPLOYDB_VERSION": "v1", "NO_COLOR": "1", + "PWD": "/workspace", "PYTHONPATH": "/workspace", } @@ -288,6 +289,12 @@ def _run_readme_demo(container: str, executable: str) -> dict[str, Any]: release_payload = json.loads(releases.stdout) if release_payload.get("active_release_id") != deployment_payload.get("release_id"): _stop("clean Linux release history does not select the deployed release") + selected_releases = release_payload.get("releases") + if not isinstance(selected_releases, list) or len(selected_releases) != 1: + _stop(f"clean Linux release history was unexpected: {release_payload!r}") + operation_log = selected_releases[0].get("log_path") + if not isinstance(operation_log, str) or not operation_log.startswith("/workspace/"): + _stop(f"clean Linux release log path was not absolute: {operation_log!r}") database_check = _exec( container, diff --git a/tests/integration/test_deploy_end_to_end.py b/tests/integration/test_deploy_end_to_end.py index 08a3812..d73649d 100644 --- a/tests/integration/test_deploy_end_to_end.py +++ b/tests/integration/test_deploy_end_to_end.py @@ -23,6 +23,8 @@ from dploydb.config import STARTER_CONFIGURATION from dploydb.models import new_operation_id +from dploydb.redaction import SecretRegistry +from dploydb.releases import ReleaseStore from dploydb.storage.local import LocalBackupStorage ROOT = Path(__file__).resolve().parents[2] @@ -390,6 +392,8 @@ def test_real_docker_deploy_flows_and_pre_activation_traffic_isolation( time.sleep(0.025) assert any(record["observed"] == "old" for record in monitor.records) + deployment_environment = _environment(data, production_port, release) + deployment_environment["PWD"] = str(tmp_path) result = subprocess.run( [ str(ROOT / ".venv" / "bin" / "dploydb"), @@ -401,8 +405,8 @@ def test_real_docker_deploy_flows_and_pre_activation_traffic_isolation( "--json", "--non-interactive", ], - cwd=ROOT, - env=_environment(data, production_port, release), + cwd=tmp_path, + env=deployment_environment, capture_output=True, text=True, timeout=300, @@ -413,6 +417,10 @@ def test_real_docker_deploy_flows_and_pre_activation_traffic_isolation( assert payload["outcome"] == expected_outcome assert payload["traffic_activated"] is (scenario == "success") assert payload["recovery_required"] is False + release_store = ReleaseStore(tmp_path / "state", secrets=SecretRegistry()) + persisted_release, _pointers = release_store.lookup_history_release(payload["release_id"]) + assert persisted_release.operation_log_path.is_absolute() + assert persisted_release.operation_log_path.is_relative_to(tmp_path) if scenario == "success": _wait_for_release(production_port, "v2") diff --git a/tests/test_redaction.py b/tests/test_redaction.py index 9592394..345fd7e 100644 --- a/tests/test_redaction.py +++ b/tests/test_redaction.py @@ -46,6 +46,9 @@ def test_empty_secret_values_are_ignored_without_corrupting_output() -> None: "key", ( "password", + "pwd", + "DATABASE_PWD", + "--pwd", "DATABASE_PASSWORD", "api-key", "S3_SECRET_ACCESS_KEY", @@ -61,7 +64,10 @@ def test_sensitive_keys_are_recognized(key: str) -> None: assert is_sensitive_key(key) -@pytest.mark.parametrize("key", ("project", "candidate_port", "secretary", "tokenizer")) +@pytest.mark.parametrize( + "key", + ("PWD", "project", "candidate_port", "secretary", "tokenizer"), +) def test_non_sensitive_keys_are_not_over_redacted(key: str) -> None: assert not is_sensitive_key(key) diff --git a/tests/unit/test_deploy.py b/tests/unit/test_deploy.py index 580bbdd..ce72638 100644 --- a/tests/unit/test_deploy.py +++ b/tests/unit/test_deploy.py @@ -72,6 +72,7 @@ CapturedOutput, CommandOutcome, CommandResult, + SubprocessRunner, TerminationReason, ) from dploydb.traffic import TrafficAction, TrafficHookResult @@ -868,6 +869,36 @@ def test_successful_coordinator_activates_checked_release_in_exact_order( assert "traffic_activation_started" in {event.stage for event in events} +def test_standard_pwd_environment_preserves_absolute_release_paths(tmp_path: Path) -> None: + config_path, loaded = loaded_project(tmp_path) + command = SubprocessRunner(secrets=loaded.secrets).run( + [sys.executable, "-c", "pass"], + timeout_seconds=5, + environment={"PWD": str(tmp_path)}, + working_directory=tmp_path, + ) + assert command.succeeded + assert command.working_directory == str(tmp_path) + + selected = harness(loaded, config_path) + result = run_deploy(loaded, config_path, selected) + + persisted = ReleaseStore( + loaded.config.state_directory, + secrets=loaded.secrets, + ).read_manifest(result.release.release_id) + assert result.active is True + assert persisted.status is DeploymentState.ACTIVE + assert persisted.operation_log_path.is_absolute() + assert persisted.operation_log_path.is_relative_to(tmp_path) + assert persisted.previous_application is not None + assert persisted.previous_application.database_directory.is_absolute() + assert persisted.previous_application.database_directory.is_relative_to(tmp_path) + assert persisted.new_application is not None + assert persisted.new_application.database_directory.is_absolute() + assert persisted.new_application.database_directory.is_relative_to(tmp_path) + + def test_required_remote_final_backup_is_verified_before_production_migration( tmp_path: Path, ) -> None: diff --git a/tests/unit/test_milestone8_docs.py b/tests/unit/test_milestone8_docs.py index a2edeba..3a19521 100644 --- a/tests/unit/test_milestone8_docs.py +++ b/tests/unit/test_milestone8_docs.py @@ -38,7 +38,7 @@ def test_readme_quick_start_uses_the_installed_cli_and_parses_real_json() -> Non readme = (ROOT / "README.md").read_text(encoding="utf-8") for required in ( - "pipx install dploydb==0.1.0", + "pipx install dploydb==0.1.1", "git clone https://github.com/recursiveway/dployDB.git", "python3 -m demo.prepare", "dploydb --no-color doctor --deep", diff --git a/tests/unit/test_release_readiness.py b/tests/unit/test_release_readiness.py index e1891ba..71bd6a3 100644 --- a/tests/unit/test_release_readiness.py +++ b/tests/unit/test_release_readiness.py @@ -1,4 +1,4 @@ -"""Release-readiness contracts for DployDB 0.1.0 Alpha.""" +"""Release-readiness contracts for DployDB 0.1.1 Alpha.""" from __future__ import annotations @@ -29,7 +29,7 @@ def test_public_package_metadata_declares_alpha_license_owner_and_urls() -> None assert metadata["build-system"]["requires"] == ["hatchling>=1.27"] assert project["name"] == "dploydb" - assert project["version"] == "0.1.0" + assert project["version"] == "0.1.1" assert project["license"] == "Apache-2.0" assert project["license-files"] == ["LICENSE", "NOTICE"] assert project["authors"] == [{"name": "RecursiveWay"}] @@ -80,8 +80,8 @@ def test_license_notice_and_public_policies_are_present() -> None: def test_readme_is_explicitly_alpha_and_publicly_installable() -> None: readme = (ROOT / "README.md").read_text(encoding="utf-8") - assert "DployDB 0.1.0 is Alpha software" in readme - assert "pipx install dploydb==0.1.0" in readme + assert "DployDB 0.1.1 is Alpha software" in readme + assert "pipx install dploydb==0.1.1" in readme assert "Apache License 2.0" in readme assert "1.0.0" in readme assert "SECURITY.md" in readme diff --git a/uv.lock b/uv.lock index ca6542e..e1e1253 100644 --- a/uv.lock +++ b/uv.lock @@ -298,7 +298,7 @@ wheels = [ [[package]] name = "dploydb" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "boto3" },