Skip to content

the SORT axis' runtime premise is unpinned: neither door refuses an ORDER BY over an unprovisioned injected anchor, and nothing asserts it #10744

Description

@claude

Found while implementing #10474 (the SORT axis' provenance wiring). ⛔ Filed, not fixed — out of that card's scope, which is a packages/lint rule shape.

The observation

#10474 added sort-field-unprovisioned, an author-time WARNING about a list view ordering by a registry-injected anchor on an ADR-0015 external object. That rule's whole justification is a runtime fact: neither runtime door on the SORT axis refuses such an ORDER BY, so the authoring gate is the only door available for it.

Measured on claude/issue-10474-sort-axis-provenance with a real SqlDriver over better-sqlite3, the object declared exactly as examples/app-showcase/src/data/objects/external/customer.object.ts declares it, against a remote customers table carrying [id, name, email, region, lifetime_value] and none of the seven injected anchors:

orderBy name asc -> [c1,c2,c3] desc -> [c3,c2,c1] (a real column: reverses)
orderBy created_at asc -> [c1,c2,c3] desc -> [c1,c2,c3] asc === desc, 3 rows, no error
orderBy owner_id asc -> [c1,c2,c3] desc -> [c1,c2,c3] asc === desc, 3 rows, no error

The mechanism, read at both doors:

An injected anchor is a datetime or a lookup, so it clears every verdict and reaches the driver, where the #3821 unknown-column backstop returns the rows unordered under a success.

Why it is worth pinning

Nothing in packages/objectql or packages/drivers/driver-sql asserts this. query-expression-conformance.test.ts pins the four doors' agreement about wording and the formula axis, not the unprovisioned-anchor case. So:

  • if a future change widens either door to refuse an unprovisioned anchor, the new lint WARNING silently becomes wrong — it would describe a silent degradation that no longer happens, on a path that is now a loud 400, and it would duplicate a runtime verdict at the wrong severity;
  • if a future change narrows the backstop into a hard error instead, the same;
  • either way the lint rule's docblock (which records the measurement above as its justification) goes stale with nothing turning red.

This is the shape #7738 pinned for the tenant-scope half of the same defect family (packages/objectql/src/engine-external-tenant-scope.test.ts asserts the premise it rests on rather than assuming it, and says so in its header).

Context: why no sweep observed this before

examples/app-showcase declares two federated objects (showcase_ext_customer, showcase_ext_order) and no list view, view record or page binds either of them — verified by grep across examples/. So the federated read path has no authored SORT or SEARCH surface anywhere in the example apps, which is precisely why the #8999 census could record the SORT gap only as a code-path reading and never as an observed missing finding.

Suggested shape (not prescriptive)

A test in packages/drivers/driver-sql (which already has the federated SQLite harness — see sql-driver-external-remote-name.test.ts) asserting the asc === desc signature above, plus the reversing baseline on a real column that makes it a dropped sort rather than a coincidence. The baseline leg is the load-bearing half: without it a driver that stopped sorting entirely would keep the test green.

Related: #10474 (the lint rule resting on this) · #8116 / #7865 (the provenance derivation) · #6994 / #7095 (the two doors) · #7738 (the pinned-premise precedent) · #3821 (the backstop)


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions