Skip to content

fix(validate): the ready-ack advisory names the reason it fell back, and wraps - #269

Merged
ZacxDev merged 4 commits into
mainfrom
fix/258-readyack-advisory-names-the-cause
Aug 10, 2026
Merged

fix(validate): the ready-ack advisory names the reason it fell back, and wraps#269
ZacxDev merged 4 commits into
mainfrom
fix/258-readyack-advisory-names-the-cause

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Closes #258.

The defect

In the canonical #206 shape — a static scaffold whose civitai-host.js has
been deleted — the presence-tier ready-ack advisory listed reasons it might have
fallen back, and none of them was the actual reason:

there is no index.html at the project root, or it holds a reference this CLI
cannot follow — a bundler alias, a generated file, an off-project URL

Not one is true of a five-file no-build app. The real reason is that
<script src="./civitai-host.js"> points at a file that is not there — and
EntryGraph.Gaps had already recorded exactly that, per reference, before
readyAckChecks returned the constant readyAckAdvicePresenceOnly and
discarded the slice.

The tiering is unchanged and correct. AGENTS.md item 20 deliberately treats
a reference to a missing file as a gap rather than a decided absence, and
records this case as a known trade. The message was the defect.

The fix

Surface Gaps generally, not the dangling reference specially — so all six
gap kinds (dangling reference, bare specifier, off-project URL, unreadable file,
file budget, depth truncation) reach the author from one change:

… it did NOT check that the file is loaded. Here is what it could not follow, in
this project's own terms — one of these is usually the actual bug: (1)
index.html <script src> "./civitai-host.js" points at civitai-host.js, which
does not exist — restore that file or fix the reference. …
  • Capped at 3, with the overflow counted out loud (; and 2 more this message does not list). A silently truncated list reads as "that was all of
    them" — the same class of lie as the guess it replaced.
  • The gap strings got a copy pass now that they are author-facing: one carried
    "this resolver's model of the project is incomplete", a fact about us.
    What sets a gap is untouched — only how it reads.
  • The reasons are spliced between …PresenceOnlyHead and …PresenceOnlyTail,
    before the shared remedy rather than after it: the remedy is the longest
    fragment, so appending put the one project-specific sentence two thirds of the
    way down a wall of generic advice.

Second half: the 1938-character line

The advisory is ~2 kB and app validate printed it as one line. Layout now
happens at the printer (internal/cmd/validate_print.go), wrapping every
finding to 79 columns with a hanging indent — one place fixes every long
message, including ones added later.

Wrapping inside Finding.Message would corrupt --json. This is the inverse of
AGENTS.md item 23: the field comes from the producer, the layout does not.
wrapRunes is reused as-is from exitcodes_doc.go (same package — no export,
no edit to that file).

Mutation matrix

--- FAIL leaf lines counted from output (never an exit code); every mutation
checksum-gated, so an edit that silently failed to apply aborts instead of
reading as a survivor.

mutation FAIL
discard the gaps (pre-#258 behaviour) 11
restore the guess to the head 1
remove the cap 2
truncate silently (no "+K more") 2
drop the one-line collapse 1
newline inside the message 2 (incl. the --json guard)
revert the printer to one unwrapped line 1
revert the gap wording to the maintainer form 3
splice the report after the tail 24
weak tier loses its disclosure 2
strong tier gains the disclosure (inverse) 1
report leaks another tier's own literal 1
comment-only null mutant 0 (survives, as it must)

🔴 One mutant survived the first round and is why an assertion moved.
Restoring the guess to …PresenceOnlyHead while keeping the real reasons
the most likely regression — reddened 0 subtests, because the absence check
was scoped to the gap report. TestPresenceAdviceNoLongerSpeculates now reads
the whole emitted message at a fixture where every quoted phrase is provably
impossible, with a positive control that the real cause is present so "says none
of the wrong things" is not satisfied by a message that says nothing.

TestReadyAckAdvisoriesStateTheirOwnStrength still passes and still can fail
in both directions (K: 2, L: 1 above). It reads the FIXED bases, so it
structurally cannot see text appended at runtime —
TestGapReportCannotSatisfyAnotherTiersStrengthAssertion covers that, and kills
the "report leaks orphan" mutant the strength test cannot.

Coverage

  • static and page-vite with the emitter deleted: the report names the
    referencing file (index.html / src/main.jsx), the specifier and the
    missing target — asserted on the gap section, not the whole message, since
    the shared remedy names index.html in every tier and a whole-message
    Contains would be vacuous.
  • Three further gap kinds: bare specifier, unreadable (over-cap) file,
    no root index.html, plus an off-project URL.
  • Controls: the orphan case keeps readyAckAdviceUnwired; all shipped templates
    stay silent; the strong tiers never acquire the gap apparatus.
  • --json is asserted on the decoded map[string]any (a Contains over
    raw stdout cannot tell a real newline from the \n escape).
  • The cap, at the fixture and at the function.

make ci

--- FAIL: 0 · build failed: 0 · 18 packages ok · gofmt -s -l .
clean over 299 .go files (count quoted as the positive control).

🤖 Generated with Claude Code

ZacxDev and others added 4 commits August 7, 2026 15:07
…and wraps

The presence-tier advisory GUESSED at why the entry graph had not resolved —
"there is no index.html at the project root, or it holds a reference this CLI
cannot follow — a bundler alias, a generated file, an off-project URL" — while
`EntryGraph.Gaps` already held the real, per-reference reason and readyack.go
discarded it. In the canonical #206 shape (a `static` scaffold whose
`civitai-host.js` has been deleted) none of the guesses is true: index.html
plainly references a file that is not there. A five-file no-build app sent its
author hunting for a bundler alias that cannot exist in it. Issue #258.

The gaps are now surfaced GENERALLY rather than special-casing the dangling
reference, so all six gap kinds reach the author at once — capped at 3 with the
overflow counted out loud, because a silently truncated list reads as "that was
all of them". The tiering is unchanged: AGENTS.md item 20's judgement that a
missing target is a GAP rather than a decided absence stands. The message was
the defect.

Second half: `app validate` printed the ~2 kB advisory as ONE 1938-character
line. Layout now happens at the printer (internal/cmd/validate_print.go), which
fixes every finding rather than the one that provoked it. Wrapping inside the
message would have corrupted `--json` — the inverse of item 23: the field comes
from the producer, the layout does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…p report

A mutation found the hole: restoring the shipped speculation to
`readyAckAdvicePresenceOnlyHead` while KEEPING the real reasons — the most
likely way #258 regresses — reddened 0 subtests, because the absence assertion
was scoped to the gap report. `TestPresenceAdviceNoLongerSpeculates` now reads
the emitted message at a fixture where every quoted phrase is provably
impossible, with a positive control so "says none of the wrong things" cannot be
satisfied by a message that says nothing.

Records the measurement, the tiering-unchanged boundary and the mutation matrix
in AGENTS.md items 18 and 20.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…solute paths

Audit fixes for #269. Four 🟡 and the 🟢 set.

1. THE FOURTH PRINT SITE. `app_submit.go`'s error loop kept its raw Fprintf, so
   one `app submit` run printed a 412-rune unwrapped error AND a wrapped warning
   — two layouts in a run, on the highest-traffic path — which made this change's
   own "one place fixes every long message" rationale false. Headers stay
   unwrapped deliberately: they interpolate the directory the user typed.

2. AN ABSOLUTE PATH IN A USER-FACING GAP. The root-index.html gap was the only
   one of seven not going through relTo, so it printed `stat /abs/.../index.html`
   — machine-specific noise and a single unbreakable token: measured 120 runes,
   producing a 136-rune line under a 79-rune budget. `readableErr` renders it
   relative; the guard checks the root and a token ceiling over four fixtures, so
   a NEW site cannot reintroduce it.

3. THE CAP COULD BURY THE ACTUAL CAUSE — this PR's own thesis failing in a new
   shape. Three CDN script tags above a dangling ./civitai-host.js pushed the
   real bug out of a three-item list whose lead-in claimed the cause was among
   them. Two fixes: `rankGaps` orders most-likely-cause first (stable, so
   document order survives within a kind), and the lead stops claiming the cause
   is present once anything is withheld.

4. THE WRAP SPLIT PASTEABLE REMEDY TEXT. `"pnpm run build"` came out as `"pnpm` /
   `run build"`. `findingTokens` keeps a double-quoted span whole, with two
   fallbacks that make it safe: an overlong span splits back into words, and an
   unbalanced quote cannot swallow the tail.

Plus: the cap VALUE is pinned (99 reddened 0 subtests before), `Complete` iff
`Gaps` is asserted where it can break, the two guards that cannot fail are
labelled as such, and AGENTS.md's "six gap kinds" (seven) and "1938-character
line" (pre-fix 1847 / post-fix 1936) are corrected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ty guard able to fail

Delta-audit fixes for #269.

F1 (the 🟡): TestGapRankingIsStableWithinAKind could not fail. Its 2-gap fixture
sat under Go's insertion-sort cutoff, so `sort.SliceStable` -> `sort.Slice`
reddened 0 subtests. Widening alone was NOT enough — with one gap kind the
comparator is all-equal and pdqsort leaves the partition untouched, so 40
same-kind gaps still reddened 0. The fixture now mixes two kinds across 30 gaps,
which kills the mutant. Stability decides WHICH THREE gaps the capped advisory
shows, so the ranking argument rests on it.

F2: the per-file size cap was reported as an unreadable file — "make it
readable" about a perfectly readable file — and ranked gapUnreadable, so a limit
this check imposes outranked a genuine dangling reference in the capped list.
errOverCap/errIsDirectory are now sentinels and readGapFor sorts them to
gapBudget.

F5: the leak guard's "unreadable" row forced the size cap, which returns a plain
fmt.Errorf and never reaches readableErr's *fs.PathError branch. Both packages
now carry a chmod-000 row, with a root skip and a fixture positive control.

F3: the >79-rune justification was misattributed. Measured: app validate's
header is 130 runes and DOES interpolate the user's path; app submit's is a
CONSTANT 82 with no path; and a 184-rune `Error:` line comes from main.go's
CLI-wide error path. Wrapping that last one is a deliberate non-change — it
would alter every command's stderr and item 24 pins those byte-for-byte.

F4: cited a TestIncompleteIsExactlyHavingGaps that does not exist; the invariant
is an assertion inside TestEntryGraphCompleteness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ZacxDev
ZacxDev merged commit c783b19 into main Aug 10, 2026
12 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.

the ready-ack advisory names every cause except the actual one (a dangling script reference)

1 participant