Repository navigation
probe image: one tag built alike, and a credential nobody can measure is not a posture - #3
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The work stack's three probes share one image tag,
hookprobe:work, and a compose builds every service that has abuild:block — so the tag holds whichever build ran last.probe-watch's build passed noKUBECTL_VERSION; from 2026-09-29 the image had no kubectl. At every boot after that the planner and the work node recordedcan-i --list: kubectl: not found, the planner's verdict wasunverifiable, the work node'srecordedwith 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 adocker runof the tag.The build.
probe-watchnow passesKUBECTL_VERSIONexactly asprobe-plandoes, andprobe-workuses the samePROBE_PLAN_APT_EXTRASexpression as the other two instead of aPROBE_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. Inscripts/gate.shandci-stack.The posture. A mounted credential whose measuring tool is not in the image is
unmeasurable, a new verdict that is never a pass:readonlynode refuses to start underenforceand names the tool;HOOKPROBE_ALARM_URLat once (app.pyhandson_startupthe service's alarm);posture.jsoncarriestool_missingon the credential it could not ask. Tests for each branch;assert_guards_are_tested.pycarries the mutation (turningunmeasurableback intorecordedfailstest_posture.py) and the entry that my rewrite ofverdict()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.mdanddocs/containment.mdlist 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.