Skip to content

probe image: one tag built alike, and a credential nobody can measure is not a posture - #3

Merged
itswl merged 2 commits into
mainfrom
probe-image-kubectl
Oct 12, 2026
Merged

itswl merged 2 commits into
mainfrom
probe-image-kubectl

Conversation

@itswl

@itswl itswl commented Oct 12, 2026

Copy link
Copy Markdown
Owner

The work stack's three probes share one image tag, hookprobe:work, and a compose builds every service that has a build: block — so the tag holds whichever build ran last. probe-watch's build passed no KUBECTL_VERSION; from 2026-09-29 the image had no kubectl. At every boot after that the planner and the work node recorded can-i --list: kubectl: not found, the planner's verdict was unverifiable, the work node's recorded with an empty mutating list, both started, the hourly selftest answered held, and nothing paged — until the stack was stopped on 10-09. Read off the stopped containers' posture.json, the image history and a docker run of the tag.

The build. probe-watch now passes KUBECTL_VERSION exactly as probe-plan does, and probe-work uses the same PROBE_PLAN_APT_EXTRAS expression as the other two instead of a PROBE_WORK_ twin with the same default (the new check caught that second difference). One tag, one set of build args.

The gate. scripts/assert_shared_images_build_alike.py: services that share an image tag must build it with the same context, dockerfile and args, as text, across every compose. Indentation parser (CI's interpreter has no YAML library), a floor so a lost file fails rather than passes. In scripts/gate.sh and ci-stack.

The posture. A mounted credential whose measuring tool is not in the image is unmeasurable, a new verdict that is never a pass:

  • a readonly node refuses to start under enforce and names the tool;
  • a writing node starts, records it, and pages on HOOKPROBE_ALARM_URL at once (app.py hands on_startup the service's alarm);
  • the hourly selftest's posture check fails until the tool is back;
  • a violation measured on one credential still outranks a missing tool on the other.

posture.json carries tool_missing on the credential it could not ask. Tests for each branch; assert_guards_are_tested.py carries the mutation (turning unmeasurable back into recorded fails test_posture.py) and the entry that my rewrite of verdict() had rotted is re-anchored. Decision note: .agents/notes/implemented/2026-10-12-a-credential-nobody-can-measure-is-not-a-posture.md. hookprobe/README.md and docs/containment.md list the verdict.

Not in this PR: rebuilding the image. The stack is stopped; the image is rebuilt after the merge and read back for kubectl, and the containers pick it up at the next recreate.

itswl added 2 commits October 12, 2026 14:07
The work stack's three probes share hookprobe:work and a compose builds every service that has a build block, so the tag holds whichever build ran last. probe-watch's build passed no KUBECTL_VERSION, and from 2026-09-29 the image had no kubectl: the planner and the work node started every day with "kubectl: not found" in their posture records and measured nothing on their cluster side. probe-watch now passes KUBECTL_VERSION as probe-plan does, and probe-work uses the same PROBE_PLAN_APT_EXTRAS expression as the other two instead of a PROBE_WORK_ twin with the same default.

scripts/assert_shared_images_build_alike.py refuses the shape at the gate: services that share an image tag must build it with the same context, dockerfile and args, as text, across every compose. Indentation parser because CI's interpreter has no YAML library; a floor so a lost file fails rather than passes. Wired into scripts/gate.sh and ci-stack.
A mounted credential whose measuring tool is not in the image is "unmeasurable", a verdict that is never a pass: a readonly node refuses to start under enforce and names the tool, a writing node starts, records it and pages on the alarm channel at once, the hourly selftest's posture check fails until the tool is back, and a violation measured on one credential still outranks a missing tool on the other. posture.json carries tool_missing on the credential it could not ask.

Ten days of "kubectl: not found" (2026-09-29 to 10-09) had passed as unverifiable on the planner and recorded on the work node, both of which start, while the hourly check asked only "wider than declared?" and answered held. The decision note says why a missing binary is a different silence from an identity that may not ask about itself.

Tests for each branch; assert_guards_are_tested.py carries the mutation (unmeasurable back to recorded must fail test_posture.py) and re-anchors the declared-radius entry this rewrite of verdict() had rotted.
@itswl
itswl merged commit 391f367 into main Oct 12, 2026
4 checks passed
@itswl
itswl deleted the probe-image-kubectl branch October 12, 2026 06:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant