Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 4 additions & 25 deletions docs/release-notes/unreleased.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
109 changes: 109 additions & 0 deletions docs/release-notes/v0.6.1.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/korvid/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""korvid — AI-native Kubernetes TUI."""

__version__ = "0.6.0"
__version__ = "0.6.1"
11 changes: 5 additions & 6 deletions tests/test_docs_readability.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"
Expand Down
97 changes: 86 additions & 11 deletions tests/test_release_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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",
Comment thread
hellices marked this conversation as resolved.
"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"(?<!\w)#\d+\b", notes), "link pages, not issue numbers"


def test_upgrade_source_is_the_previous_minor_release() -> None:
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading