Skip to content

Feature: Query Adapter Seam#91

Merged
jhweir merged 3 commits into
devfrom
feat/query-adapter-seam
Jul 19, 2026
Merged

Feature: Query Adapter Seam#91
jhweir merged 3 commits into
devfrom
feat/query-adapter-seam

Conversation

@jhweir

@jhweir jhweir commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Inject the backend query adapter + finish query-plane neutrality (feat/query-adapter-seamdev)

Summary

Two AD4M couplings remained in the query/render plane after the neutral-vocab work, and this branch
removes both. First, the renderer hardcoded the IR→backend lowering (compileQuery → irToAd4mQuery
imported directly in SchemaRenderer), so schema-solid was silently AD4M-bound. Second, one template
still embedded a raw AD4M protocol identifier in a query (parent: { id, predicate: 'ad4m://has_child' }).

This branch injects a real QueryAdapter port that the host provides — the renderer routes every
query through it and imports no backend — and neutralizes the drill-down to a backend-agnostic scope
that the adapter resolves. Along the way it finishes the job the vocab sweep deliberately scoped to the
authoring surface: the internal FlatQuery dialect is now neutral too (modelentity; parent/
perspective removed). After this, no model/perspective/parent/ad4m:// remains anywhere in the
query layer
— authored or internal.

A correction to the earlier framing is baked in: irToAd4mQuery turned out to emit only the neutral flat
$query dialect (no AD4M predicates), so it was misnamed — it's renamed irToFlatQuery and stays in
@we/schema-shared. Nothing AD4M-specific "moved"; what's new in app-framework is the adapter object
that composes the neutral pieces with AD4M's capability profile.

The existing AD4M app is unaffected: the IR path runs behind seed.features.useQueryIR (now on), native
queries are verified identical, and gaps fall back to the direct path.

Changes

The QueryAdapter port + injection

  • dataSource.ts — new QueryAdapter interface ({ capabilities, plan(ir), lower(ir) }) + $queryAdapter
    on RendererDataBindings. SchemaRenderer routes compileQuery → plan → lower through the injected
    adapter; it no longer imports irToAd4mQuery/ad4mCapabilities.
  • ad4mAdapter.tscreateAd4mQueryAdapter(getModels) composes ad4mCapabilities + planQuery + the
    neutral irToFlatQuery, and folds in AD4M's two conditional degradations no capability boolean can
    express (OR/AND/NOT disables the sort/pagination pushdown; a projection/relation-path sort needs a
    limit). This makes planQuery live — it was dead code before.
  • TemplateProvider injects the adapter; the in-memory harness ships its own AD4M-free QueryAdapter,
    so its flag-on test exercises the same renderer path.
  • irToAd4mQueryirToFlatQuery (neutral; stays in schema-shared).

Neutral drill-down (scope) — Tier-2 adapter-rewrite

  • Authoring grammar: scope: { anchor, via, anchorId } added to QueryToken + zQuery; compileQuery
    passes it to the IR's existing Scope.
  • ad4mQueryAdapter.lower resolves scope.via on scope.anchor to the relation's RDF predicate (from
    the perspective's model manifest, ModelManifestProperty.predicate) and emits AD4M's parent: { id, predicate }. This sidesteps AD4M's broken resolveParentPredicate by handing it the predicate form
    directly — so no @coasys/ad4m change and no second backend needed. The adapter is a factory closing
    over getModels for exactly this lookup.
  • FluxConversationsNestedList migrated from the raw predicate to scope — no ad4m:// in the template.

FlatQuery fully neutral

  • modelentity (compiler + renderer + adapter + harness + tests).
  • parent removed (superseded by scope; the adapter still emits it internally as AD4M's contract).
  • perspective removed (dead — compileQuery dropped it; the dataset handle is injected).

Seam-binding cleanups

  • $me: adamStore.me (mirroring $currentDataset), seam typed neutrally (() => unknown, not AD4M's
    Agent); the hand-rolled identity-assembly function is gone.
  • The adamStore.currentPerspective fallback removed from both renderer query paths (both now read the
    injected $currentDataset); queryToken test fixtures migrated accordingly.

AI-context — the drill-down docs teach scope instead of parent; regenerated CLAUDE.md /
copilot-instructions / cursor rules / schemaContext.ts.

Tests — new ad4mAdapter.test.ts (the two conditional degradations + scope→predicate resolution +
surfaced-error path); compileQuery/corpus updated for scope and entity; queryToken fixtures on
$currentDataset.

Known follow-ups (out of scope)

  • Browser-verify the scope drill-down (Flux nested conversations, flag on). The spike proved the
    predicate is in the manifest, but the resolution runs against a synced dynamic Flux model, so only live
    testing confirms it end-to-end. On failure the adapter throws a clear error (surfaced, not silent).
  • Compute-up is deferred by decision — fail-loud (author-time validation) is the intended mechanism for
    genuine capability gaps; the current gap-fallback is an AD4M-phase punt, not a general disposition (a
    second backend forces compute-up or fail-loud).
  • The renderer still duck-types the injected bindings ((stores as Record<string, unknown>).$X); typing
    it against the declared RendererDataBindings/QueryAdapter is a candidate next increment.
  • The adamStore $store/$action bindings (store/shell decoupling, Phase 6) and the mutation-API
    perspective/parent options (the write path) are the remaining AD4M coupling — both consciously deferred.

Test plan

  • @we/schema-shared443 (queryCompiler incl. scope, dataSource port, engine, capabilities)
  • @we/schema-solid34 (renderer over the injected adapter; $currentDataset fixtures)
  • portable-ui-slice harness — 7 (renders identically with an AD4M-free QueryAdapter, flag on/off)
  • @we/app-framework60 (+10 todo) — ad4mAdapter plan degradations + scope resolution, corpus
  • tsc --noEmit clean — schema-shared, schema-solid, app-framework, harness, ai-context
  • Schema validation pnpm --filter @we/schema-shared validate0 errors (template on scope)
  • ESLint clean on all changed files
  • Swept: no model:/perspective:/parent: query keys remain; no raw ad4m:// in any template query
  • Manual: Flux nested-conversations drill-down renders the right subgroups with the flag on

Not committed — scratch PR description per repo convention.

jhweir and others added 3 commits July 19, 2026 17:43
… hardcodes AD4M

Correction to the earlier framing: `irToAd4mQuery` emitted only the neutral flat `$query` dialect
(no AD4M predicates) — it was misnamed, and the genuine AD4M coupling (the `ModelClass` ORM) is
already injected via `$getModel`. So the fix isn't "move a file" — it's stop the renderer from
hardcoding the IR→backend composition and inject a real adapter that owns the AD4M-specific pieces.

- Rename `irToAd4mQuery` → `irToFlatQuery` (schema-shared) — it's the neutral inverse of
  `compileQuery`, consumed by any `ModelClass` ORM (AD4M's, the in-memory harness's).
- New `QueryAdapter` port (dataSource.ts): `{ capabilities, plan(ir), lower(ir) }`. Added to
  `RendererDataBindings` as `$queryAdapter`. The renderer routes `compileQuery → plan → lower`
  through the injected adapter and knows nothing about any backend.
- `ad4mQueryAdapter` (app-framework) composes the neutral pieces with AD4M's profile: `plan` is
  `planQuery(ir, ad4mCapabilities)` PLUS the two conditional degradations no capability boolean can
  express — OR/AND/NOT in `where` disables AD4M's sort/pagination pushdown, and a projection/
  relation-path sort needs a `limit`. This finally makes `planQuery` live (it was dead code).
- The in-memory harness gets its own AD4M-free `QueryAdapter`, so its flag-on test exercises the
  same renderer path the AD4M adapter does.
- Drop the `adamStore.currentPerspective` fallback in the renderer (both query paths) now that
  `$currentDataset` is injected — no AD4M store reference left in schema-solid's data path. Migrated
  the queryToken test fixtures to inject `$currentDataset`.
- Simplify the `$me` binding to `$me: adamStore.me` (mirroring `$currentDataset:
  adamStore.currentPerspective`) instead of a hand-rolled identity-assembly function. The seam type
  stays neutral (`() => unknown`, not AD4M's `Agent`); templates read `$me.did`.

Increment 1 of 2: the adapter is wired and gaps are correctly classified, but a gap still falls back
to the direct backend path (logged). Increment 2 runs compute-up (executeQueryIR) for the gaps.

schema-shared 443 · schema-solid 34 · harness 7 · app-framework 58 green; tsc + lint clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…trality

Removes the last AD4M-specific vocabulary from the query/template layer.

**Drill-down → neutral `scope`.** The one template with a raw AD4M predicate
(`parent: { id, predicate: 'ad4m://has_child' }`) now authors `scope: { anchor, via, anchorId }`.
The pipeline: `scope` added to the authoring grammar (QueryToken + zQuery) → `compileQuery` passes it
to the IR's existing `Scope` → the AD4M adapter's `lower` resolves `via` on `anchor` to the relation's
predicate (from the perspective's model manifest, `ModelManifestProperty.predicate`) and emits AD4M's
`parent: { id, predicate }` — the Tier-2 "adapter-rewrite". This also sidesteps AD4M's own broken
relation-name resolver (`resolveParentPredicate`) by handing it the predicate form directly, so **no
AD4M-repo change and no second backend needed**. `ad4mQueryAdapter` becomes a factory closing over the
perspective's model entries (`createAd4mQueryAdapter(getModels)`).

**Removed the vestigial AD4M fields from the internal `FlatQuery` dialect** — the earlier vocab sweep
neutralized the authoring surface + templates but deliberately left `FlatQuery` as the flat "ORM shape":
- `parent` — superseded by `scope`; the adapter still emits it internally (AD4M's contract).
- `perspective` — dead (`compileQuery` dropped it; the dataset handle is injected).
- `model` → `entity` — aligns the flat dialect with the neutral surface end-to-end
  (compiler + renderer + adapter + harness + tests).

Swept the codebase: no `model:`/`perspective:`/`parent:` query keys remain (the surviving `perspective`
is the mutation-API option, a separate deferred write-path concern).

schema-shared 443 · schema-solid 34 · harness 7 · app-framework 60 green; tsc + lint clean; schemas
validate (0 errors); no raw AD4M predicate in any template query. ai-context regen follows.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Update the drill-down docs so AI-authored templates emit the neutral `scope: { anchor, via, anchorId }`
instead of the removed `parent: { id, relation/predicate }`; regenerate CLAUDE.md, copilot-instructions,
cursor rules, and schemaContext.ts.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@netlify

netlify Bot commented Jul 19, 2026

Copy link
Copy Markdown

Deploy Preview for coasys-we ready!

Name Link
🔨 Latest commit 328d388
🔍 Latest deploy log https://app.netlify.com/projects/coasys-we/deploys/6a5d301264b6c40008c57d98
😎 Deploy Preview https://deploy-preview-91--coasys-we.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@jhweir
jhweir merged commit 84812ab into dev Jul 19, 2026
3 of 5 checks passed
@jhweir jhweir changed the title Feat/query adapter seam Feature: Query Adapter Seam Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant