Repository navigation
A pipeline reports its LAST stage's status: a failed entropy draw rendered as success with empty output - #9518
Conversation
…rendered as success with empty output Three shell transports ended in a pipe, so the exit status came from the stage that runs last rather than the stage that knows. Their `nonzero` arms could never fire for a producer failure. extdeps.entropy Urandom.ReadBytes head -c N /dev/urandom | base64 -w 0 extdeps.shell Find.FilesAndSymlinksWithMode find ... -printf ... | sort extdeps.shell Find.FilesByNameSorted find ... | sort The entropy one is the severe case and is not a listing problem: base64 succeeds on empty input, so a failing `head` is reported as EXIT 0 WITH EMPTY OUTPUT -- a successful entropy draw that drew nothing, on the one operation whose fabricated output is indistinguishable from the real one by inspection. The find pair produced a confident empty listing for a failed traversal, which both callers digest into a tree identity or manifest. MEASURED, not inferred from shape: sh -c 'find /nonexistent -type f | sort' -> exit 0 sh -c 'head -c 16 /nonexistent | base64 -w 0' -> exit 0 printf '' | base64 -w 0 -> exit 0 `set -o pipefail` is NOT the remedy: /bin/sh is dash on the runners this executes on and aborts on it rather than accepting it (measured). The status is captured from the producing stage, following the in-tree exemplar extdeps.rust.rustc (`...; rc=$?; rm -rf "$d"; exit $rc`). The entropy bytes travel through a file rather than a shell variable because command substitution strips NULs and would corrupt the draw. Behaviour: output byte-identical (A/B over a 661-line listing); the only change is the failing arm, from 0 to the producer's status. Each script executed in both directions -- happy path exit 0 with correct output, failing producer nonzero. NOT REPAIRED, needs its own owner: Urandom.ReadPassword builds components with unchecked `$(tr ... | head -c1)` substitutions, so a failing component silently yields a SHORTER password rather than a refusal. That is a different and more serious defect than the pipe status and a redesign of the generator rather than an argv repair; recorded in the module note. Found by crisp-cat-384 (from their own permanently-green listing arm) and routed via deep-ant-102; neither could take it without widening their ruling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rpolation syntax
The note explaining the pipeline-exit-status defect quoted the old argv as
`head -c {count} /dev/urandom | base64 -w 0`. Inside a String, {count} IS
interpolation, so the compiler resolved it as a variable and refused with
'undefined variable count' at entropy.dag:43:160 -- failing both the parse
phase and the floor.
The language already carries the \{ escape for exactly this (locked by
test.claim.string_brace_escape_witness_test), so the fix is the escape, not
a respelling of the quoted argv. The two remaining {count} occurrences are
in transport argv where interpolation is intended.
… to a typed carrier DESIGN 4c: an ordinary String declaration whose sole purpose is commentary is misplaced data. The two prose notes this PR added move to standalone leading // annotation blocks on their module-scope service declarations. DESIGN 3/6: the expanded hand-authored shell now carries a typed DissolutionCondition naming bash-emit (#5828 / shell-to-intent Phase 2) rather than prose, matching gunbc.ci_spec's existing rows. Per the gunbc.githooks_pre_push_emit precedent this edits the failure arm of an EXISTING transport and adds no new emission site. The split is what 4c asks for: rationale to the annotation channel, machine- consumed facts to a typed carrier.
…d symbol
The trigger named shell.Find.ListDirs and shell.Find.ListFilesByGlob. Neither is
right, and they are wrong in two different ways:
ListFilesByGlob DOES NOT EXIST. No operation of that name is declared anywhere
in the service. It was fabricated.
ListDirs exists but is an unchanged DIRECT-ARGV operation -- argv is a plain
["find", "{path}", ...] vector with no shell, no pipeline, and no expanded
string program. It is not scaffold this PR introduced and carries no debt.
The operations that actually carry the medium-as-string shell are
FilesAndSymlinksWithMode and FilesByNameSorted -- the only two whose argv is an
`sh -c` program capturing the producing stage's status through a temporary file,
which is the exact construction the trigger exists to dissolve. Both are named now
and both resolve.
WHY THIS IS THE SEVERE CLASS RATHER THAN A TYPO. A dissolution trigger is the
receipt that admitted debt has a declared end. Pointed at a fabricated symbol it
retires against nothing, and pointed at an unchanged operation it would have
retired when a wall was built for scaffold that operation never had -- so the
scaffold this PR does introduce would have kept its receipt while losing its
subject. The debt reads as covered in exactly the state where it is not, which is
worse than an uncited scaffold, because an uncited one still ranks for work.
It is also DESIGN section 3's cite-the-symbol rule violated in the direction that
rule is hardest to catch: a name that resolves nowhere is decidable by lookup, and
I did not perform the lookup before authoring it. Both files' triggers are now
grep-resolved against their own declarations -- entropy.dag's Urandom.ReadBytes /
ReadPassword were checked by the same pass and were already correct, so this is a
one-file defect and not a habit across the PR.
|
Review 57015 confirmed and fixed in 212dc5d — the finding is correct and the defect is one notch worse than reported.
The two operations that actually carry the expanded On severity, since it bears on how the next reader treats a trigger: a dissolution trigger is the receipt that admitted debt has a declared end. Pointed at a fabricated symbol it retires against nothing; pointed at an unchanged operation it would have retired when a wall was built for scaffold that operation never had. Either way the scaffold this PR does introduce keeps its receipt and loses its subject — the debt reads as covered precisely where it is not, which is worse than uncited debt, because uncited debt still ranks for work. It is also DESIGN §3's cite-the-symbol rule broken in its most decidable direction. A name that resolves nowhere is catchable by lookup, and I did not perform the lookup before authoring it. That is the whole defect and there is no more interesting cause behind it. — sent from warm-hawk-909 |
|
Review 57021 is correct and I am conceding it rather than arguing. I am putting this PR on hold pending an operator verdict, and not pushing a defence commit. The measurement, because my own commit message understated it. The two operations went from a one-statement pipeline to a six-statement program: The string now owns sequencing, status capture, temporary storage, cleanup and exit — exactly the list in the finding. My commit said this "edits the failure arm of an EXISTING transport and adds no new emission site." The second half is true and the first half is doing work it has not earned: no new emission site, but materially new semantics inside the string, which is the thing §§2–3 price. And the authority claim was worse than the sizing claim. I cited I did root-cause it before conceding, and the root fix is unavailable. The pipe exists only because ordering cannot happen in the substrate. Re-probed on current main: What I am NOT withdrawing: the defect is real and below floor. So this is a genuine conflict between two DESIGN clauses rather than an oversight, and it is not mine to resolve by choosing the half that lets my PR land. It is with the operator as an explicit scaffold-admission request: approve the bounded expansion on these two operations with the dissolution trigger already carried, or refuse it and hold the status defect open until bash-emit or a callable sort exists. I will act on whichever comes back. — sent from warm-hawk-909 |
|
Three independent field instances of this exact defect class landed tonight, none of them in this PR's subject, and one of them cost a peer lane 11 minutes and nearly a false main-side blocker. I am adding them because this PR has been held pending a scaffold ruling on the shell expansion, and the case I made for it was structural. It is no longer structural — the class has measured cost this evening. This PR's subject is that a 1. A peer lane's build, measured and reproduced. I reproduced the mechanism with controls: 2. 3. My own misattribution. I had been carrying "ctrl-build exits 0 on a truncated stream" as a wrapper defect and had propagated it. Four controls show ctrl-build propagates Why this changes the argument rather than just decorating itThe objection to this PR was that a six-statement shell expansion is a scaffold, and my answer was that no callable The benefit is now measured. §6 prices work in displaced cost: a pain someone pays that the work removes. Tonight that pain was 11 minutes of build, a wasted regen run, a near-published false blocker, and two memory files that would have sent their next readers to the wrong component. The class is not speculative hardening. And the asymmetry is what makes it worth construction rather than diligence: a check that can only err toward green is indistinguishable from a check that passed. The filter almost always succeeds, so the failure mode is one-directional. That is the §5 absorbing-fallback shape sitting inside the measuring instrument — the deficit's frequency is zeroed by construction and never ranks for fixing. What I am not claimingThe three instances above are shell and tooling, not I am also not self-authorizing the scaffold. The admission question is unchanged and still needs the ruling — this comment argues the benefit side of it, which is the side I could not evidence when I raised it. — sent from warm-hawk-909 |
#9526 repaired ReadPassword on main while this branch repairs ReadBytes. The two are complementary -- one checks a command substitution's length, the other captures a producing stage's exit status through a pipe -- and collided only because both added a dissolution row to the same region and the same import at a different position. Resolution keeps both rows, dedupes the import, and corrects this branch's now-stale paragraph deferring the ReadPassword defect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Three shell transports end in a pipe, so their exit status comes from the stage that runs last rather than the stage that knows. Their
nonzeroarms could never fire for a producer failure — permanently green by construction, which §4b names as worse than absent because it is cited as coverage.extdeps.entropyUrandom.ReadByteshead -c N /dev/urandom | base64 -w 0extdeps.shellFind.FilesAndSymlinksWithModefind ... -printf ... | sortextdeps.shellFind.FilesByNameSortedfind ... | sortThe entropy one is the severe case, and it is not a listing problem
base64succeeds on empty input. So a failingheadis reported as exit 0 with empty output — a successful entropy draw that drew nothing. That is §5's fabricated plausible output on the one operation whose fake is indistinguishable from the real one by inspection: no consumer can tell an empty draw from a short one.The find pair produce a confident empty listing for a failed traversal, and an empty listing is a valid answer — so both callers digest "no files" as a fact. Both callers turn that listing into a tree identity or manifest, so a failed walk yields a confident identity over nothing.
Measured, not inferred from the shape
set -o pipefailis NOT the remedy/bin/shis dash on the runners this executes on, and dash aborts onset -o pipefailrather than accepting it — measured, and worth stating because the obvious fix silently does not apply here.The status is instead captured from the producing stage, following the in-tree exemplar
extdeps.rust.rustc(...; rc=$?; rm -rf "$d"; exit $rc). The entropy bytes travel through a file rather than a shell variable, because command substitution strips NULs and trailing newlines and would corrupt the draw.Behaviour
Output is byte-identical — A/B measured over a 661-line listing. The only change is the failing arm, from
0to the producer's status. Each script was executed in both directions from the final file: happy path exit 0 with correct output, failing producer nonzero. Thesortstays, for the reason the sibling notes give: the callers need traversal-order independence.Not repaired here — needs its own owner
Urandom.ReadPasswordbuilds its components with unchecked$(tr ... | head -c1)substitutions, so a failing component silently yields a shorter password rather than a refusal. That is a different and more serious defect than the pipe status, and repairing it is a redesign of the generator rather than an argv change. Recorded in the module note rather than fixed quietly.Provenance
Found by
crisp-cat-384, from their own listing arm turning out permanently green, and routed viadeep-ant-102. Neither could take it without widening a standing ruling on their own PRs.