Parent
#282
What to build
select() reaches across relations, end to end. Lambda selectors traverse forward-FK paths at any depth (select(lambda t: (t.amount, t.account.name))), and the dict-returning lambda names output fields (select(lambda t: {"account_name": t.account.name}) — CONTEXT.md: Output alias, Traversed projection). Projection traversal narrows per ADR-0006 (INNER, one join per path, shared with where/order_by traversal of the same path); .left_join keeps relation-less rows and the traversed field decodes to None — even from a non-nullable source column (a projected record is not the related model). Unaliased traversed fields take the bare leaf column name; two selected fields resolving to the same output name is a build-time error naming the dict form. A selector returns a dict, a tuple, or a single field — mixed nesting is a build-time error; string selectors still never traverse. The include() × select() rejection goes final: flattened-as-final (grill decision) — error text points at traversed projection, not "until #282".
Acceptance criteria
Blocked by
Parent
#282
What to build
select()reaches across relations, end to end. Lambda selectors traverse forward-FK paths at any depth (select(lambda t: (t.amount, t.account.name))), and the dict-returning lambda names output fields (select(lambda t: {"account_name": t.account.name})— CONTEXT.md: Output alias, Traversed projection). Projection traversal narrows per ADR-0006 (INNER, one join per path, shared withwhere/order_bytraversal of the same path);.left_joinkeeps relation-less rows and the traversed field decodes toNone— even from a non-nullable source column (a projected record is not the related model). Unaliased traversed fields take the bare leaf column name; two selected fields resolving to the same output name is a build-time error naming the dict form. A selector returns a dict, a tuple, or a single field — mixed nesting is a build-time error; string selectors still never traverse. Theinclude()×select()rejection goes final: flattened-as-final (grill decision) — error text points at traversed projection, not "until #282".Acceptance criteria
left_joininterplay pinned: rows kept,Nonefields, NULL-tolerant decode; shared-path join identity visible in rendered SQL.Blocked by