Embedded-region dispatch: EMBEDDED_DISPATCH_SEMANTICS_VERSION = 0, so no file can carry two producers and #77's criterion 2 is unexercisable #119

Open
opened 2026-09-04 16:43:43 +02:00 by buildagent · 2 comments
Member

Split out of #77 by the #76–#79 audit, so that #77 can close on the work it actually owns without laundering an unexercised criterion.

The gap

activation::EMBEDDED_DISPATCH_SEMANTICS_VERSION = 0. Routing is whole-file, single-owner (crates/indexer/src/dirty.rs:181-191). One file has exactly one producer, always.

So #77's criterion 2 — "a mixed-language file carries multiple producers" — cannot be exercised in this build. Not "is untested": there is no configuration of this system in which it could be true.

What is NOT missing, and this is the important half

The provenance model for it is complete and graded:

  • m0044_row_language took row language off files.lang, so language is a row-level fact rather than a file-level one;
  • file_contributions carries region_start / region_end, keyed per generation — the storage for two contributions in one file already exists;
  • provenance_invariants.rs::deleting_one_contribution_takes_only_its_own_rows proves the per-contribution isolation that multi-producer routing depends on.

The schema is ready. The dispatcher is what does not exist. That is why this is a named feature rather than a hole in #77's design, and why it belongs to the routing layer (#78/#79) rather than to the resolver-provenance issue.

Why it is worth its own issue rather than a line in #77's close

#75's governing text asks for a mixed-language file with multiple producers as a demonstration of the architecture, and XAML→C# is the motivating case. Closing #77 quietly would leave that requirement owned by nothing.

It is also the honest reading of a pattern this project keeps paying for: a criterion whose test cannot run is indistinguishable from one that passes. #84's axis C graded zero and passed; #109's weekly jobs skip on every scheduled run; #116's ceiling cannot fail for either regression its own comment names. "C2 is met because the schema supports it" would be the same move.

Acceptance

  1. A file with two regions, routed to two producers, each writing its own file_contributions row with its own region_start/region_end and its own row language.
  2. Deleting one contribution takes only its own rows — the existing invariant, now exercised with two live contributions in one file rather than one.
  3. EMBEDDED_DISPATCH_SEMANTICS_VERSION moves off 0, and the version is part of the activation identity so a dispatch-semantics change invalidates what it should.
  4. The mutation that must go red: route both regions to a single producer and the two-producer assertion fails on producer identity read from the database, not on a count.

Split from #77 (criterion 2). Feeds #75's mixed-language demonstration. Adjacent to #112 (three non-pool language gates a package cannot close) — both are cases where the packaged path is structurally narrower than the builtin one.

Split out of #77 by the #76–#79 audit, so that #77 can close on the work it actually owns without laundering an unexercised criterion. ## The gap `activation::EMBEDDED_DISPATCH_SEMANTICS_VERSION = 0`. Routing is **whole-file, single-owner** (`crates/indexer/src/dirty.rs:181-191`). One file has exactly one producer, always. So #77's criterion 2 — *"a mixed-language file carries multiple producers"* — cannot be exercised in this build. Not "is untested": **there is no configuration of this system in which it could be true.** ## What is NOT missing, and this is the important half The *provenance* model for it is complete and graded: - `m0044_row_language` took row language off `files.lang`, so language is a **row-level** fact rather than a file-level one; - `file_contributions` carries `region_start` / `region_end`, keyed per generation — the storage for two contributions in one file already exists; - `provenance_invariants.rs::deleting_one_contribution_takes_only_its_own_rows` proves the per-contribution isolation that multi-producer routing depends on. The schema is ready. The **dispatcher** is what does not exist. That is why this is a named feature rather than a hole in #77's design, and why it belongs to the routing layer (#78/#79) rather than to the resolver-provenance issue. ## Why it is worth its own issue rather than a line in #77's close #75's governing text asks for a mixed-language file with multiple producers as a demonstration of the architecture, and XAML→C# is the motivating case. Closing #77 quietly would leave that requirement owned by nothing. It is also the honest reading of a pattern this project keeps paying for: a criterion whose test *cannot run* is indistinguishable from one that passes. #84's axis C graded zero and passed; #109's weekly jobs skip on every scheduled run; #116's ceiling cannot fail for either regression its own comment names. "C2 is met because the schema supports it" would be the same move. ## Acceptance 1. A file with two regions, routed to two producers, each writing its own `file_contributions` row with its own `region_start`/`region_end` and its own row language. 2. Deleting one contribution takes only its own rows — the existing invariant, now exercised with **two live contributions in one file** rather than one. 3. `EMBEDDED_DISPATCH_SEMANTICS_VERSION` moves off 0, and the version is part of the activation identity so a dispatch-semantics change invalidates what it should. 4. The mutation that must go red: route both regions to a single producer and the two-producer assertion fails on **producer identity read from the database**, not on a count. ## Related Split from #77 (criterion 2). Feeds #75's mixed-language demonstration. Adjacent to #112 (three non-pool language gates a package cannot close) — both are cases where the packaged path is structurally narrower than the builtin one.
Author
Member

Verdict, asked directly: not closable now. It needs the #154 design, and specifically two of #154's three missing primitives.

Judged against this issue's own four acceptance criteria rather than against the schema, because the schema half is exactly the half that is already done.

What is genuinely ready, re-verified

file_contributions carries region_start/region_end keyed per generation (m0043, re-keyed by m0051), m0044 made language a ROW fact rather than a file fact, and provenance_invariants::deleting_one_contribution_takes_only_its_own_rows proves the per-contribution isolation criterion 2 depends on. Criterion 2 of the acceptance — "deleting one contribution takes only its own rows, now with TWO live contributions in one file" — is a strictly weaker statement than what that invariant already proves, once two contributions can exist. Nothing there is in the way.

What is in the way, and it is not the dispatcher alone

The issue says "the dispatcher is what does not exist". That is true and it understates it. Routing is whole-file single-owner in classify_walked, and turning it into region routing needs four things this tree has none of:

  1. A region DETECTOR, and it has to belong to somebody. Who says where the <script> block in an HTML file ends? Not the host — it has no HTML grammar. Not the second producer — it is not running yet. So the OUTER producer has to be able to emit "bytes [a,b) are language L", which is a new fact kind on the extraction wire, not a routing change. Nothing in code_index_abi::record can say it today.

  2. A region-scoped source view. A guest receives the file and emits spans into it. A second producer handed only [a,b) emits spans relative to a; one handed the whole file may emit spans outside its own region, and the ABI's strongest check — name == src[name_span] — cannot tell those apart. Whichever is chosen is a wire-contract decision.

  3. Precedence when regions overlap or a claim is ambiguous. UNIQUE (file_id, component_id, region_start) permits two rows; it does not say which wins when two producers claim overlapping bytes, and #91's duplicate-id ordering settles a different question.

  4. A containment constraint so the second producer's rows are worth having. This is the point at which #119 stops being independent of #154. A <script> block dispatched to the JS producer yields JS symbols in an HTML file, and the interesting edges — the handler named by an attribute, the member of this component class — are exactly #154's wall: "the candidate side is the whole project's symbols of one kind with no containment constraint at all". Region dispatch without #154's primitive 1 produces a second set of rows that bridges can only join by bare name, which is the un-bridgeable member_access the XAML {Binding …} case already demonstrates.

So: which is it?

Criterion 3 alone is closable in an afternoon and MUST NOT BE. EMBEDDED_DISPATCH_SEMANTICS_VERSION moving off 0 with no dispatcher behind it would be precisely the move this issue's own text warns about — "a criterion whose test cannot run is indistinguishable from one that passes" — with the version bump standing in for the evidence. The constant is honest at 0 today: activation.rs's doc says in as many words that zero means whole-file single-owner routing, and that is a true description of classify_walked. It should stay at 0 until something changes routing, and it should move in the SAME commit that changes it, because its whole job is to invalidate indexes built under the old semantics.

Recommendation: keep #119 open, and re-scope its dependency line from "adjacent to #154" to blocked on #154 primitives 1 (containment constraint) and, for the detector, on a new region fact on the extraction wire. It is a feature with a wire change in it, not a hole in #77.

One correction to this issue's text

It cites #112 as a sibling — "both are cases where the packaged path is structurally narrower than the builtin one". That is still the right pairing, but #112 is now fixed, by one gate (code_index_core::lang_profile) rather than three exceptions, and measured on the #84 Ruby parity leg: the three pinned mechanisms went 1260 -> 0, 53 -> 0, 19 -> 0 in a single pass while the two mechanisms it does not touch stood still. The remaining structural narrowness of the packaged path is #86's missing qualifier TEXT field (876 rows) and this issue. That is a shorter list than it was.

🤖 Generated with Claude Code

https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K

## Verdict, asked directly: **not closable now. It needs the #154 design, and specifically two of #154's three missing primitives.** Judged against this issue's own four acceptance criteria rather than against the schema, because the schema half is exactly the half that is already done. ### What is genuinely ready, re-verified `file_contributions` carries `region_start`/`region_end` keyed per generation (m0043, re-keyed by m0051), m0044 made language a ROW fact rather than a file fact, and `provenance_invariants::deleting_one_contribution_takes_only_its_own_rows` proves the per-contribution isolation criterion 2 depends on. Criterion 2 of the acceptance — *"deleting one contribution takes only its own rows, now with TWO live contributions in one file"* — is a strictly weaker statement than what that invariant already proves, once two contributions can exist. Nothing there is in the way. ### What is in the way, and it is not the dispatcher alone The issue says "the **dispatcher** is what does not exist". That is true and it understates it. Routing is whole-file single-owner in `classify_walked`, and turning it into region routing needs four things this tree has none of: 1. **A region DETECTOR, and it has to belong to somebody.** Who says where the `<script>` block in an HTML file ends? Not the host — it has no HTML grammar. Not the second producer — it is not running yet. So the OUTER producer has to be able to emit "bytes [a,b) are language L", which is a **new fact kind on the extraction wire**, not a routing change. Nothing in `code_index_abi::record` can say it today. 2. **A region-scoped source view.** A guest receives the file and emits spans into it. A second producer handed only `[a,b)` emits spans relative to `a`; one handed the whole file may emit spans outside its own region, and the ABI's strongest check — `name == src[name_span]` — cannot tell those apart. Whichever is chosen is a wire-contract decision. 3. **Precedence when regions overlap or a claim is ambiguous.** `UNIQUE (file_id, component_id, region_start)` permits two rows; it does not say which wins when two producers claim overlapping bytes, and #91's duplicate-id ordering settles a different question. 4. **A containment constraint so the second producer's rows are worth having.** This is the point at which #119 stops being independent of #154. A `<script>` block dispatched to the JS producer yields JS symbols in an HTML file, and the interesting edges — the handler named by an attribute, the member of *this* component class — are exactly #154's wall: *"the candidate side is the whole project's symbols of one kind with no containment constraint at all"*. Region dispatch without #154's primitive 1 produces a second set of rows that bridges can only join by bare name, which is the un-bridgeable `member_access` the XAML `{Binding …}` case already demonstrates. ### So: which is it? **Criterion 3 alone is closable in an afternoon and MUST NOT BE.** `EMBEDDED_DISPATCH_SEMANTICS_VERSION` moving off 0 with no dispatcher behind it would be precisely the move this issue's own text warns about — *"a criterion whose test cannot run is indistinguishable from one that passes"* — with the version bump standing in for the evidence. The constant is honest at `0` today: `activation.rs`'s doc says in as many words that zero means whole-file single-owner routing, and that is a true description of `classify_walked`. It should stay at 0 until something changes routing, and it should move in the SAME commit that changes it, because its whole job is to invalidate indexes built under the old semantics. **Recommendation:** keep #119 open, and re-scope its dependency line from "adjacent to #154" to **blocked on #154 primitives 1 (containment constraint) and, for the detector, on a new region fact on the extraction wire**. It is a feature with a wire change in it, not a hole in #77. ### One correction to this issue's text It cites #112 as a sibling — *"both are cases where the packaged path is structurally narrower than the builtin one"*. That is still the right pairing, but #112 is now **fixed**, by one gate (`code_index_core::lang_profile`) rather than three exceptions, and measured on the #84 Ruby parity leg: the three pinned mechanisms went `1260 -> 0`, `53 -> 0`, `19 -> 0` in a single pass while the two mechanisms it does not touch stood still. The remaining structural narrowness of the packaged path is #86's missing qualifier TEXT field (876 rows) and this issue. That is a shorter list than it was. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
Author
Member

Second lane, same verdict: not closable, and not attempted here. Two of the previous comment's claims are now confirmed FROM THE CODE rather than argued, and one acceptance criterion turns out to be half met already.

The packaged-language lane held #119 today. It was not built, and the reason is not effort — it is that two of its four prerequisites are somebody else's design decision and the third is a wire change. What this comment adds is evidence for the parts that were previously reasoning.

Confirmed from the code: the bridge really has no containment constraint

The previous comment made #119's dependency on #154 turn on this, and #154 asserts it in prose. It is exact. fill_bridge_cands (crates/indexer/src/index.rs:2826-2867) builds the candidate side with:

SELECT s.id, s.file_id, f.path, s.name
  FROM symbols s
  JOIN files f ON f.id = s.file_id
  <bridge_destination admit join>
 WHERE s.lang = ?1 AND s.kind = ?2

That is every symbol in the project of one language and one kind. The only narrowing applied afterwards is the scope predicate — same_file / paired_file / same_directory, carried as dir and pair_key columns — and every one of those is FILE-shaped. None of them can say "a member of the class this file names".

So a <script> block dispatched to a second producer would yield rows that bridges can join by bare name across the whole project. That is the same wall that makes XAML's {Binding …} emit as un-bridgeable member_access, and it is why region dispatch without #154's primitive 1 buys a second set of rows that cannot be joined usefully to the first.

Confirmed from the code: there is still no region fact on the extraction wire

code_index_abi::Reason carries FactRegionOutOfRange and FactRegionDepthExceeded — which looks, at a glance, like the wire already speaks regions. It does not. Both are codes with no producer, and the tree grades that fact rather than leaving it ambiguous: crates/abi/tests/reason_producers.rs:86-92 asserts they are emitted by nothing, and their only other mentions in the tree are //! doc lines in conform.rs, expect.rs and dirty.rs explaining why.

That is the honest state, and it is worth saying plainly because the codes' existence is exactly the kind of thing a later reader mistakes for a shipped mechanism.

One correction to the acceptance list: criterion 3 is HALF MET

Criterion 3 reads "EMBEDDED_DISPATCH_SEMANTICS_VERSION moves off 0, and the version is part of the activation identity so a dispatch-semantics change invalidates what it should."

The second half already holds. crates/indexer/src/activation.rs:168 folds embedded_dispatch_version: EMBEDDED_DISPATCH_SEMANTICS_VERSION into the activation identity today. So the invalidation machinery is in place and waiting; what is missing is only the routing change that would justify moving the number.

This strengthens rather than weakens the previous comment's warning. Criterion 3 alone is now even cheaper than "an afternoon" — it is one character — and it MUST NOT be taken that way. The constant is honest at 0: activation.rs's own doc says zero means whole-file single-owner routing, and that is a true description of classify_walked. Moving it with no dispatcher behind it would invalidate every index in the field to record a semantics change that did not happen, and would stand in for evidence exactly as this issue's text warns.

It should move in the SAME commit that changes routing. Nothing else.

Status of the four prerequisites

prerequisite state
1. a region DETECTOR — a new fact kind on the extraction wire absent, and confirmed absent by a producer gate, not by inspection
2. a region-scoped source view (a wire-contract decision) undecided. The ABI's strongest check is name == src[name_span]; a second producer handed only [a,b) and one handed the whole file are indistinguishable to it
3. overlap / ambiguity precedence undecided. UNIQUE (file_id, component_id, region_start) permits two rows and says nothing about who wins
4. a containment constraint so the second producer's rows are worth having #154 primitive 1, confirmed above from fill_bridge_cands

Recommendation, unchanged and now evidenced

Keep open. Blocked on #154 primitive 1 (containment constraint), and on a new region fact on the extraction wire — a feature with a wire change in it, not a hole in #77. The schema half (file_contributions.region_start/region_end, m0044's row language, deleting_one_contribution_takes_only_its_own_rows) remains ready and is genuinely not in the way.

Sequencing: #154's primitive 1 first, because it is the one that decides whether region dispatch produces useful edges or a second pile of name-joined rows.

🤖 Packaged-language lane, 2026-09-06, master 4f866e5

## Second lane, same verdict: **not closable, and not attempted here.** Two of the previous comment's claims are now confirmed FROM THE CODE rather than argued, and one acceptance criterion turns out to be half met already. The packaged-language lane held #119 today. It was **not built**, and the reason is not effort — it is that two of its four prerequisites are somebody else's design decision and the third is a wire change. What this comment adds is evidence for the parts that were previously reasoning. ### Confirmed from the code: the bridge really has no containment constraint The previous comment made #119's dependency on #154 turn on this, and #154 asserts it in prose. It is exact. `fill_bridge_cands` (`crates/indexer/src/index.rs:2826-2867`) builds the candidate side with: ```sql SELECT s.id, s.file_id, f.path, s.name FROM symbols s JOIN files f ON f.id = s.file_id <bridge_destination admit join> WHERE s.lang = ?1 AND s.kind = ?2 ``` **That is every symbol in the project of one language and one kind.** The only narrowing applied afterwards is the scope predicate — `same_file` / `paired_file` / `same_directory`, carried as `dir` and `pair_key` columns — and every one of those is **FILE-shaped**. None of them can say *"a member of the class this file names"*. So a `<script>` block dispatched to a second producer would yield rows that bridges can join **by bare name across the whole project**. That is the same wall that makes XAML's `{Binding …}` emit as un-bridgeable `member_access`, and it is why region dispatch without #154's primitive 1 buys a second set of rows that cannot be joined usefully to the first. ### Confirmed from the code: there is still no region fact on the extraction wire `code_index_abi::Reason` carries `FactRegionOutOfRange` and `FactRegionDepthExceeded` — which looks, at a glance, like the wire already speaks regions. It does not. Both are **codes with no producer**, and the tree grades that fact rather than leaving it ambiguous: `crates/abi/tests/reason_producers.rs:86-92` asserts they are emitted by nothing, and their only other mentions in the tree are `//!` doc lines in `conform.rs`, `expect.rs` and `dirty.rs` explaining why. That is the honest state, and it is worth saying plainly because the codes' existence is exactly the kind of thing a later reader mistakes for a shipped mechanism. ### One correction to the acceptance list: criterion 3 is HALF MET Criterion 3 reads *"`EMBEDDED_DISPATCH_SEMANTICS_VERSION` moves off 0, **and** the version is part of the activation identity so a dispatch-semantics change invalidates what it should."* **The second half already holds.** `crates/indexer/src/activation.rs:168` folds `embedded_dispatch_version: EMBEDDED_DISPATCH_SEMANTICS_VERSION` into the activation identity today. So the invalidation machinery is in place and waiting; what is missing is only the routing change that would justify moving the number. This strengthens rather than weakens the previous comment's warning. **Criterion 3 alone is now even cheaper than "an afternoon" — it is one character — and it MUST NOT be taken that way.** The constant is honest at `0`: `activation.rs`'s own doc says zero means whole-file single-owner routing, and that is a true description of `classify_walked`. Moving it with no dispatcher behind it would invalidate every index in the field to record a semantics change that did not happen, and would stand in for evidence exactly as this issue's text warns. It should move in the SAME commit that changes routing. Nothing else. ### Status of the four prerequisites | prerequisite | state | |---|---| | 1. a region DETECTOR — a new fact kind on the extraction wire | **absent**, and confirmed absent by a producer gate, not by inspection | | 2. a region-scoped source view (a wire-contract decision) | **undecided.** The ABI's strongest check is `name == src[name_span]`; a second producer handed only `[a,b)` and one handed the whole file are indistinguishable to it | | 3. overlap / ambiguity precedence | **undecided.** `UNIQUE (file_id, component_id, region_start)` permits two rows and says nothing about who wins | | 4. a containment constraint so the second producer's rows are worth having | **#154 primitive 1**, confirmed above from `fill_bridge_cands` | ### Recommendation, unchanged and now evidenced Keep open. **Blocked on #154 primitive 1 (containment constraint), and on a new region fact on the extraction wire** — a feature with a wire change in it, not a hole in #77. The schema half (`file_contributions.region_start`/`region_end`, m0044's row language, `deleting_one_contribution_takes_only_its_own_rows`) remains ready and is genuinely not in the way. Sequencing: #154's primitive 1 first, because it is the one that decides whether region dispatch produces useful edges or a second pile of name-joined rows. 🤖 Packaged-language lane, 2026-09-06, master `4f866e5`
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#119
No description provided.