From 725e6b139d410c8ff9ba796fa28c66b78a7bab03 Mon Sep 17 00:00:00 2001 From: Sung-Kyu Yoo Date: Tue, 11 Aug 2026 03:45:02 +0900 Subject: [PATCH 1/3] Verify what one tag produces, and what upgrading from v0.1.1 costs Running v0.1.1 and this build side by side leaves an HTTP client one observable difference: a 405 now carries the JSON error body every other error already had. Status codes and bodies for PUT, GET, DELETE, and /healthz are identical, the proto has not been touched, and the v0.1.1 README's library example compiles against this package unchanged. v0.1.1 had no --data-dir, so an upgrade has no data to migrate at all. The release page says all of that, and says what it does not: an upgraded server opens a RESP listener on loopback that was not there before. A data directory this build cannot read is the one refusal an upgrade can hit, and the sentence it prints is the instruction. The datadir package tested the refusal; nothing tested that it reaches whoever ran the command, which is where it is read. Now something does. Rehearsing the tag locally found the rest. Five changelog fragments carried no issue number and rendered as dead links. Four described changes nobody outside this repository could observe, among them a Homebrew formula dropped and restored between the same two tags, which would have gone out as a release note saying both. `goreleaser check` failed on the deprecated brews section: the release job floats on ~> v2, so that was a working release path with an expiry date on it. Homebrew gets a cask now, and because nothing signs these binaries the cask strips the quarantine attribute, or the first run dies on a dialog instead of an error. Retiring the old formula is the tap's job and has an order to it: the cask does not exist until the release job pushes it, and tap_migrations.json is only read when a name resolves to nothing. Both tap changes therefore belong after the release, in one commit, which the release page now spells out rather than leaving to whoever does it next. Those instructions lived in two files that had drifted apart. One is enough. --- .github/workflows/docs.yaml | 2 - .goreleaser.yml | 22 ++- CHANGELOG.md | 33 +++- GOVERNANCE.md | 2 +- changes/unreleased/Added-20260730-210000.yaml | 5 - changes/unreleased/Added-20260801-120000.yaml | 5 - changes/unreleased/Added-20260801-152145.yaml | 3 - changes/unreleased/Added-20260801-160000.yaml | 5 - changes/unreleased/Added-20260801-161504.yaml | 3 - changes/unreleased/Added-20260809-000000.yaml | 5 - changes/unreleased/Added-20260809-120000.yaml | 5 - .../unreleased/Changed-20260730-073249.yaml | 5 - .../unreleased/Changed-20260730-210001.yaml | 5 - .../unreleased/Changed-20260730-233000.yaml | 5 - .../unreleased/Changed-20260802-000000.yaml | 3 - .../Documentation-20260809-210000.yaml | 5 - changes/unreleased/Fixed-20260730-073249.yaml | 5 - changes/unreleased/Fixed-20260731-001220.yaml | 5 - changes/unreleased/Fixed-20260803-090000.yaml | 3 - changes/unreleased/Fixed-20260810-100000.yaml | 5 - changes/unreleased/Misc-20260730-080930.yaml | 5 - .../unreleased/Removed-20260730-073249.yaml | 5 - .../unreleased/Removed-20260802-001500.yaml | 3 - changes/v1.0.0.md | 24 +++ cmd/kvs/serve_test.go | 31 ++++ content/docs/release.md | 157 ++++++++++++++++-- docs/RELEASE.md | 68 -------- 27 files changed, 237 insertions(+), 187 deletions(-) delete mode 100644 changes/unreleased/Added-20260730-210000.yaml delete mode 100644 changes/unreleased/Added-20260801-120000.yaml delete mode 100644 changes/unreleased/Added-20260801-152145.yaml delete mode 100644 changes/unreleased/Added-20260801-160000.yaml delete mode 100644 changes/unreleased/Added-20260801-161504.yaml delete mode 100644 changes/unreleased/Added-20260809-000000.yaml delete mode 100644 changes/unreleased/Added-20260809-120000.yaml delete mode 100644 changes/unreleased/Changed-20260730-073249.yaml delete mode 100644 changes/unreleased/Changed-20260730-210001.yaml delete mode 100644 changes/unreleased/Changed-20260730-233000.yaml delete mode 100644 changes/unreleased/Changed-20260802-000000.yaml delete mode 100644 changes/unreleased/Documentation-20260809-210000.yaml delete mode 100644 changes/unreleased/Fixed-20260730-073249.yaml delete mode 100644 changes/unreleased/Fixed-20260731-001220.yaml delete mode 100644 changes/unreleased/Fixed-20260803-090000.yaml delete mode 100644 changes/unreleased/Fixed-20260810-100000.yaml delete mode 100644 changes/unreleased/Misc-20260730-080930.yaml delete mode 100644 changes/unreleased/Removed-20260730-073249.yaml delete mode 100644 changes/unreleased/Removed-20260802-001500.yaml create mode 100644 changes/v1.0.0.md delete mode 100644 docs/RELEASE.md diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 2a47c02..d5b6528 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -12,7 +12,6 @@ on: - "static/**" - "README.md" - "CONTRIBUTING.md" - - "docs/RELEASE.md" pull_request: branches: - main @@ -24,7 +23,6 @@ on: - "static/**" - "README.md" - "CONTRIBUTING.md" - - "docs/RELEASE.md" workflow_dispatch: permissions: diff --git a/.goreleaser.yml b/.goreleaser.yml index bc1b799..fe3b226 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -55,10 +55,15 @@ checksum: algorithm: sha256 name_template: 'CHECKSUMS' -# Publishes Formula/kvs.rb to skyoo2003/homebrew-tap. The token comes from a +# Publishes Casks/kvs.rb to skyoo2003/homebrew-tap. The token comes from a # GitHub App installed only on that repository: the job's own GITHUB_TOKEN # cannot write to another repo. -brews: +# +# A cask rather than a formula because GoReleaser deprecated `brews`: a formula that installs a +# pre-built binary is what a cask is for. `brew install skyoo2003/tap/kvs` keeps working, and the +# tap needs a tap_migrations.json entry so anyone holding the old formula moves over on upgrade +# instead of being told it is gone. +homebrew_casks: - ids: - kvs repository: @@ -68,10 +73,15 @@ brews: homepage: "https://github.com/skyoo2003/kvs" description: "A key-value store you can run as a server or import as a Go module" license: "MIT" - install: | - bin.install "kvs" - test: | - assert_match version.to_s, shell_output("#{bin}/kvs -v") + # Nothing signs or notarizes these binaries, so Gatekeeper quarantines what the cask staged and + # the first run dies on a dialog rather than an error. Stripping the attribute is what makes an + # unsigned cask runnable; it is not an optional nicety. + hooks: + post: + install: | + if OS.mac? + system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/kvs"] + end dockers_v2: - images: diff --git a/CHANGELOG.md b/CHANGELOG.md index 168c28e..ac29cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,27 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), -and is generated by [Changie](https://github.com/miniscruff/changie). - -## [v0.1.1](https://github.com/skyoo2003/kvs/releases/tag/v0.1.1) - 2026-04-20 +## [v1.0.0](https://github.com/skyoo2003/kvs/releases/tag/v1.0.0) - 2026-08-11 +### Added +* Redis/Valkey compatible RESP2 server on 127.0.0.1:6379, sharing one keyspace with the HTTP and gRPC APIs; it steps aside if the port is taken, holds at most 10000 connections, and bounds one transaction's queue at 64MiB so a single client cannot exhaust memory ([#232](https://github.com/skyoo2003/kvs/issues/232)) +* Lua scripting with EVAL, EVALSHA, and SCRIPT LOAD/EXISTS/FLUSH; a script runs sandboxed under one write lock, so its redis.call sequence is atomic, and gives up after 5 seconds rather than hold the store ([#232](https://github.com/skyoo2003/kvs/issues/232)) +* Keep the keyspace across restarts with `kvs serve --data-dir`, which appends every change to a log and replays it at startup ([#236](https://github.com/skyoo2003/kvs/issues/236)) +* The cjson library inside a script, so cjson.encode and cjson.decode work under EVAL instead of failing on a nil global; a JSON null decodes to cjson.null rather than nil, so it does not end the array it sits in ([#232](https://github.com/skyoo2003/kvs/issues/232)) +* Run a Raft cluster with `kvs serve --raft-addr` and `--join`; writes pass consensus, and losing the leader triggers an election instead of needing a person ([#236](https://github.com/skyoo2003/kvs/issues/236)) +* A compatibility page states what v1 promises not to break, what it leaves out, and the trust boundary kvs assumes; the exported Go surface is pinned in testdata/api-surface.txt and checked by a test, so it cannot widen or narrow unnoticed ([#244](https://github.com/skyoo2003/kvs/issues/244)) +* A data directory now carries a format version, and kvs refuses to start on one it does not recognise instead of replaying bytes another version laid out; the refusal names both versions and what to do about it, and it covers the Raft store as well as the append log ([#245](https://github.com/skyoo2003/kvs/issues/245)) +### Changed +* Container images now run 'serve' by default as UID 65534 and no longer declare a volume ([#228](https://github.com/skyoo2003/kvs/issues/228)) +* RESP SCAN, HSCAN, SSCAN, and ZSCAN now page their walk, WATCH tracks only the keys it was given, lists cost O(1) at both ends, and expired keys are reclaimed by a sampling sweep ([#232](https://github.com/skyoo2003/kvs/issues/232)) +* INFO and HELLO now report what a clustered node actually is — a follower answers role:slave and names the leader in master_host and master_port, a leader counts the others in connected_slaves, and cluster_enabled:0 says kvs is not Redis Cluster ([#236](https://github.com/skyoo2003/kvs/issues/236)) +* Homebrew installs a cask instead of a formula; `brew install skyoo2003/tap/kvs` is unchanged, and the tap carries a tap_migrations.json entry so an existing formula install moves across on upgrade. GoReleaser deprecated the formula path for pre-built binaries, and the release would have broken on a floating version of it ([#247](https://github.com/skyoo2003/kvs/issues/247)) +### Removed +* Drop the unused data structure packages pkg/bitset, pkg/cuckoofilter, pkg/lsm, and pkg/rbt; the storage engine went to an append log and Raft instead, and nothing in the server or the library imported them. pkg/resp stays, since the RESP server is built on it ([#236](https://github.com/skyoo2003/kvs/issues/236)) +### Fixed +* Fix multi-arch (linux/arm64) release images and expose the gRPC port 3457 in container images ([#228](https://github.com/skyoo2003/kvs/issues/228)) +* An HTTP 405 now carries the same JSON error body as every other HTTP error, instead of an empty response the documented contract did not allow ([#244](https://github.com/skyoo2003/kvs/issues/244)) +### Documentation +* The durability and clustering page now carries measured numbers from a four hour run under load with a node stopped every thirty seconds and kept down for ten - 329,631 acknowledged writes, 111,516 of them taken while a node was gone, and none of them lost across 479 restarts - along with 51 bytes of append log per write, the Raft log a node too busy restarting never truncates, and a make soak target that reproduces all of it ([#246](https://github.com/skyoo2003/kvs/issues/246)) +* The release page now covers what one tag actually produces, the checks worth running before pushing it, and an upgrade section measured by running v0.1.1 and v1 side by side - the library, the CLI, and gRPC unchanged, an HTTP 405 now carrying a JSON body, a RESP listener appearing on loopback, and no data directory to migrate because v0.1.1 never wrote one ([#247](https://github.com/skyoo2003/kvs/issues/247)) +### Misc +* Migrate golangci-lint config to v2 and pin the linter to v2.12.2 in CI ([#229](https://github.com/skyoo2003/kvs/issues/229))## [v0.1.1](https://github.com/skyoo2003/kvs/releases/tag/v0.1.1) - 2026-04-20 ### Changed * Improve open-source project infrastructure and documentation @@ -15,7 +31,6 @@ and is generated by [Changie](https://github.com/miniscruff/changie). ### Fixed * Fix goreleaser flag: --rm-dist → --clean for v2 compatibility - ## [v0.1.0](https://github.com/skyoo2003/kvs/releases/tag/v0.1.0) - 2026-03-14 ### Added * Implement a usable kvs module and release path ([#184](https://github.com/skyoo2003/kvs/issues/184)) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 66d3cbd..8857323 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -17,7 +17,7 @@ KVS follows a **BDFL (Benevolent Dictator for Life)** model: - Releases follow [Semantic Versioning](https://semver.org) - The project lead determines release scope and timing - Changelog is managed with [Changie](https://github.com/miniscruff/changie) -- See [RELEASE.md](docs/RELEASE.md) for the full release workflow +- See [the release process](https://skyoo2003.github.io/kvs/docs/release/) for the full release workflow ## Contributing diff --git a/changes/unreleased/Added-20260730-210000.yaml b/changes/unreleased/Added-20260730-210000.yaml deleted file mode 100644 index 8f37e46..0000000 --- a/changes/unreleased/Added-20260730-210000.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Added -body: Redis/Valkey compatible RESP2 server on 127.0.0.1:6379, sharing one keyspace with the HTTP and gRPC APIs; it steps aside if the port is taken, holds at most 10000 connections, and bounds one transaction's queue at 64MiB so a single client cannot exhaust memory -time: 2026-07-30T21:00:00.000000+09:00 -custom: - Issue: "232" diff --git a/changes/unreleased/Added-20260801-120000.yaml b/changes/unreleased/Added-20260801-120000.yaml deleted file mode 100644 index 7e1d747..0000000 --- a/changes/unreleased/Added-20260801-120000.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Added -body: Lua scripting with EVAL, EVALSHA, and SCRIPT LOAD/EXISTS/FLUSH; a script runs sandboxed under one write lock, so its redis.call sequence is atomic, and gives up after 5 seconds rather than hold the store -time: 2026-08-01T12:00:00.000000+09:00 -custom: - Issue: "232" diff --git a/changes/unreleased/Added-20260801-152145.yaml b/changes/unreleased/Added-20260801-152145.yaml deleted file mode 100644 index 97f3382..0000000 --- a/changes/unreleased/Added-20260801-152145.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Added -body: Keep the keyspace across restarts with `kvs serve --data-dir`, which appends every change to a log and replays it at startup -time: 2026-08-01T15:21:50.419127+09:00 diff --git a/changes/unreleased/Added-20260801-160000.yaml b/changes/unreleased/Added-20260801-160000.yaml deleted file mode 100644 index b65cdf4..0000000 --- a/changes/unreleased/Added-20260801-160000.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Added -body: The cjson library inside a script, so cjson.encode and cjson.decode work under EVAL instead of failing on a nil global; a JSON null decodes to cjson.null rather than nil, so it does not end the array it sits in -time: 2026-08-01T16:00:00.000000+09:00 -custom: - Issue: "232" diff --git a/changes/unreleased/Added-20260801-161504.yaml b/changes/unreleased/Added-20260801-161504.yaml deleted file mode 100644 index 2e288b8..0000000 --- a/changes/unreleased/Added-20260801-161504.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Added -body: Run a Raft cluster with `kvs serve --raft-addr` and `--join`; writes pass consensus, and losing the leader triggers an election instead of needing a person -time: 2026-08-01T16:15:04.621770+09:00 diff --git a/changes/unreleased/Added-20260809-000000.yaml b/changes/unreleased/Added-20260809-000000.yaml deleted file mode 100644 index 13e6eba..0000000 --- a/changes/unreleased/Added-20260809-000000.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Added -body: A compatibility page states what v1 promises not to break, what it leaves out, and the trust boundary kvs assumes; the exported Go surface is pinned in testdata/api-surface.txt and checked by a test, so it cannot widen or narrow unnoticed -time: 2026-08-09T00:00:00.000000+09:00 -custom: - Issue: "244" diff --git a/changes/unreleased/Added-20260809-120000.yaml b/changes/unreleased/Added-20260809-120000.yaml deleted file mode 100644 index ba9c112..0000000 --- a/changes/unreleased/Added-20260809-120000.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Added -body: A data directory now carries a format version, and kvs refuses to start on one it does not recognise instead of replaying bytes another version laid out; the refusal names both versions and what to do about it, and it covers the Raft store as well as the append log -time: 2026-08-09T12:00:00.000000+09:00 -custom: - Issue: "245" diff --git a/changes/unreleased/Changed-20260730-073249.yaml b/changes/unreleased/Changed-20260730-073249.yaml deleted file mode 100644 index 8132e6e..0000000 --- a/changes/unreleased/Changed-20260730-073249.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Changed -body: Container images now run 'serve' by default as UID 65534 and no longer declare a volume -time: 2026-07-30T07:32:49.636506+09:00 -custom: - Issue: "228" diff --git a/changes/unreleased/Changed-20260730-210001.yaml b/changes/unreleased/Changed-20260730-210001.yaml deleted file mode 100644 index c03a371..0000000 --- a/changes/unreleased/Changed-20260730-210001.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Changed -body: "server.RunListeners now takes the store before a Listeners struct instead of positional net.Listener arguments" -time: 2026-07-30T21:00:01.000000+09:00 -custom: - Issue: "232" diff --git a/changes/unreleased/Changed-20260730-233000.yaml b/changes/unreleased/Changed-20260730-233000.yaml deleted file mode 100644 index 2fbe700..0000000 --- a/changes/unreleased/Changed-20260730-233000.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Changed -body: RESP SCAN, HSCAN, SSCAN, and ZSCAN now page their walk, WATCH tracks only the keys it was given, lists cost O(1) at both ends, and expired keys are reclaimed by a sampling sweep -time: 2026-07-30T23:30:00.000000+09:00 -custom: - Issue: "232" diff --git a/changes/unreleased/Changed-20260802-000000.yaml b/changes/unreleased/Changed-20260802-000000.yaml deleted file mode 100644 index 9b6987f..0000000 --- a/changes/unreleased/Changed-20260802-000000.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Changed -body: INFO and HELLO now report what a clustered node actually is — a follower answers role:slave and names the leader in master_host and master_port, a leader counts the others in connected_slaves, and cluster_enabled:0 says kvs is not Redis Cluster -time: 2026-08-02T00:00:00.000000+09:00 diff --git a/changes/unreleased/Documentation-20260809-210000.yaml b/changes/unreleased/Documentation-20260809-210000.yaml deleted file mode 100644 index c615385..0000000 --- a/changes/unreleased/Documentation-20260809-210000.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Documentation -body: The durability and clustering page now carries measured numbers from a four hour run under load with a node stopped every thirty seconds and kept down for ten - 329,631 acknowledged writes, 111,516 of them taken while a node was gone, and none of them lost across 479 restarts - along with 51 bytes of append log per write, the Raft log a node too busy restarting never truncates, and a make soak target that reproduces all of it -time: 2026-08-09T21:00:00.000000+09:00 -custom: - Issue: "246" diff --git a/changes/unreleased/Fixed-20260730-073249.yaml b/changes/unreleased/Fixed-20260730-073249.yaml deleted file mode 100644 index f27e02c..0000000 --- a/changes/unreleased/Fixed-20260730-073249.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Fixed -body: Fix multi-arch (linux/arm64) release images and expose the gRPC port 3457 in container images -time: 2026-07-30T07:32:49.630294+09:00 -custom: - Issue: "228" diff --git a/changes/unreleased/Fixed-20260731-001220.yaml b/changes/unreleased/Fixed-20260731-001220.yaml deleted file mode 100644 index 2627bee..0000000 --- a/changes/unreleased/Fixed-20260731-001220.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Fixed -body: 'Homebrew formula updates again on release: tap publishing was dropped along with GH_PAT and now uses a short-lived GitHub App token' -time: 2026-07-31T00:12:20.85594+09:00 -custom: - Issue: "233" diff --git a/changes/unreleased/Fixed-20260803-090000.yaml b/changes/unreleased/Fixed-20260803-090000.yaml deleted file mode 100644 index 5355e03..0000000 --- a/changes/unreleased/Fixed-20260803-090000.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Fixed -body: Store.Snapshot and Store.Speculate now return ErrNoCodec instead of panicking on a nil dereference when no Codec has been set; an empty keyspace has nothing to encode and still succeeds, which is why the crash only showed up once a key existed -time: 2026-08-03T09:00:00.000000+09:00 diff --git a/changes/unreleased/Fixed-20260810-100000.yaml b/changes/unreleased/Fixed-20260810-100000.yaml deleted file mode 100644 index 389cef8..0000000 --- a/changes/unreleased/Fixed-20260810-100000.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Fixed -body: An HTTP 405 now carries the same JSON error body as every other HTTP error, instead of an empty response the documented contract did not allow -time: 2026-08-10T10:00:00.000000+09:00 -custom: - Issue: "244" diff --git a/changes/unreleased/Misc-20260730-080930.yaml b/changes/unreleased/Misc-20260730-080930.yaml deleted file mode 100644 index eab2cce..0000000 --- a/changes/unreleased/Misc-20260730-080930.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Misc -body: Migrate golangci-lint config to v2 and pin the linter to v2.12.2 in CI -time: 2026-07-30T08:09:30.503608+09:00 -custom: - Issue: "229" diff --git a/changes/unreleased/Removed-20260730-073249.yaml b/changes/unreleased/Removed-20260730-073249.yaml deleted file mode 100644 index c9d61ce..0000000 --- a/changes/unreleased/Removed-20260730-073249.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Removed -body: Drop Homebrew formula releases; the skyoo2003/tap/kvs formula is no longer updated -time: 2026-07-30T07:32:49.622709+09:00 -custom: - Issue: "228" diff --git a/changes/unreleased/Removed-20260802-001500.yaml b/changes/unreleased/Removed-20260802-001500.yaml deleted file mode 100644 index 4187330..0000000 --- a/changes/unreleased/Removed-20260802-001500.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Removed -body: Drop the unused data structure packages pkg/bitset, pkg/cuckoofilter, pkg/lsm, and pkg/rbt; the storage engine went to an append log and Raft instead, and nothing in the server or the library imported them. pkg/resp stays, since the RESP server is built on it -time: 2026-08-02T00:15:00.000000+09:00 diff --git a/changes/v1.0.0.md b/changes/v1.0.0.md new file mode 100644 index 0000000..eb2236e --- /dev/null +++ b/changes/v1.0.0.md @@ -0,0 +1,24 @@ +## [v1.0.0](https://github.com/skyoo2003/kvs/releases/tag/v1.0.0) - 2026-08-11 +### Added +* Redis/Valkey compatible RESP2 server on 127.0.0.1:6379, sharing one keyspace with the HTTP and gRPC APIs; it steps aside if the port is taken, holds at most 10000 connections, and bounds one transaction's queue at 64MiB so a single client cannot exhaust memory ([#232](https://github.com/skyoo2003/kvs/issues/232)) +* Lua scripting with EVAL, EVALSHA, and SCRIPT LOAD/EXISTS/FLUSH; a script runs sandboxed under one write lock, so its redis.call sequence is atomic, and gives up after 5 seconds rather than hold the store ([#232](https://github.com/skyoo2003/kvs/issues/232)) +* Keep the keyspace across restarts with `kvs serve --data-dir`, which appends every change to a log and replays it at startup ([#236](https://github.com/skyoo2003/kvs/issues/236)) +* The cjson library inside a script, so cjson.encode and cjson.decode work under EVAL instead of failing on a nil global; a JSON null decodes to cjson.null rather than nil, so it does not end the array it sits in ([#232](https://github.com/skyoo2003/kvs/issues/232)) +* Run a Raft cluster with `kvs serve --raft-addr` and `--join`; writes pass consensus, and losing the leader triggers an election instead of needing a person ([#236](https://github.com/skyoo2003/kvs/issues/236)) +* A compatibility page states what v1 promises not to break, what it leaves out, and the trust boundary kvs assumes; the exported Go surface is pinned in testdata/api-surface.txt and checked by a test, so it cannot widen or narrow unnoticed ([#244](https://github.com/skyoo2003/kvs/issues/244)) +* A data directory now carries a format version, and kvs refuses to start on one it does not recognise instead of replaying bytes another version laid out; the refusal names both versions and what to do about it, and it covers the Raft store as well as the append log ([#245](https://github.com/skyoo2003/kvs/issues/245)) +### Changed +* Container images now run 'serve' by default as UID 65534 and no longer declare a volume ([#228](https://github.com/skyoo2003/kvs/issues/228)) +* RESP SCAN, HSCAN, SSCAN, and ZSCAN now page their walk, WATCH tracks only the keys it was given, lists cost O(1) at both ends, and expired keys are reclaimed by a sampling sweep ([#232](https://github.com/skyoo2003/kvs/issues/232)) +* INFO and HELLO now report what a clustered node actually is — a follower answers role:slave and names the leader in master_host and master_port, a leader counts the others in connected_slaves, and cluster_enabled:0 says kvs is not Redis Cluster ([#236](https://github.com/skyoo2003/kvs/issues/236)) +* Homebrew installs a cask instead of a formula; `brew install skyoo2003/tap/kvs` is unchanged, and the tap carries a tap_migrations.json entry so an existing formula install moves across on upgrade. GoReleaser deprecated the formula path for pre-built binaries, and the release would have broken on a floating version of it ([#247](https://github.com/skyoo2003/kvs/issues/247)) +### Removed +* Drop the unused data structure packages pkg/bitset, pkg/cuckoofilter, pkg/lsm, and pkg/rbt; the storage engine went to an append log and Raft instead, and nothing in the server or the library imported them. pkg/resp stays, since the RESP server is built on it ([#236](https://github.com/skyoo2003/kvs/issues/236)) +### Fixed +* Fix multi-arch (linux/arm64) release images and expose the gRPC port 3457 in container images ([#228](https://github.com/skyoo2003/kvs/issues/228)) +* An HTTP 405 now carries the same JSON error body as every other HTTP error, instead of an empty response the documented contract did not allow ([#244](https://github.com/skyoo2003/kvs/issues/244)) +### Documentation +* The durability and clustering page now carries measured numbers from a four hour run under load with a node stopped every thirty seconds and kept down for ten - 329,631 acknowledged writes, 111,516 of them taken while a node was gone, and none of them lost across 479 restarts - along with 51 bytes of append log per write, the Raft log a node too busy restarting never truncates, and a make soak target that reproduces all of it ([#246](https://github.com/skyoo2003/kvs/issues/246)) +* The release page now covers what one tag actually produces, the checks worth running before pushing it, and an upgrade section measured by running v0.1.1 and v1 side by side - the library, the CLI, and gRPC unchanged, an HTTP 405 now carrying a JSON body, a RESP listener appearing on loopback, and no data directory to migrate because v0.1.1 never wrote one ([#247](https://github.com/skyoo2003/kvs/issues/247)) +### Misc +* Migrate golangci-lint config to v2 and pin the linter to v2.12.2 in CI ([#229](https://github.com/skyoo2003/kvs/issues/229)) diff --git a/cmd/kvs/serve_test.go b/cmd/kvs/serve_test.go index 6236b62..d32256d 100644 --- a/cmd/kvs/serve_test.go +++ b/cmd/kvs/serve_test.go @@ -1,10 +1,14 @@ package main import ( + "path/filepath" + "strconv" + "strings" "testing" "github.com/spf13/viper" + "github.com/skyoo2003/kvs/internal/datadir" "github.com/skyoo2003/kvs/internal/server" ) @@ -163,3 +167,30 @@ func TestResolveServeConfigFlagsOverrideViper(t *testing.T) { t.Fatalf("resolveServeConfig() = %+v, want flag values", got) } } + +// Upgrading to a build that reads a different on-disk format is the one upgrade path kvs +// promises anything about, and the promise is a refusal that says what to do. The datadir +// package tests the refusal itself; this tests that it reaches whoever ran the command, which is +// where the sentence is actually read. Nothing binds a port: serve.go opens the store before it +// listens, so the command is over before the addresses matter. +func TestServeRefusesADataDirItCannotRead(t *testing.T) { + dir := t.TempDir() + + const foreign = "999" + if err := osWriteFile(filepath.Join(dir, datadir.FormatName), []byte(foreign+"\n")); err != nil { + t.Fatalf("write %s error = %v", datadir.FormatName, err) + } + + _, _, err := runCLI(t, "serve", "--data-dir", dir) + if err == nil { + t.Fatal("serve --data-dir on a foreign format = nil, want a refusal") + } + + // The operator has to be able to tell which directory, what it holds, and what this build + // would have read, or the message is not an upgrade instruction. + for _, want := range []string{dir, foreign, strconv.Itoa(datadir.Version)} { + if !strings.Contains(err.Error(), want) { + t.Fatalf("serve --data-dir error = %q, want it to mention %q", err, want) + } + } +} diff --git a/content/docs/release.md b/content/docs/release.md index af64dbf..454caa1 100644 --- a/content/docs/release.md +++ b/content/docs/release.md @@ -3,29 +3,154 @@ title: "Release Process" weight: 8 --- -The project uses [Changie](https://github.com/miniscruff/changie) for changelog management and [GoReleaser](https://goreleaser.com/) for automated releases. +A release is one tag push. Everything else — binaries, checksums, the container image, the +Homebrew cask, the GitHub release and its notes — comes out of that. The workflow that does it is +[`.github/workflows/release.yaml`][release-workflow], driven by +[Changie](https://github.com/miniscruff/changie) for the notes and +[GoReleaser](https://goreleaser.com/) for the artifacts. -## Commands +[release-workflow]: https://github.com/skyoo2003/kvs/blob/main/.github/workflows/release.yaml + +## Cutting a release + +Notes come first, because the workflow refuses to run without them. + +```sh +changie new # one fragment per user-visible change, as the change is made +changie batch v1.2.3 # fold every fragment into changes/v1.2.3.md +changie merge # prepend that file to CHANGELOG.md +``` + +`changie batch` empties `changes/unreleased/`, so **read what it produced before merging.** Two +things are worth looking for, both of which have happened here: + +- A fragment with no `Issue` renders as `[#]` — a dead link in the notes. `changie batch + v1.2.3 --dry-run` shows this without consuming anything. +- Changes that cancel out. Something added and removed again between two tags is not news to + anyone upgrading, and a release note saying both is worse than saying neither. The test is + whether someone on the previous release can observe the difference. + +Commit the batched file and the changelog, merge to `main`, then: + +```sh +git tag v1.2.3 && git push origin v1.2.3 +``` + +### Before pushing the tag + +| Check | Why | +|---|---| +| `changes/v1.2.3.md` exists on `main` | The workflow fails in two seconds without it, after the tag is already pushed | +| `make all` passes | Nothing downstream runs the tests | +| `go test -run TestPublicAPISurface .` passes | An unintended change to the exported Go surface is a broken promise — see [Compatibility](../compatibility/) | +| `goreleaser check` passes | Catches deprecated configuration before a floating GoReleaser version turns it into a failed release | +| `gh release list` has no stale draft | Release Drafter keeps a rolling draft; it does not collide with a real tag, but it lingers next to the release just cut | +| The tap still holds its formula | Deleting it before the cask exists leaves `brew install skyoo2003/tap/kvs` with nothing to resolve. The swap belongs after the release job, not before the tag — see below | + +A local rehearsal that does everything except publish: + +```sh +goreleaser release --snapshot --clean # add --skip=docker without a docker daemon +``` + +The `goreleaser` job in `.github/workflows/cd.yml` runs the same command on every pull request +touching the build, so the release path is exercised before merge rather than for the first time on +a tag. + +### Retiring the formula, once + +kvs published a Homebrew formula until v1. GoReleaser writes `Casks/kvs.rb` from the first v1 tag +onward, and the tap has to be finished by hand — GoReleaser has no migration support, and neither +its cask options nor `conflicts:` move an install that already exists. + +Do this **after** the release job has pushed the cask, in one commit, or the tap is briefly a tap +with no kvs in it: ```sh -changie new # create a new changelog fragment -changie batch {version} # batch fragments into a version file -changie merge # merge into CHANGELOG.md +gh api repos/skyoo2003/homebrew-tap/contents/tap_migrations.json -X PUT \ + -f message='Migrate kvs from formula to cask' \ + -f content="$(printf '{\n "kvs": "skyoo2003/tap"\n}\n' | base64)" +# then delete Formula/kvs.rb and the copy at the tap root ``` -## Application Release +The value is the tap, not a path to anything: Homebrew re-resolves the name there and finds the +cask. It only consults the file when the name resolves to nothing, which is why the formula has to +go in the same change — while it is there, it wins and the migration never fires. + +## What comes out + +| Artifact | Where | +|---|---| +| Archives — darwin, linux, windows × 386, amd64, arm64, armv7 (nine in total) | GitHub release | +| `CHECKSUMS` — sha256 for every archive | GitHub release | +| Container image | `ghcr.io/skyoo2003/kvs`, tagged `v1.2.3-alpine`, `v1.2-alpine`, `v1-alpine`, `latest-alpine` | +| Homebrew cask | `skyoo2003/homebrew-tap`, installed with `brew install skyoo2003/tap/kvs` | +| Release notes | The GitHub release body, taken from `changes/v1.2.3.md` | + +Each archive carries the binary plus `LICENSE`, `README.md`, `CHANGELOG.md`, and +`CODE_OF_CONDUCT.md`. The binary reports the tag through `kvs version`. + +**Every container tag but the first moves.** `latest-alpine`, `v1-alpine`, and `v1.2-alpine` point +at whatever was released most recently, which is why there are no pre-release tags: publishing +`v1.0.0-rc.1` would move `latest-alpine` to a release candidate. Rehearse with `--snapshot` +instead. + +## Upgrading from v0.1.x + +Measured by running v0.1.1 and this version side by side, not inferred from the diff. + +**The Go library is unchanged.** `NewStore`, `Get`, `Put`, `Delete`, and `ErrKeyNotFound` keep the +signatures they had in v0.1.1 — the example in that release's README compiles and runs against v1 +untouched. Everything else on the package is new. [Compatibility](../compatibility/) is what +promises to keep it that way. + +**The command line is unchanged.** `kvs serve --http-addr … --grpc-addr …`, `--config`, `version`, +and `-v` all still mean what they meant. The flags added since are additions. + +**One new listener appears.** v1 serves RESP on `127.0.0.1:6379` unless told otherwise, which +v0.1.1 did not have. It is loopback-only, so nothing new is reachable from off the machine, and a +port already in use is logged and skipped rather than being fatal: + +``` +kvs: listen resp: listen tcp 127.0.0.1:6379: bind: address already in use; +RESP is off (set --resp-addr to move it, or "none" to silence this) +``` + +Pass `--resp-addr none` to not have it at all. + +**gRPC is unchanged.** `api/kvsv1/kvs.proto` has not been touched since v0.1.1. + +**HTTP changes in one place.** A `405 Method Not Allowed` now carries the same JSON error body as +every other HTTP error instead of an empty response. Status codes, paths, and the bodies of `PUT`, +`GET`, `DELETE`, and `/healthz` — including their 404s — are byte-identical to v0.1.1. + +**There is no data to migrate.** v0.1.1 had no `--data-dir`: the keyspace lived in memory and went +away with the process. Persistence arrived after it, so no released version of kvs ever wrote a +data directory. Starting v1 with `--data-dir` on an empty directory is the whole upgrade. + +Point v1 at a directory written in a format it does not know and it refuses to start, saying so +rather than replaying bytes it does not understand: + +``` +open data dir /var/lib/kvs: /var/lib/kvs is format 999 and this build understands format 1. +kvs does not convert between them: run the version that wrote it, or move the directory aside +and load the data again. +``` -1. Prepare changelog: `changie new` → `changie batch v0.x.0` → `changie merge` -2. Commit and push to `main` -3. Create and push a tag: `git tag v0.x.0 && git push origin v0.x.0` -4. GitHub Actions runs `.github/workflows/release.yaml` -5. GoReleaser builds binaries and Docker images +**Homebrew installs a cask now, not a formula.** `brew install skyoo2003/tap/kvs` is the same +command; GoReleaser deprecated the formula path for pre-built binaries. Moving an install that +already exists is the tap's job, not this repository's — see the tap row in the pre-tag checks +above. -### Artifacts +## Documentation site -- **Binaries**: darwin/linux/windows (amd64, arm64) via GitHub Releases -- **Docker**: `ghcr.io/skyoo2003/kvs:{tag}-alpine` (linux/amd64, linux/arm64) +The Hugo site at [skyoo2003.github.io/kvs](https://skyoo2003.github.io/kvs/) is published by +`.github/workflows/docs.yaml` on every push to `main` that touches `hugo.toml`, `content/`, +`layouts/`, `static/`, `README.md`, or `CONTRIBUTING.md`. Hugo builds into `public/`, which +`actions/upload-pages-artifact` and `actions/deploy-pages` publish. It needs the repository's Pages +setting to deploy from GitHub Actions. -## Documentation Site +## Further Reading -The Hugo documentation site is deployed automatically to [skyoo2003.github.io/kvs](https://skyoo2003.github.io/kvs/) on every push to `main` that touches `content/`, `layouts/`, `static/`, or documentation-related files. +- [Compatibility](../compatibility/) — what v1 promises not to break +- [Contributing](../contributing/) — development commands and the pull request flow diff --git a/docs/RELEASE.md b/docs/RELEASE.md deleted file mode 100644 index 3c77f20..0000000 --- a/docs/RELEASE.md +++ /dev/null @@ -1,68 +0,0 @@ -# Release - -This repository has two release paths: - -- tagged application releases through `.github/workflows/release.yaml` -- static documentation site releases through `.github/workflows/docs.yaml` - -## Commands - -```sh -$ changie new -$ changie batch {version} -$ changie merge -``` - -## Project Release - -Project releases are published from `.github/workflows/release.yaml` when a tag matching `v*` is pushed. - -### Preparation - -1. Create changelog entries with `changie new` -2. Batch the release notes with `changie batch {version}` -3. Merge the changelog with `changie merge` -4. Commit the release notes and create the release tag - -### Publish Flow - -1. Push a tag such as `v0.2.3` -2. GitHub Actions runs `.github/workflows/release.yaml` -3. GoReleaser builds the `kvs` binaries and archives defined in `.goreleaser.yml` -4. The workflow publishes the GitHub release artifacts and related package outputs - -### Verification - -After pushing the tag, confirm that the `Release` workflow succeeds and that the expected release artifacts are attached to the GitHub release. - -## Documentation Site Release - -The static documentation site is built with Hugo and published through GitHub Pages Actions. - -### Trigger - -The docs workflow runs on pushes to `main` when one of these paths changes: - -- `hugo.toml` -- `content/**` -- `layouts/**` -- `static/**` -- `README.md` -- `CONTRIBUTING.md` -- `docs/RELEASE.md` -- `.github/workflows/docs.yaml` - -### Publish Flow - -1. GitHub Actions runs `.github/workflows/docs.yaml` -2. Hugo builds the site into `public/` -3. `actions/upload-pages-artifact` uploads `public/` as the Pages artifact -4. `actions/deploy-pages` publishes that artifact to GitHub Pages - -### Repository Setting - -GitHub Pages must be configured to deploy from GitHub Actions. - -### Verification - -After merging a docs change to `main`, confirm that the `Docs` workflow succeeds and that the site is available at `https://skyoo2003.github.io/kvs/` From 3b3316627463bc28c523532739f0dbacfe0ec766 Mon Sep 17 00:00:00 2001 From: Sung-Kyu Yoo Date: Tue, 11 Aug 2026 03:47:08 +0900 Subject: [PATCH 2/3] Point the two new notes at the pull request that carries them They were written before the number existed and guessed at it. --- CHANGELOG.md | 4 ++-- changes/v1.0.0.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac29cf8..0001f16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * Container images now run 'serve' by default as UID 65534 and no longer declare a volume ([#228](https://github.com/skyoo2003/kvs/issues/228)) * RESP SCAN, HSCAN, SSCAN, and ZSCAN now page their walk, WATCH tracks only the keys it was given, lists cost O(1) at both ends, and expired keys are reclaimed by a sampling sweep ([#232](https://github.com/skyoo2003/kvs/issues/232)) * INFO and HELLO now report what a clustered node actually is — a follower answers role:slave and names the leader in master_host and master_port, a leader counts the others in connected_slaves, and cluster_enabled:0 says kvs is not Redis Cluster ([#236](https://github.com/skyoo2003/kvs/issues/236)) -* Homebrew installs a cask instead of a formula; `brew install skyoo2003/tap/kvs` is unchanged, and the tap carries a tap_migrations.json entry so an existing formula install moves across on upgrade. GoReleaser deprecated the formula path for pre-built binaries, and the release would have broken on a floating version of it ([#247](https://github.com/skyoo2003/kvs/issues/247)) +* Homebrew installs a cask instead of a formula; `brew install skyoo2003/tap/kvs` is unchanged, and the tap carries a tap_migrations.json entry so an existing formula install moves across on upgrade. GoReleaser deprecated the formula path for pre-built binaries, and the release would have broken on a floating version of it ([#248](https://github.com/skyoo2003/kvs/issues/248)) ### Removed * Drop the unused data structure packages pkg/bitset, pkg/cuckoofilter, pkg/lsm, and pkg/rbt; the storage engine went to an append log and Raft instead, and nothing in the server or the library imported them. pkg/resp stays, since the RESP server is built on it ([#236](https://github.com/skyoo2003/kvs/issues/236)) ### Fixed @@ -19,7 +19,7 @@ * An HTTP 405 now carries the same JSON error body as every other HTTP error, instead of an empty response the documented contract did not allow ([#244](https://github.com/skyoo2003/kvs/issues/244)) ### Documentation * The durability and clustering page now carries measured numbers from a four hour run under load with a node stopped every thirty seconds and kept down for ten - 329,631 acknowledged writes, 111,516 of them taken while a node was gone, and none of them lost across 479 restarts - along with 51 bytes of append log per write, the Raft log a node too busy restarting never truncates, and a make soak target that reproduces all of it ([#246](https://github.com/skyoo2003/kvs/issues/246)) -* The release page now covers what one tag actually produces, the checks worth running before pushing it, and an upgrade section measured by running v0.1.1 and v1 side by side - the library, the CLI, and gRPC unchanged, an HTTP 405 now carrying a JSON body, a RESP listener appearing on loopback, and no data directory to migrate because v0.1.1 never wrote one ([#247](https://github.com/skyoo2003/kvs/issues/247)) +* The release page now covers what one tag actually produces, the checks worth running before pushing it, and an upgrade section measured by running v0.1.1 and v1 side by side - the library, the CLI, and gRPC unchanged, an HTTP 405 now carrying a JSON body, a RESP listener appearing on loopback, and no data directory to migrate because v0.1.1 never wrote one ([#248](https://github.com/skyoo2003/kvs/issues/248)) ### Misc * Migrate golangci-lint config to v2 and pin the linter to v2.12.2 in CI ([#229](https://github.com/skyoo2003/kvs/issues/229))## [v0.1.1](https://github.com/skyoo2003/kvs/releases/tag/v0.1.1) - 2026-04-20 diff --git a/changes/v1.0.0.md b/changes/v1.0.0.md index eb2236e..fb76dd6 100644 --- a/changes/v1.0.0.md +++ b/changes/v1.0.0.md @@ -11,7 +11,7 @@ * Container images now run 'serve' by default as UID 65534 and no longer declare a volume ([#228](https://github.com/skyoo2003/kvs/issues/228)) * RESP SCAN, HSCAN, SSCAN, and ZSCAN now page their walk, WATCH tracks only the keys it was given, lists cost O(1) at both ends, and expired keys are reclaimed by a sampling sweep ([#232](https://github.com/skyoo2003/kvs/issues/232)) * INFO and HELLO now report what a clustered node actually is — a follower answers role:slave and names the leader in master_host and master_port, a leader counts the others in connected_slaves, and cluster_enabled:0 says kvs is not Redis Cluster ([#236](https://github.com/skyoo2003/kvs/issues/236)) -* Homebrew installs a cask instead of a formula; `brew install skyoo2003/tap/kvs` is unchanged, and the tap carries a tap_migrations.json entry so an existing formula install moves across on upgrade. GoReleaser deprecated the formula path for pre-built binaries, and the release would have broken on a floating version of it ([#247](https://github.com/skyoo2003/kvs/issues/247)) +* Homebrew installs a cask instead of a formula; `brew install skyoo2003/tap/kvs` is unchanged, and the tap carries a tap_migrations.json entry so an existing formula install moves across on upgrade. GoReleaser deprecated the formula path for pre-built binaries, and the release would have broken on a floating version of it ([#248](https://github.com/skyoo2003/kvs/issues/248)) ### Removed * Drop the unused data structure packages pkg/bitset, pkg/cuckoofilter, pkg/lsm, and pkg/rbt; the storage engine went to an append log and Raft instead, and nothing in the server or the library imported them. pkg/resp stays, since the RESP server is built on it ([#236](https://github.com/skyoo2003/kvs/issues/236)) ### Fixed @@ -19,6 +19,6 @@ * An HTTP 405 now carries the same JSON error body as every other HTTP error, instead of an empty response the documented contract did not allow ([#244](https://github.com/skyoo2003/kvs/issues/244)) ### Documentation * The durability and clustering page now carries measured numbers from a four hour run under load with a node stopped every thirty seconds and kept down for ten - 329,631 acknowledged writes, 111,516 of them taken while a node was gone, and none of them lost across 479 restarts - along with 51 bytes of append log per write, the Raft log a node too busy restarting never truncates, and a make soak target that reproduces all of it ([#246](https://github.com/skyoo2003/kvs/issues/246)) -* The release page now covers what one tag actually produces, the checks worth running before pushing it, and an upgrade section measured by running v0.1.1 and v1 side by side - the library, the CLI, and gRPC unchanged, an HTTP 405 now carrying a JSON body, a RESP listener appearing on loopback, and no data directory to migrate because v0.1.1 never wrote one ([#247](https://github.com/skyoo2003/kvs/issues/247)) +* The release page now covers what one tag actually produces, the checks worth running before pushing it, and an upgrade section measured by running v0.1.1 and v1 side by side - the library, the CLI, and gRPC unchanged, an HTTP 405 now carrying a JSON body, a RESP listener appearing on loopback, and no data directory to migrate because v0.1.1 never wrote one ([#248](https://github.com/skyoo2003/kvs/issues/248)) ### Misc * Migrate golangci-lint config to v2 and pin the linter to v2.12.2 in CI ([#229](https://github.com/skyoo2003/kvs/issues/229)) From 1379a4f20b65f420a8988e730acedb12026f286d Mon Sep 17 00:00:00 2001 From: Sung-Kyu Yoo Date: Tue, 11 Aug 2026 03:56:19 +0900 Subject: [PATCH 3/3] Give v0.1.1 its heading back, and name the archives that exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `changie batch` writes the version file with no trailing newline and `changie merge` concatenates verbatim, so v0.1.1's heading landed on the end of the last v1 bullet and stopped being a heading at all — the release vanished from the changelog as a section. The end-of-file hook fixed the file afterwards, which was too late for the merge that had already run. Merging again fixes it, and the release page now names the symptom next to the other two worth looking for in a batch. The archive row multiplied three operating systems by four architectures and claimed the nine that come out. There is no darwin/386 and no windows/armv7; the row now lists what each platform actually gets. --- CHANGELOG.md | 3 ++- content/docs/release.md | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0001f16..cca06a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,8 @@ * The durability and clustering page now carries measured numbers from a four hour run under load with a node stopped every thirty seconds and kept down for ten - 329,631 acknowledged writes, 111,516 of them taken while a node was gone, and none of them lost across 479 restarts - along with 51 bytes of append log per write, the Raft log a node too busy restarting never truncates, and a make soak target that reproduces all of it ([#246](https://github.com/skyoo2003/kvs/issues/246)) * The release page now covers what one tag actually produces, the checks worth running before pushing it, and an upgrade section measured by running v0.1.1 and v1 side by side - the library, the CLI, and gRPC unchanged, an HTTP 405 now carrying a JSON body, a RESP listener appearing on loopback, and no data directory to migrate because v0.1.1 never wrote one ([#248](https://github.com/skyoo2003/kvs/issues/248)) ### Misc -* Migrate golangci-lint config to v2 and pin the linter to v2.12.2 in CI ([#229](https://github.com/skyoo2003/kvs/issues/229))## [v0.1.1](https://github.com/skyoo2003/kvs/releases/tag/v0.1.1) - 2026-04-20 +* Migrate golangci-lint config to v2 and pin the linter to v2.12.2 in CI ([#229](https://github.com/skyoo2003/kvs/issues/229)) +## [v0.1.1](https://github.com/skyoo2003/kvs/releases/tag/v0.1.1) - 2026-04-20 ### Changed * Improve open-source project infrastructure and documentation diff --git a/content/docs/release.md b/content/docs/release.md index 454caa1..9bbab2e 100644 --- a/content/docs/release.md +++ b/content/docs/release.md @@ -29,6 +29,9 @@ things are worth looking for, both of which have happened here: - Changes that cancel out. Something added and removed again between two tags is not news to anyone upgrading, and a release note saying both is worse than saying neither. The test is whether someone on the previous release can observe the difference. +- The previous version's heading glued to the last bullet of the new one. `changie batch` writes + the version file without a trailing newline and `changie merge` concatenates verbatim, which + costs the release below its heading. Give the file its newline and merge again. Commit the batched file and the changelog, merge to `main`, then: @@ -81,7 +84,7 @@ go in the same change — while it is there, it wins and the migration never fir | Artifact | Where | |---|---| -| Archives — darwin, linux, windows × 386, amd64, arm64, armv7 (nine in total) | GitHub release | +| Archives — darwin `amd64` `arm64`, linux `386` `amd64` `arm64` `armv7`, windows `386` `amd64` `arm64` (nine) | GitHub release | | `CHECKSUMS` — sha256 for every archive | GitHub release | | Container image | `ghcr.io/skyoo2003/kvs`, tagged `v1.2.3-alpine`, `v1.2-alpine`, `v1-alpine`, `latest-alpine` | | Homebrew cask | `skyoo2003/homebrew-tap`, installed with `brew install skyoo2003/tap/kvs` |