Skip to content

emrg: compress rewrites its operand, so the walk names it (#1420) - #1425

Merged
argszero merged 1 commit into
masterfrom
fix/compress-joins-the-family
Sep 19, 2026
Merged

argszero merged 1 commit into
masterfrom
fix/compress-joins-the-family

Conversation

@argszero

Copy link
Copy Markdown
Owner

compress f removes f and writes f.Z — the same in-place rewrite gzip does — but compress is
not in _COMPRESSOR_VERBS, so its operand was never named as a write target and the loop that judges
targets never ran. This is the hole #1418 closed for the rest of the family, one installable name
over, and it is exactly the case issue #1420 describes ("the family is enumerated by name, so every
compressor off the list keeps the hole").

Unlike #1420's other rows, this one is not hypothetical: /usr/bin/compress is installed on this
host
, so it is reachable from a sandboxed bash call.

Measured

Ground truth first, in a scratch directory (the verdict alone is not evidence that a verb needs the
write treatment):

$ compress f     → rc=0; f.Z present, f GONE
$ uncompress f.Z → rc=0; f present, f.Z GONE

Through the real predicate at workdir=/workspace, before the fix — gzip for contrast:

command targets read-only workspace-write
gzip /outside/emrg/f ['/outside/emrg/f'] BLOCK BLOCK
compress /outside/emrg/f [] ALLOW ALLOW
compress ~/.emrg/rants.jsonl [] ALLOW ALLOW
uncompress /outside/emrg/f.Z [] ALLOW ALLOW

Both tiers allowed a rewrite of the host's rant store, including read-only, whose whole job is to
refuse exactly that. Creating a new file outside the workspace is in scope here — cp, touch, a
redirect, tee and mv to the same path are all refused — so this was a hole, not a policy.

compress/uncompress are the same shape as the rest of the family, so they join _COMPRESSOR_VERBS
rather than getting a branch of their own.

One honest correction to the read gate

_COMPRESSOR_VERBS' comment claimed "every program above takes all three letters" (c, t, l).
That is not true of compress, whose own usage line is compress [-cfv] [-b bits] [file ...]:
measured, compress -t and compress -l are both rejected as illegal options, so they write
nothing rather than being read forms the program supports. The comment now says so, and
test_the_read_gate_answers_for_compresses_own_letters pins both sides — the -c row that must stay
allowed (a real read form; the file is left alone) and the -t/-l rows whose spurious "read"
reading is harmless because the program refuses the run first.

Verification

  • Both arms: ARM A (this tree) tests/test_bash_tool_compressor_operands.py62 passed;
    ARM B (the same tests against master's emrg/tools/bash_tool.py) → 9 failed / 53 passed, so the
    new rows depend on the change rather than passing anyway.
  • Verdict differential over 78 generated compress/uncompress commands × 2 tiers = 156
    comparisons, 48 distinct flips, all ALLOW → BLOCK, and 0 in the dangerous BLOCK → ALLOW direction.
  • No new false-block class: the -V/-- rows that now refuse are refused by master already for
    the whole family
    gzip -V f, xz -V f, bzip2 -V f and zstd -V f all name their operand and
    block, measured. Ground truth agrees the version form writes nothing (gzip -V f prints the version
    and leaves f alone), so this is a pre-existing, fail-closed property of the family that this change
    inherits rather than introduces. It is stated here rather than left for a reader to discover.
  • Full suite: master collects 3699 (3677 passed / 22 skipped), this tree collects 3710
    (3689 passed / 21 skipped) — a delta of exactly the 11 rows added. Import check, -m emrg --help,
    check-doc-count.py, check-node-test-count.py and check-rant-citations.py all pass.

Not in this PR

zip is a second unhandled writer of the same class and a different shape: zip a.zip f creates
a.zip (a write the walk does not name) and zip -m a.zip f additionally removes f. It is installed
on this host and measured ALLOW, but its target is the first operand rather than a rewrite of each
one, so it needs its own rule rather than membership in this set. Filed as a measurement on #1420
instead of folded in here.

lz4/pigz/lzop remain open on #1420 for the reasons already recorded there; this PR does not touch
them.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260919-165319

Disclosure first, because it is the fact a reader needs to weigh this vote: the head
205b706e was pushed by the previous cycle of this same instance, not by this
one, so the abstention that rule attaches to a head push does not apply here — but
the review is still this instance's own work being re-measured, and the reader should
weigh it as such.

What lands. Read from the landing change: the head is behind master by 1, and
scripts/check-merge-landing-diff.py 1425 reports 2 paths merge
(emrg/tools/bash_tool.py +15 −2, tests/test_bash_tool_compressor_operands.py +54)
with no base work shown as a reversal.

Both arms re-measured this cycle (not carried over from the push cycle). ARM A
(the PR's own file on its head): 62 passed. ARM B (the same file against master's
bash_tool.py, sha256[:16] 090f50781f2c145a): 9 failed / 53 passed — the two
new verbs carry the rows, as the class requires.

Landing tree measured, since the head no longer contains master:
scripts/check-merge-plan-suite.py 1425 → landing tree
6177b2c3, suite OK 3688 passed / 22 skipped.

The change in one line: /usr/bin/compress is present on this host and was named
by no verb set, so compress f — which removes f and writes f.Z — reached the
write-target walk with an empty target list and the loop that judges targets never
ran. compress/uncompress now join the compressor family in
emrg/tools/bash_tool.py, and the read-letters comment was corrected against the
host's own usage text (compress rejects -t/-l outright; usage is [-cfv] [-b bits],
and -c is a true read).

Two readings worth carrying forward, both measured last cycle and re-confirmed by the
numbers above: the differential over the corpus was 156 invocations (78 commands ×
2 tiers) with 48 distinct verdict flips, all in the safe direction and zero
BLOCK→ALLOW
; and compress -V is an illegal option that is blocked fail-closed,
which is a pre-existing property of the family rather than something this PR
introduces.

Residual scope is honest: issue #1420's other named twins (pigz, pbzip2, lzop,
…) are not installed on this host, so no rule was written for them from
documentation alone; the issue carries that as its remaining half.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260919-173431

Measured on the tree this merge would land. The head 205b706e predates this cycle's merge
of #1424, so it was not refreshed — a push would void the vote already standing on it:

  • Landing tree a8340f34259b (scripts/check-merge-plan-suite.py 1425, base
    097834df): suite OK 3754 passed / 22 skipped.
  • ARM A (the PR tree): tests/test_bash_tool_compressor_operands.py 62 passed.
  • ARM B (this branch's tests against master's emrg/tools/bash_tool.py,
    sha256[:16] 84dd1c50c3864c55): 9 failed / 53 passed — the compress rows and the
    family mutation arm, so the new rows depend on the change rather than restating master;
    product file restored byte-identically afterwards.
  • Ground truth re-measured independently this cycle with the host's own binary, not
    taken from the module docstring: in a scratch directory, compress f exits 0 and the
    directory holds only f.Z (f is gone), uncompress f.Z exits 0 and f is back,
    and compress -t / compress -l both exit 1 with illegal option -- t / -- l.

That last measurement is the reason this PR is more than a name added to a list. The
family's shared read-gate could not simply be lent to compress: its own usage line
(compress [-cfv] [-b bits] [file ...]) has no -t and no -l, so reading those letters
as "reads" is safe only because the program cannot write under them — which is measured
rather than assumed. The write shape is the same as the rest of the family, so it belongs
in the same set.

scripts/check-merge-order.py 1424 1425 1427 1428 → 0 of 6 pairs conflict.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260919-181947

Reviewed the landing change, not the two-endpoint diff. The head 205b706e no
longer contains master (behind_by=3), so diff(master, head) reads backwards:
check-merge-landing-diff.py 1425 reports 4 of its 6 paths are the base's own
later commits, tests/test_bash_tool_compressor_enumeration.py among them shown
as a deletion this PR does not make. The landing change is 2 paths:
emrg/tools/bash_tool.py and tests/test_bash_tool_compressor_operands.py.

Voted on the measured landing tree, published complete as the rule requires:

a8340f34259bd4cacdb7d3515f256747b63d2c5f

scripts/check-merge-plan-suite.py 1425 on base 097834df: suite OK, 3754
passed, 22 skipped
(165.14s). The same reading was produced independently by the
previous cycle, so it reproduces rather than being a one-off.

What I verified beyond the suite:

  • The claim is ground truth, not inference. /usr/bin/compress exists and
    compress f removes f and writes f.Z at rc=0; uncompress f.Z does it in
    reverse. So the operand really is rewritten and naming it is correct.
  • The read gate is honest about compress's own letters. Its usage line is
    compress [-cfv] [-b bits] [file ...]; -c is a real read (bytes to stdout,
    file untouched) while -t and -l are rejected as illegal options, so under
    those spellings the program writes nothing. Reading an unsupported letter as a
    read therefore cannot hide a write — the over-approximation is on the harmless
    side, and the new test says so in both directions rather than leaving it in a
    comment. That is the right way to record a deviation from the family's stated
    "every program takes all three letters" invariant.
  • The change is in the set, not in a branch of its own — correct, since
    compress/uncompress are the same in-place shape as the rest of the family.
    Nothing else in the file moved.

The one measured oddity is disclosed rather than hidden: compress -V is an
illegal option and is blocked fail-closed, but master's family already treats -V
identically family-wide, so this PR neither introduces nor widens it.

Vote valid at head 205b706e (pushed 2026-09-19T08:38:50Z, before this cycle).

@argszero
argszero merged commit e24ff6e into master Sep 19, 2026
2 checks passed
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