From 916deaacfad240d5fc101fff88329a11f4052797 Mon Sep 17 00:00:00 2001 From: hellices Date: Thu, 29 Oct 2026 00:26:16 +0900 Subject: [PATCH 1/4] release: set v0.6.1 metadata and its release contracts Set the package and runtime version to 0.6.1 and keep 0.5.0 as the upgrade source, as v0.4.1 kept 0.3.0. The lock's own korvid entry follows the version; no dependency changes. The v0.6.0 note's feature contracts (the summary sections, the stricter priority-key validation, the Action Palette and connection-test links) stay attached to the v0.6.0 note. The current note now has to be a security patch whose dependency table matches uv.lock. Part of #507. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyproject.toml | 2 +- src/korvid/__init__.py | 2 +- tests/test_docs_readability.py | 11 ++--- tests/test_release_policy.py | 85 +++++++++++++++++++++++++++++----- uv.lock | 2 +- 5 files changed, 82 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ed47fc14b..5c7f2f278 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "korvid" -version = "0.6.0" +version = "0.6.1" description = "AI-native Kubernetes TUI" readme = "README.md" license = "Apache-2.0" diff --git a/src/korvid/__init__.py b/src/korvid/__init__.py index f2db2a9ae..984bc1699 100644 --- a/src/korvid/__init__.py +++ b/src/korvid/__init__.py @@ -1,3 +1,3 @@ """korvid — AI-native Kubernetes TUI.""" -__version__ = "0.6.0" +__version__ = "0.6.1" diff --git a/tests/test_docs_readability.py b/tests/test_docs_readability.py index 7814892bc..df9e46207 100644 --- a/tests/test_docs_readability.py +++ b/tests/test_docs_readability.py @@ -3,11 +3,10 @@ import re from pathlib import Path -from korvid import __version__ - ROOT = Path(__file__).parent.parent DOCS = ROOT / "docs" -CURRENT_RELEASE_NOTE = f"release-notes/v{__version__}.md" +#: The note that announced the Action Palette and the connection-test change. +V0_6_RELEASE_NOTE = "release-notes/v0.6.0.md" def _source(name: str) -> str: @@ -879,8 +878,8 @@ def test_tui_presents_the_three_discovery_surfaces_as_complementary() -> None: def test_release_note_promises_the_palette_without_weakening_the_approval_rule() -> None: """The note that announces a new way to *reach* a write must say, in the same breath, that it does not change how a write is approved.""" - flat = " ".join(_source(CURRENT_RELEASE_NOTE).split()) - assert "`Ctrl-P`" in flat, "the current release note must announce the Action Palette" + flat = " ".join(_source(V0_6_RELEASE_NOTE).split()) + assert "`Ctrl-P`" in flat, "the v0.6.0 release note must announce the Action Palette" assert re.search(r"remapped key|effective key", flat, re.I), ( "the note must say results show the effective (remapped) key" ) @@ -952,7 +951,7 @@ def test_agent_guide_tells_a_self_hosted_operator_how_to_diagnose_a_refused_stre def test_the_release_note_sends_a_broken_gateway_to_that_section() -> None: """A behaviour change an operator can hit needs its recovery linked from the note that announces it, not only from the guide.""" - notes = _source(CURRENT_RELEASE_NOTE) + notes = _source(V0_6_RELEASE_NOTE) assert "https://hellices.github.io/korvid/agent/#self-hosted-endpoints-and-proxies" in notes, ( "the release note must link the agent guide's troubleshooting anchor" diff --git a/tests/test_release_policy.py b/tests/test_release_policy.py index 40bb95a5e..cc0f4f618 100644 --- a/tests/test_release_policy.py +++ b/tests/test_release_policy.py @@ -21,6 +21,7 @@ _RUNBOOK = _ROOT / "docs" / "release.md" _SECURITY = _ROOT / "SECURITY.md" _V0_5_RELEASE_NOTE = _ROOT / "docs" / "release-notes" / "v0.5.0.md" +_V0_6_RELEASE_NOTE = _ROOT / "docs" / "release-notes" / "v0.6.0.md" _UNPUBLISHED_RELEASE_NOTE = _ROOT / "docs" / "release-notes" / "v0.4.0.md" _FIRST_PUBLISHED_0_4_VERSION = "0.4.1" _FIRST_PUBLISHED_0_4_NOTE = _ROOT / "docs" / "release-notes" / f"v{_FIRST_PUBLISHED_0_4_VERSION}.md" @@ -540,8 +541,8 @@ def test_current_release_docs_only_name_allowed_versions() -> None: ) -def test_current_release_note_is_a_concise_v0_6_summary() -> None: - notes = _release_notes(_project_version()) +def test_v0_6_0_release_note_is_a_concise_summary() -> None: + notes = _V0_6_RELEASE_NOTE.read_text(encoding="utf-8") expected_sections = [ "Why upgrade", "Breaking changes and migration", @@ -629,7 +630,9 @@ def test_release_dispatch_contract_rejects_misplaced_commands(mutation: str) -> def test_v0_6_release_note_explains_stricter_priority_key_validation() -> None: notes = _normalized( - markdown_section(_release_notes(_project_version()), "Breaking changes and migration") + markdown_section( + _V0_6_RELEASE_NOTE.read_text(encoding="utf-8"), "Breaking changes and migration" + ) ) assert "configuration format is unchanged" in notes @@ -640,11 +643,35 @@ def test_v0_6_release_note_explains_stricter_priority_key_validation() -> None: assert "Existing keybindings and namespace settings continue to work" not in notes -def test_current_release_note_uses_canonical_public_guide_urls() -> None: - notes = _release_notes(_project_version()) - canonical_urls = ( - "https://hellices.github.io/korvid/keybindings/#remap-an-app-action", - "https://hellices.github.io/korvid/tui/#namespace-shortcuts", +@pytest.mark.parametrize( + ("notes_path", "canonical_urls"), + [ + pytest.param( + _V0_6_RELEASE_NOTE, + ( + "https://hellices.github.io/korvid/keybindings/#remap-an-app-action", + "https://hellices.github.io/korvid/tui/#namespace-shortcuts", + ), + id="v0.6.0", + ), + pytest.param( + None, + ( + "https://hellices.github.io/korvid/release-notes/v0.6.0/", + "https://hellices.github.io/korvid/getting-started/#practice-on-a-throwaway-cluster", + "https://hellices.github.io/korvid/kubernetes-support/", + ), + id="current", + ), + ], +) +def test_release_notes_use_canonical_public_guide_urls( + notes_path: Path | None, canonical_urls: tuple[str, ...] +) -> None: + notes = ( + _release_notes(_project_version()) + if notes_path is None + else notes_path.read_text(encoding="utf-8") ) missing = [url for url in canonical_urls if f"]({url})" not in notes] @@ -899,10 +926,46 @@ def test_publish_step_requires_reviewed_commit_version_guard() -> None: _assert_release_runbook_contracts(runbook.replace(publish, mutated)) -def test_v0_6_release_metadata_uses_v0_5_as_upgrade_source() -> None: - assert _project_version() == "0.6.0" +def test_v0_6_1_release_metadata_keeps_v0_5_as_upgrade_source() -> None: + assert _project_version() == "0.6.1" assert UPGRADE_SOURCE_VERSION == "0.5.0" - assert __version__ == "0.6.0" + assert __version__ == "0.6.1" + + +#: The runtime dependencies v0.6.1 updates for advisories against the +#: versions v0.6.0 was packaged with. +_V0_6_1_SECURITY_UPDATES = ("certifi", "aiohttp", "filelock", "litellm", "opentelemetry-api") + + +def _locked_version(package: str) -> str: + lock = tomllib.loads((_ROOT / "uv.lock").read_text(encoding="utf-8")) + (version,) = [entry["version"] for entry in lock["package"] if entry["name"] == package] + assert isinstance(version, str) + return version + + +def test_current_release_note_is_a_v0_6_1_security_patch() -> None: + notes = _release_notes(_project_version()) + + assert re.findall(r"^## (.+)$", notes, re.MULTILINE) == [ + "Why upgrade", + "Security fixes", + "Reliability fixes", + "Documentation", + "Kubernetes versions verified", + "Install or upgrade", + "Verify", + "Known limits", + ] + assert len(notes.splitlines()) <= 120 + security = markdown_section(notes, "Security fixes") + for package in _V0_6_1_SECURITY_UPDATES: + assert f"| `{package}` | {_locked_version(package)} |" in security, package + flat = _normalized(notes) + assert "changes no feature" in flat + assert "proxy server, which korvid never runs or imports" in flat + assert "1.33, 1.34, 1.35 and 1.36" in markdown_section(notes, "Kubernetes versions verified") + assert not re.search(r"(? None: diff --git a/uv.lock b/uv.lock index 19d33fcf4..7eb20a402 100644 --- a/uv.lock +++ b/uv.lock @@ -1334,7 +1334,7 @@ wheels = [ [[package]] name = "korvid" -version = "0.6.0" +version = "0.6.1" source = { editable = "." } dependencies = [ { name = "kubernetes-asyncio" }, From c58bdaf102866b646bcf0686ad07af147ab2eab8 Mon Sep 17 00:00:00 2001 From: hellices Date: Thu, 29 Oct 2026 00:26:16 +0900 Subject: [PATCH 2/4] docs: write the v0.6.1 release notes v0.6.1 ships the runtime dependency updates for advisories against v0.6.0's packaged versions (certifi critical, aiohttp, filelock and LiteLLM high, opentelemetry-api moderate), the two shutdown fixes and the Intel Homebrew toolchain fix, and the documentation already on main. The Kubernetes support page and the throwaway-cluster walkthrough move from the unreleased page into the v0.6.1 notes. Part of #507. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/release-notes/unreleased.md | 29 ++------ docs/release-notes/v0.6.1.md | 109 +++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 docs/release-notes/v0.6.1.md diff --git a/docs/release-notes/unreleased.md b/docs/release-notes/unreleased.md index d5e2e01de..fcd59aa56 100644 --- a/docs/release-notes/unreleased.md +++ b/docs/release-notes/unreleased.md @@ -1,34 +1,13 @@ # Unreleased (main) -This page tracks unreleased changes on `main` for the next release after `v0.6.0`. +This page tracks changes on `main` after the v0.6.1 release candidate. -The [latest published release](https://github.com/hellices/korvid/releases/latest), -v0.6.0, is the stable install; see the [v0.6.0 release notes](v0.6.0.md). The +The [latest published release](https://github.com/hellices/korvid/releases/latest) +remains the stable install until v0.6.1's publication is verified. See the +[v0.6.1 release notes](v0.6.1.md) for the candidate summary. The [current release milestone, v0.7.0](https://github.com/hellices/korvid/milestone/9) tracks the next release scope. -## Practice on a throwaway cluster - -- Getting Started now walks through a disposable [kind](https://kind.sigs.k8s.io/) - cluster with three small workloads, one healthy and two deliberately broken, - to try the problems view, logs, relationships, an approved write, and the - agent without risking a real cluster. -- The walkthrough runs in its own kubeconfig file, so its `kubectl` and korvid - commands cannot reach your other clusters. The workloads need no host access - or API token, and the whole cluster is deleted with one command. - -See [Practice on a throwaway cluster](../getting-started.md#practice-on-a-throwaway-cluster). - -## Kubernetes support - -- A new [Kubernetes support](../kubernetes-support.md) page defines which - Kubernetes versions korvid supports and what "verified" means. The oldest - supported version is 1.33. -- A version matrix verifies 1.33, 1.34, 1.35 and 1.36 on throwaway kind - clusters, with read-only scenarios for API version resolution, unavailable - APIs, denied principals, and ConfigMap and Secret metadata that must never - carry their data. - ## Current configuration and extension contracts Existing migration links continue in the diff --git a/docs/release-notes/v0.6.1.md b/docs/release-notes/v0.6.1.md new file mode 100644 index 000000000..09fecb10b --- /dev/null +++ b/docs/release-notes/v0.6.1.md @@ -0,0 +1,109 @@ +# korvid v0.6.1 + +v0.6.1 is a security and packaging update to v0.6.0. It changes no feature: +everything in the +[v0.6.0 release notes](https://hellices.github.io/korvid/release-notes/v0.6.0/) +still applies. + +## Why upgrade + +v0.6.0 was packaged with runtime dependencies that now have published +advisories, one of them critical. The Homebrew formula installs exactly the +versions a release was tested with, and the `[agent]` extra pins LiteLLM +exactly, so neither moves until korvid is released again. + +## Security fixes + +| Package | v0.6.1 ships | Advisories fixed | +| --- | --- | --- | +| `certifi` | 2026.10.19 | critical: the CA bundle trusted emSign roots whose intermediate keys were compromised | +| `aiohttp` | 3.14.4 | three high (unbounded memory and CPU use, no initial header timeout in the server, multipart file descriptor exhaustion) and five lower | +| `filelock` | 4.2.4 | high (a planted symlink or journal file redirects `ReadWriteLock`), and two low ones on Windows | +| `litellm` | 1.100.4 | high: CVE-2026-114612, privilege escalation in the LiteLLM proxy | +| `opentelemetry-api` | 1.45.1 | moderate: unbounded `tracestate` parsing | + +- **certifi matters most.** The agent's HTTPS connections to model providers + trust its CA bundle. +- **aiohttp** is the HTTP stack of korvid's Kubernetes client. +- **The LiteLLM advisory** is in LiteLLM's proxy server, which korvid never + runs or imports, so korvid was not exploitable through it. The update stops + dependency scanners from flagging it. + +The development tooling also moves to patched GitPython, virtualenv and +filelock versions. None of them ships with korvid. + +## Reliability fixes + +- Quitting right after approving a Deployment scale or restart no longer fails + while the operation result screen opens. +- Quitting right after interrupting an agent turn no longer fails when the + turn settles during shutdown. +- On Intel macOS, the Homebrew formula uses a prebuilt Rust toolchain instead + of building one from source. + +The safety perimeter is unchanged. Agent investigation remains read-only. +Agent writes remain structured tools with no shell. The resolved policy arms +only exact registry tool names, import-time metadata validates dispatch, and +`ToolExecutor` rejects an unknown tool. It performs typed argument validation +before every dispatch. Every write opens an approval dialog, requires a fresh +approval keystroke, and revalidates the exact target UID. Results still pass +through masking, and audit logging remains fail-closed. + +## Documentation + +- [Practice on a throwaway cluster](https://hellices.github.io/korvid/getting-started/#practice-on-a-throwaway-cluster) + walks through a disposable kind cluster with healthy and deliberately broken + workloads. It runs in its own kubeconfig file, so it cannot reach your other + clusters. +- [Kubernetes support](https://hellices.github.io/korvid/kubernetes-support/) + defines which Kubernetes versions korvid supports and what "verified" means. + The oldest supported version is 1.33. + +## Kubernetes versions verified + +Kubernetes 1.33, 1.34, 1.35 and 1.36, by the Kubernetes version matrix on +throwaway kind clusters. + +## Install or upgrade + +Install with `uv`: + +```bash +uv tool install 'korvid[all]==0.6.1' +``` + +Reinstall the exact version with `uv` or `pipx` when upgrading: + +```bash +uv tool install --force 'korvid[all]==0.6.1' +pipx install --force 'korvid[all]==0.6.1' +``` + +Homebrew users get v0.6.1 from `brew upgrade korvid` once the tap is updated. + +## Verify + +After the maintainer publishes the +[GitHub release](https://github.com/hellices/korvid/releases/tag/v0.6.1), +download its artifacts, verify both attestations, and check the wheel against +the checksum manifest: + +```sh +set -eu +gh release download v0.6.1 --dir dist/v0.6.1 --repo hellices/korvid +gh attestation verify dist/v0.6.1/korvid-0.6.1-py3-none-any.whl --repo hellices/korvid +gh attestation verify dist/v0.6.1/SHA256SUMS --repo hellices/korvid +(cd dist/v0.6.1 && shasum --algorithm 256 --check SHA256SUMS) +uvx --from 'korvid[all]==0.6.1' korvid --version +``` + +The final command must report `korvid 0.6.1`. + +## Known limits + +- v0.6.1 changes no behaviour, so the known limits in the + [v0.6.0 release notes](https://hellices.github.io/korvid/release-notes/v0.6.0/) + still apply. +- This source candidate does not itself publish PyPI, GitHub, or Homebrew + artifacts. Use the verification steps after the maintainer publishes the + release. diff --git a/mkdocs.yml b/mkdocs.yml index b1632291b..eaf4dde87 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ nav: - Contributor docs: dev/README.md - Release notes: - Unreleased (main): release-notes/unreleased.md + - v0.6.1: release-notes/v0.6.1.md - v0.6.0: release-notes/v0.6.0.md - v0.5.0: release-notes/v0.5.0.md - v0.4.1: release-notes/v0.4.1.md From 4c35e239762f430feda0c34c4986a9e8416dbd35 Mon Sep 17 00:00:00 2001 From: hellices Date: Thu, 29 Oct 2026 01:25:03 +0900 Subject: [PATCH 3/4] docs: filelock ships with the agent extra filelock reaches the agent extra through LiteLLM, tokenizers and huggingface-hub, so it is a runtime fix in the table above; only GitPython and virtualenv are development-only. Part of #507. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/release-notes/v0.6.1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/v0.6.1.md b/docs/release-notes/v0.6.1.md index 09fecb10b..99dc0357d 100644 --- a/docs/release-notes/v0.6.1.md +++ b/docs/release-notes/v0.6.1.md @@ -29,8 +29,8 @@ exactly, so neither moves until korvid is released again. runs or imports, so korvid was not exploitable through it. The update stops dependency scanners from flagging it. -The development tooling also moves to patched GitPython, virtualenv and -filelock versions. None of them ships with korvid. +The development tooling also moves to patched GitPython and virtualenv +versions, which do not ship with korvid. ## Reliability fixes From c2f8562cab29e10eaf8ddd69997b7f816b4e01fb Mon Sep 17 00:00:00 2001 From: hellices Date: Thu, 29 Oct 2026 07:46:21 +0900 Subject: [PATCH 4/4] tests: the v0.6.1 lock must be at or past each advisory's fixed version The note contract checked only that the note's table matched uv.lock, so a lock kept at v0.6.0's vulnerable versions with a matching table would pass. Each security update now has the first version that fixes all of its advisories, and the locked version must reach it. Part of #507. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/test_release_policy.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/test_release_policy.py b/tests/test_release_policy.py index cc0f4f618..31ae583da 100644 --- a/tests/test_release_policy.py +++ b/tests/test_release_policy.py @@ -6,6 +6,7 @@ from pathlib import Path import pytest +from packaging.version import Version from korvid import __version__ from tests.release_contracts import UPGRADE_SOURCE_VERSION, markdown_section @@ -933,8 +934,17 @@ def test_v0_6_1_release_metadata_keeps_v0_5_as_upgrade_source() -> None: #: The runtime dependencies v0.6.1 updates for advisories against the -#: versions v0.6.0 was packaged with. -_V0_6_1_SECURITY_UPDATES = ("certifi", "aiohttp", "filelock", "litellm", "opentelemetry-api") +#: versions v0.6.0 was packaged with, each with the first version that fixes +#: every one of them (GHSA-6q48-6c7w-9gw2; the aiohttp 3.14.4 set; +#: GHSA-v4x8-3wr5-3wq8 and the two low filelock ones; GHSA-7hp6-4w63-5g45; +#: GHSA-xcq7-p3pf-p88h). +_V0_6_1_SECURITY_FLOORS = { + "certifi": "2026.10.18", + "aiohttp": "3.14.4", + "filelock": "4.2.1", + "litellm": "1.100.4", + "opentelemetry-api": "1.45.1", +} def _locked_version(package: str) -> str: @@ -959,8 +969,10 @@ def test_current_release_note_is_a_v0_6_1_security_patch() -> None: ] assert len(notes.splitlines()) <= 120 security = markdown_section(notes, "Security fixes") - for package in _V0_6_1_SECURITY_UPDATES: - assert f"| `{package}` | {_locked_version(package)} |" in security, package + for package, floor in _V0_6_1_SECURITY_FLOORS.items(): + locked = _locked_version(package) + assert Version(locked) >= Version(floor), f"{package} {locked} predates the fix {floor}" + assert f"| `{package}` | {locked} |" in security, package flat = _normalized(notes) assert "changes no feature" in flat assert "proxy server, which korvid never runs or imports" in flat