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..99dc0357d --- /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 and virtualenv +versions, which do not ship 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 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..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 @@ -21,6 +22,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 +542,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 +631,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 +644,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 +927,57 @@ 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, 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: + 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, 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 + 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" },