ID |
|
|---|---|
Status |
Backlog |
Bucket |
bug |
Priority |
3 |
Theme |
nodeid |
Created |
2026-09-07 |
Updated |
2026-09-07 |
A @referenceFor route is a discovered key in the store
Goal
A @nodeId filter whose route is a per-participant @referenceFor path is stated in the fact store as
the path the author wrote, so the store’s answer for that slot and the generator’s agree. Today they
disagree with nothing failing, and every store consumer that asks how such a slot navigates gets the
wrong answer.
@referenceFor steps are captured: graphitron_reference_for_step_entry holds them at a field coordinate and
graphitron_argument_reference_for_step_entry at an argument coordinate, both read as the same element
grammar as @reference. No view in
graphitron-model/src/main/resources/no/sikt/graphitron/model/graphitron-model.sql reads either table.
intent_node_id_instruction_live derives carries_reference_path from graphitron_field_reference_step_entry
and graphitron_argument_reference_step_entry alone, and intent_node_id_decode_hop resolves an
AUTHORED_PATH’s hops through `intent_argument_reference_step_target and
intent_input_field_reference_step_target, both built over those same two @reference step tables.
So intent_node_id_decode_endpoint.navigation reads SAME_TABLE or DISCOVERED_KEY for such a slot,
and the hop it carries where it carries one is the single foreign key auto-discovery would find, not
the chain the author wrote. NodeIdLeafResolver.selectRoute takes its Route.ParticipantRoute arm on
exactly those branches and walks the authored chain, so the walk and the store state different
navigations, different hops and different landings for one slot.
Nothing fails today. FactCaptureAgreementTest, whose job is to hold the walk and the store to one
answer, pins no @referenceFor @nodeId case at all, so the disagreement is not a regression that
slipped a gate; it is a shape the gate never covered.
What makes this bigger than a fourth navigation value
The endpoint relation’s key is the coordinate plus the departing table, not the participant.
intent_node_id_decode_endpoint takes its departure from intent_argument_scope_table, which joins
intent_field_scope_table, whose participant arm is SELECT DISTINCT ... 'PARTICIPANT_TABLE' over
intent_field_participant_scope_table: the projection drops the participant name deliberately, and
intent_field_scope_table’s own comment says a consumer needing the participant reads the
participant relation directly. Two participants of one consuming field may bind one table, and the
walk decides per participant (selectRoute` dispatches on ParticipantRef.TableBound), so it can take
Route.ParticipantRoute for one of them and Route.AutoDiscover for the other while the store holds
one endpoint row for both. A per-participant route therefore needs the participant in the endpoint
family’s key, which is a re-keying of that family onto an axis the scope projection drops, not a
fourth value in a closed vocabulary. Whoever picks this up should decide that before writing any
step-target view, because it decides the grain of everything under it.
Why it is filed on its own
The disagreement exists whether or not any consumer of the decode family lands.
roadmap/nodeid-key-landing-is-never-verified.md verifies a @nodeId key landing over the store and
excludes a participant-route branch from both of its verdicts for exactly this reason, stating the
exclusion as a fact that stops being true when this item lands rather than as a scope note. That
exclusion is the pin: when the endpoint family models a per-participant route, the predicate matches
nothing by construction and that item’s boundary test inverts. Neither item blocks the other, and the
two can land in either order.