fix(agent-setup): the docs-link gate could report ok without probing the link (#549 audit) - #553
Merged
Merged
Conversation
…the link (#549 audit) An adversarial audit of #549 found five defects in the GUARDS around the managed block's `### Docs` section, not in the section itself. Each was reproduced on the merged tree before it was fixed. 1. The gate could pass without probing the link it exists to check. The per-link loop did `case err != nil: t.Skipf(...)`, abandoning the whole run on the first transport error. With only the FIRST link's host made unresolvable and `/apps/examples` pointed at a known-404 path, the pre-change test printed one skip line and exited `ok` — the dead link was never fetched. Every link is now probed before anything is diagnosed; transport errors are collected per URL and a run that could not reach them all reports SKIP with `checked n of m`. 2. Negative control present, positive control absent. `mustNotResolve >= 400` is satisfied by an edge that 403s everything — which is also what makes every real link >= 400. Against a local server answering 403 to every path, the pre-change test emitted four `DEAD LINK IN THE MANAGED BLOCK … correct it in internal/cmd/templates/agents-app.md` errors, the exact wrong instruction; this host is known to hard-block non-browser agents with Cloudflare `error code: 1010`. Statuses are now split into gone (404/410) and CANNOT TELL (401/403/429/5xx), with different messages, and the run skips rather than guesses when no link answered < 300. 3. The probe followed redirects while the comment claimed the spelling was measured. Go's default CheckRedirect follows up to 10 hops; `/apps/guide` 301s to `http://developer.civitai.com/apps/guide/` — a different spelling AND a downgrade to cleartext http — which a following probe scores as a clean 200. CheckRedirect now returns ErrUseLastResponse and a 3xx is reported as MOVED, with its Location. The comment at the foot of the file also carried a false measurement: it said `/apps/showcase` is 200 while `/apps/showcase/` 404s (true) "while `/apps/guide/` is the other way round", implying `/apps/guide` 404s. It does not — it 301s. Re-measured all nine paths; the claim is withdrawn, not replaced with a narrower one, because the table shows no rule to state. 4. "Correcting the URL is those two edits and nothing else" was false when it was written — #549 itself added a third spelling, in claudedocs/decisions/36-agents-block-per-project.md. Measured over the whole tree, ignored files included: 9 lines in 4 files name developer.civitai.com/apps/examples. Replaced by `TestEveryDocsURLSpellingIsLedgered`, a bidirectional ledger that walks the repo and fails both when an unledgered file spells a docs URL and when a ledgered one stops. Dated `claudedocs/handoff-` records are exempt: they record what was true when written and are not corrected. 5. The three-branch control counted shapes, not kinds. `len(shapes) < 3` is satisfied by three shapes of one kind. The wanted set is now read from `agent_setup_project.go`'s own `projectKind` constants — which also repairs an adjacent comment the audit flagged: `allProjectShapesForTest` claimed to be "derived from the projectKind constants' own list rather than from a literal" while being a literal. `TestAllProjectShapesCoversEveryDeclaredKind` is the derivation that makes the sentence true. Red-then-green matrix, `make ci` and `make lint` output are in the PR body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mJg86oQbTwuyPo5zcq3qP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #549 (already merged as
7b7d5f1). An adversarial audit found fivedefects in the guards around the managed block's
### Docssection, not in thesection itself. Every one was reproduced on merged
mainbefore it was fixed.Context that changed since the audit:
https://developer.civitai.com/apps/examplesnow resolves 200. Re-verified here — all four links answer 200 at exactly the
spelling that ships, with zero redirect hops. The findings are still live,
because they are about the guard, which is now the only thing that would catch the
link dying later.
Red → green matrix
Every row is a real run. "before" =
origin/main'sagent_setup_docs_test.gorestored over the same mutated tree; "after" = this branch.
/apps/examples→ known-404 path--- SKIP→ok(the dead link was never fetched)--- FAIL:DEAD LINK IN THE MANAGED BLOCK … url: …/apps/examples-does-not-exist status: 404, preceded bychecked 3 of 4 link(s); 1 unreachable, 2 live, 0 moved, 1 gone, 0 undiagnosable--- SKIP→ok--- SKIP:PARTIAL RUN: 3 of 4 link(s) checked, 1 unreachable at the transport layer. … so it reports SKIP rather than a pass that reads as 'all links verified'.— and the other three were probedmustNotResolve--- FAIL×4DEAD LINK IN THE MANAGED BLOCK … fix: correct it in internal/cmd/templates/agents-app.md(the wrong instruction)--- SKIP:CANNOT TELL whether these links are dead — they answered a status that a BLOCKED or FAILING EDGE produces just as readily as a broken page (401/403/407/429/5xx)+🔴 This is NOT a dead-link finding.+NO link in this run answered < 300, so there is no positive control at allDEAD LINKtext as a genuine 404--- FAIL: sameCANNOT TELLmessage +(At least one other link in this run answered < 300, so this prober is not being blocked wholesale — which is what makes this worth failing on rather than skipping.)DEAD LINKs, no caveat--- FAIL: fourDEAD LINKs plusPOSITIVE CONTROL ABSENT: 4 link(s) are reported gone above and NOT ONE link in this run answered < 300.301→200--- PASS, logginghttp://127.0.0.1:18733/apps/guide -> 200--- FAIL:MOVED LINK IN THE MANAGED BLOCK — the page answers, but NOT at the spelling that ships. status: 301 location: http://127.0.0.1:18733/apps/guide/--- FAIL:claudedocs/zz-mutant-unledgered.md spells one of the block's docs URLs and is NOT in docsURLSpellingLedger.claudedocs/handoff-zz-mutant.md--- PASS, logging2 exempt dated record(s)— the exemption is exercised, not assumeddecisions/36)--- FAIL:docsURLSpellingLedger names claudedocs/decisions/36-… but it no longer spells ANY of the block's docs URLs.repoRootDir(t)→t.TempDir())--- FAIL:CONTROL failure, not a finding: the walk read 0 file(s) … want >= 200— positive control, count moved 696 → 0allProjectShapesForTestkind list →{npm, npm, npm}--- PASS(4 shapes ≥ 3)--- FAIL:CONTROL failure, not a finding: allProjectShapesForTest returned 4 shape(s) covering kind(s) npm, but no-build, none is declared in agent_setup_project.go and never rendered.projectKindconstant added, no shape--- FAIL×2: the control above namingmutant-4th-kind, andprojectKind "mutant-4th-kind" is declared … and rendered by NO shape in allProjectShapesForTest.--- FAIL:allProjectShapesForTest renders kind "bogus-undeclared", which is not declared as a projectKind constant …declaredProjectKindRebroken--- FAIL:CONTROL failure … matched 0 constant(s) … want >= 3Harness validation for rows 2–3: the local status servers were themselves probed
first (
18731/anything -> 403,18732/x -> 404,18733/apps/reference -> 301 loc=…/apps/reference/,18733/apps/guide/ -> 200) before any verdict was readoff them.
Finding 3's other half: a false measured claim, withdrawn
The comment at the foot of the file said
/apps/showcaseis 200 and/apps/showcase/404s — true — "while/apps/guide/is the other way round",which reads as
/apps/guide404ing. It does not. Re-measured 2026-09-11 from thishost, probe User-Agent, no redirect following:
Note the redirect target's scheme: the hop lands on cleartext
http, so afollowing probe scores an https→http downgrade as a clean 200. The claim is
withdrawn, not replaced with a narrower one — five paths show four distinct
behaviours and there is no rule to state.
Finding 4: the grep count
Whole tree, ignored files included (
find … | xargs gnugrep, not the.gitignore-blind wrapper):9 lines, 4 files — the auditor's "four places" was a file count and is right;
"two edits and nothing else" was wrong, and was wrong on the day it was written,
since
decisions/36was added by #549 itself.The replacement is not a narrower sentence.
TestEveryDocsURLSpellingIsLedgeredwalks the tree and asserts the file set both ways. The one exemption —
claudedocs/handoff-— is stated in the code with its reason (dated records areevidence; correcting them falsifies the record) and is itself exercised by
mutation 4b.
Out-of-scope neighbour, called out separately
The audit flagged
agent_setup_project_test.go:27-30: the comment claimedallProjectShapesForTestis "derived from the projectKind constants' own listrather than from a literal" while the code is a literal. It is fixed here,
because finding 5's fix needed the derivation anyway:
declaredProjectKindsreadsthe
projectKindconstants out ofagent_setup_project.go's source (Go cannotenumerate a string-constant set at runtime), and
TestAllProjectShapesCoversEveryDeclaredKindcompares the two sets both ways.Mutations 5b/5c/5d are its red-then-green.
Gates
22 packages reported
ok, 1[no test files]— no package silently compiled tozero tests.
That zero is instrument-validated: with a planted
ineffassignin the new test itwent red —
internal/cmd/agent_setup_docs_test.go:283:2: ineffectual assignment to lintProbe (ineffassign)/make: *** [Makefile:30: lint] Error 1— then back to0 issues.once reverted.gofmt -s -l .flagged 0 files, with 472.gofiles present (so the zero is not azero-files zero).
Live probe on this branch:
Docs
claudedocs/decisions/36-agents-block-per-project.md: added the no-redirectre-measurement table, the withdrawn-claim note, the new guards, an "audit
repairs" subsection, and corrected WHAT IS NOT ESTABLISHED — the page is
live now, stated as a measurement of that day rather than a standing fact.
AGENTS.mditem 36's trigger now reads "…or what its Docs section links andwhere?", so a change to where a docs URL is spelled routes to the item.
+10 bytes: 30,453 / 30,500. The ceiling was not raised.
Not verified
make cihere is a full clone, andmake ci-shallowwas not run (no test in this change reads git history).developer.civitai.com. The Cloudflare1010block is quoted from the handoff,not re-triggered — deliberately, since re-triggering it means getting this host
blocked.
🤖 Generated with Claude Code
https://claude.ai/code/session_018mJg86oQbTwuyPo5zcq3qP