index_coverage reports "Indexed and current" for a file whose facts were refused by the validator #136

Closed
opened 2026-09-05 09:10:26 +02:00 by buildagent · 0 comments
Member

Found while building #72's refusal-stage registry, and written into the fact_validator row's reason there so it cannot be forgotten. Deliberately not fixed in that change — see below.

The over-promise

A fact-validator refusal (abi::validate → Parse::Err) writes a files row with parse_error set and zero symbols. index_coverage then reports:

verdict: "indexed"
reason:  "row_present"
hint:    "Indexed and current."

The file has no symbols, its facts were rejected, and the tool says it is indexed and current.

Why the barrier is fine and the tool is not

This is worth separating, because the two behaviours are graded by different things and only one is wrong.

The freshness barrier is correct. A row exists, so the path is settled rather than pending — which is exactly #72's own bullet ("a file that parses to an error gets a row with parse_error set, so it is not this class; confirm the barrier treats it as settled rather than pending"). It does. Nothing hangs.

The tool over-promises. index_coverage exists to answer "will this file ever carry symbols, and does it now" — and here it answers "yes, and it is current" about a file whose facts were refused. An agent that trusts that reads an empty outline as the file has no symbols rather than this file's facts were rejected.

That is the same shape as #101 (a truncated extraction served silently) and #124 (a refused package invisible on the MCP surface), on a third surface.

The mechanical cause

FileOverlap carries no parse_error field — only SymbolNames does. So the coverage path structurally cannot see the refusal even though the row records it.

Why it was not fixed in #72

It needs a new wire field on FileOverlap (daemon side) plus both skew directions tested with non-empty payloads — a mission, not a hunk. And crates/daemon/src/local_index.rs was being actively edited by a concurrent lane at the time. Doing it badly would have been worse than recording it precisely.

What closing it needs

  1. Carry parse_error (or a derived refusal discriminator) on FileOverlap.
  2. index_coverage distinguishes indexed-with-facts from indexed-but-facts-refused, with the refusal's own reason code — the vocabulary already exists in coverage.rs.
  3. Three states kept apart: absent = this daemon did not report, no refusal = a measurement, a refusal = the finding.
  4. Both wire-skew directions with non-empty payloads. Key normalisation, not serde alias plus dual-emit — that combination is a duplicate-field landmine and this repo has the scar.
  5. Mutation: a file whose facts are refused must stop reporting "Indexed and current." — red on a payload produced by a real refusal, not a hand-built struct.

#72 (found there; the fact_validator registry row records it), #101 and #124 (same asymmetry on other surfaces), #82 (the barrier's own history — note that this one is not a barrier defect).

Found while building #72's refusal-stage registry, and written into the `fact_validator` row's reason there so it cannot be forgotten. Deliberately not fixed in that change — see below. ## The over-promise A fact-validator refusal (`abi::validate` → `Parse::Err`) writes a `files` row with `parse_error` set and **zero symbols**. `index_coverage` then reports: ``` verdict: "indexed" reason: "row_present" hint: "Indexed and current." ``` The file has no symbols, its facts were rejected, and the tool says it is indexed and current. ## Why the barrier is fine and the tool is not This is worth separating, because the two behaviours are graded by different things and only one is wrong. **The freshness barrier is correct.** A row exists, so the path is *settled* rather than pending — which is exactly #72's own bullet (*"a file that parses to an error gets a row with `parse_error` set, so it is not this class; confirm the barrier treats it as settled rather than pending"*). It does. Nothing hangs. **The tool over-promises.** `index_coverage` exists to answer "will this file ever carry symbols, and does it now" — and here it answers "yes, and it is current" about a file whose facts were refused. An agent that trusts that reads an empty outline as *the file has no symbols* rather than *this file's facts were rejected*. That is the same shape as #101 (a truncated extraction served silently) and #124 (a refused package invisible on the MCP surface), on a third surface. ## The mechanical cause `FileOverlap` carries **no `parse_error` field** — only `SymbolNames` does. So the coverage path structurally cannot see the refusal even though the row records it. ## Why it was not fixed in #72 It needs a new wire field on `FileOverlap` (daemon side) plus **both skew directions** tested with non-empty payloads — a mission, not a hunk. And `crates/daemon/src/local_index.rs` was being actively edited by a concurrent lane at the time. Doing it badly would have been worse than recording it precisely. ## What closing it needs 1. Carry `parse_error` (or a derived refusal discriminator) on `FileOverlap`. 2. `index_coverage` distinguishes **indexed-with-facts** from **indexed-but-facts-refused**, with the refusal's own reason code — the vocabulary already exists in `coverage.rs`. 3. Three states kept apart: absent = this daemon did not report, no refusal = a measurement, a refusal = the finding. 4. Both wire-skew directions with non-empty payloads. Key normalisation, **not** serde alias plus dual-emit — that combination is a duplicate-field landmine and this repo has the scar. 5. Mutation: a file whose facts are refused must stop reporting `"Indexed and current."` — red on a payload produced by a real refusal, not a hand-built struct. ## Related #72 (found there; the `fact_validator` registry row records it), #101 and #124 (same asymmetry on other surfaces), #82 (the barrier's own history — note that this one is *not* a barrier defect).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
h-dv/code-index#136
No description provided.