diff --git a/CHANGELOG.md b/CHANGELOG.md index d974529..cdd223a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ Per-release notes, in the form they were published on GitHub, are in ## [Unreleased] +## [0.2.0] - 2026-08-23 + Everything since 0.1.3 has been about a dashboard that says what it knows, and what it does not. @@ -58,8 +60,52 @@ what it does not. - **Contrast is a test.** `internal/theme` holds text to 4.5:1, structural colours to 3:1, and signal pairs to a minimum distance in OKLab. +- **Focus mode, clustering and a minimap.** At 200 workloads across 40 + namespaces the whole picture is not a picture. `F` reduces the graph to a + node's neighbourhood and reports what was left out; the remainder becomes one + counted card rather than nothing at all. Past 220 nodes the graph is refused + rather than drawn, with search and the namespace level offered instead. The + minimap keeps unprotected workloads red even when they are off screen, since + focus must never hide the thing you are hunting. +- **A "reachable from outside" filter.** Directed, unlike the focus traversal: + being able to call the internet is not being reachable from it, and private + ranges are somebody's VPC rather than the internet. +- **System namespaces collapse.** `kube-system` and its neighbours become one + counted card each, because on most clusters they hold the only unprotected + workloads there are and the headline number ends up pointing at infrastructure + nobody will write a policy for. Configurable by flag and Helm value, always + reversible, and never hidden. +- **A list at phone width.** Below 700px the graph is replaced rather than + shrunk — a node-link diagram at that size is not a small diagram — grouped by + namespace and ordered worst-first, with the panels becoming sheets. +- **The graph, for a keyboard.** A canvas is a rectangle with no contents to a + screen reader, so every workload and rule in the picture was absent to anyone + not reading it with their eyes and unreachable to anyone without a mouse. It + is now one tab stop with arrow navigation along real edges, `E` to step onto + the edges leaving a node, and a sentence describing each one. +- **A shortcut list**, on `?`. Every key in it already worked; none of them was + written down anywhere a person would look. +- **Policies are searchable** by name, through `GET /api/policies`, with how + many workloads each one actually selects — zero usually means label drift. + ### Changed +- The palette is the single way in: search and simulate were adjacent buttons + doing the same thing to different halves of one question, and `⌘↵` on any + workload now opens the simulation already framed by it. +- Three edge kinds get three dash patterns, so hue is never the only thing + carrying the meaning — for readers who cannot separate the green from the + amber, and for print. +- One focus ring everywhere, drawn as an outline with an offset so that focusing + something never nudges it. There had been three different treatments across + the components that had one at all. +- Namespace cards say "1 of 3 unprotected" rather than "3": the count is how big + it is, the posture is whether anything is wrong with it. Aggregated edges + carry a port pill. +- Empty namespaces are reported along the bottom rather than drawn, having no + posture to show and no edges to place them. +- The README's screenshots render the example cluster and are regenerated by + `make screenshots` rather than captured by hand. - The header leads with the finding: one chip, in danger colour, reading "N unprotected of M workloads", and a control rather than a statement. The other counts are faint context. @@ -172,7 +218,8 @@ First release. a Docker-only toolchain so no Go, Node, or Kubernetes tooling is needed locally. -[Unreleased]: https://github.com/FathiQ/marsad/compare/v0.1.3...HEAD +[Unreleased]: https://github.com/FathiQ/marsad/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/FathiQ/marsad/compare/v0.1.3...v0.2.0 [0.1.3]: https://github.com/FathiQ/marsad/compare/v0.1.2...v0.1.3 [0.1.2]: https://github.com/FathiQ/marsad/compare/v0.1.1...v0.1.2 [0.1.1]: https://github.com/FathiQ/marsad/compare/v0.1.0...v0.1.1 diff --git a/charts/marsad/Chart.yaml b/charts/marsad/Chart.yaml index 7b72ec7..9ba995a 100644 --- a/charts/marsad/Chart.yaml +++ b/charts/marsad/Chart.yaml @@ -6,8 +6,8 @@ type: application # The chart version tracks the application version. Marsad is a single # component with no subcharts, so a chart change that is not an app change is # rare enough that two independent version lines would cost more than it saves. -version: 0.1.3 -appVersion: "0.1.3" +version: 0.2.0 +appVersion: "0.2.0" kubeVersion: ">=1.25.0-0" diff --git a/docs/releases/v0.2.0.md b/docs/releases/v0.2.0.md new file mode 100644 index 0000000..8e1bf6c --- /dev/null +++ b/docs/releases/v0.2.0.md @@ -0,0 +1,73 @@ +# v0.2.0 + +A dashboard that says what it knows, and what it does not. + +Everything since 0.1.3 has gone into the two questions a read-only tool has to +answer honestly: is what I am showing you current, and is what I am not showing +you important. + +## The screens that were missing + +**A cluster Marsad cannot read** used to produce no screen at all — preflight +failed, the process exited, Kubernetes restarted it, and the explanation lived +in a pod log somebody had to know to look for. The UI now comes up either way, +shows the API server's own words verbatim, and hands you the ClusterRole that +answers it, generated from the same rule list `deploy/rbac.yaml` is tested +against. + +**Freshness is a state.** A graph built four minutes ago looks exactly like one +built a second ago. The header says live, reconnecting or snapshot, with the +time of the last update or a countdown to the next attempt. + +**Startup reports real progress**, read from `/api/health` — the one endpoint +that answers while the informer caches are still filling and everything else +is returning 503. + +## The questions the panels were leaving open + +**Closest misses.** "No policy selects this workload" states a fact and stops. +The inspector now ranks the policies in that namespace that nearly did, and +shows both halves: what the policy wanted, and what the workload actually has. + +**The rule beside the edge.** Clicking an edge opens a popover at the click +point holding the matching rule — not the hundred-line document it lives in. + +**What the refusing half does accept.** A denial says the connection fails; it +does not say what the policy was written for, and without that the next step is +a guess. + +## Scale, and what it costs to hide things + +Focus reduces the graph to a neighbourhood and says how much it left out. +System namespaces collapse to one counted card each, because on most clusters +they hold the only unprotected workloads there are. Past 220 nodes the graph is +refused rather than drawn. Every one of those is reversible and none of them is +silent — the minimap keeps unprotected workloads red even off screen, because +focus must never hide the thing you are hunting. + +## Reachable without a mouse + +The graph was a rectangle with no contents to a screen reader. It is now one tab +stop with arrow navigation along real edges, a sentence describing every node, +and `?` for the shortcut list. Below 700px the graph is replaced by a list +rather than shrunk. + +Colour no longer carries meaning alone: three edge kinds, three dash patterns. +Contrast is a test rather than an intention, and `--node-world` is no longer the +same red as `--danger` — "outside the cluster" and "unprotected" were +indistinguishable. + +## Upgrading + +No action required. `GET /api/rules` and `GET /api/policies` are new; nothing +was removed. `config.systemNamespaces` is a new Helm value and defaults to a +built-in list. + +One behaviour change worth knowing: Marsad no longer exits when it cannot read +the cluster. It serves the UI and explains the failure instead, so a permission +problem shows as a screen rather than a CrashLoopBackOff. Readiness still +reports false, and the liveness probe still passes — deliberately, since +restarting would take away the screen that explains the problem. + +Still read-only. RBAC is `get`, `list`, `watch`, and there is no write path in +the code.