searchQueryBuilder still emits the pre-NIP-AMB ext path shape (ext.30168.<pub>.<d>.<field>) #54

Closed
opened 2026-07-30 05:41:00 +00:00 by laoc · 0 comments
Owner

Summary

src/lib/helpers/educational/searchQueryBuilder.js still builds ext filters from the old kind-coordinate path shape (ext.30168.<pub>.<d>.<fieldId> / #ext:30168:<pub>:<d>:<fieldId>). The read and write paths were migrated to the conformant NIP-AMB grammar ext:<ns>:<facet>[:sub]; the search/filter path was not. A filter this builder produces can therefore never match a tag the write path produces.

Where

Two helpers and their doc comments:

  • searchQueryBuilder.js:46-47extPathToSearchPath()`ext.${extKey.replace(/:/g, '.')}`
  • searchQueryBuilder.js:57-58extPathToTagKey()`ext:${extKey}`
  • Doc comments asserting the stale shape: lines 20, 42, 53

Call sites: searchQueryBuilder.js:120 (NIP-50 search string) and :151 (multi-letter #ext:... tag filters).

Why it's wrong

The conformant grammar, per parseTagKey() in src/lib/helpers/educational/parseExtensionTags.js:151-171, is left-anchored and namespace-based:

ext:<ns>:<facet>          -> bare scalar
ext:<ns>:<facet>:<sub>    -> e.g. :id for concept values

ekwNamespace.js:27-32 documents the same shape and the target serialization, e.g. ext:org.edufeed.ekw.konfi:<slug>:id.

Feed the builder's key through that parser and the segments land in the wrong slots:

ext:30168:aaa:amb-full:interactivityType
   ns    = "30168"
   facet = "aaa"
   sub   = "amb-full:interactivityType"

So the filter targets namespace 30168, facet aaa — neither of which exists in the data.

Severity: latent today, but the tests lock the bug in

The emission path is reachable in production — src/lib/loaders/amb-search.js:42 and :78 both call buildSearchFilterObject(). But filters.extFields is never populated outside tests: grepping extFields across src/ returns hits only in searchQueryBuilder.js and src/lib/__tests__/searchQueryBuilder.test.js. Nothing in urlParams.js or the route components sets it.

So no user-visible search is broken right now. The real risk is that src/lib/__tests__/searchQueryBuilder.test.js:25-60 asserts the stale shape as correct — e.g.

const extKey = '30168:aaa:amb-full:interactivityType';
expect(q).toBe(`ext.30168.${pubkey}.amb-full.interactivityType.id:...`);

Whoever next wires ext facets into the filter UI will inherit the wrong shape with a green suite behind it.

Suggested fix

  1. Rekey filters.extFields on <ns>:<facet> instead of 30168:<pub>:<d>:<fieldId>.
  2. extPathToTagKey()ext:<ns>:<facet> (+ :id for concept values, which :151-160 already appends).
  3. Decide what extPathToSearchPath() should emit, or drop it — see the note below.
  4. Update searchQueryBuilder.test.js to assert the conformant shape, and the doc comments at lines 20 / 42 / 53.

Note on the dot-path (search) half

Worth an explicit decision rather than a mechanical rewrite. With reverse-DNS namespaces a dot path is ambiguous: ext.org.edufeed.ekw.konfi.themen.id gives no way to know where <ns> ends and <facet> begins. The multi-letter tag filter (#ext:org.edufeed.ekw.konfi:themen:id) is exact-match and unambiguous, because the event literally carries that key. Recommendation: make the #ext: tag filter the primary mechanism and treat the dot path as optional/removable.

Relay-side context: the ext grammar parser lives in nostrlib eventstore/typesense30142/nostr_amb.go and now indexes bare scalar ext:<ns>:<facet> tags (nostrlib edufeed @ 99dfee4, deployed via amb-relay main @ a07fb3a).


Found while designing the WordPress AMB plugin's facet filters (channel #edufeed-wordpress-integration), which deliberately does not mirror this builder for the reasons above.

## Summary `src/lib/helpers/educational/searchQueryBuilder.js` still builds ext filters from the **old kind-coordinate path shape** (`ext.30168.<pub>.<d>.<fieldId>` / `#ext:30168:<pub>:<d>:<fieldId>`). The read and write paths were migrated to the conformant NIP-AMB grammar `ext:<ns>:<facet>[:sub]`; the **search/filter path was not**. A filter this builder produces can therefore never match a tag the write path produces. ## Where Two helpers and their doc comments: - `searchQueryBuilder.js:46-47` — `extPathToSearchPath()` → `` `ext.${extKey.replace(/:/g, '.')}` `` - `searchQueryBuilder.js:57-58` — `extPathToTagKey()` → `` `ext:${extKey}` `` - Doc comments asserting the stale shape: lines `20`, `42`, `53` Call sites: `searchQueryBuilder.js:120` (NIP-50 `search` string) and `:151` (multi-letter `#ext:...` tag filters). ## Why it's wrong The conformant grammar, per `parseTagKey()` in `src/lib/helpers/educational/parseExtensionTags.js:151-171`, is left-anchored and namespace-based: ``` ext:<ns>:<facet> -> bare scalar ext:<ns>:<facet>:<sub> -> e.g. :id for concept values ``` `ekwNamespace.js:27-32` documents the same shape and the target serialization, e.g. `ext:org.edufeed.ekw.konfi:<slug>:id`. Feed the builder's key through that parser and the segments land in the wrong slots: ``` ext:30168:aaa:amb-full:interactivityType ns = "30168" facet = "aaa" sub = "amb-full:interactivityType" ``` So the filter targets namespace `30168`, facet `aaa` — neither of which exists in the data. ## Severity: latent today, but the tests lock the bug in The emission path *is* reachable in production — `src/lib/loaders/amb-search.js:42` and `:78` both call `buildSearchFilterObject()`. But `filters.extFields` is never populated outside tests: grepping `extFields` across `src/` returns hits only in `searchQueryBuilder.js` and `src/lib/__tests__/searchQueryBuilder.test.js`. Nothing in `urlParams.js` or the route components sets it. So no user-visible search is broken right now. The real risk is that **`src/lib/__tests__/searchQueryBuilder.test.js:25-60` asserts the stale shape as correct** — e.g. ```js const extKey = '30168:aaa:amb-full:interactivityType'; expect(q).toBe(`ext.30168.${pubkey}.amb-full.interactivityType.id:...`); ``` Whoever next wires ext facets into the filter UI will inherit the wrong shape with a green suite behind it. ## Suggested fix 1. Rekey `filters.extFields` on `<ns>:<facet>` instead of `30168:<pub>:<d>:<fieldId>`. 2. `extPathToTagKey()` → `ext:<ns>:<facet>` (+ `:id` for concept values, which `:151-160` already appends). 3. Decide what `extPathToSearchPath()` should emit, or drop it — see the note below. 4. Update `searchQueryBuilder.test.js` to assert the conformant shape, and the doc comments at lines 20 / 42 / 53. ## Note on the dot-path (`search`) half Worth an explicit decision rather than a mechanical rewrite. With reverse-DNS namespaces a dot path is ambiguous: `ext.org.edufeed.ekw.konfi.themen.id` gives no way to know where `<ns>` ends and `<facet>` begins. The multi-letter tag filter (`#ext:org.edufeed.ekw.konfi:themen:id`) is exact-match and unambiguous, because the event literally carries that key. Recommendation: make the `#ext:` tag filter the primary mechanism and treat the dot path as optional/removable. Relay-side context: the ext grammar parser lives in nostrlib `eventstore/typesense30142/nostr_amb.go` and now indexes bare scalar `ext:<ns>:<facet>` tags (nostrlib `edufeed` @ `99dfee4`, deployed via amb-relay `main` @ `a07fb3a`). --- Found while designing the WordPress AMB plugin's facet filters (channel `#edufeed-wordpress-integration`), which deliberately does not mirror this builder for the reasons above.
laoc closed this issue 2026-07-30 10:31:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
edufeed/edufeed-app#54
No description provided.