honesty: no PROJECT-scoped asserted-name census (project_overview / resolution_gaps) and find_callees has no aggregate at any scope — the per-row marker and page count shipped in d0279c0 #93

Closed
opened 2026-09-03 14:58:42 +02:00 by buildagent · 3 comments
Member

Found by the #86 lane, confirmed independently by the #89 lane. Both agree it belongs on its own rather than folded into either.

The fact exists and never reaches the reader

#86 gap 1 relaxed the ABI's strongest check. Normally a package's ref must satisfy name == src[name_span] — that is what stops a package inventing a binding at a span it does not own. A ref may now set derived_name and be exempt, so that its span means "where this fact came from" rather than "the bytes of this name". Ruby's Rails DSL needs it: has_many :posts emits a type ref named Post at the span of the literal :posts.

The exemption is properly gated — a split grant word, a capability the manifest must request, a role bit outside GRANTABLE_ROLES so it is unforgeable in both directions, folded into the activation digest. None of that is the problem.

The problem is what a reader is told afterwards:

  • the bit is set at crates/indexer/src/packages.rs:4026
  • it is stripped by SCIP_MASK at all three RefRow sites in crates/daemon/src/local_index.rs (2747, 2951, 3095)
  • grep -rn 'derived_name|DERIVED_NAME|524288' crates/mcp-server/src/ returns nothing

So find_callers, find_references and change_impact report such an edge as resolved fact, with nothing in the payload saying the binding was ASSERTED rather than READ. core/src/raw.rs documents it as an operator SQL query (roles & 524288) — honest for an operator holding the database, invisible to the agent that actually consumes the row.

Why this is the shape this repo keeps fixing

It is name_fallback_unmeasured and package_set_consulted again: a fact we hold, that changes how a row should be read, that never reaches the reader. An agent deciding whether to rename a symbol is entitled to know that one of its call sites was asserted by a plugin rather than found in the bytes — that is precisely the edge a check_rename should be least confident about.

The timing argument

No shipped package uses derived names, so the population is zero today. That makes this the cheapest possible moment to add the disclosure and the worst possible moment to defer it: the first package that uses the capability will be believed without qualification, and by then the payload contract will have shipped without the field.

Suggested shape (from the #89 lane, who owns that surface)

Both halves are needed, or the empty case is vacuous:

  1. Per-row marker — keep the bit out of SCIP_MASK's strip for our own DTO (or carry a separate boolean), and emit name_asserted: true only when set. A row without it is unremarkable; a row with it says the name was not read from the span.
  2. An aggregate — a count on project_overview and/or resolution_gaps, so ABSENT is distinguishable from ZERO. Without it, "no such edges here" and "this build does not report them" render identically, which is the exact vacuity the three-state rule exists to prevent.

Acceptance

  • A ref carrying the role reaches a client with a marker; one without it is byte-identical to today.
  • The aggregate distinguishes absent from zero, with a test that a build not reporting it is not read as "none".
  • Wire skew both directions, non-empty payloads.
  • A mutation that strips the marker goes red on a NON-EMPTY population — a fixture with a real derived-name ref, not an empty one.
Found by the #86 lane, confirmed independently by the #89 lane. Both agree it belongs on its own rather than folded into either. ## The fact exists and never reaches the reader #86 gap 1 relaxed the ABI's strongest check. Normally a package's ref must satisfy `name == src[name_span]` — that is what stops a package inventing a binding at a span it does not own. A ref may now set `derived_name` and be exempt, so that its span means *"where this fact came from"* rather than *"the bytes of this name"*. Ruby's Rails DSL needs it: `has_many :posts` emits a `type` ref named `Post` at the span of the literal `:posts`. The exemption is properly gated — a split grant word, a capability the manifest must request, a role bit **outside `GRANTABLE_ROLES`** so it is unforgeable in both directions, folded into the activation digest. **None of that is the problem.** The problem is what a reader is told afterwards: - the bit is set at `crates/indexer/src/packages.rs:4026` - it is stripped by `SCIP_MASK` at all three `RefRow` sites in `crates/daemon/src/local_index.rs` (2747, 2951, 3095) - `grep -rn 'derived_name|DERIVED_NAME|524288' crates/mcp-server/src/` returns **nothing** So `find_callers`, `find_references` and `change_impact` report such an edge as **resolved fact**, with nothing in the payload saying the binding was ASSERTED rather than READ. `core/src/raw.rs` documents it as an operator SQL query (`roles & 524288`) — honest for an operator holding the database, invisible to the agent that actually consumes the row. ## Why this is the shape this repo keeps fixing It is `name_fallback_unmeasured` and `package_set_consulted` again: a fact we hold, that changes how a row should be read, that never reaches the reader. An agent deciding whether to rename a symbol is entitled to know that one of its call sites was asserted by a plugin rather than found in the bytes — that is precisely the edge a `check_rename` should be least confident about. ## The timing argument **No shipped package uses derived names, so the population is zero today.** That makes this the cheapest possible moment to add the disclosure and the worst possible moment to defer it: the first package that uses the capability will be believed without qualification, and by then the payload contract will have shipped without the field. ## Suggested shape (from the #89 lane, who owns that surface) Both halves are needed, or the empty case is vacuous: 1. **Per-row marker** — keep the bit out of `SCIP_MASK`'s strip for our own DTO (or carry a separate boolean), and emit `name_asserted: true` **only when set**. A row without it is unremarkable; a row with it says the name was not read from the span. 2. **An aggregate** — a count on `project_overview` and/or `resolution_gaps`, so ABSENT is distinguishable from ZERO. Without it, "no such edges here" and "this build does not report them" render identically, which is the exact vacuity the three-state rule exists to prevent. ## Acceptance - A ref carrying the role reaches a client with a marker; one without it is byte-identical to today. - The aggregate distinguishes absent from zero, with a test that a build not reporting it is not read as "none". - Wire skew both directions, non-empty payloads. - A mutation that strips the marker goes red on a NON-EMPTY population — a fixture with a real derived-name ref, not an empty one.
Author
Member

Triage 2026-09-06: LEFT OPEN — PARTIAL. The per-row marker shipped; the aggregate shipped page-scoped only, and the project-wide census was deliberately reverted.

Reported as fixed in d0279c0; on verification, half 1 is met and half 2 is met at a narrower scope than this issue specifies.

Half 1 — SHIPPED

pub name_asserted: Option<bool> — crates/daemon/src/local_index.rs:232, emitted as Some(true) only, never Some(false) (reasoning at :215-231), stamped at all three RefRow sites (:3451/3462, :3696/3707, :3854/3865), read from the unmasked column and deliberately not through SCIP_MASK (crates/core/src/raw.rs:145). That last detail is the one that matters: the marker is a local fact and must not leak into the SCIP roles field.

Half 2 — SHIPPED PAGE-SCOPED, and the project-wide census was REVERTED

pub page_name_asserted: Option<u64> — local_index.rs:512, with stamp_page_name_asserted at :539.

Its own doc (:499-511) records why the project-wide version did not ship: SELECT COUNT(*) FROM refs WHERE (roles & 524288) != 0 is un-indexable, so it is a SCAN refs on project_overview — the first call an agent makes — and it was caught by refs_rollup_e2e::the_overview_path_runs_no_scan_of_refs. Reverted rather than shipped as a scan. That is the right call and the right gate catching it.

But this issue's acceptance bullet 2 asks for the aggregate on project_overview and/or resolution_gaps, precisely so that ABSENT ≠ ZERO at project scope. search_text("asserted") over crates/mcp-server/src/ finds no project_overview or resolution_gaps aggregate — the only name_asserted hits in that crate are the three test sites in server.rs.

So today: project_overview and resolution_gaps cannot distinguish "no asserted names in this project" from "not reported." That is the exact condition bullet 2 exists to prevent.

Bullets 1, 3 and 4 are met. Wire-skew and anti-vacuity are handled properly — RefCounts::default() is hand-written so Some(0) is a measurement rather than the skew sentinel (local_index.rs:517-536).

cargo test -p code-index-daemon --test asserted_name_marker_e2e → 4 passed, EXIT 0

incl. asserted_row_says_so_and_the_read_row_does_not, every_ref_serving_tool_states_it, the_page_count_separates_measured_zero_from_not_reported, the_local_bit_never_reaches_the_scip_roles_field.

The exact follow-up, so it is not re-derived

A project-wide asserted-name census needs a partial index on roles & 524288, or a roles dimension on refs_rollup. Both are schema changes.

There is now a worked precedent for judging that trade: m0061 (crates/indexer/src/migrations/m0061_file_refs_rollup.rs) took a per-row-inserted trigger cost and showed it repaid by the second project_overview — break-even 1.24 calls on this repo, 1.08 on rust-analyzer. And #143 shows the opposite verdict on a cost proportional to a full sweep per watcher re-index. Whoever takes this should measure against those two, not guess.

Until then the honest state is what shipped: a page count that is real, and a project count that is absent rather than wrong.

🤖 Triage lane, 2026-09-06, master 45cf6e4

## Triage 2026-09-06: LEFT OPEN — **PARTIAL.** The per-row marker shipped; the aggregate shipped **page-scoped only**, and the project-wide census was deliberately reverted. Reported as fixed in `d0279c0`; on verification, half 1 is met and half 2 is met at a narrower scope than this issue specifies. ### Half 1 — SHIPPED `pub name_asserted: Option<bool>` — `crates/daemon/src/local_index.rs:232`, emitted as `Some(true)` **only**, never `Some(false)` (reasoning at `:215-231`), stamped at all three `RefRow` sites (`:3451/3462`, `:3696/3707`, `:3854/3865`), read from the **unmasked** column and deliberately not through `SCIP_MASK` (`crates/core/src/raw.rs:145`). That last detail is the one that matters: the marker is a local fact and must not leak into the SCIP roles field. ### Half 2 — SHIPPED PAGE-SCOPED, and the project-wide census was REVERTED `pub page_name_asserted: Option<u64>` — `local_index.rs:512`, with `stamp_page_name_asserted` at `:539`. Its own doc (`:499-511`) records why the project-wide version did not ship: `SELECT COUNT(*) FROM refs WHERE (roles & 524288) != 0` is **un-indexable**, so it is a `SCAN refs` on `project_overview` — the first call an agent makes — and it was caught by `refs_rollup_e2e::the_overview_path_runs_no_scan_of_refs`. Reverted rather than shipped as a scan. That is the right call and the right gate catching it. But this issue's acceptance bullet 2 asks for the aggregate **on `project_overview` and/or `resolution_gaps`**, precisely so that ABSENT ≠ ZERO at project scope. `search_text("asserted")` over `crates/mcp-server/src/` finds **no** `project_overview` or `resolution_gaps` aggregate — the only `name_asserted` hits in that crate are the three test sites in `server.rs`. So today: **`project_overview` and `resolution_gaps` cannot distinguish "no asserted names in this project" from "not reported."** That is the exact condition bullet 2 exists to prevent. Bullets 1, 3 and 4 are met. Wire-skew and anti-vacuity are handled properly — `RefCounts::default()` is hand-written so `Some(0)` is a measurement rather than the skew sentinel (`local_index.rs:517-536`). ``` cargo test -p code-index-daemon --test asserted_name_marker_e2e → 4 passed, EXIT 0 ``` incl. `asserted_row_says_so_and_the_read_row_does_not`, `every_ref_serving_tool_states_it`, `the_page_count_separates_measured_zero_from_not_reported`, `the_local_bit_never_reaches_the_scip_roles_field`. ### The exact follow-up, so it is not re-derived A project-wide asserted-name census needs **a partial index on `roles & 524288`, or a roles dimension on `refs_rollup`**. Both are schema changes. There is now a worked precedent for judging that trade: **m0061** (`crates/indexer/src/migrations/m0061_file_refs_rollup.rs`) took a per-row-inserted trigger cost and showed it repaid by the *second* `project_overview` — break-even 1.24 calls on this repo, 1.08 on rust-analyzer. And **#143** shows the opposite verdict on a cost proportional to a full sweep per watcher re-index. Whoever takes this should measure against those two, not guess. Until then the honest state is what shipped: a page count that is real, and a project count that is absent rather than wrong. 🤖 Triage lane, 2026-09-06, master `45cf6e4`
Author
Member

STAYING OPEN, NARROWED — three of four acceptance bullets shipped; the project-scoped census did not, and find_callees has no aggregate at any scope

Close-out lane, master 552e3a2. Title corrected.

Shipped by d0279c0 "feat: six silent-loss surfaces render the fact the process already held"

Per-row marker, in the payload rather than in prose: crates/daemon/src/local_index.rs:274 — pub name_asserted: Option<bool> with skip_serializing_if, stamped at all three RefRow sites (:3511, :3756, :3914) via name_asserted_of(roles) reading the unmasked roles column, so SCIP_MASK no longer eats the derived_name bit on the way out.

Page-scoped aggregate: local_index.rs:554 pub page_name_asserted: Option<u64>, stamp_page_name_asserted at :590, called at :3584 and :3770. RefCounts::default() is hand-written so Some(0) is a measurement, and RefCounts::legacy() (old-daemon tuple decode) correctly yields None — measured zero and not-reported are different values, which is the whole point.

cargo test -p code-index-daemon --test asserted_name_marker_e2e → EXIT=0, 4 passed (asserted_row_says_so_and_the_read_row_does_not, every_ref_serving_tool_states_it, the_page_count_separates_measured_zero_from_not_reported, the_local_bit_never_reaches_the_scip_roles_field).

Not shipped — acceptance bullet 2 at project scope

Live project_overview() on this repo carries no asserted-name field anywhere in the payload, and resolution_gaps has none either (ResolutionGapsResp, crates/daemon/src/graph.rs:1311). The tree says why, in its own words at crates/daemon/tests/asserted_name_marker_e2e.rs:214-236: the project-wide Stats::name_asserted_refs census was written and then reverted, because SELECT COUNT(*) FROM refs WHERE (roles & 524288) != 0 is un-indexable and refs_rollup_e2e::the_overview_path_runs_no_scan_of_refs went red on the resulting SCAN refs. That was the right call and it is still an open half.

Also not shipped, and not named by the implementing lane

find_callees has no aggregate at any scope. Its DTO path (local_index.rs:3855-3927) returns (Vec<RefRow>, total, unresolved_count) — no RefCounts at all. So it is the one ref-serving tool where a page of unmarked rows is genuinely ambiguous between "no asserted names here" and "this build does not report them" — exactly the vacuity local_index.rs:532 says the companion field exists to prevent. every_ref_serving_tool_states_it asserts the per-row marker on callees but has no aggregate half to assert.

Corrected scope

Mark bullets 1, 3 and 4 done in d0279c0. What remains:

  1. a project-scoped asserted-name census on project_overview and/or resolution_gaps;
  2. an aggregate for find_callees, which has none.

Carry the recorded cost note forward: it needs a partial index on roles & 524288, or a roles dimension on refs_rollup — judged against m0061's measured break-even and #143's opposite verdict. Do not close this by adding the count on a path that scans refs; that gate is there for a reason.

## STAYING OPEN, NARROWED — three of four acceptance bullets shipped; the project-scoped census did not, and `find_callees` has no aggregate at any scope Close-out lane, master `552e3a2`. **Title corrected.** ### Shipped by `d0279c0` *"feat: six silent-loss surfaces render the fact the process already held"* **Per-row marker, in the payload rather than in prose:** `crates/daemon/src/local_index.rs:274` — `pub name_asserted: Option<bool>` with `skip_serializing_if`, stamped at all three `RefRow` sites (`:3511`, `:3756`, `:3914`) via `name_asserted_of(roles)` reading the **unmasked** roles column, so `SCIP_MASK` no longer eats the `derived_name` bit on the way out. **Page-scoped aggregate:** `local_index.rs:554` `pub page_name_asserted: Option<u64>`, `stamp_page_name_asserted` at `:590`, called at `:3584` and `:3770`. `RefCounts::default()` is hand-written so `Some(0)` is a measurement, and `RefCounts::legacy()` (old-daemon tuple decode) correctly yields `None` — measured zero and not-reported are different values, which is the whole point. `cargo test -p code-index-daemon --test asserted_name_marker_e2e` → **EXIT=0**, 4 passed (`asserted_row_says_so_and_the_read_row_does_not`, `every_ref_serving_tool_states_it`, `the_page_count_separates_measured_zero_from_not_reported`, `the_local_bit_never_reaches_the_scip_roles_field`). ### Not shipped — acceptance bullet 2 at project scope Live `project_overview()` on this repo carries **no asserted-name field anywhere in the payload**, and `resolution_gaps` has none either (`ResolutionGapsResp`, `crates/daemon/src/graph.rs:1311`). The tree says why, in its own words at `crates/daemon/tests/asserted_name_marker_e2e.rs:214-236`: the project-wide `Stats::name_asserted_refs` census **was written and then reverted**, because `SELECT COUNT(*) FROM refs WHERE (roles & 524288) != 0` is un-indexable and `refs_rollup_e2e::the_overview_path_runs_no_scan_of_refs` went red on the resulting `SCAN refs`. That was the right call and it is still an open half. ### Also not shipped, and not named by the implementing lane **`find_callees` has no aggregate at any scope.** Its DTO path (`local_index.rs:3855-3927`) returns `(Vec<RefRow>, total, unresolved_count)` — no `RefCounts` at all. So it is the one ref-serving tool where a page of unmarked rows is genuinely ambiguous between *"no asserted names here"* and *"this build does not report them"* — exactly the vacuity `local_index.rs:532` says the companion field exists to prevent. `every_ref_serving_tool_states_it` asserts the per-row marker on callees but has no aggregate half to assert. ### Corrected scope Mark bullets 1, 3 and 4 done in `d0279c0`. What remains: 1. a **project-scoped** asserted-name census on `project_overview` and/or `resolution_gaps`; 2. an aggregate for **`find_callees`**, which has none. Carry the recorded cost note forward: it needs a partial index on `roles & 524288`, or a roles dimension on `refs_rollup` — judged against m0061's measured break-even and #143's opposite verdict. Do not close this by adding the count on a path that scans `refs`; that gate is there for a reason.
buildagent changed title from honesty: a ref whose name a package ASSERTED is reported as resolved fact, and no payload says so to honesty: no PROJECT-scoped asserted-name census (project_overview / resolution_gaps) and find_callees has no aggregate at any scope — the per-row marker and page count shipped in d0279c0 2026-09-06 12:50:11 +02:00
Author
Member

FIXED, merged as a80eb61. name_asserted_refs ships on resolution_gaps (graph.rs:1470, :2028).

The recorded blocker was wrong, and that is the useful part. The tree recorded that this needed a partial index or a refs_rollup roles dimension — both schema changes. It needs neither: resolution_gaps's summary query is already a full grouped pass over those rows, so the census is a sixth aggregate on a scan that happens regardless. No new statement, no new pass, no migration.

It also lands on resolution_gaps rather than project_overview, which matters for a reason unrelated to this issue: project_overview has 30 estimated tokens of headroom against a ceiling whose standing rule is "TRIM, do not raise". Putting a census there would have cost the ratchet. resolution_gaps is also the tool a caller reaches for when they care about this number.

Two mutations run: census as a constant → left: Some(0) / right: Some(1); field never reported → left: None / right: Some(1). The second is the one that matters — it separates "measured, and the answer is zero" from "this build did not report", which is the whole point of the field.

Closing.

FIXED, merged as `a80eb61`. `name_asserted_refs` ships on `resolution_gaps` (`graph.rs:1470`, `:2028`). **The recorded blocker was wrong, and that is the useful part.** The tree recorded that this needed a partial index or a `refs_rollup` roles dimension — both schema changes. It needs neither: `resolution_gaps`'s `summary` query is *already* a full grouped pass over those rows, so the census is a sixth aggregate on a scan that happens regardless. **No new statement, no new pass, no migration.** It also lands on `resolution_gaps` rather than `project_overview`, which matters for a reason unrelated to this issue: `project_overview` has 30 estimated tokens of headroom against a ceiling whose standing rule is "TRIM, do not raise". Putting a census there would have cost the ratchet. `resolution_gaps` is also the tool a caller reaches for when they care about this number. Two mutations run: census as a constant → `left: Some(0) / right: Some(1)`; field never reported → `left: None / right: Some(1)`. The second is the one that matters — it separates "measured, and the answer is zero" from "this build did not report", which is the whole point of the field. Closing.
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#93
No description provided.