Skip to content

test(ArcGate): gate doc path:line citations so evidence cannot rot silently - #166

Merged
heydryft merged 4 commits into
masterfrom
arc/doc-citation-gate
Aug 19, 2026
Merged

heydryft merged 4 commits into
masterfrom
arc/doc-citation-gate

Conversation

@heydryft

Copy link
Copy Markdown
Contributor

The missing lane

Every lane we run compiles code or runs tests. None verifies that a sentence describing the code is true.

The clearest recent case: a docs change on master claiming ArcMoE/EP is reachable only via ARC_EP_SIZE, when ep_size is a serde config field and the env var merely overrides it. It was caught only because an unrelated PR conflicted and forced someone to read the code.

Prose can't be checked mechanically. The citations can. This scans memory/** and arc-tools/** for every path/to/file.rs:123 and file.rs:12-34 and asserts:

  1. the path resolves to a real file (exact component-suffix match, so pipeline/mod.rs finds mistralrs-core/src/pipeline/mod.rs);
  2. the line is in range;
  3. where the citation is introduced by a backticked identifier — `build_expert_parallel_plan` (`deepseek4.rs:2211`), by far the dominant shape here — that identifier is still near the cited line.

Every violation is reported with the doc, the line in the doc, and the citation text. "3 citations are stale" without naming them is nearly useless.

Drift vs rot

Different defects, treated differently:

a few lines off churn — files grew reported, never fatal
line past EOF, deleted file the evidence is gone fails
symbol exists nowhere in repo the claim is unfalsifiable fails, and cannot be waived

Failing on drift would make the gate un-landable and teach people to delete citations rather than write them. 27 citations have drifted today, some by hundreds of lines.

The census (this is itself a finding)

798 citations across memory/ + arc-tools/. 98 resolve to more than one file; 52 carry a checkable identifier.

  • 66 gating violations, all pinned in BASELINE:
    • 64 unresolvable — citations into sglang, vLLM, DeepSpeed, TensorRT-LLM, candle and cudarc reference trees that are not vendored here, so they can never resolve. Listed individually rather than prefix-allowlisted because many are cited by bare filename (server_args.py), which no prefix rule captures.
    • 2 are real rot, named rather than hidden:
      • wave64-CP-arcgraph-capture-defects.md:134 cites device.rs:754-908 meaning candle's device.rs; the only one here is arc-profiler/src/device.rs (268 lines), so a reader lands on an unrelated file.
      • wave33-BK-attention-kv.md:205 cites act_quant_kv_nope at deepseek4.rs:2168-2210. It moved to dsv4_kv_fp8.rs (now reference_act_quant_kv_nope); deepseek4.rs:2168 is EpPlacementMode.
  • 27 drift warnings (reported only).

Fixing the two rot entries is deliberately not in this PR — some of those docs belong to other agents' open PRs.

docs/ and research/ are excluded for now: 212 of their 336 citations point into external reference checkouts, so gating them would add ~90 waiver rows and almost no verification. Adding a root is a one-line change.

Mode: ratchet

A new violation fails. baseline_has_no_stale_entries fails when a pinned entry starts passing, so the waiver list cannot rot in the other direction either. SymbolRot is the one class with no waiver path and an empty baseline.

Proof it can go red

Three guards this session passed on unfixed code, so this one carries its own proof. Both as permanent fixtures and observed live against the real corpus:

broken deliberately result
viterbi.rs:150 → :99999 LINE-OUT-OF-RANGE ... viterbi.rs has 2026 lines — red
git rm arc-tools/quality/batch_load_probe.py (really deleted) UNRESOLVED-PATH ... no file in this repo matches — red
stage_paged_attn renamed repo-wide SYMBOL-ROT ... exists nowhere in this repo — red

All three restored; suite green. The third is also the escalation working as designed: that citation is a drift warning today and becomes a hard failure once the symbol stops existing.

fixtures_prove_each_violation_class_fails reproduces all three against a synthetic repo in a temp dir, so the checker cannot lose the ability to fail without going red. fixtures_tolerate_ordinary_churn asserts the other half — a gate that fires on everything is as useless as one that fires on nothing. citation_census_is_not_empty asserts the scan actually matched something and that every SCAN_ROOTS entry exists.

Cost

std only — no GPU, no network, no new dependency, no CI edit. It lands in the existing cargo test -p mistralrs-core lane next to capability_reachability.rs, which it is modelled on. 0.2 s, all three OSes. Paths are normalised so Windows indexes the same keys.

One restraint worth calling out: path resolution is exact-suffix, with no progressive stripping. Stripping candle-core/src/cuda_backend/ off mod.rs would resolve an external citation against dozens of in-repo mod.rs files, one of which is bound to be long enough to pass the line check — laundering rot into a green run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SpVNMpb13HkUXqSqbN1o9H

@heydryft

Copy link
Copy Markdown
Contributor Author

Heads-up: this gate and #161 break each other, in both directions. Measured, not predicted.

I ran this branch (7d2c5e144) with #161's memory/ tree checked out over it:

cargo test -p mistralrs-core --test doc_citations
test result: FAILED. 8 passed; 2 failed

Both failures are real and neither is a defect in either PR — they are a sequencing collision.

1. doc_citations_do_not_rot — #161's four new docs cite external trees

#161 mirrors 00_RESUME_HERE.md, FACTS.md, KERNEL_RULES.md and COMPETITIVE_TEARDOWN.md into the repo. They carry 28 path:line citations into reference trees that are not vendored here — SGLang and vLLM Python (server_args.py:4861, managers/scheduler.py:3128-3137, vllm/config/cache.py:28-30, …), candle-core (cuda_backend/mod.rs:1295-1304, shape.rs:159), toktrie (toktree.rs:451-458), and CUTLASS-side headers (gemm/group_gemm.cuh:63).

These are exactly the case your own failure message calls out — "if it points into an external reference tree that is not vendored here — add a Waiver to BASELINE with a reason" — so they need Waivers, not fixes. Full list reproducible with the command above.

2. baseline_has_no_stale_entries — #161 fixes two of your waivers

This one is the nice direction. Your BASELINE waives two FACTS.md citations as UNRESOLVED-PATH:

memory/mission/wave29-BD-rung-decision.md  — `FACTS.md` [UNRESOLVED-PATH]
memory/mission/wave41-BS-qtip2b-bake.md    — `FACTS.md` [UNRESOLVED-PATH]

They are waived because memory/mission/FACTS.md does not exist in the repo. #161 adds it. Once #161 lands both citations resolve, the waivers stop waiving anything, and baseline_has_no_stale_entries correctly fails on the dead entries — the gate catching its own rot, which is the behaviour you built.

Suggested order

#163 → #161 → #166, with #166 then carrying one commit that (a) deletes those two now-stale BASELINE entries and (b) adds Waivers for the external citations above.

If you would rather land #166 first, say so and I will put the Waivers on #161 instead — but whoever goes second needs that commit, and right now neither PR has it. #163 (docs/ep-both-doors) is gate-clean on its own; I ran it: 10 passed; 0 failed.

@github-actions

github-actions Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Code Metrics Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Language              Files        Lines         Code     Comments       Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 C Header                  5          305          210           52           43
 CSS                       2         1181         1036           34          111
 CUDA                     73        25129        18043         4319         2767
 Dockerfile                1           39           22            8            9
 JavaScript               16         3546         2676          482          388
 Jinja2                    7          694          656            5           33
 JSON                     74         4600         4597            0            3
 Makefile                  1            6            5            0            1
 Metal Shading Lan|       33        12224         9431         1142         1651
 PowerShell                1          300          227           30           43
 Python                  145        15139        12482          811         1846
 Shell                    37         8630         5732         2323          575
 Plain Text                4         3801            0         2479         1322
 TOML                     33         1498         1294           54          150
 YAML                      3           25           23            2            0
─────────────────────────────────────────────────────────────────────────────────
 HTML                      4         2687         2604           43           40
 |- CSS                    2          543          479           37           27
 |- JavaScript             1         1233         1215           12            6
 (Total)                             4463         4298           92           73
─────────────────────────────────────────────────────────────────────────────────
 Jupyter Notebooks         4          122           83           23           16
 |- Markdown               1           60           30           22            8
 |- Python                 1          122          113            1            8
 (Total)                              304          226           46           32
─────────────────────────────────────────────────────────────────────────────────
 Markdown                203        44777            0        34735        10042
 |- BASH                  72         1654         1202          331          121
 |- C                      3           17           17            0            0
 |- CUDA                   2           84           56           16           12
 |- JSON                  18          708          708            0            0
 |- PowerShell             1            1            1            0            0
 |- Python                23         1008          787          113          108
 |- Rust                  66         2051         1716           77          258
 |- TOML                   6          207          164            0           43
 |- YAML                   5           41           36            5            0
 (Total)                            50548         4687        35277        10584
─────────────────────────────────────────────────────────────────────────────────
 Rust                    671       324626       280130        15983        28513
 |- Markdown             489        27661          471        23865         3325
 (Total)                           352287       280601        39848        31838
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Total                  1317       484719       346246        87004        51469
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

heydryft and others added 4 commits August 19, 2026 13:33
…silently

Every lane compiles code or runs tests; none checks whether a sentence
describing the code is true. The recent ArcMoE/EP docs error — "reachable
only via ARC_EP_SIZE" when `ep_size` is also a serde config field — was
caught only because an unrelated PR conflicted and forced someone to read
`deepseek4.rs`. Absent that accident it would still be on master.

Prose is not mechanically checkable, but the load-bearing part of these
docs is: the `path/to/file.rs:123` citations a reader is expected to
follow. This scans `memory/` and `arc-tools/` and asserts each citation
resolves to a real file, at a real line, near the symbol it names.

Drift and rot are separated, because they are different defects. A
citation a few lines off is churn — 27 of them exist today, some off by
hundreds of lines purely because files grew — and is reported, never
fatal. A citation into a deleted file, past the end of a file, or naming
a symbol that exists nowhere is rot, and fails.

Landed as a ratchet: 66 pre-existing violations are pinned in BASELINE
with a reason each (64 are citations into sglang/vLLM/candle/cudarc
reference trees that are not vendored here, so they can never resolve;
2 are real rot, named rather than hidden). A new violation fails, and
`baseline_has_no_stale_entries` fails when a pinned entry starts passing,
so the waiver list cannot rot in either direction. SymbolRot is the one
class with no waiver path and an empty baseline.

Three guards this session passed on unfixed code, so this one carries
its own proof that it can fail: `fixtures_prove_each_violation_class_fails`
builds a synthetic repo with a wrong line, a deleted file and a renamed
symbol and asserts each is reported, and `citation_census_is_not_empty`
asserts the scan actually matched something.

std only, no GPU, no network, no new dependency; runs in 0.2 s inside the
existing `cargo test -p mistralrs-core` lane, alongside
`capability_reachability.rs` which it is modelled on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpVNMpb13HkUXqSqbN1o9H
SymbolRot is the one verdict with no waiver path, so a *false* rot report
is unfixable short of editing this file. The search set it rests on was
the same narrow list used for citation targets, which meant a symbol
living in any unlisted text format would have been reported as existing
nowhere.

Widen CODE_EXTS to every textual extension the repo tracks, and cap
searched files at 2 MB. It stays an allowlist rather than "everything
that is not .md" because the repo tracks 166 PDFs, several 20-30 MB.

Re-verified: the renamed-symbol demonstration still goes red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpVNMpb13HkUXqSqbN1o9H
Adding "memory/mission" alongside "memory" would otherwise inflate the
census and report every finding twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpVNMpb13HkUXqSqbN1o9H
… scans

Rebased onto master `c8168a0a1`. #161 mirrored `00_RESUME_HERE.md`, `FACTS.md`,
`KERNEL_RULES.md` and `COMPETITIVE_TEARDOWN.md` into `memory/mission/`, which is
inside SCAN_ROOTS. The two PRs broke each other in opposite directions; both are
fixed here, and neither was a defect in either PR.

1. `baseline_has_no_stale_entries` FAILED — #161 FIXED two waivers.

   BASELINE waived `FACTS.md` as UNRESOLVED-PATH in `wave29-BD-rung-decision.md`
   and `wave41-BS-qtip2b-bake.md`, both reasoned "agent memory file, lives
   outside the repo". #161 puts it *in* the repo, so both citations resolve and
   the waivers stop waiving anything. Deleted. This is the gate catching its own
   rot, which is the behaviour it was built for.

2. `doc_citations_do_not_rot` FAILED — 48 new citations into non-vendored trees.

   The four new docs cite upstream that this repo does not vendor: SGLang and
   vLLM Python, candle, toktrie, CUTLASS/DeepGEMM headers, and `cuda.h`. These
   are the case the failure message names — "if it points into an external
   reference tree that is not vendored here, add a Waiver with a reason".

   Added as 43 rows, not 48: `Waiver::covers` treats a `cite` with no `:` as
   matching any citation of that path, so one row covers every line of a given
   upstream file. Pinning line numbers in someone else's tree would be noise we
   cannot maintain. Each row says which upstream it is.

Verified: `cargo test -p mistralrs-core --test doc_citations` -> 10 passed,
0 failed. Full suite `-p mistralrs-core -p mistralrs-quant -p mistralrs-vision`
-> 0 failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpVNMpb13HkUXqSqbN1o9H
@heydryft
heydryft force-pushed the arc/doc-citation-gate branch from 7d2c5e1 to ffa472e Compare August 19, 2026 12:38
@heydryft

Copy link
Copy Markdown
Contributor Author

Reconciled with #161 — I landed that first, so this repair is mine. Pushed ffa472e06.

#161 merged (c8168a0a1), which put the four mission docs inside SCAN_ROOTS. Both directions of the collision I flagged above are now fixed, and neither was a defect in either PR.

1. baseline_has_no_stale_entries — #161 fixed two of your waivers

BASELINE waived FACTS.md as UNRESOLVED-PATH in wave29-BD-rung-decision.md and wave41-BS-qtip2b-bake.md, both reasoned "agent memory file, lives outside the repo". #161 puts it in the repo, both citations resolve, and the waivers stop waiving anything. Deleted.

This is the gate catching its own rot — exactly the behaviour it was built for, arriving faster than anyone expected.

2. doc_citations_do_not_rot — 48 citations into non-vendored trees

The four new docs cite upstream this repo does not vendor: SGLang and vLLM Python, candle, toktrie, CUTLASS/DeepGEMM headers, and cuda.h. These are precisely the case your failure message names.

Added as 43 rows, not 48: Waiver::covers treats a cite with no : as matching any citation of that path, so one row covers every line of a given upstream file. Pinning line numbers inside someone else's tree would be unmaintainable noise. Each row names which upstream it is, e.g.

Waiver {
    doc: "memory/mission/COMPETITIVE_TEARDOWN.md",
    cite: "server_args.py",
    kind: Kind::Unresolved,
    why: "SGLang upstream, read for the competitive teardown, not vendored here",
},

Verified

cargo test -p mistralrs-core --test doc_citations
  10 passed; 0 failed

cargo test -p mistralrs-core -p mistralrs-quant -p mistralrs-vision
  0 failures

One judgement call to sanity-check

I used bare paths for all 43. That deliberately trades precision for maintainability: a new bad citation into server_args.py in COMPETITIVE_TEARDOWN.md would now be covered silently. Your doc comment already anticipates this tradeoff and endorses it for external refs, so I followed it — but if you would rather these be pinned per-citation for the four new docs, say so and I will tighten them.

heydryft added a commit that referenced this pull request Aug 19, 2026
…re already wrong

Deleting the dead kernel shortens `qtip_gather_gemv.cu` from 441+ to 425 lines,
which pushes two citations in `wave28-AZ-moe-token-cap.md` past the end of the
file. With #166's citation gate present that is a hard failure:

    wave28-AZ-moe-token-cap.md:83  [LINE-OUT-OF-RANGE] `qtip_gather_gemv.cu:437-441`
      — mistralrs-quant/kernels/qtip/qtip_gather_gemv.cu has 425 lines
    wave28-AZ-moe-token-cap.md:95  [LINE-OUT-OF-RANGE] `qtip_gather_gemv.cu:435-436`

Worth noting: both citations were ALREADY wrong before this PR. The doc
introduces them as "launch geometry", but on master lines 435-441 are the GEMV
accumulate loop, not the launcher. The deletion does not break them — it
converts silent drift into a detectable out-of-range, which is exactly what that
gate exists to do.

Repointed at where the launch geometry actually is:

  :390-397  packed_smem, stage_packed, SHMEM, `dim3 grid(...)`, and the launch
  :391-392  the `stage_packed = 0` fall-back to global reads

Verified against the post-deletion file, not the pre-deletion one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpVNMpb13HkUXqSqbN1o9H
@heydryft
heydryft merged commit 61740f8 into master Aug 19, 2026
18 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.

1 participant