Skip to content

Commit 8217994

Browse files
committed
docs: prepare governance docs and release notes for 0.1.0
Add CONTRIBUTING.md, SECURITY.md, and VERSIONING.md so contributors and consumers have a public policy for how to contribute, how to report a vulnerability, and what the API/format stability promise actually covers pre-1.0. Update the CHANGELOG and README to describe the 0.1.0 release as shipped — the security model, the data-vs-API versioning split, and the newly named open-parameter errors — and tidy the options.rs budget doc comments to match current enforcement rather than a since-landed future slice. Wire the release workflows to gate a tag on a matching CHANGELOG section and to build the GitHub release notes from that section instead of auto-generated commit-subject notes, via a shared scripts/ci/changelog_section.sh.
1 parent 8bbd6d7 commit 8217994

9 files changed

Lines changed: 611 additions & 78 deletions

File tree

.github/workflows/release-validate.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ on:
1818
is_full_release:
1919
description: "true when the version carries no prerelease suffix"
2020
value: ${{ jobs.validate.outputs.is_full_release }}
21+
base_version:
22+
description: "Tag version with any prerelease suffix stripped — the CHANGELOG heading"
23+
value: ${{ jobs.validate.outputs.base_version }}
2124

2225
permissions:
2326
contents: read
@@ -29,6 +32,7 @@ jobs:
2932
outputs:
3033
version: ${{ steps.version.outputs.version }}
3134
is_full_release: ${{ steps.version.outputs.is_full_release }}
35+
base_version: ${{ steps.version.outputs.base_version }}
3236
steps:
3337
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3438
with:
@@ -73,3 +77,15 @@ jobs:
7377
fi
7478
7579
echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT"
80+
echo "base_version=$TAG_BASE" >> "$GITHUB_OUTPUT"
81+
82+
# A release with no changelog entry is a release nobody can read. The gate
83+
# lives here, in the shared validator, so the tag push and the dispatched
84+
# publish cannot disagree about whether it was satisfied — and so it fails
85+
# at the tag, before the suite runs, rather than after publishing.
86+
#
87+
# A prerelease is checked against its base version: `v0.1.0-beta.1` needs
88+
# the `## [0.1.0]` section it is a candidate for, so the notes are written
89+
# once and rehearsed by every beta.
90+
- name: Require a changelog section
91+
run: bash scripts/ci/changelog_section.sh "${{ steps.version.outputs.base_version }}"

.github/workflows/release.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,36 @@ jobs:
163163
set -euo pipefail
164164
bash scripts/ci/verify_release_artifacts.sh ./artifacts "$VERSION"
165165
166+
# The changelog is the release notes. Auto-generated notes list commit
167+
# subjects, which describe what was done to the code; the changelog
168+
# describes what changed for the person upgrading. `validate-version`
169+
# has already proved this section exists and is non-empty.
170+
- name: Build release notes from the changelog
171+
env:
172+
TAG: ${{ inputs.tag }}
173+
BASE: ${{ needs.validate-version.outputs.base_version }}
174+
run: |
175+
set -euo pipefail
176+
PREVIOUS_TAG=$(git tag --sort=-v:refname 'v*' | grep -v "^${TAG}$" | head -n 1 || true)
177+
178+
bash scripts/ci/changelog_section.sh "$BASE" /tmp/changelog-section.md
179+
180+
{
181+
cat /tmp/changelog-section.md
182+
echo
183+
if [[ -n "$PREVIOUS_TAG" ]]; then
184+
echo "**Full diff:** https://github.com/nodedb-lab/pagedb/compare/${PREVIOUS_TAG}...${TAG}"
185+
else
186+
echo "**Tree:** https://github.com/nodedb-lab/pagedb/tree/${TAG}"
187+
fi
188+
} > /tmp/release-notes.md
189+
166190
- name: Create GitHub Release
167191
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
168192
with:
169193
tag_name: ${{ inputs.tag }}
170194
name: pagedb ${{ needs.validate-version.outputs.version }}
171-
generate_release_notes: true
195+
body_path: /tmp/release-notes.md
172196
draft: false
173197
prerelease: ${{ needs.validate-version.outputs.is_full_release != 'true' }}
174198
files: artifacts/*

CHANGELOG.md

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,39 @@
11
# Changelog
22

3-
All notable changes to this project are documented here. The format is based on
4-
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5-
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3+
All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
64

7-
## [Unreleased]
5+
## [0.1.0] - 2026-07-28
86

9-
The first stable release. Pre-releases are published as `0.1.0-beta.N`; the
10-
entries below describe `0.1.0` as a whole rather than deltas against a shipped
11-
version, since none exists yet.
7+
The first release. Pre-releases were published as `0.1.0-beta.N`; the entries below describe `0.1.0` as a whole rather than deltas against a shipped version, since none exists yet.
128

139
### Added
1410

15-
- **B+ tree surface** — sorted `bytes → bytes` store with copy-on-write shadow
16-
paging, A/B headers, ACID transactions, range scans, monotonic append, and
17-
bulk load. Reads return `Bytes` borrowed from the page cache; scans come
18-
bounded (`scan_from`, `scan_prefix_from`) or materialising.
19-
- **Segment File API** — engine-owned, append-mostly, atomically sealed
20-
encrypted files for formats that own their own layout (vectors, columnar
21-
blocks, FTS postings, R-trees).
22-
- **Encrypted pager** — every persistent page is authenticated; AES-256-GCM and
23-
ChaCha20-Poly1305 cipher dispatch with per-page `cipher_id` for cipher
24-
agility. SIEVE page cache with bounded memory budgets.
25-
- **Key hierarchy** — KEK → MK → DEK derivation, stateful nonce generation with
26-
a durable anchor (nonce reuse impossible under one key), `zeroize` on all key
27-
material.
28-
- **Realm isolation**`RealmId` bound into AEAD AAD on every persistent page;
29-
misrouted reads fail tag verification. Per-realm quotas.
30-
- **Cross-platform VFS** — Linux (`io_uring`), Windows (IOCP), macOS/iOS
31-
(Grand Central Dispatch), Android, WASM/OPFS, and WASI backends, plus a
32-
tokio thread-pool fallback and an in-memory backend, with format-bit identity
33-
across targets. Backends may complete a positional read or write in several
34-
calls; PageDB finishes the caller's whole buffer before treating authenticated
35-
metadata as present or durable. On Linux the backend choice is made at run
36-
time: where the kernel refuses an `io_uring` ring — `RLIMIT_MEMLOCK`
37-
exhausted, seccomp blocking `io_uring_setup`, a kernel older than 5.1 — the
38-
open logs a warning and continues on the thread pool rather than failing.
39-
Every native backend shares one advisory-lock implementation, so processes on
40-
different backends still exclude each other on the same store.
41-
- **Snapshots**`snapshot_to`, `restore_from`, and incremental apply, each
42-
authenticated against the exact state its manifest describes. Destinations
43-
must be empty; malformed or incomplete artifacts fail closed.
44-
- **Recovery** — open-flow GC, apply-journal replay, deep-walk `fsck`, and the
45-
`pagedb-fsck` binary.
46-
- **Online rekey** — rekey the database under a new key with mixed-cipher and
47-
mixed-epoch page coexistence (no full-file migration).
11+
- **B+ tree surface** — sorted `bytes → bytes` with copy-on-write shadow paging, A/B headers, ACID transactions, range scans, monotonic append, and bulk load. Reads return `Bytes` borrowed from the page cache; scans are bounded (`scan_from`, `scan_prefix_from`) or materialising.
12+
- **Segment File API** — engine-owned, append-mostly, atomically sealed encrypted files for formats that own their own layout (vectors, columnar blocks, FTS postings, R-trees).
13+
- **Encrypted pager** — every persistent page is authenticated; AES-256-GCM and ChaCha20-Poly1305 with per-page `cipher_id` for cipher agility. SIEVE page cache with bounded memory budgets.
14+
- **Key hierarchy** — KEK → MK → per-`(realm, file)` DEK/IK, stateful nonce generation with a durable anchor, `zeroize` on all key material.
15+
- **Realm isolation**`RealmId` bound into AEAD AAD on every persistent page and recorded in the `main.db` header. Per-realm quotas.
16+
- **Cross-platform VFS** — Linux (`io_uring`), Windows (IOCP), macOS/iOS (Grand Central Dispatch), Android, WASM/OPFS and WASI backends, plus a tokio thread-pool fallback and an in-memory backend, with format-bit identity across targets. On Linux the backend is chosen at run time: a kernel that refuses an `io_uring` ring falls back to the thread pool with a warning instead of failing the open. All native backends share one advisory-lock implementation, so processes on different backends still exclude each other on one store.
17+
- **Snapshots**`snapshot_to`, `restore_from`, and incremental apply, each authenticated against the state its manifest describes. Destinations must be empty; malformed or incomplete artifacts fail closed.
18+
- **Recovery** — open-flow GC, apply-journal replay, deep-walk `fsck`, and the `pagedb-fsck` binary.
19+
- **Online rekey** — rekey under a new key with mixed-cipher and mixed-epoch page coexistence; no full-file migration.
4820
- **Handle modes**`Standalone`, `Follower`, `ReadOnly`, and `Observer`.
49-
- **Failures report themselves** — an unreadable free-list chain, main file, or
50-
segment catalog fails `stats()` instead of reporting zero; compaction never
51-
skips a catalog entry whose file it cannot open; segment open distinguishes a
52-
missing file from a permission or backend error; and only genuine contention
53-
is reported as contention. Persisted named-counter rows are validated at open,
54-
and commit-history keys are rejected unless exactly eight bytes.
21+
- **Open refusals name the parameter, not the store**`KeyMismatch`, `PageSizeMismatch`, and `RealmMismatch`, each decided before anything is read or written, and none reported as corruption.
22+
- **Failures report themselves** — an unreadable free-list chain, main file, or segment catalog fails `stats()` instead of reporting zero; compaction never skips a catalog entry whose file it cannot open; segment open distinguishes a missing file from a permission or backend error; and only genuine contention is reported as contention. Persisted named-counter rows are validated at open, and commit-history keys are rejected unless exactly eight bytes.
23+
24+
### Security
25+
26+
- Threat model documented in the README; disclosure policy in `SECURITY.md`.
27+
- Data keys are scoped per file, so no two files share a nonce space.
28+
- `io_uring`: an error mid-submission no longer frees a transfer buffer the kernel may still be writing into.
29+
- `pagedb-fsck` requires an explicit KEK — no all-zero default.
30+
- Diagnostic reports redact embedder-chosen segment names.
5531

5632
### Known limitations
5733

58-
- Pre-1.0: the on-disk format may change between minor versions until 1.0.
34+
- Pre-1.0: the API may break in a minor bump. The on-disk format is frozen for the `0.1.x` line and any later format change ships a migrator — see `VERSIONING.md`. Stores written by a `0.1.0-beta` pre-release are not readable by `0.1.0`.
35+
- pagedb detects tampering, not reversion: an attacker who can write to the store files can substitute an older genuine ciphertext and it will authenticate. Freshness belongs above pagedb.
36+
- `main.db` cannot be reconstructed from `seg/` — back the directory up as a unit.
5937
- Single-writer per database; multi-writer cross-process is not supported.
60-
- Writes carry per-page AEAD and copy-on-write overhead; for throughput-bound
61-
plaintext KV workloads a generic store may be faster.
62-
- Applying an incremental snapshot stages a full copy of `main.db` alongside the
63-
original and commits it with a rename, so a follower needs room for two copies
64-
of the database during an apply.
38+
- Writes carry per-page AEAD and copy-on-write overhead; for throughput-bound plaintext KV workloads a generic store may be faster.
39+
- Applying an incremental snapshot stages a full copy of `main.db` alongside the original, so a follower needs room for two copies during an apply.

0 commit comments

Comments
 (0)