Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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: |
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
136 changes: 129 additions & 7 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -2410,12 +2486,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.

---

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
```

Expand Down Expand Up @@ -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
```

Expand Down
8 changes: 8 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion docs/first-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
5 changes: 4 additions & 1 deletion dploydb/redaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading