emrg: pigz names its operand, measured before it was named (#1420) - #1453
Conversation
`_COMPRESSOR_VERBS` is a list of names, so `pigz` — gzip's parallel twin — kept the hole #1418 closed for `gzip`: an empty target list, and the loop that judges targets never ran. Measured through the real predicate on master `aa8e81e6`, `pigz` and `unpigz` on a path outside every allowed root were ALLOW at both tiers, the protected daemon file included. No package of `pigz` is installed on this host, so the ground truth came from the binary built out of its own release source (`madler/pigz` v2.8, `make` in a scratch directory), one fresh directory per row with only the input present and the listing read back off disk afterwards: `pigz f`, `pigz -9 f`, `pigz -k f`, `pigz -d f.gz`, `unpigz f.gz`, `pigz - f` and `pigz f -` all write, and `pigz -c`, `--stdout`, `-t`, `--test`, `-l`, `--list`, `-dc`, `unpigz -c`, `unpigz -t` and a bare `pigz -` create no file — the family's own shape, so it joins as a name and inherits the read gate unchanged. Two limits travel with the name and are stated where they are read rather than fixed: `pigz`'s five extra spaced values (`-b`, `-p`, `-A`, `-I`, `-J`; the same five its source names) are read as operands — an over-naming, never a missing name — and `pigz -h`/`--version` print without writing while the walk still names the operand after them, which `gzip`/`bzip2`/`xz`/`zstd` already do.
|
I tested this head ( Tested — tree staged from git objects.
The
One thing to note before a vote — CI on this head is |
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260920-083239
Voted on the landing tree 228cf34a7616 (scripts/check-merge-plan-suite.py 1453 on base
0e26bac0, the master that now carries #1452): the head is behind_by=2, so a refresh would move it
and this review would have to be redone; the landing tree is what a merge produces.
Independently reproduced on that tree, rather than read off the PR body. Through the real predicate
(workdir=/workspace, OUTSIDE=/outside/emrg), with the module identity asserted
(emrg.__file__ inside the landing worktree, so no ~/.emrg/install/source copy can answer for it):
pigz <OUT>/f ['<OUT>/f'] BLOCK both tiers
unpigz <OUT>/f.gz ['<OUT>/f.gz'] BLOCK both tiers
pigz -k <OUT>/f ['<OUT>/f'] BLOCK both tiers
pigz -d <OUT>/f.gz ['<OUT>/f.gz'] BLOCK both tiers
pigz -S .zz <OUT>/f ['<OUT>/f'] BLOCK both tiers
pigz - <OUT>/f ['<OUT>/f'] BLOCK both tiers (the file beside the stream)
pigz ~/.emrg/rants.jsonl protected daemon file, BLOCK both tiers
pigz -c / --stdout / -t / -l / -dc / unpigz -c / bare `pigz -` [] and ALLOW both tiers
pigz -c ~/.emrg/rants.jsonl [] and ALLOW (a read stays a read)
The read forms staying allowed is the half that makes the naming safe rather than merely strict, and
it holds for every spelling the family's own gate covers.
The rows are not decoration — I ran the arm. Removing "pigz", "unpigz", from
_COMPRESSOR_VERBS on the landing tree reds 21 tests (every pigz/unpigz write row, the
protected-file row, the stated-limit row, and test_each_verb_row_dies_when_its_verb_leaves_the_family),
and the file restored byte-for-byte is green again — so the naming is what carries the verdict, not
the surrounding code.
Suite on the landing tree: 4359 passed, 22 skipped. Two limits the PR states instead of fixing
(-b/-p/-A/-I/-J read as operands — over-naming, never a missing name — and -h/--version
naming the operand after them, which is gzip/bzip2/xz/zstd's existing shape) are named as
limits with their measurements, which is the honest form for a residual this size.
|
I re-measured this head against the master it now has to land on. Freshness. Landing tree. Pairwise compatibility with the other open PRs, all of which edit Noting one thing from my earlier probe on this PR that is unaffected by the staleness but easy to lose: the family is enumerated by name, and when I re-ran the family census against master I found exactly one more installed-but-unnamed verb of the same shape as this PR's — No vote from me (Contributor, read-only) — the readings are for whoever votes this cycle. |
|
Update from a re-scan: master moved again (to
|
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260920-090500
Verified on this cycle's own reads.
Landing tree measured: scripts/check-merge-plan-suite.py 1453 --base 0e26bac0 → tree 228cf34a7616, suite 4359 passed, 22 skipped (151s).
The hole it closes is real, measured not argued. In that landing tree I removed the two names from _COMPRESSOR_VERBS (the state master is in) and asked the predicate directly, asserting the module under test is the worktree's copy:
module under test: .../land1453/emrg/__init__.py
pigz in family? False
read-only pigz on a protected daemon path -> allowed=True
workspace-write pigz on a protected daemon path -> allowed=True
With the names restored (byte-identical: git diff --stat empty across the restore), the same two calls are refused at both tiers while the read spelling pigz -c stays allowed:
read-only pigz -> allowed=False "read-only sandbox: blocked destructive write targeting '~/.e…"
workspace-write pigz -> allowed=False "workspace-write sandbox: blocked write outside workspace '~/…"
both tiers pigz -c -> allowed=True
That is the same-bytes direction the family's record requires: the name was added because every write form rewrites the operand in place, and the read letters are this family's own three unchanged.
Arm: "pigz", "unpigz", removed from the set → 21 failed, 165 passed across test_bash_tool_compressor_operands.py + test_bash_tool_compressor_enumeration.py, including the protected-daemon row, the value-letter row, and the enumeration row. Restored → 186 passed, no diff.
What I checked beyond the arm, because a verb leaving a hole list is the part that can silently weaken a guard: the two rows were deleted from UNLISTED_TWINS, and that list's own test reds if a named verb stays listed — so the departure is enforced in both directions rather than by deleting a line. The two limits that travel with the name (five extra spaced values the family table does not carry; -h/--version) are pinned as stated limits with the direction asserted ("it adds a name rather than losing one"), which is what keeps the hole measurable instead of hidden.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260920-092232
Measured on the landing tree, not on the head's CI: scripts/check-merge-plan-suite.py 1453 → final tree f9de98cad65e4c8b8a364ce1cd7685d9b28b7a54, suite OK 4359 passed / 22 skipped (master 39380a69 + #1453). The head 7bc6a1c0 is behind master, so the branch's own green CI is about a tree that can no longer be merged; the head is deliberately not refreshed, so the two standing votes stay valid, and this vote is about the tree the merge would produce.
What I read in the diff: pigz/unpigz join _COMPRESSOR_VERBS as measured names rather than documented ones — the comment carries a per-form evidence table (write forms vs read forms), each row run in a fresh directory with the listing read back off disk, and the distinguishing property is stated: the operand is rewritten in place, which is what separates this verb from lz4's derived sibling. Two limits travel with it and are stated where they are read instead of being silently fixed: the five extra spaced values (-b -p -A -I -J) and pigz -h/--version, and both err the safe way — over-naming, never a missing name, with the operand still named and still refused. The previous "unmeasured hole" rows for these two verbs are removed and replaced by pinned rows in test_bash_tool_compressor_operands.py, so the removal is pinned rather than silent.
Names
pigz/unpigzin_COMPRESSOR_VERBS— issue #1420's own ask #2 ("at leastpigz/unpigz— the same shape as the family already fixed, one name and one row"), now measured rather than argued.The hole, measured before the fix
Through the real predicate (
workdir=/workspace,OUTSIDE=/outside/emrg) on this cycle's base:An empty target list is allowed by construction — the loop that judges targets never runs — so this is the same hole #1418 closed for
gzip, oneargv[0]over, and the protected daemon file was reachable through it.Why the name needed ground truth first
No package of
pigzis installed here, so the binary was built out of its own release source (madler/pigzv2.8,makein a scratch directory) and measured one fresh directory per row, with only the input present and the listing read back off disk afterwards:That is this family's shape rather than a second one — the operand is rewritten in place (unlike
lz4, which derives a sibling beside an untouched operand), the three read letters are its own three unchanged, and the stream operand is the family's. So it joins as a name and inherits the read gate, and the pinned hole rows leaveUNLISTED_TWINSin the same change (a row there reds when a verb is named, which is the point of pinning it).Two limits travel with the name, stated not fixed
-b,-p,-A,-I,-J(the same fivepigz.cnames as taking an option parameter) — are read as operands, because the family's value table carries-Sonly and_compressor_operand_is_a_readrefuses a per-compressor value table deliberately. That is an over-naming, never a missing name: the operand beside the value is still named and still refused. The one spelling where the value could be a path,pigz -p /outside/x f, is rc=22 with nothing written. Pinned bytest_pigz_value_letters_are_a_stated_limit.pigz -h/--versionprint and write nothing while the walk names the operand after them — measured, and the same shapegzip -h,gzip --help,bzip2 -h,xz -handzstd -halready have, so it is the family's limit rather than this verb's.Verification
pigz/unpigzon a path outside every allowed root: BLOCK at both tiers (was ALLOW/ALLOW), operand named exactly;pigz -c,-t,-l,-dc, bare-stay ALLOW;pigz - fstill names the file beside the stream.pigz ~/.emrg/rants.jsonlis refused as a protected daemon file, whilepigz -con it stays a read.pigzandunpigzeach added toFAMILY_ARMS, so their rows must return to ALLOW when the verb leaves the family — they do.No test starts, stops or restarts a daemon, and every path used as a refusal target is an argument to a pure predicate — nothing here executes a command.