You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filed from #6994's implementation (PR pending). Recorded, not claimed. Routing suggestion: domain:engine-core — this is a contract decision about engine.find, not a gate fix.
#6994 refuses a non-dotted orderBy naming a formula field at ingress (assertSortFieldsExist, 400 INVALID_SORT). That covers everything reaching findData: the REST list route, POST /data/:object/query, the export route (its $orderby funnels through findData), and the RPC dispatcher.
It does not cover a caller that reaches engine.find() directly — hooks, flows, reports, expand sub-reads. Measured on the #6994 branch with the fix in place, one script, real SqlDriver (better-sqlite3, on-disk) + real ObjectQL + the real protocol on top, five rows inserted C A E B D, sort_key a formula over record.title:
PROTOCOL {"sort":"sort_key"} REFUSED 400 INVALID_SORT: … a formula field on 'repro_contact' …
PROTOCOL {"orderBy":["sort_key"]} REFUSED 400 INVALID_SORT
PROTOCOL {"sort":"-sort_key"} REFUSED 400 INVALID_SORT
FORMULA orderBy sort_key asc ["C","A","E","B","D"] engine.find direct — still silent
FORMULA orderBy sort_key desc ["C","A","E","B","D"]
asc === desc (byte-identical)? true
Both halves in one run: the door refuses, the direct path still answers 200 in arbitrary order. PR for #6994 carries a test explicitly labelled RECORD OF A KNOWN HOLE pinning this — it records the unfixed behaviour and says it should go red the day this issue is closed.
The decision, not the patch
This is deliberately not filed as "add the same check to the engine", because the engine's posture here is documented and load-bearing in the opposite direction. assertProjectionFieldsExist states it in the protocol source: the engine's tolerance "guards INTERNAL callers (hooks, flows, expand sub-reads, registry-less hosts) that never pass through this ingress". planFormulaProjection acts on the same posture — it drops virtual formula names from ast.fields rather than refusing them. So making engine.find refuse is a new posture, not an extension of an existing one.
Options, all real:
engine.find refuses an unmaterializable orderBy. One place, every driver and every caller covered. Cost: reverses a documented tolerance; any internal caller that currently sorts by a formula field starts throwing where it used to silently under-deliver. Needs a sweep of internal callers first.
engine.find drops it the way it drops virtual names from the projection, but observably — e.g. via the existing DroppedFieldsEvent channel rather than silently. Keeps the tolerance, removes the invisibility. Cheaper, and consistent with planFormulaProjection's existing behaviour.
Leave it at ingress. The external door is the one an author can hit by hand; internal callers are tsc-covered code we own. Cost: the two halves of one contract answer differently, which is exactly the split REST 列表:sort / select / expand 指向不存在的字段时被静默丢弃(filter 轴已收口,这三条轴还没有) #4226 was filed about on the other axis.
⚠️ One trap worth writing down before anyone reaches for it: the engine could sort post-hoc, after applyFormulaPlan evaluates the formulas. That looks like "make formula sorting work" and is a trap — driver.find has already applied limit/offset, so post-sorting reorders an arbitrary page. It would appear to work on small result sets and be wrong the moment pagination is involved.
Also worth deciding here: one spelling of "virtual"
"Which field types materialise no column" is currently spelled in six places, none of them packages/spec: driver-sql's fieldHasColumn and createColumn, driver-turso's remote-transport, objectql's planFormulaProjection and search-companion, plugin-audit's VIRTUAL_FIELD_TYPES — and now metadata-protocol's UNMATERIALIZED_SORT_TYPES (#6994 added the sixth deliberately, local and documented, rather than opening a cross-package spec edit for one string). Note the spec's COMPUTED_VALUE_TYPES (formula/summary/autonumber) is not this set — it is the write contract, and summary/autonumber both get real columns and sort correctly. Consolidating is a cross-package change (#6298 cross-seat declaration) and is a candidate to fold into whichever option above wins.
Refs: #6994 (the ingress half, PR pending), #6924 (the dotted hint text), #6673 (the SEARCH axis), #3821 (the backstop that swallows it), #4226 / #4256 (the family).
Filed from #6994's implementation (PR pending). Recorded, not claimed. Routing suggestion:
domain:engine-core— this is a contract decision aboutengine.find, not a gate fix.What #6994 closed, and what it did not
#6994 refuses a non-dotted
orderBynaming aformulafield at ingress (assertSortFieldsExist,400 INVALID_SORT). That covers everything reachingfindData: the REST list route,POST /data/:object/query, the export route (its$orderbyfunnels throughfindData), and the RPC dispatcher.It does not cover a caller that reaches
engine.find()directly — hooks, flows, reports, expand sub-reads. Measured on the #6994 branch with the fix in place, one script, realSqlDriver(better-sqlite3, on-disk) + realObjectQL+ the real protocol on top, five rows insertedC A E B D,sort_keya formula overrecord.title:Both halves in one run: the door refuses, the direct path still answers 200 in arbitrary order. PR for #6994 carries a test explicitly labelled
RECORD OF A KNOWN HOLEpinning this — it records the unfixed behaviour and says it should go red the day this issue is closed.The decision, not the patch
This is deliberately not filed as "add the same check to the engine", because the engine's posture here is documented and load-bearing in the opposite direction.
assertProjectionFieldsExiststates it in the protocol source: the engine's tolerance "guards INTERNAL callers (hooks, flows, expand sub-reads, registry-less hosts) that never pass through this ingress".planFormulaProjectionacts on the same posture — it drops virtual formula names fromast.fieldsrather than refusing them. So makingengine.findrefuse is a new posture, not an extension of an existing one.Options, all real:
engine.findrefuses an unmaterializableorderBy. One place, every driver and every caller covered. Cost: reverses a documented tolerance; any internal caller that currently sorts by a formula field starts throwing where it used to silently under-deliver. Needs a sweep of internal callers first.engine.finddrops it the way it drops virtual names from the projection, but observably — e.g. via the existingDroppedFieldsEventchannel rather than silently. Keeps the tolerance, removes the invisibility. Cheaper, and consistent withplanFormulaProjection's existing behaviour.tsc-covered code we own. Cost: the two halves of one contract answer differently, which is exactly the split REST 列表:sort/select/expand指向不存在的字段时被静默丢弃(filter 轴已收口,这三条轴还没有) #4226 was filed about on the other axis.applyFormulaPlanevaluates the formulas. That looks like "make formula sorting work" and is a trap —driver.findhas already appliedlimit/offset, so post-sorting reorders an arbitrary page. It would appear to work on small result sets and be wrong the moment pagination is involved.Also worth deciding here: one spelling of "virtual"
"Which field types materialise no column" is currently spelled in six places, none of them
packages/spec:driver-sql'sfieldHasColumnandcreateColumn,driver-turso'sremote-transport,objectql'splanFormulaProjectionandsearch-companion,plugin-audit'sVIRTUAL_FIELD_TYPES— and nowmetadata-protocol'sUNMATERIALIZED_SORT_TYPES(#6994 added the sixth deliberately, local and documented, rather than opening a cross-package spec edit for one string). Note the spec'sCOMPUTED_VALUE_TYPES(formula/summary/autonumber) is not this set — it is the write contract, andsummary/autonumberboth get real columns and sort correctly. Consolidating is a cross-package change (#6298 cross-seat declaration) and is a candidate to fold into whichever option above wins.Refs: #6994 (the ingress half, PR pending), #6924 (the dotted hint text), #6673 (the SEARCH axis), #3821 (the backstop that swallows it), #4226 / #4256 (the family).