Skip to content

docs: the README reduction plan, and a handoff that declares its closing condition - #623

Open
ZacxDev wants to merge 3 commits into
mainfrom
docs/readme-reduction-plan
Open

ZacxDev wants to merge 3 commits into
mainfrom
docs/readme-reduction-plan

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Two documents, no code. Phase 1 of the README reduction arc merged as #611; this records what was measured and what happens next, so neither lives only in a session transcript.

Why now

🔴 The README is currently bigger than when this arc started — 314,712 bytes against 310,647 at 426288f. Phase 1 was an accuracy pass, not a reduction: six corrections, a behaviour fix and four guards. Reading #611 as progress toward "make the README smaller" is the misreading most available to the next reader, so both docs say so in their opening lines.

claudedocs/readme-reduction-plan.md

The plan, built from three parallel audits (~390 claims checked against source and the built binary). Carries what is nowhere else in the repo:

  • The constraint map. 19 Go test files read README.md by path; roughly 31% of the file sits inside a section-scoped assertion. The generated exit-code blocks (byte-identical, and there is no generator command — the failing test prints the text to paste), the dotenv golden, the #389 bullet's exact-normalised-string pin, and the 10 strings.Index-searched headings are enumerated, because each one fails rather than silently passes when prose moves.
  • The structural cause. There is no README size ceiling anywhere in the repo, while ~12 assertions are floors (len(raw) > 10_000, subs >= 25, symptoms >= 15, …). The suite punishes this file for shrinking and never for growing — that asymmetry is most of how 429 lines became 4,234 in ten weeks.
  • The unlock. Troubleshooting's 60 rows are pinned on column one only; readme_troubleshooting_test.go states in its own comments that it asserts nothing about the middle column. That 35 KB of cause cells is the largest unpinned region in the file.
  • The link-out policy, which turns on a fact easy to miss: .goreleaser.yaml archives only README.md and LICENSE, so moving prose into AGENTS.md or claudedocs/ loses it for every reader who did not clone. The sanctioned mechanism is the absolute https://github.com/civitai/cli/blob/main/<doc> URL — which readme_contributor_links_test.go's own failure message prescribes.
  • Per-section size map, the cut list with byte counts, compression targets, measured reader-path costs, and three options for how far to go with their trade-offs.

claudedocs/handoff-readme-reduction.md

State, ranked next steps, and the audit gotchas.

It declares a closing-condition:, which no existing handoff in this repo does (measured: 0 of 183). README at or below 250,000 bytes on main and the README guard suite green. Both halves are mechanical, so "is this arc finished?" stops being a judgement call — which is the failure mode the resume tooling reports on every other doc here.

It also records the one thing this arc found and deliberately did not fix: the README says every model-file download needs a token, while download.go's Long and its error string say most do and some public files do not. Settling it needs one live anonymous download — a measurement, not an edit — and no test pins either side. Filed as an open investigation with a named next probe rather than left as a loose end.

Gates

make ci — 21/21 packages ok, build ok. Docs-only; no source file touched, nothing under claudedocs/decisions/ so the AGENTS.md evidence ledger is unaffected.

…ing condition

Phase 1 (accuracy, #611) is merged and the README is BIGGER than when the arc
started -- 314,712 bytes against 310,647. That is expected and is stated at the
top of both docs, because reading #611 as progress toward the goal is the
misreading most available to the next reader.

The plan doc carries what this session measured and what is nowhere else:
the constraint map (~31% of the file sits in a section-scoped test assertion,
with the generated exit codes, the dotenv golden and the #389 bullet enumerated),
the per-section size map, the cut list with byte counts, the compression targets,
and the link-out policy -- which turns on the fact that .goreleaser.yaml ships
only README.md and LICENSE, so moving prose out of the README loses it for every
reader who did not clone.

The handoff declares a closing-condition, which no existing handoff in this repo
does: README at or below 250,000 bytes on main AND the README guard suite green.
Both halves are mechanical, so "is this arc finished?" stops being a judgement call.

Also records the open download-auth contradiction as an investigation with a
named next probe, and the four-round audit gotchas -- chiefly that every finding
across four rounds was the same shape: a sentence claiming more than its code does.
ZacxDev added a commit that referenced this pull request Sep 15, 2026
…l-through by status

Round 1 (nine axes) on #619. Both findings verified independently against the
tree before this commit; neither is a payload change, and the four gates are
untouched.

1. THE GATE BOUNDS THE RUNE CLASS, NOT THE LENGTH — residual, measured, still
   live, filed as #624 (same class as #605). safeTermSingle guarantees one line
   per line-break rune it can SEE, and a terminal's soft wrap has no rune.
   Measured driving runGenerate: a 5,120-char balance error renders as ONE
   logical line of 5,224 runes, zero ESC, zero TAB — passing every assertion
   TestGenerateBuzzBalanceWarningCannotForgeALine makes — which an 80-column
   terminal lays out as ~66 rows, most starting at column zero with a complete
   counterfeit `Cost: 1 Buzz (balance 999999).` a few rows above the real one.

   It is reachable with no cap at all through appblocks.go:1185, which
   interpolates the WHOLE raw body on a 200 that is not the expected envelope.
   #612 F1 named that arm; this comment's first draft described only the
   non-2xx arm at :2221 and so under-described the operand's own origin. Both
   arms are now named.

   Not fixed here. The repo owns a remedy it applies at exactly one site
   (wrapServerText, workflows_list.go:253) and cannot measure the width anyway
   — x/term.GetSize appears nowhere. Bounding the operand is the operator fork
   #605 already carries, and one decision governs both. Recorded at the call
   site and in the runGenerate row, each saying the named test does NOT assert
   length, so closing #612 is not read as closing this.

2. "every 401/403/404/429/503, every 5xx, every unmatched 400" is wrong in the
   REASSURING direction, and it was restated in three files. FOUR of the five
   arms match status-agnostically — has(...) with no status test, deliberately,
   per their own comments — so the documented 403 carrying "account has been
   restricted" is caught by an ARM and never reaches the fall-through. Only the
   fifth tests a status. The path is still dominant; the enumeration of statuses
   was never what made it so. All three sites now phrase it by MESSAGE.

The #624 reference was written as #623 first and corrected before commit: 623
was taken by another session mid-edit. A cross-reference is a claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All three were claims this doc made and measurement refuted. Recorded as
corrections rather than quietly edited, because the original reasoning is what
a later reader would otherwise re-derive.

1. "The middle column is pinned by NOTHING" was FALSE. It rested on a comment in
   readme_troubleshooting_test.go that is narrower than the file: rows 19 and 20
   ARE pinned, row 20 by nine exact strings.Replace spans that t.Fatal when
   absent -- one of them carrying a trailing space -- plus a prose parser that
   checks positive and negative command attribution. Left verbatim, 969 bytes.

2. The gate every brief in this arc specified is TOO NARROW. -run
   README|Readme|readme matches ZERO of the guards on row 20 (verified by
   counting matches). It reports green while those frozen spans are unguarded.

3. The ~13,000 Troubleshooting target was below the floor, and the ~249,000
   end-state projection with it. 7,081 bytes of the section are not cause cells
   at all, so 13,000 leaves 85 bytes per unfrozen cell. Actual floor at two
   sentences: ~19-20 KB; achieved 20,229.

The consequence is the part that needs a decision: recomputed from the phase-3
MEASUREMENT rather than the estimate, Option A lands at ~266,261 against a
closing condition of 250,000 -- a 16,261 shortfall. Either the condition moves
or part of Option B is needed. Flagged, not chosen.
ZacxDev added a commit that referenced this pull request Sep 15, 2026
…) (#619)

* fix(generate): gate three server operands that had no gate at all (#612)

Three values on the `generate` path were interpolated into output with no
terminal gate, so raw ANSI — cursor-up plus erase-line — passed straight
through. This is a WIDER class than #604, which is about safeTerm retaining
\n and \t at sites that already had a gate.

F1, generate.go:1286 — the Buzz-balance read FAILURE warning printed
appapi.GetBuzzAccount's error ungated. Its text is the server's `message`
verbatim, and the line sits directly above confirmGenerate's real
`Cost: … Buzz` line and `Generate? [y/N]:`, so `\x1b[1A\x1b[2K` erased the
warning and left a counterfeit `Cost:` line the SERVER wrote on the last
screen before an irreversible spend. Now safeTermSingle.

F2, generate.go:2164 — classifyGenerateError's fall-through returned the
*genapi.APIError unchanged. #604 gated `shown` at the top, but `shown` is
read only by the five MATCHING arms; the fall-through is the DOMINANT path
(every 401/403/404/429/503, every 5xx, every unmatched 400) and main.go
prints its text as `Error: <it>`, so the same escape pair deleted that line
and replaced it with a counterfeit `✓ Generation submitted` banner — a
FAILED generation reading as a submitted one. Now safeTermErr, which leaves
errors.Is/As reaching the original so the published exit codes (AGENTS.md
items 7 and 24) are unchanged.

F3, generate.go:1006 and :1022 — `--checkpoint %d: %w` / `--lora %s: %w`
wrapped pkg/civitai's readError, whose `snippet` strips the invisible class
but deliberately KEEPS \n. The issue filed this as DERIVED; it is MEASURED
now, driving the real pkg/civitai client rather than this package's faked
resolve seam, and it REPRODUCES — a 404 body carrying a newline rendered a
second, fully attacker-written line at column zero. Now safeTermErr.

Also corrects two ledger claims a comment made and the code did not keep:
classifyGenerateError's safeTermCoveredBy row disclaimed the fall-through
("not this function's surface"), which is the sentence that kept F2
invisible; and safeTermErr's doc comment's caller count. runGenerate gets
its first safeTermCoveredBy row — it called safeTerm zero times, so no row
was ever DEMANDED of it and the missing gate was not merely unrecorded, it
was unrecordable.

Measured and left UNFIXED, deliberately: classifyGenerateError's
`!errors.As` early return carries raw ANSI too, because internal/genapi
interpolates an unparsed HTTP body into an error at seven sites. Only some
of those come back through this function, so gating here would close a
subset of one class; the fix belongs at genapi's own interpolations and is
filed separately. The comment at that return states it.

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

* docs(generate): point the ungated-return residual at #620

The comment at classifyGenerateError's !errors.As return, the safeTermErr
ledger row and the safeTermCoveredBy row each state the residual is filed
rather than closed; name the issue so the claim is checkable.

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

* docs(generate): do not lean on an unmeasured reachability claim

The comment at classifyGenerateError's !errors.As return listed "a graph
that would not marshal" among the errors arriving there. Graph.MarshalJSON
can fail into that seam and would echo a byte of the user's own --input
file, but parseGraphInput compacts the file first, so the path looks
unreachable — which was reasoned, not measured. State it as the open
question rather than as part of the reason the return is left ungated.

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

* docs(generate): retract a false coverage claim the round-0 audit measured

Round 0 on #619 found two statements this file makes that the code contradicts,
and one attribution in the PR body that a mutation re-run refuted. All three
were verified independently before this commit, not taken on the audit's word.

1. "The runGenerate row this change adds to safeTermCoveredBy is what makes the
   next absence recordable" is FALSE. GREW iterates the functions that CALL
   safeTerm and demands a row only for those, so once runGenerate has one call
   and one row the predicate is satisfied however many further ungated operands
   the function grows. The row's own tail already stated the narrower true thing
   twelve lines later, so the file contradicted itself. Replaced with what the
   row actually buys, plus the cost this design carries, recorded rather than
   papered over: finding the next one still takes the hand enumeration.

2. The enumeration count read "(102 sites)", which is the count at e4d4996.
   Measured at this commit: 103 (94 in generate.go + 9 in generate_wait.go),
   the +1 being a comment that quotes fmt.Errorf(. A reader re-running the
   instrument named by closing condition 7 could not tell drift from this
   file's own line. Both numbers are now stated with their shas.

3. TestClassifyGenerateErrorFallThroughPreservesClassification is labelled as
   the invariant guard it is. It is green at the base and CANNOT FAIL ALONE:
   sanitizedCause.Unwrap -> nil kills eleven tests, nine of them pre-existing,
   and the realistic non-unwrapping site-local mutant kills seven plus the
   ledger. Closing condition 4 was satisfied by the tree before this test
   existed. Kept deliberately, for documentation and not detection — it is the
   only test whose name states the property — with the nine named so the
   redundancy is visible instead of hidden.

Payload unchanged: the four gates are untouched by this commit.

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

* docs(generate): record the length residual, and stop phrasing the fall-through by status

Round 1 (nine axes) on #619. Both findings verified independently against the
tree before this commit; neither is a payload change, and the four gates are
untouched.

1. THE GATE BOUNDS THE RUNE CLASS, NOT THE LENGTH — residual, measured, still
   live, filed as #624 (same class as #605). safeTermSingle guarantees one line
   per line-break rune it can SEE, and a terminal's soft wrap has no rune.
   Measured driving runGenerate: a 5,120-char balance error renders as ONE
   logical line of 5,224 runes, zero ESC, zero TAB — passing every assertion
   TestGenerateBuzzBalanceWarningCannotForgeALine makes — which an 80-column
   terminal lays out as ~66 rows, most starting at column zero with a complete
   counterfeit `Cost: 1 Buzz (balance 999999).` a few rows above the real one.

   It is reachable with no cap at all through appblocks.go:1185, which
   interpolates the WHOLE raw body on a 200 that is not the expected envelope.
   #612 F1 named that arm; this comment's first draft described only the
   non-2xx arm at :2221 and so under-described the operand's own origin. Both
   arms are now named.

   Not fixed here. The repo owns a remedy it applies at exactly one site
   (wrapServerText, workflows_list.go:253) and cannot measure the width anyway
   — x/term.GetSize appears nowhere. Bounding the operand is the operator fork
   #605 already carries, and one decision governs both. Recorded at the call
   site and in the runGenerate row, each saying the named test does NOT assert
   length, so closing #612 is not read as closing this.

2. "every 401/403/404/429/503, every 5xx, every unmatched 400" is wrong in the
   REASSURING direction, and it was restated in three files. FOUR of the five
   arms match status-agnostically — has(...) with no status test, deliberately,
   per their own comments — so the documented 403 carrying "account has been
   restricted" is caught by an ARM and never reaches the fall-through. Only the
   fifth tests a status. The path is still dominant; the enumeration of statuses
   was never what made it so. All three sites now phrase it by MESSAGE.

The #624 reference was written as #623 first and corrected before commit: 623
was taken by another session mid-edit. A cross-reference is a claim.

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

* docs(generate): stop restating claims two drafts already got wrong

Round 2 (delta) on #619. All five findings are defects the ROUND 1 FIX
introduced — the ladder's signature shape, and the reason this commit states
properties and stops enumerating. Each verified against the tree before fixing;
payload untouched, the four gates byte-identical to d464539.

F1/F2 — "the :1185 arm is what makes it reachable without any cap at all" was
false as an exclusive claim, and the route list around it was wrong twice in
opposite directions (first naming only appapi's non-2xx arm, then naming :1185
as the one uncapped route). appapi.serverMessage ends
`return strings.TrimSpace(string(raw))`, so ANY response lacking a
message/error key yields the whole body: every route to berr is unbounded, and
capping one appapi site would have left the identical forgery reachable through
the others — the sentence actively misdirected the remedy #624 points at. The
comment now states that property and says to read serverMessage rather than
re-derive a route list. The 64 MiB transport bound (readResponseBody) is named
so "NO CAP" is not read wider than it is.

F3 — the round-1 reword replaced a false absolute with a narrower absolute that
is also false: the fifth arm is a CONJUNCTION (Status >= 500 && has("unknown
ecosystem")), so a 400 carrying "unknown ecosystem" matches a needle AND still
reaches the fall-through. Neither a status list nor a message list can describe
this set. Rather than write a third rule, both retracted drafts are recorded
with the axis each got wrong, and the five arms are named as the only authority.

F4 — the enumeration tripwire falsified itself. "Re-run it and expect 103" was
written in beb7373; a0ef7a9 added two more comment lines quoting fmt.Errorf(,
so the instrument that counts its own prose now yields 104. A reader following
the instruction would hunt an ungated operand that does not exist. Nothing
asserts on the total, so it was unpinned by construction: the file stops quoting
it. The instrument is the enumeration and the trace, never the count.

F5 — the "both arms" correction was not swept to the second copy in the same
file (the doc comment of the very test the residual points at) or to the
ledger row. Swept now, and every surviving occurrence of either retracted
phrasing was re-grepped and sits inside a sentence that retracts it.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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