From 85ed74dd87655beabbc20120fde6f49e56336923 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Mon, 6 Jul 2026 12:05:54 +1000 Subject: [PATCH 1/4] docs: dump the jsonb domains + per-domain terms/extractors in the manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves #365 and #366 — both via the catalog dump, no SQL autogen. eql_domains::CATALOG (Shape::SteVec), but their SQL is deliberately hand-written (hand-tuned CHECKs, #354) and `scalar_families()` filters them out of the dump. Add them as a new **additive** `stevec` field on `CatalogDump` — scalar-only consumers (the fixture-coverage task) read only `types[]`, so they're unaffected. The manifest now carries all 51 domains (48 scalar + 3 jsonb). from eql_domains::Term) to each `DomainEntry`, linking a domain to its extractor functions (e.g. integer_ord -> eql_v3.ord_term). Authoritative — resolves the docs drift-lint false-flags on eq_term / ord_term / match_term. Both fields are additive; all eql-codegen + eql-domains tests pass (89 + 91). The manifest generator maps them through (termFunctions on scalars; the jsonb family as `json`-capability domains). Full pipeline verified end-to-end (doxygen -> XML + dump-catalog -> 984 functions + 51 domains). Stacked on #364. Claude-Session: https://claude.ai/code/session_01CqDNqLSEEkCi7xAJFq7HJA --- crates/eql-codegen/src/dump.rs | 80 ++++++++++++++++++++++++- tasks/docs/generate/test_xml_to_json.py | 21 ++++++- tasks/docs/generate/xml-to-json.py | 23 +++++++ 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/crates/eql-codegen/src/dump.rs b/crates/eql-codegen/src/dump.rs index f8f3cd07b..065cf40f3 100644 --- a/crates/eql-codegen/src/dump.rs +++ b/crates/eql-codegen/src/dump.rs @@ -8,10 +8,15 @@ use eql_domains::Term; use serde::Serialize; -/// The catalog surface: every scalar type and its domains. +/// The catalog surface: every scalar type and its domains, plus the non-scalar +/// SteVec (`jsonb`) family. #[derive(Serialize)] pub struct CatalogDump { pub types: Vec, + /// The `jsonb` (SteVec) family — `eql_v3.json` / `jsonb_entry` / `jsonb_query`. + /// Their SQL is hand-written under `src/v3/jsonb/`; the catalog owns only + /// their inventory (scalar-only consumers ignore this field). + pub stevec: Vec, } #[derive(Serialize)] @@ -36,6 +41,44 @@ pub struct DomainEntry { /// SQL operators the domain's terms support, in catalog order. Empty for /// the storage domain (no terms). pub supported_ops: Vec<&'static str>, + /// The index terms this domain carries, with their extractor + SEM ctor. + pub terms: Vec, +} + +/// A domain's index term: payload key + generated extractor + SEM constructor +/// (from `eql_domains::Term`) — links a domain to its extractor functions. +#[derive(Serialize)] +pub struct TermInfo { + /// Payload key: `hm` / `ob` / `bf` / `op`. + pub key: &'static str, + /// Generated extractor function (unqualified): `eq_term` / `ord_term` / + /// `match_term` / `ord_ope_term`. + pub extractor: &'static str, + /// SEM index-term constructor (unqualified): `hmac_256` / `ore_block_256` / + /// `bloom_filter` / `ope_cllw`. + pub ctor: &'static str, +} + +/// One `jsonb` (SteVec) domain: catalog inventory only — its SQL surface +/// (CHECK, operators) is hand-written and not derivable from the catalog. +#[derive(Serialize)] +pub struct SteVecEntry { + /// The `eql_v3`-relative domain name: `json` / `jsonb_entry` / `jsonb_query`. + pub full_name: String, + /// The catalog domain name: `json` / `entry` / `query`. + pub name: &'static str, + pub terms: Vec, +} + +fn term_infos(terms: &[Term]) -> Vec { + terms + .iter() + .map(|t| TermInfo { + key: t.json_key(), + extractor: t.extractor(), + ctor: t.ctor(), + }) + .collect() } /// Build the catalog surface description from `eql_domains::CATALOG`. @@ -57,6 +100,7 @@ pub fn dump_catalog() -> CatalogDump { format!("_{}", d.name) }, supported_ops: Term::operators_for_terms(d.terms), + terms: term_infos(d.terms), }) .collect(); TypeEntry { @@ -66,7 +110,21 @@ pub fn dump_catalog() -> CatalogDump { } }) .collect(); - CatalogDump { types } + + // The hand-written SteVec (jsonb) family — catalog inventory only. Kept out + // of `types` so scalar-only consumers (the fixture-coverage task) are + // unaffected; the docs manifest reads both `types` and `stevec`. + let stevec = eql_domains::JSONB + .domains + .iter() + .map(|d| SteVecEntry { + full_name: d.full_name(eql_domains::JSONB.name), + name: d.name, + terms: term_infos(d.terms), + }) + .collect(); + + CatalogDump { types, stevec } } #[cfg(test)] @@ -109,6 +167,24 @@ mod tests { assert_eq!(ord_ope.supported_ops, ["=", "<>", "<", "<=", ">", ">="]); } + #[test] + fn ordered_domain_exposes_its_extractor_and_ctor() { + let dump = dump_catalog(); + let integer = dump.types.iter().find(|t| t.token == "integer").unwrap(); + let ord = integer.domains.iter().find(|d| d.segment == "ord").unwrap(); + assert_eq!(ord.terms.len(), 1); + assert_eq!(ord.terms[0].key, "ob"); + assert_eq!(ord.terms[0].extractor, "ord_term"); + assert_eq!(ord.terms[0].ctor, "ore_block_256"); + } + + #[test] + fn stevec_jsonb_family_is_dumped() { + let dump = dump_catalog(); + let names: Vec<&str> = dump.stevec.iter().map(|e| e.full_name.as_str()).collect(); + assert_eq!(names, ["json", "jsonb_entry", "jsonb_query"]); + } + /// Pins the hand-re-derived `suffix` wire field — the one channel with no /// other automated reader — so its underscore-prefixed values stay /// byte-stable after the catalog dropped the leading underscore from its diff --git a/tasks/docs/generate/test_xml_to_json.py b/tasks/docs/generate/test_xml_to_json.py index 0070ac1e1..07d894597 100755 --- a/tasks/docs/generate/test_xml_to_json.py +++ b/tasks/docs/generate/test_xml_to_json.py @@ -24,11 +24,20 @@ CATALOG_JSON = """{ "types": [ { "token": "text", "is_eq_only": false, "domains": [ - { "segment": "storage", "suffix": "", "supported_ops": [] }, - { "segment": "eq", "suffix": "_eq", "supported_ops": ["=", "<>"] }, + { "segment": "storage", "suffix": "", "supported_ops": [], "terms": [] }, + { "segment": "eq", "suffix": "_eq", "supported_ops": ["=", "<>"], + "terms": [{"key": "hm", "extractor": "eq_term", "ctor": "hmac_256"}] }, { "segment": "search", "suffix": "_search", - "supported_ops": ["=", "<>", "<", "<=", ">", ">=", "@>", "<@"] } + "supported_ops": ["=", "<>", "<", "<=", ">", ">=", "@>", "<@"], + "terms": [ + {"key": "hm", "extractor": "eq_term", "ctor": "hmac_256"}, + {"key": "bf", "extractor": "match_term", "ctor": "bloom_filter"} + ] } ]} + ], + "stevec": [ + { "full_name": "json", "name": "json", "terms": [] }, + { "full_name": "jsonb_entry", "name": "entry", "terms": [] } ] }""" @@ -100,13 +109,19 @@ def test_load_domains(): domains = load_domains(cat) by_name = {x["name"]: x for x in domains} + # v3 user domains live in `public`; the extractor functions are `eql_v3`. assert by_name["public.text"]["capabilities"] == ["storage"] assert by_name["public.text_eq"]["type"] == "text" assert by_name["public.text_eq"]["variant"] == "eq" assert by_name["public.text_eq"]["capabilities"] == ["equality"] assert by_name["public.text_eq"]["supportedOperators"] == ["=", "<>"] + assert by_name["public.text_eq"]["termFunctions"] == ["eql_v3.eq_term"] # text_search carries all three capabilities, derived from its operators. assert by_name["public.text_search"]["capabilities"] == ["equality", "order", "match"] + # SteVec (jsonb) domains come from the `stevec` section. + assert "public.json" in by_name + assert by_name["public.jsonb_entry"]["capabilities"] == ["json"] + assert by_name["public.jsonb_entry"]["shape"] == "stevec" if __name__ == "__main__": diff --git a/tasks/docs/generate/xml-to-json.py b/tasks/docs/generate/xml-to-json.py index 25ec2eeac..2d2f2ff3d 100755 --- a/tasks/docs/generate/xml-to-json.py +++ b/tasks/docs/generate/xml-to-json.py @@ -83,6 +83,11 @@ def _capabilities_from_ops(ops): return caps or ["storage"] +def _term_functions(terms): + """Qualified extractor functions for a domain's terms (e.g. eql_v3.ord_term).""" + return [f"eql_v3.{t['extractor']}" for t in terms] + + def load_domains(catalog_path: Path) -> list: """Map `eql-codegen dump-catalog` JSON into manifest domain entries.""" if not catalog_path.exists(): @@ -91,6 +96,8 @@ def load_domains(catalog_path: Path) -> list: catalog = json.loads(catalog_path.read_text()) domains = [] + + # Scalar families: capability + operators + extractor functions. for type_entry in catalog.get("types", []): token = type_entry["token"] for dom in type_entry["domains"]: @@ -105,7 +112,23 @@ def load_domains(catalog_path: Path) -> list: "base": "jsonb", "capabilities": _capabilities_from_ops(ops), "supportedOperators": ops, + "termFunctions": _term_functions(dom.get("terms", [])), }) + + # SteVec (jsonb) family: hand-written SQL, catalog inventory only. Like the + # scalar domains these live in `public`; the extractor functions are eql_v3. + for entry in catalog.get("stevec", []): + domains.append({ + "name": f"public.{entry['full_name']}", + "type": "jsonb", + "variant": "", + "base": "jsonb", + "shape": "stevec", + "capabilities": ["json"], + "supportedOperators": [], + "termFunctions": _term_functions(entry.get("terms", [])), + }) + domains.sort(key=lambda d: (d["type"], d["name"])) return domains From ab7f84e2cccd851d793314f195afc82e3b29a4a9 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Mon, 6 Jul 2026 15:16:02 +1000 Subject: [PATCH 2/4] address review: fix jsonb schema in docs + hardcode ste_vec terms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per @tobyhede's review on #368: - dump.rs doc comments referenced `eql_v3.json` / "eql_v3-relative" — the jsonb domains resolve under `public` (only the extractor *functions* are eql_v3). Corrected the CatalogDump.stevec and SteVecEntry.full_name docs. - The catalog models no per-SteVec-entry terms (JSONB_DOMAINS carry `terms: &[]`, enforced by `shape_and_terms_are_consistent`), so `term_infos(d.terms)` was provably empty and the searchable SteVec family rendered inert (no termFunctions). Hardcode the real hand-written ste_vec extractors for now: `hm` -> eq_term, `oc` -> ore_cllw (src/v3/jsonb/operators.sql). The manifest now links public.json / jsonb_entry / jsonb_query to eql_v3.eq_term / eql_v3.ore_cllw. Operators for the SteVec family remain a follow-up (DB introspection of pg_operator is the reliable source). Claude-Session: https://claude.ai/code/session_01CqDNqLSEEkCi7xAJFq7HJA --- crates/eql-codegen/src/dump.rs | 33 +++++++++++++++++++++---- tasks/docs/generate/test_xml_to_json.py | 14 ++++++++--- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/crates/eql-codegen/src/dump.rs b/crates/eql-codegen/src/dump.rs index 065cf40f3..8089d9136 100644 --- a/crates/eql-codegen/src/dump.rs +++ b/crates/eql-codegen/src/dump.rs @@ -13,9 +13,9 @@ use serde::Serialize; #[derive(Serialize)] pub struct CatalogDump { pub types: Vec, - /// The `jsonb` (SteVec) family — `eql_v3.json` / `jsonb_entry` / `jsonb_query`. - /// Their SQL is hand-written under `src/v3/jsonb/`; the catalog owns only - /// their inventory (scalar-only consumers ignore this field). + /// The `jsonb` (SteVec) family — `public.json` / `public.jsonb_entry` / + /// `public.jsonb_query`. Their SQL is hand-written under `src/v3/jsonb/`; the + /// catalog owns only their inventory (scalar-only consumers ignore this field). pub stevec: Vec, } @@ -63,7 +63,8 @@ pub struct TermInfo { /// (CHECK, operators) is hand-written and not derivable from the catalog. #[derive(Serialize)] pub struct SteVecEntry { - /// The `eql_v3`-relative domain name: `json` / `jsonb_entry` / `jsonb_query`. + /// The bare domain name (resolved under the `public` schema, like a scalar's + /// `integer_eq`): `json` / `jsonb_entry` / `jsonb_query`. pub full_name: String, /// The catalog domain name: `json` / `entry` / `query`. pub name: &'static str, @@ -81,6 +82,22 @@ fn term_infos(terms: &[Term]) -> Vec { .collect() } +/// Index terms for the `jsonb` (SteVec) family, hardcoded for now. +/// +/// The catalog does not model per-SteVec-entry terms — `JSONB_DOMAINS` declare +/// `terms: &[]` and the `shape_and_terms_are_consistent` invariant fails CI if a +/// non-`Scalar` domain ever gains one — so `term_infos(d.terms)` is provably +/// empty here, which would render the searchable SteVec family inert in the +/// manifest. Until the catalog carries them, source the real hand-written +/// extractors (`src/v3/jsonb/operators.sql`): every sv entry carries `hm` +/// (hash-equality, `eql_v3.eq_term`) or `oc` (CLLW-ORE ordering, `eql_v3.ore_cllw`). +fn stevec_terms() -> Vec { + vec![ + TermInfo { key: "hm", extractor: "eq_term", ctor: "hmac_256" }, + TermInfo { key: "oc", extractor: "ore_cllw", ctor: "ore_cllw" }, + ] +} + /// Build the catalog surface description from `eql_domains::CATALOG`. pub fn dump_catalog() -> CatalogDump { let types = eql_domains::scalar_families() @@ -120,7 +137,8 @@ pub fn dump_catalog() -> CatalogDump { .map(|d| SteVecEntry { full_name: d.full_name(eql_domains::JSONB.name), name: d.name, - terms: term_infos(d.terms), + // Catalog terms are empty for SteVec (see stevec_terms); hardcode. + terms: stevec_terms(), }) .collect(); @@ -183,6 +201,11 @@ mod tests { let dump = dump_catalog(); let names: Vec<&str> = dump.stevec.iter().map(|e| e.full_name.as_str()).collect(); assert_eq!(names, ["json", "jsonb_entry", "jsonb_query"]); + + // The (hardcoded) SteVec extractors are surfaced, not left empty. + let entry = &dump.stevec[0]; + let extractors: Vec<&str> = entry.terms.iter().map(|t| t.extractor).collect(); + assert_eq!(extractors, ["eq_term", "ore_cllw"]); } /// Pins the hand-re-derived `suffix` wire field — the one channel with no diff --git a/tasks/docs/generate/test_xml_to_json.py b/tasks/docs/generate/test_xml_to_json.py index 07d894597..de307e044 100755 --- a/tasks/docs/generate/test_xml_to_json.py +++ b/tasks/docs/generate/test_xml_to_json.py @@ -36,8 +36,14 @@ ]} ], "stevec": [ - { "full_name": "json", "name": "json", "terms": [] }, - { "full_name": "jsonb_entry", "name": "entry", "terms": [] } + { "full_name": "json", "name": "json", "terms": [ + {"key": "hm", "extractor": "eq_term", "ctor": "hmac_256"}, + {"key": "oc", "extractor": "ore_cllw", "ctor": "ore_cllw"} + ] }, + { "full_name": "jsonb_entry", "name": "entry", "terms": [ + {"key": "hm", "extractor": "eq_term", "ctor": "hmac_256"}, + {"key": "oc", "extractor": "ore_cllw", "ctor": "ore_cllw"} + ] } ] }""" @@ -118,10 +124,12 @@ def test_load_domains(): assert by_name["public.text_eq"]["termFunctions"] == ["eql_v3.eq_term"] # text_search carries all three capabilities, derived from its operators. assert by_name["public.text_search"]["capabilities"] == ["equality", "order", "match"] - # SteVec (jsonb) domains come from the `stevec` section. + # SteVec (jsonb) domains come from the `stevec` section, with hardcoded + # extractors (hm -> eq_term, oc -> ore_cllw) so the family isn't inert. assert "public.json" in by_name assert by_name["public.jsonb_entry"]["capabilities"] == ["json"] assert by_name["public.jsonb_entry"]["shape"] == "stevec" + assert by_name["public.jsonb_entry"]["termFunctions"] == ["eql_v3.eq_term", "eql_v3.ore_cllw"] if __name__ == "__main__": From f1b110d0bc7c034849e6917b0ca830f5949e6b40 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Mon, 6 Jul 2026 15:34:14 +1000 Subject: [PATCH 3/4] style: rustfmt stevec_terms() struct literals The inline TermInfo { .. } literals added in the review-response commit tripped cargo fmt --check (Rust workspace crates CI). Expand them to the one-field-per-line form rustfmt wants. Claude-Session: https://claude.ai/code/session_01CqDNqLSEEkCi7xAJFq7HJA --- crates/eql-codegen/src/dump.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/eql-codegen/src/dump.rs b/crates/eql-codegen/src/dump.rs index 8089d9136..7eb710d1f 100644 --- a/crates/eql-codegen/src/dump.rs +++ b/crates/eql-codegen/src/dump.rs @@ -93,8 +93,16 @@ fn term_infos(terms: &[Term]) -> Vec { /// (hash-equality, `eql_v3.eq_term`) or `oc` (CLLW-ORE ordering, `eql_v3.ore_cllw`). fn stevec_terms() -> Vec { vec![ - TermInfo { key: "hm", extractor: "eq_term", ctor: "hmac_256" }, - TermInfo { key: "oc", extractor: "ore_cllw", ctor: "ore_cllw" }, + TermInfo { + key: "hm", + extractor: "eq_term", + ctor: "hmac_256", + }, + TermInfo { + key: "oc", + extractor: "ore_cllw", + ctor: "ore_cllw", + }, ] } From e00880a0e682820af5e75465285314ec1c8fe50c Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Mon, 6 Jul 2026 16:22:25 +1000 Subject: [PATCH 4/4] fix(dump): scope SteVec terms to jsonb_entry only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hardcoded hm/oc terms were stamped onto all three SteVec domains, but per src/v3/jsonb/{functions,operators}.sql the eq_term/ore_cllw extractors take public.jsonb_entry (the sv *element* type) exclusively: - eq_term(jsonb_entry) -> = / <> (coalesce(hm, oc)) - ore_cllw(jsonb_entry) -> < <= > >= (oc) The public.json container and public.jsonb_query domains carry no term extractors — their surface is containment (@>, <@) and path navigation. stevec_terms() now keys on the catalog domain name and returns terms only for `entry`; json/query resolve to []. Tests assert the per-domain split. Claude-Session: https://claude.ai/code/session_01CqDNqLSEEkCi7xAJFq7HJA --- crates/eql-codegen/src/dump.rs | 52 +++++++++++++++++++------ tasks/docs/generate/test_xml_to_json.py | 16 ++++---- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/crates/eql-codegen/src/dump.rs b/crates/eql-codegen/src/dump.rs index 7eb710d1f..fb6248d33 100644 --- a/crates/eql-codegen/src/dump.rs +++ b/crates/eql-codegen/src/dump.rs @@ -68,6 +68,9 @@ pub struct SteVecEntry { pub full_name: String, /// The catalog domain name: `json` / `entry` / `query`. pub name: &'static str, + /// Index terms for this SteVec domain. Non-empty only for `jsonb_entry` + /// (the sv element type); the `json` container and `jsonb_query` domains + /// carry no term extractors — see `stevec_terms`. pub terms: Vec, } @@ -82,16 +85,24 @@ fn term_infos(terms: &[Term]) -> Vec { .collect() } -/// Index terms for the `jsonb` (SteVec) family, hardcoded for now. +/// Index terms for one `jsonb` (SteVec) domain, hardcoded for now. /// /// The catalog does not model per-SteVec-entry terms — `JSONB_DOMAINS` declare /// `terms: &[]` and the `shape_and_terms_are_consistent` invariant fails CI if a /// non-`Scalar` domain ever gains one — so `term_infos(d.terms)` is provably -/// empty here, which would render the searchable SteVec family inert in the -/// manifest. Until the catalog carries them, source the real hand-written -/// extractors (`src/v3/jsonb/operators.sql`): every sv entry carries `hm` -/// (hash-equality, `eql_v3.eq_term`) or `oc` (CLLW-ORE ordering, `eql_v3.ore_cllw`). -fn stevec_terms() -> Vec { +/// empty here. Until the catalog carries them, source the real hand-written +/// extractors from `src/v3/jsonb/{functions,operators}.sql`. +/// +/// Terms live on `jsonb_entry` — the sv *element* type — ONLY: `eql_v3.eq_term` +/// reads `coalesce(hm, oc)` for `=`/`<>`, and `eql_v3.ore_cllw` reads `oc` for +/// `<`/`<=`/`>`/`>=`. The `json` container and `jsonb_query` domains carry no +/// term extractors (their surface is containment `@>`/`<@` and path navigation), +/// so they return no terms. Keyed on the catalog domain name (`json`/`entry`/ +/// `query`). +fn stevec_terms(name: &str) -> Vec { + if name != "entry" { + return Vec::new(); + } vec![ TermInfo { key: "hm", @@ -145,8 +156,9 @@ pub fn dump_catalog() -> CatalogDump { .map(|d| SteVecEntry { full_name: d.full_name(eql_domains::JSONB.name), name: d.name, - // Catalog terms are empty for SteVec (see stevec_terms); hardcode. - terms: stevec_terms(), + // Catalog terms are empty for SteVec; hardcode per-domain — only + // `jsonb_entry` carries extractors (see stevec_terms). + terms: stevec_terms(d.name), }) .collect(); @@ -210,10 +222,26 @@ mod tests { let names: Vec<&str> = dump.stevec.iter().map(|e| e.full_name.as_str()).collect(); assert_eq!(names, ["json", "jsonb_entry", "jsonb_query"]); - // The (hardcoded) SteVec extractors are surfaced, not left empty. - let entry = &dump.stevec[0]; - let extractors: Vec<&str> = entry.terms.iter().map(|t| t.extractor).collect(); - assert_eq!(extractors, ["eq_term", "ore_cllw"]); + let by_name = |n: &str| { + dump.stevec + .iter() + .find(|e| e.full_name == n) + .unwrap_or_else(|| panic!("{n} present")) + }; + + // Term extractors live on `jsonb_entry` (the sv element type) ONLY: + // `eq_term` (hm/oc equality) + `ore_cllw` (oc ordering). + let entry_extractors: Vec<&str> = by_name("jsonb_entry") + .terms + .iter() + .map(|t| t.extractor) + .collect(); + assert_eq!(entry_extractors, ["eq_term", "ore_cllw"]); + + // The `json` container and `jsonb_query` domains carry no term + // extractors — their surface is containment (@>, <@) and path nav. + assert!(by_name("json").terms.is_empty()); + assert!(by_name("jsonb_query").terms.is_empty()); } /// Pins the hand-re-derived `suffix` wire field — the one channel with no diff --git a/tasks/docs/generate/test_xml_to_json.py b/tasks/docs/generate/test_xml_to_json.py index de307e044..52930da2a 100755 --- a/tasks/docs/generate/test_xml_to_json.py +++ b/tasks/docs/generate/test_xml_to_json.py @@ -36,14 +36,12 @@ ]} ], "stevec": [ - { "full_name": "json", "name": "json", "terms": [ - {"key": "hm", "extractor": "eq_term", "ctor": "hmac_256"}, - {"key": "oc", "extractor": "ore_cllw", "ctor": "ore_cllw"} - ] }, + { "full_name": "json", "name": "json", "terms": [] }, { "full_name": "jsonb_entry", "name": "entry", "terms": [ {"key": "hm", "extractor": "eq_term", "ctor": "hmac_256"}, {"key": "oc", "extractor": "ore_cllw", "ctor": "ore_cllw"} - ] } + ] }, + { "full_name": "jsonb_query", "name": "query", "terms": [] } ] }""" @@ -124,9 +122,11 @@ def test_load_domains(): assert by_name["public.text_eq"]["termFunctions"] == ["eql_v3.eq_term"] # text_search carries all three capabilities, derived from its operators. assert by_name["public.text_search"]["capabilities"] == ["equality", "order", "match"] - # SteVec (jsonb) domains come from the `stevec` section, with hardcoded - # extractors (hm -> eq_term, oc -> ore_cllw) so the family isn't inert. - assert "public.json" in by_name + # SteVec (jsonb) domains come from the `stevec` section. Term extractors are + # hardcoded on `jsonb_entry` (the sv element type) ONLY — hm -> eq_term, + # oc -> ore_cllw; the `json` container and `jsonb_query` carry none. + assert by_name["public.json"]["termFunctions"] == [] + assert by_name["public.jsonb_query"]["termFunctions"] == [] assert by_name["public.jsonb_entry"]["capabilities"] == ["json"] assert by_name["public.jsonb_entry"]["shape"] == "stevec" assert by_name["public.jsonb_entry"]["termFunctions"] == ["eql_v3.eq_term", "eql_v3.ore_cllw"]