feat(spec)!: $empty joins FILTER_OPERATORS, and is_empty / is_not_empty lower to it (#20446) - #20570
Conversation
The measured diff of the pre-flight: `$empty` joins `FILTER_OPERATORS`, and
`is_empty` / `isempty` / `is_not_empty` / `isnotempty` lower to
`$empty: true | false` in `AST_OPERATOR_MAP`, the array-sugar lowering and
`canonicalAstOperator`. Texts, tables, pins and the changeset are NOT done:
the pre-flight measured the diff refusing inputs accepted today (a record
write carrying `{ $empty: ... }` as a text value; a lowered `is_empty` on a
column the driver holds no declaration for), so the work stopped at the
report, as ordered. Tests in this tree still assert the staging and are red
on purpose.
Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…operators Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…operators Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…bles and the QueryAST arm follow (WIP) Retires the staging texts the flip falsifies, adds `$empty` to every operator-enumeration table that goes red, un-partitions the engine number door's `$empty` row, and aligns driver-memory's QueryAST-node `is_empty` case with the canonical fold. Pins, changeset and generated artifacts follow. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…1 and N2 refusals (WIP) Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…numeration (WIP) Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…check:generated --fix) Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…wing) (WIP, marker pending the gate) Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…et carries its registration marker Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…table, as the engine does Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
One content conflict, in packages/objectql/src/engine-number-comparand-declared-type-door.test.ts, at the GUARD's partition lines. Both truths kept: this branch's removal of the STAGED partition (the $empty row is one of PASSES, driven end to end) and #20502's refused form-set, which adds NON_NUMERIC_VALUE_FORMS beside NON_NUMERIC_STRING_FORMS. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…pty-joins-filter-operators
📓 Docs Drift CheckThis PR changes 7 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b2b34b21e459a1dd54bd26a09b21718084040fb7 && git checkout b2b34b21e459a1dd54bd26a09b21718084040fb7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 542670da6dfc17d3a2ec919139afb7caae018d02 c96e1feacafd896e4b646dcb5e3b2c0480a01c40 && git checkout -B drift-repro 542670da6dfc17d3a2ec919139afb7caae018d02 && git merge --no-ff c96e1feacafd896e4b646dcb5e3b2c0480a01c40
node scripts/docs-audit/affected-docs.mjs --json 542670da6dfc17d3a2ec919139afb7caae018d02
|
… what was decided, not which card The migrate-meta guidance pin (packages/cli/test/migrate-meta-engine-guidance.test.ts) covers the filter- family: no author-shown field of a covered entry may cite a tracker id. This entry's reason cited six, plus three comment record ids. Each sentence now states the decision in words; ADR ids stay. A cut: the reason is one line shorter. The registry is regenerated by gen:migration-registry; spec-changes.json and the upgrade guide carry no protocol-18 entry yet, and their generators wrote no change. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…y record key alone main retired the inner `name` on cube measures and dimensions (the record key is the member's name), and spec's tree-scoped absence pin (cube-member-inner-name-retirement.test.ts) refuses any authoring of it inside its radius. This PR's where-empty-flip-host.test.ts, written before that retirement landed and merged in with it, still spelled `name` on its one measure and one dimension. Both are dropped; the host test's three pins are unchanged and still pass. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs: card #20446 (body and all 13 comments, rulings ① Derived judgmentsAccept-set changes the diff implies — each judged.
Newly REFUSED inputs, per face, and whether the changeset's FROM → TO names each.
Rulings' premise, tested against the diff: after the flip, an author-declared column on an object the face was handed ( Newly ANSWERED-differently inputs. (a) a stored 「is empty」 on a text-like column ( Author-shown text, sentence by sentence. Changeset — every FROM → TO line, the N1 message, the type lists, the boot-error description, the dialect clause ("a knex client other than SQLite, PostgreSQL or MySQL"), the Tests edited or deleted. The card's pins. Ruling A's multi-value pin ( Check-runs on ② Semver levelChangeset ③ Boundary flags
Implemented-by: VERDICT: PASS Adopted and posted by Generated by Claude Code |
…uidance and one from its changeset The at-tier review's wording flags, folded in as cuts: - acceptanceCriteria: "it now also selects the rows holding the empty string or the empty list" read as if is_not_empty gained those rows; it loses them. - acceptanceCriteria: "fails with INVALID_FILTER" — the analytics read scope fails with a different code. - reason: "while the three builders already showed them as empty" is cut. - changeset: "and the prescription `$null`" — the read-scope envelope's message does not spell it; the FROM → TO lines carry the remedy. registry.ts regenerated by gen:migration-registry. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…refusal prescribes $null ", loudly and with the $null prescription," is cut from the entry's reason: the analytics read-scope refusal's message does not spell $null, the same reason the changeset bullet lost that claim. registry.ts regenerated by gen:migration-registry. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs: card #20446 (body and all 15 comments — the engine notice ① Derived judgmentsAccept-set and public-surface changes the diff implies — each judged.
Newly REFUSED inputs, per face, and whether the changeset's FROM → TO names each.
Rulings' premise, tested against the diff: after the flip an author-declared column on an object the face was handed ( Newly ANSWERED-differently inputs. (a) A stored 「is empty」 on a text-like column also finds Author-shown text, sentence by sentence. Changeset (at this head): the summary, the per-type sentence, the Tests edited or deleted. The card's pins. Ruling A's multi-value pin ( Check-runs on ② Semver levelChangeset ③ Boundary flags
Implemented-by: VERDICT: PASS Adopted and posted by Generated by Claude Code |
Closes #20446
$emptyjoinsFILTER_OPERATORS, and the view operatorsis_empty/is_not_emptylower to$empty: true | falseinstead of$null. This is the last step of ruling A on #20399, built as option A under the seat's rulings5881406205,5881556735and5886202626on the card.'', a multi-value field also finds[], andis_not_emptyis the exact complement.canonicalAstOperatorfolds the empty pair onto its own names, and driver-memory's QueryAST-node arm follows it.{ $empty: … }written as a field value is refused (VALIDATION_FAILED).is_empty/is_not_emptywhere no face holds the column's declaration is refused. Stored metadata meets that refusal on the built-inid, a federated object on a driver withoutregisterExternalObject, anAnalyticsServicebuilt withoutsourceFieldMeta, and a multi-value column on a SQL dialect driver-sql does not model.filter-is-empty-lowers-to-empty-operator. The staging texts are retired; the operator-enumeration tests and the service-analytics README follow.Clause-②: yes (narrowing)
Generated by Claude Code