Repository navigation
feat: coverage says how much was hidden, never what (ADR 0054) - #89
Merged
Merged
Conversation
An empty report and a broken agent produce identical bytes today. Everything
needed to tell them apart was already computed — twelve filter counters, two
placement drops, five node-scan counters, nine inventory counters, the watch
health of every non-gating source — and all of it went to a log line the backend
never sees. The node scanner's counters were worse off: they crossed the channel
and the join dropped them on the floor.
New kind `collection_coverage`, and a fifth provenance class `agent`: the other
four answer "where in the cluster did this come from", and this payload's subject
is the agent itself.
No hash of the configuration. A digest of a handful of short namespace names is
reversible by trying the plausible ones, and what it would expose is the deny
list — precisely the set the customer chose to hide. The payload carries the
configuration's shape (entry counts, switches) and the file's mtime instead:
nothing to reverse, and a time answers "have the filters changed" better than an
opaque token.
Effective access is measured, not declared: per source, did the cache fill and is
it failing. A grant the ClusterRole holds but a webhook defeats reads as granted
in any review of the rules and as failing here. That also removes the reason to
call SelfSubjectRulesReview, which is a `create` from an agent whose customer
document says it only gets, lists and watches.
The one inference the counters do not close — a workload disappearing as an
opt-out counter moves — is stated in the ADR and in security.md rather than left
for a reviewer, per ADR 0039. The disappearance is observable without the
counter; the counter only explains it.
go test -race ./... && make lint && make chart-lint # green
make inventory-e2e
# coverage: 12 pods observed, 41 processes scanned on 1 node(s)
# (2 Go, 26 out of scope), signals [cpu memory network psi throttling]
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YRKuL97KRndaopfpixrhp
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.
"Nothing found" is a result, and today it is indistinguishable from a broken
agent. The backend receives some payloads and has no way to say "we looked at
412 pods, 37 were excluded by your own namespace filter, and one cache we hold a
grant for never synced".
Everything needed was already computed and thrown away: twelve filter counters,
two placement drops, five node-scan counters, nine inventory counters, and the
watch health of every non-gating source — all of it into a log line the backend
never sees.
security.md§11 promised filtering is "verifiable from output", andthe output was the agent's own log. The node scanner's counters were worse off
still: they crossed the channel inside
nodescan.Reportand the join read onlythe binaries beside them.
New kind
collection_coverage, plus a fifth provenance classagent— the otherfour answer "where in the cluster did this come from", and this payload's subject
is the agent itself.
registry_test.gorecords the widening, which is themechanism working rather than being worked around.
The privacy question, which changed the design
The plan originally called for a hash of the effective configuration. That is
wrong here for a reason specific to what the file holds: it is a handful of short
namespace names, a digest of a low-entropy value is reversible by trying the
plausible ones, and the list it would expose is the deny list — precisely the
set the customer chose to hide from us. The allow list is no secret; those
namespaces are named in every other payload.
So there is no hash. The payload carries the configuration's shape — how many
entries each list holds, which switches are on — and
config.since, the file'smtime. Nothing to reverse, because nothing is concealed, and a time answers "have
the filters changed" better than an opaque token does.
TestCoverageNamesNothingItExcludedbuilds the payload from a deny list holdingreal namespace names and asserts not one reaches the bytes, while still asserting
the counts survive — privacy by saying nothing would be the other failure.
Effective access is measured, not declared
Per source: did the cache fill, is it failing. A grant the ClusterRole holds but
an admission webhook or NetworkPolicy defeats reads as granted in any review of
the rules and as failing here. That is the difference that matters to a report
resting on it — and it removes the reason to call
SelfSubjectRulesReview, whichis a
createfrom an agent whose customer document says it only ever gets, listsand watches. That call is not made.
What is not closed, and is stated rather than hidden
A workload that disappears from the collected set as
excluded_workload_annotationmoves 0 → 1 is identifiable by subtraction. The counter does not create that
channel — the disappearance is observable without it — but it does explain it,
and the alternative is the backend concluding the workload was deleted and
telling the customer something false. In the ADR and in
security.md, perADR 0039.
No RBAC, no new read, no new API call. Two
[planned]markers leavesecurity.md;backend-requirements.mdstops disagreeing with the code.Ceilings:
security.md960 → 975,backend-requirements.md530 → 555.Evidence:
That "26 out of scope" is the node scanner's own filter counter reaching a
payload for the first time.
🤖 Generated with Claude Code
https://claude.ai/code/session_011YRKuL97KRndaopfpixrhp