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
32 changes: 32 additions & 0 deletions .cargo/audit.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# cargo-audit configuration.
#
# `cargo audit` reads this file from `.cargo/audit.toml` (relative to the repo
# root). The CI `audit` job uses the `rustsec/audit-check` GitHub Action, which
# does NOT read this file — it takes the same advisory IDs via its `ignore:`
# input (see .github/workflows/ci.yml). Keep the two lists in sync.
#
# Policy: we ONLY ignore advisories whose entire dependency path is confined to
# `crates/compare` (powdb-compare), which is `publish = false` — a benchmark-only
# crate that pulls `postgres = "0.19"` and `mysql = "25"` purely to compare PowDB
# against Postgres/MySQL. None of these crates ship in powdb-storage / powdb-query
# / powdb-server / powdb-cli / powdb-auth / powdb-backup, so no published artifact
# is affected. We do NOT broadly disable auditing — only the three postgres DoS
# advisories below are vulnerabilities (the action's hard-fail trigger); the
# remaining unmaintained/unsound entries are non-failing warnings and are left
# visible on purpose.
[advisories]
ignore = [
# postgres-protocol < 0.6.12: unbounded SCRAM iteration count → CPU-exhaustion
# DoS from a malicious server. Path: postgres-protocol → tokio-postgres →
# postgres → powdb-compare (publish=false, bench-only). No shipping crate.
"RUSTSEC-2026-0179",

# postgres-protocol < 0.6.12: panic decoding a malformed `hstore` value → DoS.
# Same powdb-compare-only path. No shipping crate.
"RUSTSEC-2026-0180",

# tokio-postgres < 0.7.18: panic on a `DataRow` with fewer fields than columns
# → DoS. Path: tokio-postgres → postgres → powdb-compare (publish=false,
# bench-only). No shipping crate.
"RUSTSEC-2026-0178",
]
80 changes: 80 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,53 @@ jobs:
fi
echo "MSRV consistency OK."

msrv-build:
name: MSRV build (cargo +<msrv> check --workspace --locked)
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
RUSTFLAGS: "-C target-cpu=x86-64-v2"
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Read MSRV from Cargo.toml
id: msrv
run: |
set -euo pipefail
MSRV=$(grep -E '^rust-version' Cargo.toml | head -1 | sed -E 's/.*"([0-9]+\.[0-9]+(\.[0-9]+)?)".*/\1/')
if [ -z "$MSRV" ]; then
echo "::error::Could not parse rust-version from Cargo.toml"
exit 1
fi
echo "version=$MSRV" >> "$GITHUB_OUTPUT"
echo "MSRV is $MSRV"

- name: Install MSRV toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: ${{ steps.msrv.outputs.version }}

- name: Cargo cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: msrv-build-${{ runner.os }}-${{ steps.msrv.outputs.version }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
msrv-build-${{ runner.os }}-${{ steps.msrv.outputs.version }}-

# `--locked` so a newer-than-MSRV language feature (or a Cargo.lock that
# only resolves on newer Rust) can't slip past the doc-only consistency
# check above. This compiles the whole workspace on the real MSRV
# toolchain.
- name: cargo +MSRV check --workspace --locked
run: cargo +${{ steps.msrv.outputs.version }} check --workspace --locked

examples-smoke:
name: examples smoke (terraform validate + compose config + dev.sh)
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -234,3 +281,36 @@ jobs:
uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Ignore advisories whose ENTIRE dependency path is confined to
# crates/compare (powdb-compare), which is `publish = false` — a
# benchmark-only crate that pulls `postgres`/`mysql` to compare PowDB
# against other engines. No shipping crate (storage/query/server/cli/
# auth/backup) is affected. Keep this list in sync with
# `.cargo/audit.toml` (which the local `cargo audit` reads).
# RUSTSEC-2026-0179 postgres-protocol SCRAM DoS (→ powdb-compare)
# RUSTSEC-2026-0180 postgres-protocol hstore DoS (→ powdb-compare)
# RUSTSEC-2026-0178 tokio-postgres DataRow DoS (→ powdb-compare)
ignore: RUSTSEC-2026-0179,RUSTSEC-2026-0180,RUSTSEC-2026-0178

ts-client-version:
name: ts-client version consistency
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

# Guard against the version drift we hit once: the CLIENT_VERSION
# constant sent in the handshake (in src/ — the source of truth; dist/ is
# gitignored and built from it at publish time) must match package.json.
# Uses the runner's preinstalled Node — no extra action, no install.
- name: Assert package.json === CLIENT_VERSION
working-directory: clients/ts
run: |
PKG_VER="$(node -p "require('./package.json').version")"
SRC_VER="$(grep -oE 'CLIENT_VERSION = "[^"]+"' src/index.ts | head -1 | sed -E 's/.*"([^"]+)".*/\1/')"
echo "package.json=$PKG_VER src CLIENT_VERSION=$SRC_VER"
if [ "$PKG_VER" != "$SRC_VER" ]; then
echo "::error::TS client version drift: package.json=$PKG_VER but src CLIENT_VERSION=$SRC_VER — bump CLIENT_VERSION in clients/ts/src/index.ts to match package.json, then 'npm run build'"
exit 1
fi
43 changes: 42 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,50 @@ jobs:
powdb-cli-${{ matrix.suffix }}
powdb-server-${{ matrix.suffix }}

smoke-release:
name: durability smoke (release binary)
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
# Portable target-cpu so the binary we smoke matches the published one.
RUSTFLAGS: "-C target-cpu=x86-64-v2"

steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Install Rust toolchain (stable)
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable

- name: Cargo cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
# Share the linux-x86_64 build cache so we reuse the binaries the
# `build` job already compiled instead of rebuilding from scratch.
key: release-x86_64-unknown-linux-gnu-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
release-x86_64-unknown-linux-gnu-

- name: Build release binaries (powdb-cli + powdb-server)
run: cargo build --release -p powdb-cli -p powdb-server

# The gate whose absence caused the v0.4.1–0.4.3 data-loss yanks:
# README PowQL flow + unique-constraint enforcement + kill -9/restart
# WAL-replay recovery, all over the wire against the freshly built
# release binary. Must print SMOKE-RELEASE: ALL-PASS.
- name: Durability smoke against release binary
run: bash scripts/smoke-release.sh

release:
name: create release
needs: build
needs: [build, smoke-release]
runs-on: ubuntu-24.04
permissions:
contents: write
Expand Down
57 changes: 57 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **RBAC now enforces the full permission lattice.** The server maps each
statement to the capability it needs — reads → `Read`, row mutations
(insert/update/delete/upsert) → `Write`, schema changes (create/alter/drop
type or view) → `Ddl` — and checks it against the user's role. The
`readwrite` role now explicitly carries `Ddl` (application users create and
evolve their own tables), so **this is behavior-preserving**: readwrite and
admin keep full access, readonly is still read-only, and any authenticated
role may still run read-only queries. `Admin` remains reserved for user/role
management (CLI-only).
- **Automated post-publish durability smoke** (`scripts/smoke-release.sh`),
wired as a required gate in `release.yml`: installs the built binaries, runs
the README PowQL flow over the wire, then `kill -9`s the server and restarts
it to assert WAL replay recovers every row and the unique constraint still
holds. This is the exact gate whose absence caused the v0.4.1–0.4.3 data-loss
yanks; it now runs on every tagged release.
- **MSRV build job** in CI that compiles the workspace with the pinned 1.93
toolchain (the previous job only checked that the version string matched the
docs).

### Changed
- **Resource-limit errors now reach remote clients verbatim.** Sort, join, and
per-query memory-budget errors (e.g. "sort input exceeds row limit — add a
LIMIT clause") were being masked to the generic "query execution error" by
the wire sanitizer. They carry actionable guidance and leak no internal
state, so they are now on the safe-error allowlist.

### Fixed
- CLI `--help` showed a remote one-shot example using a `|` pipe operator that
PowQL does not have; corrected to the whitespace-pipeline syntax so the
example runs as written.
- CI `cargo audit` no longer fails on three `postgres`-only RUSTSEC advisories
whose entire dependency path is confined to the `publish = false`
`powdb-compare` benchmark crate (scoped ignore in `.cargo/audit.toml` + the
audit action, with provenance comments). No shipping crate is affected.
- Dockerfile dependency-cache stage now copies the `powdb-auth` and
`powdb-backup` manifests it was silently missing, so the cached layer covers
the full server/CLI dependency closure.
- TypeScript client version drift: the `CLIENT_VERSION` handshake constant,
the built `dist`, and the README now all agree with `package.json` (0.5.0),
and a CI job asserts they can't diverge again.

### Internal
- Documented `panic = "abort"` as a deliberate **crash-only** design: on a
panic the server exits fast and a supervisor restarts it, with WAL replay
recovering to a consistent state — safer for a stateful engine than
unwinding into a poisoned lock. Every deploy example is confirmed to run
under an auto-restart policy, and the requirement is now documented in
`examples/deploy/README.md`.
- Promoted the CI lint policy into `[workspace.lints]` (`clippy::all = deny`)
so `cargo clippy` fails locally with the same rules CI enforces.
- Removed ~190 LOC of dead, never-wired snapshot-isolation scaffolding
(`storage::mvcc`, `storage::tx`) that was shipping in the `powdb-storage`
crate; the live engine uses `RwLock` concurrency.
- Refreshed stale `powdb-auth` doc-comments that claimed the crate was "not
yet wired into the server or CLI" — it has enforced auth/RBAC since 0.4.6.

## [0.4.7] - 2026-06-10

### Added
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ If the planner emits a different shape for the same logical operation, the fast

## CI

Two workflow files:
- `.github/workflows/ci.yml` — clippy + fmt + test (+ ASan, miri, fuzz, cargo audit, MSRV, examples). **Required status checks on `main`.**
Three workflow files:
- `.github/workflows/ci.yml` — clippy + fmt + test + doctest (+ ASan, miri, cargo audit, MSRV, examples smoke). **Required status checks on `main`.**
- `.github/workflows/fuzz.yml` — cargo-fuzz targets. **Separate** from ci.yml (PR-triggered + nightly cron at 07:00 UTC + `workflow_dispatch`); not part of the required check set above.
- `.github/workflows/bench.yml` — criterion microbenchmark suite. **Manual-only (`workflow_dispatch`), NOT a required gate.** Runs on a Depot single-tenant runner (`depot-ubuntu-24.04-4`, tmpfs temp DBs), so numbers are comparable run-to-run; `baseline/main.json` must only ever be rebaselined from a Depot run of this workflow, never from a laptop. `powdb-bench` only depends on `powdb-storage`+`powdb-query`, so it gates nothing the normal suite doesn't already cover. Run it on demand: `gh workflow run bench.yml`.
28 changes: 27 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,39 @@ blake3 = "1"
# - lto = true: enables cross-crate inlining (executor → storage → page).
# This is the load-bearing flag — every fast path crosses crate boundaries.
# - codegen-units = 1: single codegen unit, slower compile, faster runtime.
# - panic = "abort": no unwinding tables on hot branches; smaller binary.
# - strip = "symbols": shrink binary, no impact on perf.
# Estimated overall impact vs default release: 5-15% across-the-board.
#
# panic = "abort" — DELIBERATE crash-only design, not just a size/perf knob.
# PowDB holds shared, mutable engine state (catalog + page cache) behind a
# single `RwLock<Engine>` shared across every connection. If a query thread
# were to panic mid-mutation while unwinding, it would (a) poison the lock,
# bricking every subsequent request with "lock poisoned", and (b) potentially
# leave the in-memory catalog/page state inconsistent with the on-disk WAL,
# with no in-process path back to a consistent state. Aborting instead turns a
# panic into a fast, clean process exit: a supervisor (Docker `restart`,
# systemd `Restart=on-failure`, Fly auto-restart, ECS task restart) brings the
# server back up, and WAL replay deterministically recovers to the last
# durable, consistent state — a path that is exercised by the durability test
# suite (crates/query/tests/durability.rs) on every CI run. For a stateful
# engine, "crash and recover from the log" is safer and more available than
# "limp along on possibly-corrupt shared state". This is why a process
# supervisor with auto-restart is REQUIRED in production (see
# examples/deploy/README.md and the Production checklist in README.md).
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
opt-level = 3
debug = false
strip = "symbols"

# Lint policy promoted from CI into the workspace so `cargo clippy` fails
# locally with the same rules CI enforces (`-D warnings`), instead of only
# surfacing after a push. Inherited by every crate via `[lints] workspace =
# true`. `clippy::all = deny` mirrors the existing CI gate exactly — the code
# already passes `clippy -D warnings`, so promoting the same group to `deny`
# introduces no new failures; it just moves the feedback from post-push to the
# local `cargo clippy`.
[workspace.lints.clippy]
all = { level = "deny", priority = -1 }
15 changes: 13 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,36 @@ FROM rust:1.95-slim-bookworm AS builder

WORKDIR /src

# Cache deps separately from source by copying manifests first
# Cache deps separately from source by copying manifests first.
# powdb-server depends on storage + query + auth; powdb-cli additionally pulls
# backup. The dep-cache stage must include the FULL dependency closure of the
# crates we build, or the cache layer is silently incomplete (the `|| true`
# below would hide the miss and re-resolve every build).
COPY Cargo.toml Cargo.lock ./
COPY crates/storage/Cargo.toml crates/storage/Cargo.toml
COPY crates/query/Cargo.toml crates/query/Cargo.toml
COPY crates/server/Cargo.toml crates/server/Cargo.toml
COPY crates/cli/Cargo.toml crates/cli/Cargo.toml
COPY crates/auth/Cargo.toml crates/auth/Cargo.toml
COPY crates/backup/Cargo.toml crates/backup/Cargo.toml

# Create empty src trees so cargo can resolve+download deps without source
RUN mkdir -p crates/storage/src crates/query/src crates/server/src crates/cli/src \
crates/auth/src crates/backup/src \
&& echo 'pub fn _stub() {}' > crates/storage/src/lib.rs \
&& echo 'pub fn _stub() {}' > crates/query/src/lib.rs \
&& echo 'pub fn _stub() {}' > crates/server/src/lib.rs \
&& echo 'pub fn _stub() {}' > crates/auth/src/lib.rs \
&& echo 'pub fn _stub() {}' > crates/backup/src/lib.rs \
&& echo 'fn main() {}' > crates/server/src/main.rs \
&& echo 'fn main() {}' > crates/cli/src/main.rs \
&& cargo build --release -p powdb-server 2>/dev/null || true

# Now copy real source and build for real
COPY crates ./crates
RUN touch crates/storage/src/lib.rs crates/query/src/lib.rs crates/server/src/lib.rs crates/server/src/main.rs crates/cli/src/main.rs \
RUN touch crates/storage/src/lib.rs crates/query/src/lib.rs crates/server/src/lib.rs \
crates/auth/src/lib.rs crates/backup/src/lib.rs \
crates/server/src/main.rs crates/cli/src/main.rs \
&& cargo build --release -p powdb-server

# ─── Runtime ────────────────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Full language reference: [docs/POWQL.md](https://github.com/zvndev/powdb/blob/ma
cargo install powdb-cli
cargo install powdb-server

# TypeScript client (Node 18+) — versions independently of the server crates (currently 0.3.x)
# TypeScript client (Node 18+) — versions independently of the server crates (see npmjs.com/package/@zvndev/powdb-client for the current version)
npm install @zvndev/powdb-client

# Prebuilt binaries (linux x86_64, macos aarch64)
Expand Down
2 changes: 1 addition & 1 deletion clients/ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
} from "./typed.js";

/** Client library version. Compared to the server's reported version. */
export const CLIENT_VERSION = "0.4.0";
export const CLIENT_VERSION = "0.5.0";

export type QueryResult =
| { kind: "rows"; columns: string[]; rows: string[][] }
Expand Down
3 changes: 3 additions & 0 deletions crates/auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ thiserror.workspace = true

[dev-dependencies]
tempfile = "3"

[lints]
workspace = true
9 changes: 6 additions & 3 deletions crates/auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//! `powdb-auth` — argon2id password hashing and a persisted user/role store.
//!
//! Slice 1 of PowDB's RBAC epic. This crate is **additive**: it is a tested
//! library + data model and is not yet wired into the server or CLI, so it
//! does not change any runtime behavior.
//! Provides PowDB's RBAC primitives: the [`role`] permission lattice, the
//! [`hash`] argon2id password hashing, and the persisted [`store::UserStore`].
//! These are live in production: `powdb-server` loads the [`store::UserStore`]
//! at startup and enforces the [`role`] lattice on every query
//! (`crates/server/src/handler.rs`), and `powdb-cli` manages users via the
//! `useradd`/`passwd`/`userdel` subcommands.

pub mod error;
pub mod hash;
Expand Down
Loading
Loading