Skip to content

A directory that was emptied for good is not a directory that might refill - #29

Merged
melbinjp merged 1 commit into
mainfrom
emptied-directory
Aug 19, 2026
Merged

A directory that was emptied for good is not a directory that might refill#29
melbinjp merged 1 commit into
mainfrom
emptied-directory

Conversation

@melbinjp

Copy link
Copy Markdown
Owner

paths.py skips a trailing-slash directory claim when the parent is tracked, because git stores files and not directories, so an empty directory is indistinguishable from an absent one. The reason is real - PostHog/posthog-python's RELEASING.md says changesets live in .sampo/changesets/, and every release empties it as the bot consumes them. The sentence says where sampo add PUTS files.

The guard's own comment then claims the tracked-parent requirement "keeps the case this must still catch: a documented directory whose entire tree really did go".

It keeps none of them. A removed subdirectory of a live tree has a tracked parent too.

stacklok/toolhive   docs/arch/06-registry-system.md:864   `pkg/container/verifier/`
                    7095e8e1  "Remove /verifier in favour of one coming from toolhive-core"

pkg/container is alive and documented. verifier is gone. The tool said nothing - two lines under a finding on the same paragraph that it did report.

Measured

Every --show-skips run over the 47 clones of sweep batches 10 and 11, captured to disk rather than sampled:

directory claims silenced by the guard 38
never tracked at all - the guard is right 31
populated and wholly emptied 7
emptied and refilled in this corpus 0

The 7 are in four repositories: toolhive x2, hive x2, onyx x2, openmed x1. The churning shape the guard exists for does not appear in these 47, though it is real - composio/.changeset has it, and the test suite builds it.

The fix

The skip keeps its reason and gains a receipt, which is the soundness rule the rest of this verifier already runs on: a path is called broken only when the repository can be shown to have HAD it and dropped it.

Git.emptied_and_stayed replays the paths oldest first and counts how many times the live set falls to empty. Once means it stayed empty. More than once is a lifecycle and stays skipped.

The replay direction is not incidental

The first version of the measurement walked newest-first, set a flag on the first D, and called it a refill at the next A. An A seen later in a newest-first walk is older, so every directory that was created before it was deleted scored as churn - which is all of them. That run reported:

38 directory claim(s) silenced by the tracked-parent guard
  never tracked        31
  churns                7

Zero emptied. Nothing about that output looks wrong, and it is the exact opposite of the truth. It was caught only because one case had been verified by hand first, which is the argument for checking a new measurement against a known answer before believing its totals.

Tests

The posthog case moved to tests/test_emptied_directory.py and now builds a real fill-consume-fill-consume history. The old fixture committed one file and deleted it once, which is not what a changeset directory does - so it asserted the posthog verdict over a history posthog does not have. Also checked against the live repository: emptied_and_stayed returns None for .sampo/changesets there.

215 tests. Verified end to end on the real toolhive clone: 13 findings to 15, both new ones naming the commit that removed the subsystem.

…efill

`paths.py` skips a trailing-slash directory claim when the parent is tracked,
because git stores files and not directories, so an empty one is
indistinguishable from an absent one. The reason is real: posthog-python's
RELEASING.md says changesets live in `.sampo/changesets/`, and every release
empties that directory as the bot consumes them.

The guard's own comment claimed the tracked-parent requirement "keeps the case
this must still catch: a documented directory whose entire tree really did go".
It keeps none of them. A removed SUBdirectory of a live tree has a tracked
parent too - toolhive documents `pkg/container/verifier/` for Sigstore
verification, `pkg/container` is alive, and `verifier` went in 7095e8e1 "Remove
/verifier in favour of one coming from toolhive-core".

Measured over the 47 clones of batches 10 and 11, every --show-skips run
captured to disk: 38 directory claims silenced, 31 never tracked at all and the
guard right about every one, 7 populated and wholly emptied across toolhive,
hive, onyx and openmed.

So the skip keeps its reason and gains a receipt, which is the soundness rule
the rest of the verifier already runs on. `Git.emptied_and_stayed` replays the
paths OLDEST FIRST and counts how many times the live set falls to empty; once
means it stayed empty, more than once is a lifecycle.

The forwards replay is not incidental. The first version of the measurement
walked newest-first and called it a refill at the first add after a delete - but
an add seen later in a newest-first walk is older, so every directory created
before it was deleted scored as churn. That run reported ZERO emptied
directories and nothing about the output looked wrong. Caught only because one
case had been verified by hand first.

The posthog test moved to tests/test_emptied_directory.py and now builds a real
fill-consume-fill-consume history, because the old fixture committed one file
and deleted it once, which is not what a changeset directory does. Also checked
against the live repository: emptied_and_stayed returns None there.

215 tests. Verified on the real toolhive clone: 13 findings to 15, both new ones
naming the commit that removed the subsystem.
@melbinjp
melbinjp merged commit 8b135f6 into main Aug 19, 2026
20 checks passed
@melbinjp
melbinjp deleted the emptied-directory branch August 19, 2026 15:20
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