Independent notes

heyimusa

Evidence-led notes on safe change, recovery, cloud security, and the systems behind production software.

Latest writing

Browse archive

GLM-5.3 is much cheaper than Kimi K3 for text-heavy coding agents, and its published results are strong. That does not make it a drop-in replacement. Kimi K3 can inspect screenshots and other visual input; GLM-5.3 cannot. In an operations workflow, that boundary matters before any benchmark chart does.

I compared the vendors' current model cards, pricing pages, and the public Terminal-Bench leaderboard. This is a source-led comparison, not an independent bake-off. The published scores use different harnesses and budgets, so they are useful for choosing what to test, not for declaring a winner.

Read article

GLM-5.3 is much cheaper than Kimi K3 for text-heavy coding agents, and its published results are strong. That does not make it a drop-in replacement. Kimi K3 can inspect screenshots and other visual input; GLM-5.3 cannot. In an operations workflow, that boundary matters before any benchmark chart does.

I compared the vendors' current model cards, pricing pages, and the public Terminal-Bench leaderboard. This is a source-led comparison, not an independent bake-off. The published scores use different harnesses and budgets, so they are useful for choosing what to test, not for declaring a winner.

Read article

Before and after rendered manifests flow into a focused security-impact review

Original diagram for this build note. It shows the tool’s review model, not a production cluster topology.

A Kubernetes pull request can look harmless when the visible change is short: a new RoleBinding, one securityContext field, a route rule, or a service type. The difficult part is that these changes rarely read like the security event they create.

A Role can gain access to Secrets. A workload can begin sharing a host network namespace. A route can make a new hostname reachable. None of that has to look dramatic in a line-by-line diff.

I wanted a small tool that starts from the artifact GitOps teams already review: rendered manifests before and after a change. That became kube-blast-radius, an offline CLI that explains the security-relevant capability or surface added by that change.

The input is the delivery artifact, not another policy file

The command compares two already-rendered states. It can also render Kustomize directories or Helm charts first.

kube-blast-radius diff \
  --before rendered/main.yaml \
  --after rendered/pr.yaml

For a simple fixture, a Role gained Secret access while a Deployment enabled hostNetwork and added a privileged container. The CLI reported:

HIGH  Role/payments/checkout: RBAC rule newly grants access to secrets
      (RBAC_SECRET_ACCESS_ADDED)

HIGH  Deployment/payments/checkout: workload newly enables hostNetwork
      (WORKLOAD_HOST_NETWORK_ADDED)

HIGH  Deployment/payments/checkout: workload newly adds a privileged container
      (WORKLOAD_PRIVILEGED_ADDED)

The intent is not to decide whether every finding is wrong. A privileged DaemonSet or a public route can be intentional. The point is to turn a subtle manifest change into a question a reviewer can answer deliberately.

I treated false assurance as the main bug

The first version found the obvious cases quickly. The more important work was finding ways it could say too little.

An independent review caught a few examples. A ClusterRole referenced by a namespace-scoped RoleBinding was initially easy to miss because ClusterRoles do not have a namespace. A kind: List wrapper could hide objects from a naive document parser. And an omitted ServiceAccount and an explicit default ServiceAccount should mean the same thing, not produce a scary but meaningless finding.

Those became regression tests, not release notes:

ClusterRole + RoleBinding -> effective Secret access
kind: List              -> expanded and analyzed
missing identity        -> analysis error, not silent skip
implicit default        -> normalized before comparison

The tool also reports unsupported resource kinds as information instead of quietly declaring the change clean. Its text output always carries the same caveat: enabled checks are not a complete security assessment.

Render first, then compare

Raw YAML is useful, but many GitOps changes live in overlays and values files. The tool supports three modes:

raw        YAML manifest stream
kustomize  kubectl kustomize <directory>
helm       helm template <chart> with before/after values

The render commands use argument vectors rather than a shell, have a fixed timeout, and do not load kubeconfig or talk to a Kubernetes cluster. That is a deliberate boundary. Rendering still means trusting the chart or overlay source, so untrusted inputs belong in an isolated runner.

I tested the renderers inside a Docker container with a read-only filesystem, a non-root user, all Linux capabilities dropped, no-new-privileges, and read-only mounted fixtures. Kustomize caught a newly enabled hostNetwork; Helm caught a values change that made a container privileged.

A real GitOps artifact changed the scope

The useful test was a read-only render of a detached Traefik route-review artifact from a GitOps repository. I exported only that tracked review directory at the commit that introduced it, rendered it with Kustomize, and compared it with an empty prior state. No cluster, DNS provider, Secret values, or live route was accessed.

The first run did not understand Traefik IngressRoute resources. That was the right result to take seriously: an “unsupported kind” message is better than pretending a route addition has no security meaning.

I added focused support for Traefik route matches. The next run marked the added desktop and mobile route rules as high-severity exposure changes. It did not call them malicious. It made their external-surface effect visible in the review output.

What the first release covers

v0.1.0 looks for changes including:

  • bound RBAC access to Secrets, wildcards, escalation verbs, and broader resource-name scope;
  • new bindings to roles that already grant Secret access;
  • privileged containers, host namespaces, hostPath volumes, added Linux capabilities, UID 0, and weakened container hardening;
  • Service external exposure, Kubernetes Ingress hosts and paths, removed NetworkPolicies, and Traefik IngressRoute rules.

It returns text for a reviewer and JSON for CI. High findings exit with code 1; malformed manifests and renderer failures exit with 2.

What it does not claim

This is not a cluster security platform. It does not calculate full NetworkPolicy reachability, inspect cloud IAM, query a live cluster, read Secret values, or certify compliance.

That limitation is part of the product. A GitOps diff tool should be trusted for the specific questions it can answer, not for imaginary coverage.

The source, release binary, and checksum are public:


Test notes: I built and tested the CLI locally and in disposable Docker containers. The hardened renderer test used a read-only filesystem, non-root UID 65532, dropped Linux capabilities, no-new-privileges, and read-only mounted synthetic fixtures. The GitOps validation was a read-only render of a detached review artifact; no cluster, Secret value, DNS provider, or live route was accessed. The examples are sanitized.

Related work: Security and regulated operations case study · A rollout needs a return address

Read article

A declared readiness rollout gate disagrees with a Docker Compose healthcheck that calls only liveness

Original diagram for this Docker-only build note. It shows a configuration disagreement, not a production incident.

I kept tripping over the same sentence while writing about health checks: “we gate the rollout on readiness.”

It sounds reassuring. Then you open the deployment file and find a healthcheck that calls /healthz, because that endpoint was easy to add and it returns 200 as long as the process is alive.

Neither file is necessarily wrong on its own. Together, they can tell two different stories.

So I made a small CLI called probe-contract. It compares a declared health contract with a Docker Compose file and reports the disagreements that are easy to miss in review.

The first release is deliberately small. It does not call a live endpoint. It does not attempt to infer whether a checkout flow, queue, or database is truly healthy. It reads configuration and asks a narrower question: does the probe in the deployment file support the health assumption you wrote down?

The smallest useful contract

The tool takes a tiny YAML file alongside Compose:

services:
  api:
    liveness: /healthz
    readiness: /readyz
    user_path: /checkout
    rollout_gate: readiness

Then it compares that declaration with the Compose healthcheck.

For the mismatch case, the contract said readiness should gate the rollout, while the Compose probe called only /healthz:

WARNING  api: rollout_gate is readiness, but Compose healthcheck does not reference /readyz (READINESS_NOT_PROBED)

That warning is the whole point of the first version. It does not prove /readyz is a good readiness endpoint. It makes the disagreement visible before somebody treats a green container as proof that a rollout is safe.

I tested the tool in a container too

I ran the released CLI against its fixtures inside a Docker container with a read-only filesystem, a non-root user, dropped capabilities, and no-new-privileges.

The valid fixture produced empty JSON:

{
  "diagnostics": []
}

A Compose file with its healthcheck explicitly disabled produced an error and exit code 1:

ERROR  api: service has no Compose healthcheck (HEALTHCHECK_MISSING)

That second case mattered. An early review of the tool found that healthcheck: { disable: true } could look like an active healthcheck to a naive YAML parser. The release now treats both disable: true and Docker’s test: [NONE] form as missing checks.

The review also caught a less obvious false pass: /ready should not match /readyz just because one string contains the other. That is fixed too. A health contract is already an approximation; the checker should not add accidental ambiguity on top.

What it checks today

probe-contract v0.1.0 checks a few things and stops there:

  • a service declared in the contract is present in Compose;
  • that service has an active healthcheck;
  • a readiness rollout gate actually probes the declared readiness path;
  • liveness and readiness are not declared as the same endpoint by accident;
  • interval, timeout, and retries are present on the healthcheck.

It emits human-readable text by default and JSON with --format json, which is enough to start using it in CI without making every warning a release blocker.

What it intentionally does not do

I do not want this to become another linter that promises too much.

It does not inspect Kubernetes manifests yet. It does not send traffic to a live service. It does not infer a business transaction from a URL. And it cannot tell whether a dependency should be part of readiness for a particular application.

Those choices belong to the team running the service. The tool only asks them to make the choice explicit, then checks whether the Compose file agrees.

That scope is small enough to be useful. It is also small enough that a reviewer can understand what a warning means without trusting a black box.

Release and next steps

The project is public under MIT and includes a Linux amd64 binary with a checksum:

The next likely steps are Kubernetes manifest support, SARIF output, and a GitHub Action. I am deliberately not calling those features until the Compose contract is useful enough to earn them.


Test notes: I built and ran probe-contract v0.1.0 only in Docker for this note. The demonstration container used a read-only filesystem, non-root UID 65532, dropped Linux capabilities, no-new-privileges, and read-only mounted fixture files. The results are configuration checks against local fixtures, not a benchmark or a production deployment.

Related work: Delivery systems case study · I made a healthy service page on purpose

Read article

One service, three health signals: liveness stays green while readiness and the checkout path fail

Original diagram for a Docker-only experiment in this note. It models a failed dependency, not a production system.

I made a service look healthy on purpose.

Not healthy to a user. Healthy to the one check that only cared whether the process was still running.

It is an easy trap. A container is up, the liveness endpoint returns 200, the dashboard stays green, and everyone gets to feel better for a few minutes. Meanwhile the dependency the service needs has gone away, requests are timing out, and the thing users came for is unavailable.

I built a tiny version of that failure in a disposable Docker container. The service had three endpoints:

/healthz   process liveness
/readyz    dependency-aware readiness
/checkout  a small user-facing path

With the simulated dependency available, all three were fine:

/healthz   http=200 body=process=up
/readyz    http=200 body=dependency=reachable
/checkout  http=200 body=checkout=accepted

Then I restarted the same demo with one environment flag that simulated a dependency failure. The Python process did not crash. That is important.

/healthz   http=200 body=process=up
/readyz    http=503 body=dependency=unavailable
/checkout  http=503 body=checkout=unavailable: dependency timeout

The liveness check was not lying. It answered the question it was given: is the process alive? The problem is that it was the wrong question for deciding whether to send a user more traffic, or whether a rollout was safe to keep.

Three questions that should not share one endpoint

I find it useful to separate health checks by the decision they support.

/healthz asks whether restarting the process is likely to help. A process that cannot accept a TCP connection, is deadlocked, or has stopped responding belongs here. This check should be narrow. If every temporary dependency wobble makes it fail, an orchestrator can turn a recoverable outage into a restart loop.

/readyz asks whether the instance should receive work. In the experiment, it included the dependency state. That made it a reasonable signal for traffic admission and rollout progress. A failing readiness check can take an instance out of rotation without pretending that the process needs to be killed.

/checkout asks the least convenient question: can someone complete the thing they actually came to do? It is usually more expensive to measure and should not become a noisy probe that hits every dependency every second. But some form of user-path signal belongs in monitoring. Otherwise a green fleet can hide a useless product.

A green container is a weak promise

Container state is still useful. It tells you whether a workload exists and whether the runtime can keep it alive. It does not tell you that the application has a connection pool, that the queue is moving, or that an important request succeeds.

That distinction matters most during a deployment. If a new version starts successfully but cannot talk to its required dependency, a liveness-only check may let the rollout continue. The failure then moves from deployment time to user time, where it is noisier and harder to unwind.

This is also why I do not like treating readiness as a cosmetic endpoint added late in a project. It is part of the contract between the application and the platform. The platform needs an honest answer before it decides to route traffic or declare a revision ready.

Do not turn readiness into a dependency census

There is a bad version of this pattern too: make /readyz call every downstream service, every time, and fail on any brief hiccup.

That can create its own outage. If a shared dependency has a short blip, hundreds of instances may all become unready at once. A probe that was meant to reduce risk becomes a traffic switch with no damping.

The useful questions are smaller:

  • Which dependency makes this instance incapable of doing its primary job?
  • How long must that dependency be unavailable before traffic should stop?
  • Can the check use a bounded timeout and cached result instead of adding load during an incident?
  • What signal tells us the user path is degraded even if the process remains alive?

The answers will differ by service. A worker may be alive and intentionally disconnected while a queue is paused. A checkout API with no database connection is not in the same situation.

What I would wire into a real rollout

For a non-critical environment, I would start with a deliberately boring drill:

  1. deploy a candidate revision;
  2. make one required dependency unavailable in a controlled way;
  3. confirm that readiness fails while liveness stays stable;
  4. confirm that the traffic or rollout controller reacts to readiness, not just process survival;
  5. restore the dependency and verify the user-facing path, not only the pod state.

That drill has a nice side effect: it makes the rollback criteria concrete. In my previous note, I argued that a rollout needs a return address. This is part of the address book. If the only evidence after a rollback is “the pods are running,” there is still a lot left to guess.


Test notes: I ran this in two disposable Docker containers using Python 3.12. Each container used a read-only root filesystem, dropped Linux capabilities, no-new-privileges, a PID limit of 64, 0.25 CPU, and 128 MiB memory. The dependency failure was a local simulation. No host service, production endpoint, cluster, or external dependency was accessed.

Related reading: A rollout needs a return address · Delivery systems case study

Read article

A small declarative rollout and rollback experiment

Original diagram for this note. It describes a Docker-only simulated release flow, not a production deployment or benchmark.

I do not trust a deployment plan until I can explain what happens when the new version is the problem.

That sounds obvious. It is still easy to build a delivery process around the happy path: create an image, update a manifest, watch the rollout, call it done. The awkward part starts when a release looks healthy enough to leave the pipeline but is not healthy enough to keep.

To keep this small, I ran a toy release flow in a disposable Docker container. There was no host change, cluster, registry, or real service. The container held a desired-state file for a fictional checkout service, plus a saved copy of the known-good release.

apply: checkout:1.4.3
healthcheck: /readyz -> FAIL (simulated)
rollback: checkout:1.4.2
healthcheck: /readyz -> OK (simulated)
final_desired_state: image=checkout:1.4.2 replicas=3

The experiment is intentionally boring. That is the point. A rollback should not require somebody to reconstruct the previous state from memory while a production graph turns red.

The release needs a return address

A deployment is a state transition. The candidate release is only one half of that transition; the other half is the state you can return to when the candidate fails.

In the tiny experiment, that state was just a saved file:

image=checkout:1.4.2
replicas=3

Real systems are less neat. There may be configuration changes, schema compatibility, feature flags, asynchronous workers, or traffic shifts. But the basic question does not change: what exact state are we restoring, and can the deployment system express it?

If the answer is “we will figure it out,” the rollback plan is not really a plan.

Rollback is not an apology button

People often talk about rollback as if it is the opposite of deployment. It is not. It is another deployment, with the same need for identity, evidence, and verification.

A useful rollback path has at least three properties:

  • The previous artifact or desired state is identifiable.
  • The path to apply it is known before the incident.
  • The system has a signal that says the restored version is actually healthy.

The third item gets skipped surprisingly often. Reverting an image tag is not proof that the service recovered. It only proves that the deployment controller accepted another instruction.

Why declarative state helps

This is where GitOps and other declarative delivery patterns earn their keep. They make the intended state visible. They also make a reversal more concrete: restore a reviewed revision, reconcile it, and watch the same health signals that justified the rollout.

That does not make every rollback safe. Database migrations can make a simple reversal impossible. A downstream dependency may have changed underneath you. A feature flag may be the safer first lever. Declarative state is not magic; it just removes one common source of panic: having to guess which version and configuration were running before the change.

The part I would test next

The toy flow did not cover the hard cases. It did not include a database, traffic management, or an actual Kubernetes controller. It only checked the shape of the idea: a failed health check should lead to a named previous state, then to a second health check.

The next useful step is to run the same exercise against a non-critical service in an isolated environment:

  1. deploy a known candidate;
  2. deliberately fail a readiness condition;
  3. reconcile the previous revision;
  4. verify the restored service through the same route and alert signal users depend on.

If that feels cumbersome in a test environment, it will feel worse during an incident.


Test notes: This note is based on a disposable Docker container limited to 0.25 CPU and 128 MiB memory. It simulated a declarative checkout release changing from 1.4.2 to 1.4.3, a failed readiness check, and restoration to 1.4.2. No production infrastructure, repository, cluster, registry, or database was accessed.

Related work: Turning deployments into a repeatable platform capability

Read article

Diagram comparing whole-corpus reading with graph queries in the code-review-graph benchmark

Diagram from code-review-graph, commit 6a1ee1c · MIT licensed · kept locally so this post does not depend on a hotlink.

I saw code-review-graph climbing GitHub Trending and nearly wrote the usual post about why AI coding needs better context. Then I stopped. That sentence is true, but it is also a pretty easy way to avoid trying the tool.

So I did a small smoke test first.

I ran it inside a Docker container, not on the host. The container had one CPU and 1.5 GiB of memory. Inside it, I made a tiny Python repository: authentication, a login endpoint, and one test. Then I installed code-review-graph 2.3.7 and built the graph.

Full build: 3 files, 6 nodes, 9 edges
Nodes: 6
Edges: 9
Files: 3
Languages: python

Nothing dramatic happened, which was reassuring. The tool connected api.py, auth.py, and the test in the way I expected. That only proves it can build a graph for a tiny repository. It does not prove that it will make pull request reviews better on a real codebase. Still, the smallest claim held up without giving the tool access to the machine running the blog.

What the tool is trying to do

code-review-graph uses Tree-sitter to map a repository. The intended payoff is simple: when an agent reviews a change, it should not need to read an entire repository just to find the few files that matter.

That sounds obvious until a change touches an API, a worker, a deployment chart, and an alert rule. At that point the problem is not a lack of context window. It is finding the context that is actually connected to the change.

For a three-file repository, I would still open the files myself. There is no prize for adding a graph database to a problem that rg can solve in ten seconds. The case gets more interesting once the repository is large enough that a change has consequences outside the diff.

About the 528x number

The project README leads with an eye-catching result: up to 528x fewer tokens. That number is real, but it is the best case from the project's FastAPI benchmark.

The more useful number is their reported median of roughly 82x per question across six repositories. Even that needs context. The comparison is whole-corpus reading versus a graph query. A good engineer does not normally paste an entire repository into an LLM and hope for the best, so this is an upper-bound baseline, not a normal day at work.

I like that the README says as much. Too many AI tool pages put the caveat in a footnote, if they include one at all.

What I liked

The project does not try to make the model magically smarter. It tries to give the model less irrelevant material. That is a healthier problem to work on.

It also has a plausible route into real workflows: a CLI, MCP support, incremental updates, and a GitHub Action. A graph that looks good in a demo is easy. Keeping one useful after a repository changes every day is the hard part.

What I still do not know

This smoke test was deliberately small. It did not answer the questions I would ask before adopting it on a serious repository:

  • How long does the first build take on a monorepo?
  • How large does the graph database become?
  • Does retrieval surface the files an experienced reviewer would open anyway?
  • Does it save time, or does it add another moving part to maintain?

I would not give a trending tool write access to a repository on the strength of a screenshot and a benchmark chart. A read-only container, a non-critical repository, and outputs that a human can inspect are enough for a first pass.

If you want to try it

Start with one repository you know well. Measure the first build and an incremental update. Then compare the suggested files with the files you would have opened during a normal review.

If the answers line up, keep going. If they do not, you have still learned something useful and avoided adding another MCP server just because it was trending.


Test notes: code-review-graph 2.3.7 ran in an isolated Docker container with Python 3.12, one CPU, and 1.5 GiB of memory. This was a graph-build smoke test on a small Python repository. I did not independently reproduce the project's token benchmark.

Sources: code-review-graph · benchmark methodology · GitHub Trending

Read article

What I write about

Cloud securityIncident recoveryGitOps & deliveryAI agent operationsExperiments