next: accumulated work for the upcoming release #114
Reference in New Issue
Block a user
Delete Branch "next"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Long-lived integration branch for this release cycle. Work units land
here as one issue-closing commit each, and this PR accumulates them
until
nextis merged tomain.What has landed so far
Lint in a container as a build step, via
Dockerfile.lint(issue #113)Every lint run now happens inside its own container, invoked through
script/lint, and linting is a build step rather than a containercommand: a successful build of the new root
Dockerfile.lintis aclean lint. That shape also works where the docker daemon is remote and
bind mounts are impossible. Its
FROMline —golangci/golangci-lint:v2.12.2, pinned by digest — is now the only pinof the linter version in the repo.
A container per run has its own lint cache and its own
golangci-lintlock, both discarded with it, so neither cross-worktree contamination
nor lock contention exists any more. Everything that defended against
them is gone: the per-worktree cache directories (
cache_home,cache_dir,path_digest,prepare_cache,prune_dead_caches), thelock-retry loop, and
script/lint-auditwith itsrun_capture/audit_outputplumbing. So is the host lint path in its entirety — thenative escape hatch, its version detection, and
VAULTIK_LINT_IN_CONTAINERin bothscript/lintandDockerfile.Nothing lints on the host, at any version.
A cached build lints nothing, so the
CHECK_EPOCHmechanism this repoalready established is what makes a green mean something:
ARG CHECK_EPOCHwith no default, below the module layers so dependencycaching survives;
RUN [ -n "$CHECK_EPOCH" ] || exit 1; the valueexpanded into each check command itself; and
epoch="$(date +%s%N)$$"as a bare assignment on its own line inscript/lint, because inline in the argument a failing substitutiondoes not abort under
set -euand yields a constant empty epoch.The product
Dockerfileloses its lint stage rather than gaining asecond linter pin. That stage ran
make lint, which is nowdocker build— docker-in-docker inside a BuildKit step with no daemon— and calling
golangci-lintdirectly there instead would have meanttwo independently bumpable digests for one tool, the drift
issue #78 was filed over.
make fmt-checkmoves besidemake testin the builder stage, andscript/cibuildbuildsDockerfile.lintand thenDockerfile, eachwith its own fresh epoch, failing on either. Consequence, stated in
comments in
script/dockerand inREADME.mdrather than left to bediscovered:
script/dockerbuilds the product image only and no longerlints;
script/checkandscript/cibuildare the gates.Decision on
golangci-lint config verify: included, as its ownepoch-keyed
RUNabove the lint, with the reason written intoDockerfile.lint. It is not belt-and-braces.golangci-lint runrejects a config it cannot PARSE but silently IGNORES an unknown
top-level KEY, so a one-character typo in
.golangci.ymldiscardsdefault: all, the disable list and every threshold, and the gateexits 0 on a tree the real config fails.
config verifycatches that,and it does so with the network genuinely off at this pin. Both facts
are demonstrated under "Rework" below.
An earlier revision of this branch omitted
config verifyand wroteinto
Dockerfile.lintthat it fetches its JSON schema over live HTTPSfrom an unpinned URL, and that
golangci-lint runrejects anunknown-key config itself. Both claims were false at v2.12.2. They are
retracted, and the comment block now records what was actually
measured.
Decision on
script/lint-fix: kept, reimplemented as abind-mounted
docker runagainst the image reference parsed out ofDockerfile.lint'sFROMline. It cannot be a build step, becausefixes have to land in the worktree; its header states outright that it
is a developer convenience, never a gate, that no gate reads its exit
status, and that it needs a local daemon.
cmd/vaultik/lintdocker_test.goparses both Dockerfiles and bothscripts and fails if any part of the mechanism is dropped: the digest
pin, the defaultless
ARGbelowgo mod download, the emptinessguard, the expansion of the epoch into each check command, the bare
per-invocation epoch assignment in both scripts,
cibuildbuildingboth files,
config verifyrunning before the lint under the epoch,and — structurally, not by searching for one retired variable name —
that no script invokes
golangci-lintexcept throughdocker. Everyone of those losses is silent — the build still exits 0 and nothing is
checked.
Note for the record:
REPO_POLICIES.mdstill mandates the olderpattern, a lint stage inside the product
Dockerfilewired up withCOPY --from=lint /src/go.sum /dev/null. The owner's ruling supersedesit here; the policy text is org-wide and was left alone as out of scope,
with a comment in
Dockerfile.lintpointing at the divergence.Rework, after the failed review
All output verbatim, reproduced in this branch's own clone rather than
taken from the review.
Finding 1 —
config verifyomitted on a false premise, leaving a live false greenThe premise, tested.
config verifyat the pinned digest, with thenetwork genuinely off, on this repo's real config and on the same file
with
linters:renamed tolinterz::It validates offline, and it rejects the typo offline. The stated
reason for omitting it did not exist.
The false green, before the fix. A single planted over-length
comment line in
internal/globals/globals.go, and nothing else, runagainst the gate as it stood — first with the real config, then with
the same tree and the one-character key typo:
Same tree, one character, and the verdict flips from a named finding to
0 issues.— in a run whose lint layer demonstrably executed for 49srather than replaying. (
reviverather thanlllbecausegolangci-lint reports one issue per line by default; neither is in its
default set, which is the whole point.)
After the fix, the same typo'd config, gate failing before the
lint:
On caching it. The
config verifylayer is keyed onCHECK_EPOCH,like the lint. Content-addressing alone would arguably be sound —
.golangci.ymlarrives throughCOPY . ., so a cache hit implies abyte-identical config was validated when the layer really ran — but
that is exactly the argument that would also excuse caching the lint
layer, and this repo has ruled it insufficient. A cached check layer
checks nothing and the cost of being wrong is silent, while forcing it
costs milliseconds and puts the epoch in the log. The reasoning is in
the comment block either way, as the review asked.
Finding 2 — the structural test guarded a dead string
TestNoHostLintPathRemainssearched for the literalVAULTIK_LINT_IN_CONTAINER, a name this commit retires, so nothingcould ever trip it again. It now asserts the property: every script
under
script/is parsed into executable shell — comments andhere-document bodies dropped, backslash continuations joined — and any
line invoking
golangci-lintmust reach it throughdocker, withdockeroccurring first on that line.script/lint-fixis notexempted; it runs the linter as a container rather than a build step,
so the property holds of it too.
Both exclusions are load-bearing:
script/lintandscript/bootstrapboth name
golangci-lintin prose, in comments and in the error textthey print, precisely to say the host binary is never used. Joining
continuations is what lets
script/lint-fix's multi-linedocker runbe recognised as containerised.
Mutation proof. Added the review's renamed escape hatch to
script/lint:Reverted,
ok sneak.berlin/go/vaultik/cmd/vaultik 1.373s.A scanner that goes blind — over-eager stripping, or failing to join
continuations — would make that test pass on every tree, so the scanner
has its own test,
TestShellCodeSeesCodeAndNotProse, over a fixturecontaining a comment, a here-document body and a continuation-joined
docker run, all three naming the linter.The gate still holds after the change
Two consecutive
script/lintruns, tree captured withgit status --shortbefore the first and after the second and compared(
TREE UNCHANGED BETWEEN RUNS), both exit 0:Dependency layers
CACHEDin both; both check layers executing inboth, under different epochs.
Withheld
--build-arg, twice (failed steps are never cached, so theguard fires on every invocation rather than once):
Negative control, unstaged: the first
make checkof this reworkfailed on a real finding in the new test code, which is the gate
catching this commit's own work:
Fixed, and then:
make check, exit 0, 1m12s, zero(cached)test lines:script/cibuild, exit 0, 3m24s, zero(cached)test lines, bothbuilds with their own epoch and every check layer executing:
No
docker builder prunewas run at any point; every invalidation wasscoped by
CHECK_EPOCH, and the twoconfig verifyexperiments ran asdocker runagainst the pinned image rather than touching the sharedbuild cache.
Verified
--output=type=cacheonlyon the Gitea runner. Used for the lintbuild, since its verdict is the exit status and the image it would
produce is never run — it saves ~90s of image export per run and
avoids leaving a dangling image behind. CI on the previous head
commit succeeded in 4m0s with it, and
script/cibuildwould haveexited non-zero had the runner's builder rejected the flag, so the
runner's builder accepts it. (Was listed as unverified before the
review; the reviewer settled it.)
Not verified
golangci-lintprints a deprecation warning on every run:The linter 'gomodguard' is deprecated (since v2.12.0) ... Replaced by gomodguard_v2. Out of scope here and left untouched; it is a.golangci.ymlchange, not a lint-transport one.Review of
739de1e— FAIL (needs-rework)Independent review, own clone, nothing changed or committed. The central
claim of the change — that the containerised gate cannot be cached green
— holds up under attack and is verified by execution below. Two findings
against it, one of them a live false green.
Finding 1 (high) — the reason recorded for omitting
config verifyis factually wrong at the pinned version, and the gap it leaves is a demonstrated false greenDockerfile.lint, theconfig verifycomment block, makes two claims.Both were tested against the pinned image
golangci/golangci-lint:v2.12.2@sha256:5cceeef0....Claim A — "it validates
.golangci.ymlagainst a JSON schema that itfetches over live HTTPS from an unpinned URL at run time". Disproven.
The schema is local at this version;
config verifyruns fully offline.With the network genuinely off — the exact test
issue #113's
implementation comment prescribed as the condition for including it:
It rejects the bad config with no network, so it is not skipping
validation when offline — it is validating, offline. The stated reason
for omitting it does not exist.
Claim B — "
golangci-lint runbelow rejects an unparseable orunknown-key config itself, at the version that is actually gating".
Half true, and the false half is the half that matters. Unparseable YAML
is rejected (
exit=3). An unknown key is silently ignored:...on a file that the correct config flags with 2 findings.
Impact, reproduced against this repo's real gate. In a clone of
next,.golangci.ymlline 11linters:changed tolinterz:— onecharacter — and nothing else:
Green, no warning, in a run where the lint layer demonstrably executed.
default: all, the six-entrydisablelist and everysettingsthreshold were silently discarded and only golangci-lint's small default
linter set ran. This is a set-but-unparseable config silently falling
back to a default instead of failing loudly — the defect class this repo
treats as automatic, applied here to the gate's own config. It is also
the one hole
config verifycloses, and it is closed for free: thenetwork objection is not real at this pin.
Acceptable: add
RUN golangci-lint config verify --config .golangci.ymlto
Dockerfile.lintabove the lintRUN(the offline evidence theissue required now exists — reproduce it and record it), and replace the
comment block with what is actually true. If it stays omitted, the
comment must not assert a network dependency that does not exist, must
not assert that
golangci-lint runcatches unknown keys, and must saywhat does defend against a typo'd config key instead.
Finding 2 (medium) —
TestNoHostLintPathRemainsguards one dead string, not the invariant it is named and advertised forcmd/vaultik/lintdocker_test.go:190-212asserts only that no file underscript/, and notDockerfile.lint, contains the literalVAULTIK_LINT_IN_CONTAINER— a variable this commit deletes and whichnothing will ever reintroduce under that name. Mutation-tested: adding
to
script/lint— a working host-lint escape hatch, on the host, at whatever version is
on
PATH— leaves--- PASS: TestNoHostLintPathRemains. The commitmessage and PR body both claim the test file guards "the absence of any
host-lint escape hatch"; it guards the absence of one retired name.
This matters because the owner's ruling is that nothing lints on the
host, and a test named for that invariant is what a future reader will
trust instead of re-deriving it. Acceptable: assert that no script other
than
script/lint-fixinvokesgolangci-lintoutside adockerinvocation — or, more narrowly, that
script/lint's only linterinvocation is a
docker build.Verified by execution, and passing
script/lintruns,tree byte-identical before and after (
git status --shortcompared,unchanged),
BUILDKIT_PROGRESS=plain: dependency layersCACHEDinboth, lint layer
DONE 77.1sthenDONE 78.8swith different epochs.Not
CACHEDin either.--output=type=cacheonlystill fails on a real finding. Planted a155-char line in
internal/globals/globals.go;script/lintexited1 naming
internal/globals/globals.go:13:1 ... (lll). Reverted, exit0. The finding also cited only this clone's own path, so the
containerised gate is not replaying a sibling worktree's analysis.
docker build -f Dockerfile.lint .with no--build-argexits 1 atRUN [ -n "$CHECK_EPOCH" ] || exit 1, onboth of two consecutive attempts. No
ARG CHECK_EPOCH=default existsanywhere in the tree.
guard deleted, epoch expansion dropped from the lint
RUN, digestunpinned,
ARGgiven a default,ARGhoisted aboveRUN go mod download, epoch inlined inscript/lint, theDockerfile.lintbuild dropped fromscript/cibuild,make lintreintroduced into
Dockerfile, and the old escape hatch revived. Allnine failed the suite with the right message. Only the tenth
(Finding 2) did not.
script/checkandscript/precommitrun allthree;
script/cibuildand.gitea/workflows/check.ymlrun lint viaDockerfile.lintandfmt-check+testin the builder stage;make fmt-checkreally is in the builder stage and really executes(
#14 [builder 8/10] RUN echo "check epoch: ..." && make fmt-check).script/dockerno longer lints — deliberate, and stated inscript/docker, theDockerfileheader andREADME.md.make checkexit 0 in 1m20s, zero(cached)test lines, lintlayer
DONE 57.9s.script/cibuildexit 0 in 3m41s, zero(cached)test lines, lintDONE,make fmt-checkandmake testboth executing under a fresh epoch,
make testDONE 102.3s.current
main(696ed9a). Commit title ends(closes #113).make fmtclean. No dangling references toscript/lint-audit, thecache machinery or
VAULTIK_LINT_IN_CONTAINERoutside historicalTODO.mdentries and the deliberateREPO_POLICIES.mdnote. Noscope creep, no attribution trailers, terminology clean.
script/lint --fixexits 2 with a usage message namingscript/lint-fixrather than dropping the flag.Notes, not defects
--output=type=cacheonlyon the Gitea runner as notverified. It is now: the head commit's CI run succeeded in 4m0s, and
script/cibuildwould have exited non-zero had the runner's builderrejected the flag, so the flag was accepted there.
REPO_POLICIES.md:102-166still mandates the in-Dockerfilelintstage and
COPY --from=lint /src/go.sum /dev/null. The divergence isthe owner's ruling, is flagged in
Dockerfile.lint, and is notcounted against this PR.
script/lint-fixhas no docker-availability preflight, unlikescript/lint. It is explicitly never a gate, so this is cosmetic.Review FAILED; to rework. The containerised gate itself holds up — the reviewer could not make it report a cached or unearned green through the epoch, the guard or
cacheonly, and CI on the head commit settles the open--output=type=cacheonlyquestion in the affirmative.The blocking finding is that omitting
golangci-lint config verifywas decided on a premise that is false at the pinned version, and the omission leaves a live false green: a one-character typo in a top-level key of.golangci.ymlis silently ignored,default: alland every threshold are discarded, andscript/lintexits 0 reporting0 issues.on a run whose lint layer demonstrably executed. That is the exact defect class this repo has spent the last two days removing, now sitting in the gate's own config. The evidence the implementation brief asked for and did not get —docker run --network none— is what disproves the premise.739de1e101tod257f8f658Rework of the failed review, point by point
Head is now
d257f8f(amended onto the same base,696ed9a; thecommit title still ends
(closes #113)). Both findings addressed, bothreproduced independently in a fresh clone before being fixed rather than
taken on the reviewer's word. Full evidence is in the PR body; the
verbatim core is repeated here.
Finding 1 (high) — accepted in full,
config verifynow runsDockerfile.lintgains, above the lintRUN:Claim A reproduced and confirmed false. At the pinned digest, with
the network genuinely off:
The schema is embedded at v2.12.2. There is no live HTTPS fetch, so
there was no network dependency to avoid.
Claim B reproduced and confirmed half false, against the real gate.
Rather than trusting that
0 issues.on a clean tree meant the typo'dconfig was in effect, I made the two configs behaviourally
distinguishable: one planted over-length comment line in
internal/globals/globals.go, and nothing else changed between the tworuns.
One character, and a named finding becomes
0 issues.in a run whoselint layer executed for 49s. (
reviveand notlllonly becausegolangci-lint reports one issue per line by default; neither linter is
in its default set, which is the point.)
After the change, the same typo'd config now fails before the lint
ever starts:
On the cache question you raised. The
config verifylayer ISkeyed on
CHECK_EPOCH, deliberately, and the comment says why. Thecontrary argument is written out there too and rejected:
.golangci.ymlarrives through
COPY . ., so a cache hit implies a byte-identicalconfig was validated when the layer really ran — but that is the same
argument that would excuse caching the lint layer, this repo has ruled
it insufficient, the cost of being wrong is silent, and forcing it costs
milliseconds while putting the epoch in the log.
The comment block is rewritten, not patched. It now states that
golangci-lint rundoes NOT catch an unknown top-level key, thatconfig verifywas verified to work offline at this pin, and that theprevious revision's live-HTTPS claim was false — so the corrected
decision is not left standing on a wrong rationale.
Finding 2 (medium) — accepted, test widened and mutation-proved
TestNoHostLintPathRemainsno longer looks forVAULTIK_LINT_IN_CONTAINERat all. It parses every file underscript/into executable shell — comments dropped, here-document bodiesdropped, backslash continuations joined — and fails if any line
invoking
golangci-lintdoes not reach it throughdocker, withdockeroccurring first on that line.script/lint-fixis notexempted: it runs the linter as a container rather than as a build step,
so the property holds of it too, and the continuation-joining is exactly
what lets its multi-line
docker runbe recognised.Mutation, using your escape hatch verbatim in
script/lint:Reverted:
ok sneak.berlin/go/vaultik/cmd/vaultik 1.373s.The scanner itself is now the thing that could go blind and make the
test pass on everything, so it has its own test,
TestShellCodeSeesCodeAndNotProse, over a fixture containing a comment,a here-document body and a continuation-joined
docker run, all threenaming the linter. Both exclusions are load-bearing rather than
cosmetic:
script/lintandscript/bootstrapboth name the linter inprose precisely to say the host binary is never used.
The gate still holds
script/lintruns on a byte-identical tree(
git status --shortcompared before and after:TREE UNCHANGED BETWEEN RUNS): dependency layersCACHEDin both, and BOTH checklayers executing in both under different epochs —
config verifyDONE 6.1s/DONE 1.4s, lintDONE 68.6s/DONE 64.8s. NeitherCACHED.docker build -f Dockerfile.lint .with no--build-argstillexits 1 at
RUN [ -n "$CHECK_EPOCH" ] || exit 1, on both of twoconsecutive attempts.
make checkexit 0 in 1m12s, zero(cached)test lines.script/cibuildexit 0 in 3m24s, zero(cached)test lines, bothbuilds under their own epoch with
config verify,golangci-lint run,make fmt-check,make testand the product build allexecuting.
make checkof this reworkfailed on a genuine
modernizefinding in the new test code(
strings.Index can be simplified using strings.Cut), which is thegate catching this commit's own work. Fixed, then green.
make fmtrun;TODO.mdupdated in the same commit, with theconfig verifyparagraph rewritten to match. PR body corrected: theconfig verifydecision paragraph now records the retraction, thehost-lint test claim is restated as the structural property it now is,
and
--output=type=cacheonlyon the Gitea runner has moved out of"Not verified" into "Verified" on the CI evidence you supplied.
No
docker builder pruneat any point; the twoconfig verifyexperiments used
docker runagainst the pinned image and did not touchthe shared build cache.
Nothing rebutted, one thing not verified
Both findings were accepted as written; nothing is skipped or argued
against. The one item still unverified is unchanged and unrelated: the
gomodguarddeprecation warning golangci-lint prints on every run,which is a
.golangci.ymlmatter rather than a lint-transport one andremains out of scope here.
Re-review of
d257f8f— FAIL (needs-rework)Independent re-review in my own clone; nothing changed, nothing committed. Both
findings from the previous review are genuinely fixed, and I reproduced both
fixes by execution rather than taking them on the rework note's word. One new
blocking finding, against the replacement test.
Finding 1 (medium-high) — the replacement scanner goes blind, and the escape hatch it exists to forbid passes it
cmd/vaultik/lintdocker_test.go. Two independent evasions, each demonstrated bymutating the real
script/lintand running the full suite, which stayed green.(a)
heredocTerminator(line 461) treats any<<on a code line as ahere-document opener, and an unterminated one silently swallows the rest of the
file.
shellCodesetsterminatefrom the first<<on a joined lineregardless of context, then drops every following line until one equals that
word exactly. Two consequences:
<<inside a quoted string, or inside an inline comment (inlinecomments are not stripped — only whole lines beginning with
#), produces aterminator word that never appears, and the scanner stops seeing code for the
remainder of that file.
cat <<-\EOF— a normal way to quote a here-document terminator —yields the word
\EOF, which never matches the realEOFline. Same result.Appended to the real
script/lint, aftermain "$@":make test:ok sneak.berlin/go/vaultik/cmd/vaultik 1.312s. Anunconditional host lint invocation in the gate script itself is invisible.
(b)
assertLinterIsContainerised(line 265) accepts any line where thesubstring
dockeroccurs before the linter, so a one-line native fallbackpasses. Inserted into the real
script/lint'smain():Same run, same green. That is precisely the escape hatch the previous review
mutated with, in its most natural spelling: a docker-availability check falling
back to the host linter.
Why it matters: the commit message and PR body both state the property as
established — "structurally, not by searching for one retired variable name —
that no script invokes
golangci-lintexcept throughdocker" — and the reworknote argues the scanner is safe because it has its own test. It has neither
property. (a) is the worse half: it needs no adversary. An ordinary later edit
adding a shift, a quoted
<<, or a<<-\EOFanywhere in a scriptdisables scanning of everything below it in that file, permanently and silently,
with the suite still green — the "gate that stops guarding without saying so"
class this branch exists to eliminate.
Acceptable, and both small:
shellCode, fail loudly when a here-document is still open at end offile (
terminate != ""after the loop). That alone converts all three formsin (a) from silent blindness into a named failure. Tightening the opener test
to an actual redirection is a bonus, not a substitute.
assertLinterIsContainerised, split the joined line on;,&&,||and|, and require the command that contains the linter to beginwith
docker.script/lint-fix's continuation-joineddocker run … golangci-lint run …is one command and still passes.
TestShellCodeSeesCodeAndNotProseto cover an unterminatedhere-document and a
<<that is not one; extend the mutation coverageto the
||fallback above.Evades too, not blocking — inherent limits of a text scan, worth one line in the comment rather than code
tool=golangci+"$tool-lint" run(name assembled at runtime); a name splitacross a backslash continuation (
golangci-\/lint run— real shell deletesthe backslash-newline, the scanner joins with a space, so the literal never
forms); files in a subdirectory of
script/(entry.IsDir()→continue); andthe
Makefile, which is not scanned at all.Probed and correctly caught
Plain call,
eval,exec,env, backticks,$(command -v golangci-lint), acasebranch,LINTER=golangci-lint+$LINTER run(the assignment trips it),<<'EOF', indented<<-EOFwith a tab-indented terminator, aterminator line with trailing whitespace, and
$(( 1 << 2 ))withspaces. Appending a host lint call to the end of each of the 17 real scripts is
reported for all 17 — so there is no blind region in the tree as it stands
today; this is a guard weaker than advertised, not a live false green.
Verified by execution, passing
(planted 120-char line in
internal/globals/globals.go): real config →script/lintexit 1 naming thelllfinding;linters:→linterz:→exit 1 at layer
#11,golangci-lint config verify, exit code 3,additional properties 'linterz' not allowed, lint layer never reached. Thehole it closes is real: the same tree and typo'd config under a bare
golangci-lint runin the pinned image gives0 issues., exit 0.config verifyrejects the typo under--network none, exit 3.script/lintruns, logs written outside the tree so the buildcontext is byte-identical,
git status --shortcompared:COPY . .CACHEDin both,
config verifyand lint layersDONEin both under differentepochs. Neither
CACHED.docker build -f Dockerfile.lint .exits 1 at the guard, twice.make checkexit 0,script/cibuildexit 0, zero(cached)test lines ineither, both
cibuildbuilds under their own epoch withconfig verify,golangci-lint run,make fmt-checkandmake testallDONE.d257f8f(2m58s). Fast-forward onto currentmain.make fmtclean, title ends
(closes #113), no attribution trailers, terminologyclean, no scope creep. No dangling references to the removed machinery.
Notes
Dockerfile.lint:73-76cites the PR as recording thatscript/lint"exits 1naming the
lllfinding"; the evidence block on this PR names arevivefinding and explains why it was
reviveand notlll. My own run doesproduce
lll, so the claim is true and only its citation is off — worth oneword when Finding 1 is reworked.
the reasoning:
Dockerfile.lintcarries ~60 comment lines over 10instructions, including a paragraph of retracted-claim history, a written-out
rejected alternative on content-addressing, and the
REPO_POLICIES.mddivergence note;
script/lintre-derives the busybox/%N/$$argument atlength inside
main(), where it also appears inscript/cibuild,Dockerfile.lintandREADME.md. Non-blocking, and the trap-avoidance coreof each is worth keeping.
docker builder pruneat any point; the two direct experiments ran asdocker runagainst the pinned image, and every build invalidation was scopedby
CHECK_EPOCH.Second review FAILED; to rework. The gate mechanism and the
config verifyfix both hold up under execution — the false green the first review found is genuinely closed, confirmed on a behaviourally distinguishable tree.What fails is the guard test's own scanner, which is weaker than the commit message and PR body claim. Two evasions were demonstrated by mutating the real
script/lintwith the suite left green, and one of them needs no adversary: any<<appearing in a string or an inline comment opens a phantom heredoc and silently discards the rest of the file from scanning. An ordinary later edit disarms the guard permanently. Not a live false green on this tree — the reviewer confirmed all 17 scripts are currently reached — but a guard that asserts a property it does not hold.Fix is two mechanical changes plus test coverage for them; sending it back rather than downgrading the claim, because this is the third false-green-shaped defect in this unit and the previous review failed the PR on a strictly weaker version of it.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.