ID

R717

Status

Backlog

Bucket

architecture

Priority

3

Theme

routine

Created

2026-08-18

Updated

2026-08-18

The routine carrier’s explicit data-field path needs a correlation arm that anchors on the captured record

A @routine write on Mutation may return a payload carrier: a type wrapping one data field beside an errors channel. The routine call is the write, and the data field owns the post-commit re-read of the committed row. Today that re-read can only reach a table the routine’s own result columns name-match directly, because the data field is not allowed to declare @reference at all. A payload whose target sits one or more hops past that table has no spelling, and the author’s only route is to abandon @routine for a hand-written @service.

Worked example

rent_film inserts a rental and returns a one-row table exposing rental_id. That name-matches `rental’s primary key, so this works:

type RentFilmPayload {
    rental: Rental          # Rental @table(name: "rental")
    errors: [RentFilmError]
}

A payload wanting the rental’s customer instead has no way to say so. @reference(path: [{table: "customer"}]) on the data field is refused outright, even though the identical hop is legal one seat over on the chained form (rentFilm: [Rental!]! @routine(…​) @reference(path: [{table: "rental"}])).

Why it is not just admitting the directive

This is inherited from R622 as its real design work, and the fold into R704 (roadmap/routine-composition-surface-from-facts.md) did not change it. ParentCorrelation.checkCarrierInvariant pairs a non-empty joinPath only with a hop-anchored correlation, while the carrier data field’s correlation is the hop-less OnLiftedSlots over the captured slots. So a residual path needs a correlation arm that anchors on the captured record and walks onward from it, plus the post-commit query emit that rides it. That is model work on the write path, and no view hands it over, which is why this left R704: none of it is a question the read surface can answer.

An open question worth settling at pickup rather than mid-implementation: whether that arm generalises to R447’s RecordTableField, which correlates from a handed record for a different reason. If it does, the arm is one shape with two callers; if it does not, the difference should be stated where a reader of either will find it.

Inherited as decided

Not reopenable here without arguing down the carrier item’s two-statements rule:

  • the write transaction contains the routine call and a projection of its own result and nothing else, at every hop count;

  • residual hops run post-commit under the caller’s identity, so read policies apply to them;

  • a multi-hop data field legitimately resolving null with empty errors is the carrier’s documented success outcome, not a defect.

In-transaction capture would not escape row-level security either, so it buys only insulation from visibility that changes at commit.

What is already in hand

The endpoint resolution is not this item’s work, and it has shipped. intent_carrier_routine_hop states where a carrier’s data field departs from and arrives at, and intent_name_matched_key_pair is the pairing underneath it. This item reads those for hop 0 and walks onward; the multi-hop walk itself is intent_field_reference_step_target, already shipped, since an element’s departure is the previous element’s arrival and position 0 is the only special one.

The two relations landing is what cleared this item’s depends-on.