diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..30ae911 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,125 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + verify: + name: verify release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master + with: + toolchain: 1.97.1 + components: clippy,rustfmt + - name: Check tag and version + shell: bash + run: | + set -euo pipefail + tag="${GITHUB_REF_NAME}" + if [[ ! "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9]+)?$ ]]; then + echo "invalid release tag: ${tag}" >&2 + exit 1 + fi + version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -n 1)" + if [[ "v${version}" != "${tag}" ]]; then + echo "Cargo version ${version} does not match tag ${tag}" >&2 + exit 1 + fi + if ! grep -Fq "## [${version}]" CHANGELOG.md; then + echo "CHANGELOG.md has no ${version} release heading" >&2 + exit 1 + fi + - run: cargo fetch --locked + - run: cargo fetch --manifest-path bench/sd-card-emulator/Cargo.toml --locked + - run: python3 -m unittest -v bench.adapters.test_questdb + - run: cargo fmt --all -- --check + - run: cargo clippy --all-targets --all-features --locked --offline -- -D warnings + - run: cargo test --all-targets --all-features --locked --offline + - run: cargo test --doc --all-features --locked --offline + - run: cargo fmt --manifest-path bench/sd-card-emulator/Cargo.toml -- --check + - run: cargo clippy --manifest-path bench/sd-card-emulator/Cargo.toml --all-targets --locked --offline -- -D warnings + - run: cargo test --manifest-path bench/sd-card-emulator/Cargo.toml --all-targets --locked --offline + - run: cargo package --locked --offline + + build: + name: build (${{ matrix.os }}) + needs: verify + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master + with: + toolchain: 1.97.1 + - run: cargo build --release --locked + - name: Build archive + id: archive + shell: bash + run: | + set -euo pipefail + host="$(rustc -vV | sed -n 's/^host: //p')" + name="ftw-${GITHUB_REF_NAME}-${host}" + mkdir -p "dist/${name}" + cp target/release/ftw README.md CHANGELOG.md LICENSE "dist/${name}/" + tar -C dist -czf "dist/${name}.tar.gz" "${name}" + ( + cd dist + shasum -a 256 "${name}.tar.gz" > "${name}.tar.gz.sha256" + ) + echo "name=${name}" >> "${GITHUB_OUTPUT}" + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: release-${{ steps.archive.outputs.name }} + path: | + dist/${{ steps.archive.outputs.name }}.tar.gz + dist/${{ steps.archive.outputs.name }}.tar.gz.sha256 + if-no-files-found: error + retention-days: 14 + + publish: + name: publish GitHub release + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + fetch-depth: 0 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + pattern: release-* + path: dist + merge-multiple: true + - name: Publish release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + sort -k2 dist/*.sha256 > dist/SHA256SUMS + prerelease=() + if [[ "${GITHUB_REF_NAME}" == *-* ]]; then + prerelease+=(--prerelease) + fi + gh release create "${GITHUB_REF_NAME}" \ + dist/*.tar.gz dist/SHA256SUMS \ + --repo "${GITHUB_REPOSITORY}" \ + --verify-tag \ + --title "FTWDB ${GITHUB_REF_NAME}" \ + --notes-from-tag \ + "${prerelease[@]}" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..358421c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +# Changelog + +This file records user-visible FTWDB changes. The project follows +[Semantic Versioning](https://semver.org/spec/v2.0.0.html) and keeps release +steps in [docs/releases.md](docs/releases.md). + +## [Unreleased] + +## [0.1.0-alpha.1] - 2026-07-21 + +First public evaluation release. + +### Added + +- Checksummed atomic point and catalog transactions with explicit durability. +- Valid, knowledge, and change time plus run and plan records. +- Immutable raw segments, persistent fixed and calendar rollups, late-data + rebuilds, and retention safety checks. +- Read-only integrity checks, verified snapshot backup, strict no-clobber + restore, and conservative raw-log salvage. +- File locking, commit identifiers for safe retry, bounded decoders, and + structured command errors. +- Deterministic energy and TSBS workloads, a sanitized real-data fixture, an + SD-card emulator, and result-verified VictoriaMetrics and QuestDB subsets. + +### Verified + +- CI passes on Ubuntu Linux and macOS with Rust 1.97.1. +- Process-kill, full-disk, sync-failure, corruption, recovery, CLI, backup, + restore, and salvage checks pass. +- The crate packages and verifies from its release source. + +### Known limits + +- FTWDB supports Unix targets only. +- The active log still rebuilds an in-memory read index, and physical raw-log + reclamation remains disabled. +- Physical target-board and SD-card power cuts during commits, long soak runs, + and remote backup policy remain open M4 work. +- Competitor adapters cover stated subsets and do not claim equal durability + or full model support. +- This release does not carry a production support or file-format stability + promise. + +[Unreleased]: https://github.com/srcfl/ftwdb/compare/v0.1.0-alpha.1...HEAD +[0.1.0-alpha.1]: https://github.com/srcfl/ftwdb/releases/tag/v0.1.0-alpha.1 diff --git a/Cargo.lock b/Cargo.lock index 9618e08..6948ca9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,7 +363,7 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "ftwdb" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "crc32fast", "criterion", diff --git a/Cargo.toml b/Cargo.toml index d12e4f8..430619a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "ftwdb" -version = "0.1.0" +version = "0.1.0-alpha.1" edition = "2024" rust-version = "1.97" description = "Forecasts, Telemetry & Watts: an SD-card-conscious embedded energy database" license = "Apache-2.0" repository = "https://github.com/srcfl/ftwdb" +readme = "README.md" +keywords = ["database", "time-series", "embedded", "energy"] +categories = ["database-implementations"] [package.metadata.ftwdb] support = "Unix targets only; Linux and macOS are tested in CI" diff --git a/README.md b/README.md index c2a8772..46b0ca4 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,15 @@ small Rust engine that is fast on time-window aggregates, survives abrupt power loss, and minimizes write amplification on SD cards and other constrained edge storage. +The current release is **v0.1.0-alpha.1**. It is an evaluation build for Unix +systems, not a production release. Download release archives from +[GitHub Releases](https://github.com/srcfl/ftwdb/releases), or install the CLI +from its immutable tag: + +```sh +cargo install --git https://github.com/srcfl/ftwdb --tag v0.1.0-alpha.1 --locked --bin ftw +``` + This repository currently contains the first executable storage slice: - append-only, checksummed atomic batches; @@ -78,6 +87,7 @@ cargo run --release -- salvage ./damaged-energy.ftwdb ./salvaged-energy.ftwdb - [Deterministic energy workload](docs/workload.md) - [Bootstrap benchmark result](docs/results/2026-07-21-macos-arm64.md) - [TSBS and robustness result](docs/results/2026-07-21-tsbs-robustness.md) +- [Release policy and process](docs/releases.md) - [Roadmap](docs/roadmap.md) ## License diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 0000000..c094719 --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,72 @@ +# Release policy and process + +FTWDB uses Semantic Versioning for the crate, tags, and GitHub releases. A tag +has the form `vMAJOR.MINOR.PATCH` or +`vMAJOR.MINOR.PATCH-(alpha|beta|rc).NUMBER`. The version in `Cargo.toml`, the +changelog heading, and the tag without its leading `v` must match exactly. + +## Release channels + +- **Alpha** is for evaluation. Core safety tests and packaging must pass. Known + hardware, scale, and support gaps must appear in the changelog. +- **Beta** requires target-board and physical SD-card tests, power cuts during + commits, a long soak run, and a tested operator backup policy. +- **Release candidate** freezes the public API and on-disk format for the + planned stable release. Only release-blocking fixes may follow. +- **Stable** requires the relevant roadmap exit, a stated compatibility and + support window, and no open release-blocking defect. + +Pre-release versions may break APIs and formats between releases. Stable +versions follow the compatibility promise published with that release. + +## Tag rules + +Release tags point to a commit on `main` after a release-prep pull request has +passed CI. Create annotated tags so the tag records its release notes. Sign a +tag only when the maintainer has a configured key whose public identity users +can check; never claim an unsigned tag is signed. + +Never move, reuse, or replace a published tag. If a release has a defect, mark +it as withdrawn in its notes and publish the next version. Delete a tag only to +remove exposed secrets or meet a legal requirement, and record that event. + +## Required alpha checks + +1. The working tree starts from current `origin/main`. +2. GitHub has no open release-blocking issue or pull request. +3. `Cargo.toml`, `Cargo.lock`, and `CHANGELOG.md` name the same version. +4. Format, Clippy, all targets and features, documentation tests, Python + adapter tests, and SD-emulator tests pass with locked dependencies. +5. `cargo package --locked` builds and verifies the crate. +6. Release notes list platform support and all known safety gaps. +7. The release-prep pull request passes Linux and macOS CI and merges to + `main` before the tag is created. + +## Automated publication + +Pushing a matching tag starts `.github/workflows/release.yml`. The workflow: + +1. checks the tag syntax and exact Cargo/changelog version; +2. repeats the release test and package checks; +3. builds native `ftw` archives on GitHub's Linux and macOS runners; +4. creates `SHA256SUMS` for the archives; and +5. creates a GitHub prerelease when the tag contains a pre-release suffix. + +The archive name includes the tag and Rust host target. Each archive contains +the `ftw` binary, README, changelog, and license. GitHub Actions records the +source commit and workflow run. Alpha releases stay on GitHub; publishing the +crate to crates.io needs a separate decision and an explicit publish step. + +## Maintainer steps + +1. Create a release-prep branch from `origin/main`. +2. Update the Cargo version, changelog, docs, and any compatibility statement. +3. Run all required checks, open a pull request, and wait for CI. +4. Merge the pull request and update local remote references. +5. Create an annotated tag on the exact merge commit with concise release + notes and known limits. +6. Push only that tag and watch the Release workflow to completion. +7. Check the GitHub release, both archives, `SHA256SUMS`, and the source commit. + +Do not create a release from a feature branch, a dirty tree, or a commit whose +CI result is unknown. diff --git a/docs/roadmap.md b/docs/roadmap.md index d8a6fab..9aaaf4c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap -## M0: durability and semantics — in progress +## M0: durability and semantics — initial exit complete - checksummed append frames and tail recovery; - explicit durability modes; @@ -9,7 +9,10 @@ - mergeable gauge/counter aggregate states; - property, corruption, and microbenchmark coverage. -Exit: recovery invariants pass under randomized truncation and bit flips. +Initial exit met: property tests cover every truncation point in the final +frame, complete checksum-corrupt frames stay intact and return corruption, and +earlier frame corruption never becomes tail recovery. Broader media-fault and +physical power-cut work continues in M4. ## M1: catalog, plans, and transactional records — complete