Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/issue-17400-text-door-formula-prose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@objectstack/spec': patch
---

Scope the text-operator declared-type door's `formula` prose to the judgement it
actually states. The module declared that a `formula` with a readable
`returnType` is judged as the field type its return type names, but at the
door's only consumer — the engine's field-aware seam — a filter over a formula
field never arrives: the earlier materializability door refuses every one of
them with `INVALID_FIELD` 400, whatever the `returnType`. The verdict function,
its sets, the class table and every case are unchanged; only the prose now says
the formula rows are a contract answer no consumer currently reaches, and why
they are kept rather than retired.
51 changes: 45 additions & 6 deletions packages/spec/src/data/filter-text-operator-declared-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@
* value (multi-option, `multiple: true`) is the evaluators' own question
* beneath the door, not this table's.
* - **Deferred** — no verdict, the filter proceeds unchanged: a `formula`
* whose `returnType` is absent (unreadable at the seam), and a DOTTED key
* whose `returnType` this table cannot read (absent, or a spelling the
* schema does not declare — but see the formula note below: at the engine
* seam NO formula reaches this door at all, whatever its `returnType`), and
* a DOTTED key
* (`address.city`), which is `filter-dotted-head`'s subject — its
* structured-JSON heads are deliberately unjudged there (live on two of
* three backends, #8371), and this door reading the head's declared type
Expand All @@ -61,6 +64,30 @@
* passes and the other three are refused through the same sets — no second
* vocabulary ({@link FORMULA_RETURN_TYPE_AS_FIELD_TYPE}).
*
* ⚠️ THE FORMULA ROWS ARE A JUDGEMENT NO CONSUMER CURRENTLY REACHES. The
* sentence above states what {@link textOperatorDoorVerdict} answers, and it
* is the ruling's answer; it does NOT describe what an author observes today.
* At this door's only consumer — the engine's field-aware seam — a filter over
* a `formula` field never arrives: `assertFilterIsMaterializable` (#8296 /
* #4419) refuses EVERY one of them one door earlier, with `INVALID_FIELD` 400,
* for the broader reason that no driver materialises a column for a formula.
* Measured on the fixture below, `$contains` over each of the five formula
* fields — `returnType` `number` / `text` / `boolean` / `date` / absent —
* answers `INVALID_FIELD` 400 alike, so the `returnType` is never the deciding
* fact and none of the three verdicts above is observable. The non-formula
* rows of this table ARE observed at that seam, with this door's own
* `INVALID_FILTER` 400; the formula rows are the exception, not the rule.
*
* The rows are kept, not retired, and nothing here moves: the verdict function
* is still consulted through its `formula` branch by the engine door, so the
* day formula fields become filterable the answer is already correct, and the
* divergence is pinned by name in the engine package
* (`engine-text-operator-declared-type-door.test.ts`) so it goes red on that
* day. Making this door overtake #8296 for formula would answer ONE condition
* ("a formula field cannot be filtered") with TWO wire codes chosen by
* `returnType`, and would reopen #8296's recorded code assignment — a
* maintainer decision, deliberately not taken here.
*
* `multiple: true` does not change a verdict: the class is the ruling's axis.
*
* ## Beneath the door: #14079's row stays (the two are one contract)
Expand Down Expand Up @@ -108,6 +135,14 @@
* Every case passes the SYNTAX door (`parseFilterAST` accepts each filter —
* pinned in this module's test), so a refusal can only be this door's.
*
* ⚠️ EXCEPT the `formula` cases, which neither branch above describes: at the
* engine seam every one of them is refused by the EARLIER #8296 door with
* `INVALID_FIELD` 400 (see the formula note above), so the refusal is not this
* door's and no driver read runs either. A suite driving this table at that
* seam must therefore partition the formula cases out and assert the
* divergence deliberately, rather than fold them into the two branches above —
* which is what `engine-text-operator-declared-type-door.test.ts` does.
*
* ## Deliberately NOT a driver case-set
*
* `scripts/check-driver-conformance.mjs` enrols every `*_CASES` export of a
Expand Down Expand Up @@ -219,9 +254,13 @@ export const FORMULA_RETURN_TYPE_AS_FIELD_TYPE: ReadonlyMap<string, string> = ne
* - `door-refusal` — refused before any driver runs (`INVALID_FILTER` / 400).
* - `passes` — a string-valued declared type; the filter proceeds unchanged.
* - `deferred` — the door records NO verdict and the filter proceeds
* unchanged: the declared type is not readable at the seam (a `formula`
* without `returnType`), or the key is not this door's subject (a dotted
* path — `filter-dotted-head`'s).
* unchanged: the declared type is not readable here (a `formula` without
* `returnType`), or the key is not this door's subject (a dotted path —
* `filter-dotted-head`'s).
*
* These are the answers of THIS function. For `formula` they are not what an
* author observes at the engine seam, where the earlier #8296 door refuses
* every formula filter first — see the module header's formula note.
*/
export type TextOperatorDoorVerdict = 'door-refusal' | 'passes' | 'deferred';

Expand Down Expand Up @@ -350,7 +389,7 @@ export const TEXT_OPERATOR_DOOR_TYPE_CLASSES: readonly TextOperatorDoorTypeClass
name: 'formula',
types: new Set(['formula']),
verdict: 'by-return-type',
note: 'Judged as the FieldType its declared `returnType` names (`text` passes; `number` / `boolean` / `date` are refused through the same sets); `returnType` absent ⇒ deferred, the declared type is not readable at the seam.',
note: 'Judged as the FieldType its declared `returnType` names (`text` passes; `number` / `boolean` / `date` are refused through the same sets); `returnType` absent ⇒ deferred. ⚠️ Unreachable at the engine seam: the earlier #8296 door refuses EVERY formula filter with INVALID_FIELD 400 whatever the `returnType`, so this row states the contract\'s answer, not an observable one — see the module header.',
},
];

Expand Down Expand Up @@ -509,7 +548,7 @@ function caseFor(
verdict,
note: dotted
? 'A dotted path into a structured-JSON field is filter-dotted-head\'s subject (deliberately unjudged there, #8371); this door must not re-close that carve-out by reading the head\'s declared type.'
: 'The declared return type is not readable at the seam — the ruling judges formula only when it is.',
: 'The declared return type is not readable here — the ruling judges formula only when it is. ⚠️ Unreachable at the engine seam: #8296 refuses every formula filter one door earlier (INVALID_FIELD 400) — see the module header.',
};
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/spec/src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ export * from './filter-comparand-type-conformance';
// type can never store a string (the six existing numeric / boolean /
// temporal / structured-JSON classes, by reference) is refused at the engine's
// field-aware seam with INVALID_FILTER 400; string-valued classes pass;
// `formula` is judged by its declared returnType or deferred. Named for the
// `formula` is judged by its declared returnType or deferred — a contract
// answer no consumer currently reaches, because the earlier #8296 door refuses
// every formula filter with INVALID_FIELD 400 first (see the module). Named for the
// door it declares, like `filter-comparand-type`, not as a driver case-set:
// drivers sit beneath this door and keep answering FILTER_TEXT_CASES' row.
export * from './filter-text-operator-declared-type';
Expand Down
Loading