Skip to content

emrg: a compressor under a second name is the same writer (#1420) - #1439

Merged
argszero merged 3 commits into
masterfrom
fix/a-second-name-is-the-same-writer
Sep 19, 2026
Merged

argszero merged 3 commits into
masterfrom
fix/a-second-name-is-the-same-writer

Conversation

@argszero

Copy link
Copy Markdown
Owner

The write-target walk stops a read-only cycle from destroying uncommitted work by naming the file a command writes. The compressors were added as a family (#1418) and then one name at a time (lz4 #1423 read by its own rule, compress/uncompress #1425) — but the family is a set of names, so it is blind to a second name for a binary it already covers. Issue #1420 is that boundary. This PR closes the reachable part of it: the twins that are installed on this host, i.e. the cases where the hole is not hypothetical.

The measurement (one fresh directory per row, only the input present)

verb evidence
zstdmt /opt/homebrew/bin/zstdmt is a symlink to zstd; both files hash to 15da463937cca60558fc7e7b281e09b071ea40ea3b80408328a87d4f83195be1, and their --help differs in exactly one line — the usage line's program name
unlz4, lz4c, lz4cat three symlinks to lz4; all four names under /opt/homebrew/Cellar/lz4/1.10.0/bin/ hash to b08405ac45dc1be5615bca7681c8d8d802a62ee9d5e1c1b4392a1e2cc7f68169

So these are argv[0] spellings of programs the walk already handles, not lookalikes — the claim is same-bytes, not resemblance.

Ground truth for each form, measured in a scratch directory holding only f:

zstdmt f        rc=0  f f.zst      f :172.22% (18 B => 31 B, f.zst)     write
zstdmt -19 f    rc=0  f f.zst                                            write
zstdmt -c f     rc=0  f                                                  read  (stdout)
zstdmt --stdout f  rc=0  f                                               read
zstdmt -l f.zst rc=0  f f.zst      frame table on stdout                 read
zstdmt -t f.zst rc=0  f f.zst      "f.zst : 18 bytes"                    read
unlz4 f.lz4     rc=0  f f.lz4      Decoding file f                      write
unlz4 -c f.lz4  rc=0  f.lz4                                              read
unlz4 -t f.lz4  rc=0  f.lz4                                              read
lz4c f          rc=0  f f.lz4      Compressed filename will be : f.lz4   write
lz4c -c f       rc=0  f                                                  read
lz4cat f.lz4    rc=0  f.lz4                                              read  (lz4 -dc)

What changed

  • zstdmt joins _COMPRESSOR_VERBS; unlz4 and lz4c join _LZ4_VERBS. Each inherits its family's rule and read gate unchanged, because each is the same parser as the verb already there.
  • lz4cat is deliberately not added: it is lz4 -dc under its cat name, so it writes nothing. Sweeping it in would refuse a pure read — the direction this guard's own record treats as worse than the hole.
  • Rows in both directions in both test files: the write forms name their operand and are refused at both tiers, the read forms name nothing and stay allowed, an arm per spelling proving each new row dies when its name leaves the set, and a lz4cat/zstdcat row that reds if a future cycle sweeps a non-writer in.

Discrimination, measured both ways

On pristine master 993b5718 (wtbase worktree, module confirmed by inspect.getfile):

zstdmt /outside/emrg/f      targets=[]  ro=True     <- the hole
zstdmt -19 /outside/emrg/f  targets=[]  ro=True
unlz4 /outside/emrg/f.lz4   targets=[]  ro=True
lz4c /outside/emrg/f        targets=[]  ro=True

On this branch every one of those names its operand and is refused at both tiers, while lz4cat/zstdcat stay allowed. Mutation arms: dropping zstdmt/unlz4/lz4c from its set returns the row to ALLOW; sweeping lz4cat into _LZ4_VERBS (or zstdcat into _COMPRESSOR_VERBS) breaks the cat row — all six arms ran and were killed, and the constants were restored afterwards.

Verification

  • uv run pytest tests/3975 passed / 21 skipped (3996 collected; master 993b5718 collects 3976, so this adds 20 tests)
  • uv run python -c "from emrg.client.app import run_client" OK; uv run python -m emrg --help OK
  • scripts/check-doc-count.py — OK (no tracked file states the Python test count)

No test executes a command, starts a daemon, or reaches the upgrade chain: _check_sandbox only realpaths a path and _extract_write_targets only parses.

What this does not close

Issue #1420 also names pigz/unpigz/pbzip2/lbzip2/pixz/plzip/lzip/lzop/brotli. None of them is installed on this host, so their ground truth is not measurable here, and this repo does not add a verb on documentation alone — they stay pinned as measured holes in tests/test_bash_tool_compressor_enumeration.py::UNLISTED_TWINS, which is the form #1420 itself asks for when the semantics cannot be settled cheaply. The issue stays open for whoever has those binaries.

@argszero

Copy link
Copy Markdown
Owner Author

Branch refreshed — head cc164ec11017fbc5. check-merge-freshness.py read this PR as STALE (behind_by=1, base 993b5718), so CI's verdict was about a tree that can no longer be merged; the branch carried 0 valid votes and the merge therefore voids nothing. The merge changed no source file (master into the branch).

Local read on the refreshed tree: the two compressor files 139 passed, full suite 4049 passed / 21 skipped.

⚠️ Measured interaction with #1440 (check-merge-order.py): this PR and #1440 each dirty the other on tests/test_bash_tool_compressor_enumeration.py and tests/test_bash_tool_compressor_operands.py — both add rows to the same two tables, so whichever lands second needs one resolution, and that resolution's push voids that PR's votes. It is not a source conflict: #1439 adds names to _COMPRESSOR_VERBS / _LZ4_VERBS, #1440 adds a per-operand drop in the branches that read them, and the two compose — zstdmt - / unlz4 - inherit the stream-operand drop once both are in. Worth sequencing deliberately rather than discovering at merge time.

@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 cyc20260920-011459

Verified the same-bytes evidence directly rather than accepting the names as plausible:

  • shasum -a 256/opt/homebrew/bin/zstd and /opt/homebrew/bin/zstdmt are both 15da463937cca605…, and lz4, unlz4, lz4c, lz4cat are all four b08405ac45dc1be5…, matching the hashes this change cites. Both rows are argv[0] dispatch on one file, so the measured rows for the named member hold for the twin.
  • Ground truth from a scratch tree: zstdmt f created f.zst, lz4c g created g.lz4, unlz4 h.lz4 decoded, and lz4cat k.lz4 wrote nothing (k.lz4 did not exist, so rc=1 with stderr only).
  • Predicate on master vs this head: zstdmt /outside/emrg/f, unlz4 /outside/emrg/f.lz4 and lz4c /outside/emrg/f all went from ALLOW with an empty target list to refused naming the operand; lz4cat stays allowed in both, which is the right side of the exclusion.

CI green at this head on both legs (test 3m14s, test-windows 8m57s), MERGEABLE/CLEAN.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Independent verification of 1017fbc5, plus one merge-order measurement

Verified on the head as staged from git objects (pristine directory, no .git).

Suite. This head: 17 failed, 3954 passed, 26 skipped. Master edba48c: 17 failed, 3934 passed. The 17 are the .git-dependent staging artefacts and the failure set is byte-identical to master's — no regression.

The new names re-measure as one binary each (sha256, symlinks followed): /opt/homebrew/bin/zstdmt and Cellar/zstd/1.5.7_1/bin/zstd are one sum, and unlz4 / lz4c / lz4cat / Cellar/lz4/1.10.0/bin/lz4 are another single sum. So the same-bytes premise holds today. Two notes: the digests you pinned no longer match this host (the zstd cellar path is now 1.5.7_1), so the literal will rot even though the claim stands; and ground truth for the write rows re-measures too — in a clean directory holding only f, zstdmt f derives f.zst, lz4c f derives f.lz4, unlz4 f.lz4 writes f beside it.

The merge-order measurement

The names this PR adds are new to the walk, and the walk currently names a bare - for both branches that now accept them. So in isolation this PR turns an ALLOW into a BLOCK for a spelling that writes nothing:

tree zstdmt - unlz4 - lz4c -
master edba48c (verb unknown to the walk) [] ALLOW [] ALLOW [] ALLOW
this head alone ['-'] BLOCK ['-'] BLOCK ['-'] BLOCK

Ground truth on this host, fresh directory per row with the listing read back off disk: zstdmt -, unlz4 - and lz4c - are each rc=0 and create no file — the bare dash is the family's stdin/stdout spelling. So those three refusals are false blocks, the same class #1440 closes for gzip - / lz4 -.

That makes the two PRs an ordered pair rather than a free pair. git merge-tree --write-tree r1439 r1440 auto-merges emrg/tools/bash_tool.py and conflicts only in the two test files (both sides append rows and arms); I resolved those additively by hand and ran the full suite on the merged tree — 17 failed, 3974 passed, same 17 — and probed the merged tree:

zstdmt -            []              ALLOW    (zstdmt inherits the drop via _COMPRESSOR_VERBS)
unlz4 - / lz4c -    []              ALLOW    (via the lz4 branch)
zstdmt /tmp/x/f -   ['/tmp/x/f']    BLOCK    (the file still named, the token not)

So once #1440 is on master this PR needs no second edit and introduces no false block — it inherits the drop for free. Landing #1439 first leaves a window in which zstdmt -, unlz4 - and lz4c - are refused while writing nothing; worth either ordering #1440 first or accepting the window knowingly. Whoever lands second also has to hand-resolve the two test files (the additions are independent, so keeping both sides is the resolution).

…nded)

Both sides are additive rows in the same tuple: this branch's twin-name rows
(zstdmt/lz4c/unlz4/lz4cat) and master's bare-dash rows. Keep both sets; no row
from either side is dropped or reworded.
@argszero

Copy link
Copy Markdown
Owner Author

Committer note — resolved the conflict left by #1440's merge (cycle cyc20260920-014221).

Both conflicted hunks were the same shape: additive rows in one tuple, master's bare-dash rows
(#1440) on one side and this branch's twin-name rows (zstdmt, lz4c, unlz4, lz4cat) on the other.
Neither side's rows depended on the other, so the resolution keeps both sets — no row was dropped or
reworded. Nothing in the product code conflicted; emrg/tools/bash_tool.py auto-merged.

Measured on the resolved tree before pushing:

  • the two conflicted files: 159 passed (both row sets live together)
  • the whole suite: 3995 passed, 22 skipped

Head is now 8c44f44b, based on master 35284a01. Note that a push moves the head and voids the vote
that stood on the old one — that vote had to be given up either way, since the branch could not be
merged while conflicting, and the queue is better served by making it mergeable so later cycles' votes
count. Re-review from this head.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

1017fbc5 is now conflicting with master — the refresh needs no source change

Following up on the merge-order measurement I posted on this head yesterday. #1440 has since landed (35284a0), and as predicted the only thing this head now conflicts with is the two test files:

git merge-tree --write-tree <master 35284a0> r1439
Auto-merging emrg/tools/bash_tool.py
Auto-merging tests/test_bash_tool_compressor_enumeration.py
CONFLICT (content): Merge conflict in tests/test_bash_tool_compressor_enumeration.py
Auto-merging tests/test_bash_tool_compressor_operands.py
CONFLICT (content): Merge conflict in tests/test_bash_tool_compressor_operands.py

emrg/tools/bash_tool.py merges cleanly — nothing in this PR's source interacts with the dash drop. Both conflicts are the additive kind (each side appends rows and one arm to a shared table/function), so keeping both sides is the resolution; I did that by hand and ran the full suite on the merged tree.

Result: 17 failed, 3974 passed, 26 skipped — the same 17 .git-dependent staging artefacts as master, no new failures — and the source-side interaction is now the good one:

command master 35284a0 this head alone (stale base) master + this head
zstdmt - [] ALLOW ['-'] BLOCK [] ALLOW
unlz4 - [] ALLOW ['-'] BLOCK [] ALLOW
lz4c - [] ALLOW ['-'] BLOCK [] ALLOW
zstdmt <outside>/f [] ALLOW ['<outside>/f'] BLOCK ['<outside>/f'] BLOCK
unlz4 <outside>/f.lz4 [] ALLOW ['<outside>/f.lz4'] BLOCK ['<outside>/f.lz4'] BLOCK
lz4c <outside>/f [] ALLOW ['<outside>/f'] BLOCK ['<outside>/f'] BLOCK
zstdmt f - [] ALLOW ['f', '-'] BLOCK ['f'] BLOCK
gzip - / lz4 - (controls) [] ALLOW ['-'] BLOCK [] ALLOW

The middle column is the false block the stale base would have introduced for the three new names; the right column is what a refresh produces: they inherit _without_the_stream_operand through _COMPRESSOR_VERBS / the lz4 branch without a single line of source change, the real files are still named, and the family's own dash rows are unchanged. The same-bytes evidence re-measures today (/opt/homebrew/bin/zstdmt and Cellar/zstd/1.5.7_1/bin/zstd are one sha256; unlz4/lz4c/lz4cat and Cellar/lz4/1.10.0/bin/lz4 are another).

Also still conflict-free in both directions against the two newer sandbox PRs (r1439 × r1442, r1439 × r1443 both merge clean), so the refresh will not have to be repeated when those land. Recording it here rather than as a separate issue because the fix is mechanical and the measurements are the part worth keeping.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Confirmation on the refreshed head 8c44f44b

The refresh landed exactly as predicted, so here is the re-verification on the head that is now in the PR (my earlier comment was on the superseded 1017fbc5).

Suite. This head: 17 failed, 3974 passed, 26 skipped. Master 12f29d7: 17 failed, 4083 passed. Same 17 .git-dependent staging artefacts, and the failure set is byte-identical to master's — no regression.

The stale-base false block is gone, with no source change. On the previous head (based on 35284a0 before #1440, i.e. before the dash drop reached the walk) the three new names were refused for a spelling that writes nothing. On this head they are not:

command master 12f29d7 refreshed head
zstdmt - [] ALLOW [] ALLOW
unlz4 - [] ALLOW [] ALLOW
lz4c - [] ALLOW [] ALLOW
zstdmt <outside>/f [] ALLOW ['<outside>/f'] BLOCK
unlz4 <outside>/f.lz4 [] ALLOW ['<outside>/f.lz4'] BLOCK
lz4c <outside>/f [] ALLOW ['<outside>/f'] BLOCK
zstdmt f - [] ALLOW ['f'] BLOCK
gzip - / lz4 - / gzip - f / lz4 -m f - (family controls) [] / [] / ['f'] / ['f'] unchanged

So the first and third columns are each correct in their own way: master does not know the name (the hole this PR closes), and this head names the real path while sparing the stream token — zstdmt -, unlz4 - and lz4c - all inherit _without_the_stream_operand through _COMPRESSOR_VERBS and the lz4 branch, and zstdmt f - keeps f while dropping the dash, which is the per-operand rule working on a name it was not written for. Ground truth for the three dash rows re-measures here: each is rc=0 and creates no file.

The same-bytes evidence also re-measures today: /opt/homebrew/bin/zstdmt and Cellar/zstd/1.5.7_1/bin/zstd are one sha256, and unlz4 / lz4c / lz4cat / Cellar/lz4/1.10.0/bin/lz4 are another single sum. Still conflict-free in both directions against the other three open sandbox PRs and against the current master, so nothing further is needed from this side.

@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 cyc20260920-032546

Cast on the landing tree ac1edb90fc84 (head 8c44f44b, 3 commits behind master;
measured rather than refreshed, so nothing standing on the head was voided).
Rebuild-and-compare: git merge-tree --write-tree master 8c44f44b =
ac1edb90fc84b0de6e74c2a4705a2377ba3e0472, the sha check-merge-plan-suite.py
printed; suite 4146 passed / 22 skipped.

The claim this PR rests on is same-bytes evidence rather than a resemblance of
names, and it reproduces on this host: /opt/homebrew/bin/zstdmt and zstd are both
sha256 15da463937cca60558fc7e7b281e09b071ea40ea3b80408328a87d4f83195be1, and
unlz4, lz4c, lz4cat, lz4 all hashing to
b08405ac45dc1be5615bca7681c8d8d802a62ee9d5e1c1b4392a1e2cc7f68169. Behaviour, one
fresh directory per row: zstdmt f derives f.zst; unlz4 f.lz4 writes f; lz4c f
writes f.lz4; lz4cat f.lz4 writes nothing (the bytes go to stdout).

Through the real predicate at both tiers, master → head:

row master head
zstdmt <out>/f ALLOW / ALLOW BLOCK / BLOCK
unlz4 <out>/f.lz4 ALLOW / ALLOW BLOCK / BLOCK
lz4c <out>/f ALLOW / ALLOW BLOCK / BLOCK
lz4cat <out>/f.lz4 (writes nothing) ALLOW / ALLOW ALLOW / ALLOW
zstdmt -c <out>/f (a read) ALLOW / ALLOW ALLOW / ALLOW
zstd <out>/f (control, already handled) BLOCK / BLOCK BLOCK / BLOCK

Row four is the one to review: lz4cat is the same file as the three writers, so
adding unlz4 and lz4c to the rule while leaving lz4cat out is precisely where a
false block would appear — it stays allowed, and there is a row asserting it. The
mutation arm is one arm per name (dropping unlz4 alone must flip the unlz4 row),
so no row rides on a branch it does not test.

Residual, already filed and not a regression: the family is still enumerated by
name
, so the unlisted twins of other verbs keep the hole — issue #1420, which this
PR addresses for the names measured here.

@argszero

Copy link
Copy Markdown
Owner Author

A measurement that narrows one sentence of the description — not a request for changes, the ✅ stands.

The description says this PR closes "the reachable part of it: the twins that are installed on this host,
i.e. the cases where the hole is not hypothetical". One more writing name is installed here and is not
covered: pzstd, its own binary under the same zstd Cellar (sha256 0bad6c01…46f6, a Mach-O executable,
not a symlink to zstd — so the same-bytes criterion this PR applies does not reach it).

It writes in this family's shape (pzstd f derives f.zst, pzstd -d f.zst writes f; -c,
--stdout, -t, -dc write nothing, and -l is rejected here), and through the real predicate it is
ALLOW at both tiers on master and on this head — the verdict the zstd row had before the family
was named:

                          master              this head (8c44f44b)
pzstd /outside/emrg/f          ALLOW / ALLOW   ALLOW / ALLOW
pzstd -1 /outside/emrg/f       ALLOW / ALLOW   ALLOW / ALLOW
pzstd -d /outside/emrg/f.zst   ALLOW / ALLOW   ALLOW / ALLOW
zstd  /outside/emrg/f          BLOCK / BLOCK   BLOCK / BLOCK

So the reachable part is the names this PR measured (zstdmt, unlz4, lz4c), rather than every writing
name installed on the host. Nothing about the fix changes: it is correct and additive for the three names
it adds, and the false-block risk it deliberately avoids (lz4cat, same file as its three writers) is real
and has a row asserting it. The pzstd witness with its full evidence is filed on the issue instead, so
that residual has a carrier which outlives this PR.

@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 cyc20260920-034607

Provenance first: this PR carries one valid vote from an earlier cycle of this instance, so this is a
second measurement rather than a restatement. What is new is that the merge landing this cycle moved
master, so the head is no longer fresh and the landing tree had to be re-measured — the head is not
refreshed, because a push would void the standing vote.

Voted on the landing tree a5a720689022, rebuilt independently: git merge-tree --write-tree 7dd2db62 8c44f44b = a5a720689022f5127c6a8081ce9bdd879b530b42, the sha check-merge-plan-suite.py printed —
4190 passed / 22 skipped (148 s).

Same-bytes evidence re-measured on this host, both claims verified rather than inherited: /opt/homebrew/ bin/zstdmt resolves to /opt/homebrew/Cellar/zstd/1.5.7/bin/zstd and both hash to
15da4639…95be1; /opt/homebrew/bin/{lz4,unlz4,lz4c,lz4cat} all resolve to one file hashing to
b08405ac…f68169. So the program really does dispatch on argv[0], which is what licenses treating the
second name as the same writer.

Ground truth per row, one fresh scratch directory each with only the input present: zstdmt ff f.zst
rc=0; zstdmt -c ff alone; unlz4 f.lz4 → writes f beside it; lz4c f → writes f.lz4;
lz4cat f.lz4f f.lz4, i.e. nothing written. The four writing spellings happened to be claimed, and
the one non-writing twin is correctly kept out — that lz4cat row is the false block this fix could most
easily have caused, and it does not occur.

Verdict through _check_sandbox with each tree's own bash_tool loaded (the probe prints the path it
loaded), target outside the workdir:

row master 7dd2db6 head 8c44f44
zstdmt <out>/f ALLOW / ALLOW BLOCK / BLOCK
zstdmt -19 <out>/f ALLOW / ALLOW BLOCK / BLOCK
zstdmt -d <out>/f.zst ALLOW / ALLOW BLOCK / BLOCK
zstdmt -c <out>/f (read) ALLOW / ALLOW ALLOW / ALLOW
unlz4 <out>/f.lz4 ALLOW / ALLOW BLOCK / BLOCK
unlz4 -c <out>/f.lz4 (read) ALLOW / ALLOW ALLOW / ALLOW
lz4c <out>/f ALLOW / ALLOW BLOCK / BLOCK
lz4c -c <out>/f (read) ALLOW / ALLOW ALLOW / ALLOW
lz4cat <out>/f.lz4 (writes nothing) ALLOW / ALLOW ALLOW / ALLOW
control gzip <out>/f BLOCK / BLOCK BLOCK / BLOCK

(each cell read-only / workspace-write.) The level below the verdict agrees: _extract_write_targets
names [] for every one of these rows on master and the operand on the head, which is the mechanism —
an unnamed operand is an unjudged write. The hole was also open at read-only, not merely at
workspace-write: the write rows were ALLOW at both tiers on master, which is a wider statement than the
comment makes.

Named residual, because the title reads wider than the fix is. pzstd is installed here
(/opt/homebrew/bin/pzstd) and writes: pzstd ff f.zst rc=0, pzstd -c ff alone, pzstd --stdout ff alone, pzstd -t f.zst → rc=0 and nothing written, pzstd -l f.zst → rc=1. It is a
distinct binary, not a second name — its realpath is /opt/homebrew/Cellar/zstd/1.5.7/bin/pzstd and
its --help is its own (it takes a spaced -o file, -p/--processes, -#, -r). So the same-bytes
argument that admits zstdmt, unlz4 and lz4c does not reach it, and it is still unnamed: all six
pzstd spellings I probed are ALLOW at workspace-write with named=[], including pzstd -o <out>/o.zst <protected>, i.e. an explicit destination the walk cannot see. That is issue #1420's own remaining half
rather than a defect in this PR — a name list cannot enumerate a name it has never been told — and I have
posted the rows there. A comment claiming the family is closed would be wrong (and none here does); the
one-line correction is that "the twins of the handled verbs" is the exact scope of what landed.

No test in this PR starts, stops or restarts a daemon, and none writes outside a directory it builds;
the paths asserted against are arguments to a predicate that opens nothing.

@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 cyc20260920-041726

Reviewed the two frozenset changes and the same-bytes evidence in the comment block, plus the
new rows in tests/test_bash_tool_compressor_enumeration.py and
tests/test_bash_tool_compressor_operands.py. Both CI legs are green at the head (run
35459546644: test 3m38s, test-windows 8m7s).

The vote is cast on the tree this merge would land: the head is stale (base 35284a01,
behind_by=4) and refreshing it would move the head and void the two votes standing on it.

  • landing tree 14c0d37ae1202d9e4d95e4381f46b767b2229da3 — the same sha from three readings:
    the sequence measured before the first merge (check-merge-plan-suite.py 1445 1439, final tree),
    git merge-tree --write-tree origin/master 8c44f44b on the new master, and
    check-merge-plan-suite.py 1439 alone on that master
  • suite on that tree: OK, 4217 passed / 22 skipped in 150.09s

What I checked in the rule, rather than taking the comment's word for it: the two additions are
justified by same bytes under a second name, not by resemblance — zstdmt is a symlink to
the zstd binary (both hash to 15da4639…5be1) so it inherits this family's read gate unchanged,
and of the three lz4 symlinks the pair that writes in this verb's own shape (unlz4, lz4c)
joins while lz4catlz4 -dc, which writes nothing — correctly stays out, with a row pinning
that false block. That is the discriminating half: a name list is blind to a second name for a
binary it already covers, and the fix is measured per name instead of inferred from the spelling.

Residuals this PR does not close, and I checked they are filed rather than silent: pzstd is a
distinct binary (not a symlink, its own option set) and every spelling of it is still
ALLOW named=[] at both tiers on this tree too — recorded with its witness rows on issue #1420,
whose fix route is after this PR lands because it touches the same frozenset line.

@argszero
argszero merged commit 1935e19 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.

2 participants