Skip to content

Commit a11cad3

Browse files
jgravelleclaude
andcommitted
release: v1.108.184 — a ranking cannot prove absence
Closes the finding disclosed when 1.108.183 shipped. search_symbols' semantic exit hand-rolled the legacy negative_evidence block and emitted no _meta.verdict, so not one absence gate shipped since 1.108.166 applied to it — while it printed "Do not claim this feature exists" anyway. Reproduced before touching code. semantic=True, token_budget=1 over a repo CONTAINING the target: result_count: 0 _meta.truncated: true best_match_score: 53.774 warning: "No implementation found ... Do not claim this exists." _meta.verdict: null A strong match reported in the same breath as its own absence. That is the item-1 defect fixed on the lexical path in 1.108.177, left standing here. - The exit builds the SAME verdict as the lexical one, so every gate applies, and it finally receives the pre-scan identity capture (#377 item 6) that was taken on the shared path and never handed to the diverging branch. - NEW verdict.absence_unprovable. The lexical path's absence rests on a corpus fact: no symbol in the index contains any query term. An embedding ranking's zero result rests on embedding geometry, and a symbol can sit in the corpus scoring at or below zero against the query vector. So a semantic or hybrid zero result is degraded and can never reach absent. Expressed as a downgrade so absence_refusal does the refusing; checked LAST among the degraded gates, because every gate above names something the caller can fix. - NEW verdict.absence_refused. Every gate since 1.108.166 downgrades to degraded, and the in-band disclosure was gated on the state still reading absent — so the better a gate worked, the less the caller was told. On a default install (meta_fields: []) a refused scan arrived as a bare empty response with no verdict and no reason. - Producer.completeness_by_mode. Giving the semantic exit a verdict made it mintable under a completeness reading "lexical BM25 over the inverted index", false for an embedding ranking. The 1.108.183 pinning test failed, the mode was reviewed, and hybrid/semantic_only now declare their own. The gate catching its own author is the design working. - Both new verdict blocks published in the schema in this commit. NOT done, deliberately: the two fusion exits still carry no verdict. They assert nothing false, and giving them one means first deciding whether a WRR ranking that includes a lexical channel can prove absence. Pinned by a test. Suite 6057 passed + the known 12 local-ONNX env failures. Replay gate green. No tool-count change, no INDEX_VERSION change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 54db050 commit a11cad3

11 files changed

Lines changed: 884 additions & 51 deletions

File tree

CHANGELOG.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,119 @@
22

33
All notable changes to jcodemunch-mcp are documented here.
44

5+
## [1.108.184] - 2026-07-26 - a ranking cannot prove absence
6+
7+
### Fixed
8+
9+
- **`search_symbols`'s semantic exit asserted absence with no verdict at all, and
10+
the reproduction is worse than the missing block.** Named as an open finding when
11+
1.108.183 shipped; this closes it. That exit hand-rolled the legacy
12+
`negative_evidence` block and emitted no `_meta.verdict`, so not one of the
13+
absence gates shipped since 1.108.166 applied to it — while it printed "Do not
14+
claim this feature exists" anyway. Third instance of the class found in
15+
1.108.179: an early return is a second implementation of the answer and inherits
16+
nothing.
17+
18+
⚠ **Reproduced before touching code, `semantic=True, token_budget=1` over a repo
19+
CONTAINING the target:**
20+
21+
```
22+
result_count: 0
23+
_meta.truncated: true
24+
best_match_score: 53.774
25+
warning: "No implementation found for 'refresh_token'.
26+
Do not claim this feature exists."
27+
_meta.verdict: null
28+
```
29+
30+
A strong match reported in the same breath as its own absence, with nothing in
31+
the response able to contradict it. That is the item-1 defect ("an empty RESPONSE
32+
is not an empty SEARCH") fixed on the lexical path in 1.108.177 and left standing
33+
here, which is exactly what a duplicated answer costs.
34+
35+
The exit now builds the **same** verdict as the lexical one, so every gate
36+
applies: stale, rebuilding, partial, unknown freshness, working tree, movement,
37+
packing. It also finally receives the pre-scan identity capture (#377 item 6),
38+
which was taken on the shared path and simply never handed to the branch that
39+
diverges. `matches_before_packing` is the post-cap match count, so a budget that
40+
empties the response can no longer read as an empty search.
41+
42+
- ⚠ **A refused scan now reaches a default-configured caller.** Every absence gate
43+
since 1.108.166 works by DOWNGRADING to `degraded`, and the in-band disclosure
44+
was gated on the state still reading `absent` — so **the better a gate worked,
45+
the less the caller was told.** A refused zero-result came back as a bare empty
46+
response, and since jcodemunch ships `meta_fields: []` by default, with no
47+
verdict either. New `verdict.absence_refused` (set by the verdict, which is the
48+
only party that knows both that the result was empty and that the claim was
49+
refused) widens the carrier to every refused zero-result. The alternative was a
50+
hand-kept tuple of per-tool result keys in the dispatcher, which is the class of
51+
bug that stops covering the next producer somebody adds.
52+
53+
### Added
54+
55+
- **`verdict.absence_unprovable`: a retrieval mode that cannot establish absence
56+
says so, on every state.** The lexical path's absence rests on a corpus fact —
57+
no symbol in the index contains any query term. An embedding ranking's zero
58+
result rests on embedding geometry, and **a symbol can sit in the corpus while
59+
scoring at or below zero against the query vector.** That is a statement about
60+
the model, not about the repository, and it must never be citable as one. A
61+
semantic or hybrid zero result is therefore `degraded` and can never reach
62+
`absent` at any freshness, at any coverage.
63+
64+
⚠ **Expressed as a DOWNGRADE, so `handoff.absence_refusal` does the refusing off
65+
the existing "only `absent` proves absence" rule — no second rule to keep in
66+
sync.** `absence_refusal` names this cause before the generic state branch,
67+
because unlike every sibling gate it describes something that cannot be different
68+
next time, and a caller told only "the verdict was degraded" would keep re-running
69+
a search that can never answer the question.
70+
71+
⚠ **Checked LAST among the degraded gates, deliberately.** Every gate above it
72+
names a condition the caller can act on (re-index, widen the scope, raise the
73+
budget). Measured: the `token_budget=1` reproduction above now reports the budget
74+
as its note and carries `absence_unprovable` alongside, rather than leading with
75+
a permanent property when a fixable one applies.
76+
77+
- Both new blocks published in `schemas/retrieval-verdict.schema.json` in the same
78+
commit that emits them. Live verdicts from all three semantic shapes are
79+
validated against it.
80+
81+
### Changed
82+
83+
- **`Producer.completeness_by_mode`, and the receipt gate caught its own author.**
84+
Giving the semantic exit a verdict made it mintable — under a `completeness`
85+
declaration reading "lexical BM25 over the inverted index", which is false for an
86+
embedding ranking. `test_v1_108_183.py`'s pinning test failed, the mode got
87+
reviewed, and `hybrid` / `semantic_only` now declare their own completeness
88+
(every filtered symbol, no inverted-index narrowing, ranked by similarity, so the
89+
receipt speaks for what was returned and never for what was not). **A capability
90+
that reads enforced and is not is the failure Phase 2 exists to prevent, so the
91+
gate catching this is the design working rather than a nuisance.** A mode absent
92+
from the table falls back to the default, so adding one without reviewing it is a
93+
visible omission instead of a silent inheritance.
94+
95+
- `semantic`, `semantic_only` and `semantic_weight` join `search_symbols`' receipt
96+
`mode_args`, and the projector records the exit's own `_meta.search_mode`: a
97+
semantic and a lexical search of one string are different operations and must not
98+
collide on one evidence id. `channels.semantic` alone could not tell hybrid from
99+
`semantic_only`.
100+
101+
### Notes
102+
103+
- **DELIBERATELY NOT DONE: the two fusion exits still carry no verdict.** They
104+
assert nothing false — no verdict and no absence prose either — so nothing there
105+
is lying to anyone. Giving them one means first deciding whether a
106+
Weighted-Reciprocal-Rank ranking that *includes* a lexical channel can prove
107+
absence, and that is its own review rather than a rider on this one. Pinned by a
108+
test so the decision cannot be made by accident.
109+
- A genuine semantic zero-result now returns no `negative_evidence` and no warning
110+
string, because the only sentence the old block could produce was one it had no
111+
standing to make. The reason rides the verdict and, on a default install, the
112+
re-attached carrier.
113+
- New `tests/test_v1_108_184.py` (31), including the reproduction pinned and the
114+
lexical path proven still able to reach `absent` — the gate has to be scoped to
115+
the mode that earned it, not applied to every search. No tool-count change, no
116+
`INDEX_VERSION` change.
117+
5118
## [1.108.183] - 2026-07-26 - a receipt says what it proves, and only that
6119

7120
### Added

0 commit comments

Comments
 (0)