@omgbase/core 0.29.0 → 0.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,7 +53,7 @@ sweep.ingested; // ["todo.md"]
53
53
 
54
54
  // Query with OQX. Nodes are projections of blocks; block_id is the anchor block.
55
55
  const open = oqxRun(ws.store, repoId,
56
- 'select block: block_id from nodes where kind == "md:task" && !attrs.checked');
56
+ 'nodes { select block: block_id where kind == "md:task" && !attrs.checked }');
57
57
  open.hits; // [{ id: "n_…", path: "/todo.md", block: "b_…" }]
58
58
 
59
59
  // Read a whole document back byte-exact, with block ids + CAS hashes.
@@ -137,16 +137,18 @@ server in your own process with `buildServer(ctx)` and any MCP SDK transport.
137
137
  OQX is the standalone [`@omgbase/oqx`](https://www.npmjs.com/package/@omgbase/oqx)
138
138
  engine bound to the store (`src/oqx-js/`): omgbase supplies a `DataContext` over
139
139
  `docs`/`blocks`/`nodes`/`edges` and a SQLite pushdown planner, and the library
140
- supplies the language (`select … from … where … follow … order by … limit … offset …`, in that fixed order,
140
+ supplies the language (`<collection> { select … where … follow … order by … limit … offset … }` — the collection heads the body, the clauses inside it in that fixed order —
141
141
  consumers `collect/count/exists/none/first/single`, nested blocks, `^` outer
142
142
  references, `entries()`, `values`). Row-scoped predicates like `text("…")` (FTS)
143
143
  and `semantic("…")` (embedding score) are omgbase additions.
144
144
 
145
145
  ```js
146
146
  oqxRun(ws.store, repoId, `
147
- select path, title, open: nodes collect { select block_id where kind == "md:task" && !attrs.checked }
148
- from docs where nodes count { where kind == "md:task" && !attrs.checked } >= 2
149
- order by path limit 10
147
+ docs {
148
+ select path, title, open: nodes { select block_id where kind == "md:task" && !attrs.checked }
149
+ where nodes count { kind == "md:task" && !attrs.checked } >= 2
150
+ order by path limit 10
151
+ }
150
152
  `);
151
153
  // hits: [{ id: "d_…", path: "/more.md", title: "More", open: [{ block_id: "b_…" }, …] }]
152
154
  ```
@@ -3,7 +3,7 @@
3
3
  // ADR-006: OQX `follow` is the ONE traversal engine; the removed
4
4
  // graph_traverse/path/subgraph tools are not resurrected. This macro owns NO
5
5
  // graph-walking logic — it compiles its arguments into an OQX `follow _out_links
6
- // collect { from _dst }` / `follow _in_links collect { from _doc }` query (the
6
+ // { from _dst }` / `follow _in_links { from _doc }` query (the
7
7
  // exact walk the `query` tool exposes over the link rows),
8
8
  // runs it through the shared runner (oqxRunAsync), and then only SHAPES the
9
9
  // walk's own output (its `$depth`/`$stop` intrinsics and projected edges) into
@@ -13,8 +13,8 @@
13
13
  // Mapping (args → follow query):
14
14
  // roots → the seed `where _id == … or …` (refs resolved to doc ids first; literals in the AST, never spliced text)
15
15
  // degrees → `follow … collect { … depth degrees+1 }` (seed is $depth 1, so N hops = depth N+1)
16
- // direction → `follow _out_links collect { from _dst }` (the linked documents)
17
- // | `follow _in_links collect { from _doc }` (the linking documents)
16
+ // direction → `follow _out_links { from _dst }` (the linked documents)
17
+ // | `follow _in_links { from _doc }` (the linking documents)
18
18
  // | both (two walks, unioned)
19
19
  // predicate → restricts the neighborhood to docs reachable via that predicate
20
20
  // (recomputed over the walk's collected edges; no `via` clause)
@@ -77,7 +77,7 @@ function buildUserSelect(select) {
77
77
  function userItems(src, alias) {
78
78
  let q;
79
79
  try {
80
- q = parse(`select ${alias}: ${src} from docs`);
80
+ q = parse(`docs { select ${alias}: ${src} }`);
81
81
  }
82
82
  catch (e) {
83
83
  if (e instanceof OqxError)
@@ -105,8 +105,8 @@ const LINKS_ALIAS = "_links";
105
105
  // The walk, as an AST (spec/oqx/AST.md §7) printed canonically — never spliced
106
106
  // strings: the seed ids are literals, the caller's projections parsed items.
107
107
  //
108
- // select _depth: $depth, _stop: $stop, _links: _<dir>_links collect { <edge fields> } <user items>
109
- // from docs where _id == <root> or … follow distinct _<dir>_links collect { from <_dst | _doc> depth <depth> }
108
+ // docs { _depth: $depth, _stop: $stop, _links: _<dir>_links { <edge fields> } <user items>
109
+ // where _id == <root> or … follow distinct _<dir>_links { from <_dst | _doc> depth <depth> } }
110
110
  //
111
111
  // The destination block walks the link rows to the documents at their other
112
112
  // end (spec/surface §1.2, 3.0 follow-up: there are no `out`/`in` relations —
@@ -1,4 +1,4 @@
1
- export declare const QUERY_SYNTAX = "# query \u2014 OQX syntax reference\n\nThe `query` tool takes ONE plain OQX string (+ optional `limit`/`cursor`, and\n`bare: true` to drop the automatic `_id`/`_path` envelope \u2014 needs a `select`).\nThere is no {from, filter, order} envelope: every concern is a clause of the\nstring. Two rules tell the two syntaxes apart: DOT navigation belongs to the\nhost object model (`_doc.layer`, `_section._blocks`, `_dst._title`); WHITESPACE\nquery directives belong to OQX \u2014 a nested query is `<receiver> collect|exists|\ncount|first|single { <block> }` (a postfix directive + a `{ \u2026 }` block), never a\nmethod. Its distinctive power is the receiver-constrained nested query that\ncorrelates to the current row: `from docs where _nodes exists { where _kind ==\n\"md:task\" }` returns only the docs that themselves contain a matching node.\n\n [select <items>] from <target> [where <pred>] [frontier <pred>] [follow <dest> [{ \u2026 }], \u2026]\n [flatten <expr> [{ \u2026 }] [as <name>]]\u2026 [group by <key>, \u2026] [having <pred>]\n [order by <expr> [asc|desc], \u2026] [limit N] [offset N]\n <target> count|exists|none|first|single { <block> } (scalar/one-row form: a bare target at the root scope)\n\nCLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset \u2014\neach at most once (`flatten` may repeat); an out-of-order clause is a parse error naming the order.\nOnly `select` may drop its keyword, and only as the first clause\n(`_path, era from docs where \u2026`). A top-level predicate needs `where`, because\n`where` follows `from` (`era > 1600 from docs` is the clause-order error); a\nblock may LEAD with a predicate (see Sugar), but a predicate after a projection\nstill needs the keyword (`{ _name where _kind == \"md:task\" }`, not\n`{ _name, _kind == \"md:task\" }`); `from docs count` is an error (write\n`docs count { \u2026 }`). `where` and later `select` items may use the same body's\n`select` aliases (`select _path, old: era < 1000 from docs where old`; an alias\nshadows a same-named field there). `and` / `or` / `not` are the connectives;\nthe symbols `&&` / `||` / `!` are accepted synonyms (the words are canonical and\nwhat `print` writes).\n\n## Sugar (OQX 0.17)\n\nEvery form here is shorthand for an explicit directive (the AST is the explicit form):\n nodes { _kind == \"md:task\" } \u2261 nodes collect { where _kind == \"md:task\" } \u2014 a receiver block with\n no consumer is collect; a block whose LEADING expression is a predicate\n (comparison, call, !/is/not, in, literal, (\u2026), a consumer test \u2014 anything\n but a bare name, a dotted path or a ^lift) is where-first. Names still\n project: _nodes { _name } \u2261 _nodes collect { _name }; filter a bare field with\n _nodes { is checked }. Wherever a value stands (0.20), call arguments included:\n size(_children { is checked }); x distinct { \u2026 } \u2261 x collect distinct { \u2026 }. After a\n follow destination too: follow _children { depth 2 } is that destination's block.\n (a, b) a tuple (0.20): two or more expressions, concatenated one level flat \u2014 a value\n (size((tags, aliases)), \"x\" in (tags, aliases)) and a receiver ((a, b) { \u2026 },\n follow (before, after) { depth 2 }); (a) alone is grouping\n refs(x)[0] \u2261 refs(x) first { offset 0 } positional (integer literal or ${binding});\n out of range \u21D2 absent; refs(x)[0]._path navigates the row\n ^docs[_path == ^company] \u2261 ^docs first { where _path == ^company } first match or absent\n ^docs[_path == ^company]! \u2261 \u2026 single { \u2026 } required: exactly one, else filter_invalid\n _title! required: _title, or an error naming the expression (and the row's $key \u2014 its _id)\n \u2014 never a filter, never a coercion (0!, \"\"! are values); tightest: a!.b vs a.b!\n is x / not x truthiness of x, and its negation (prefix; `not` \u2261 `!`)\n x is y / x is not y identity ($key: a store row's id, else structural) \u2014 compares rows, which == does not;\n x is null = absent; for scalars is \u2261 ==; comparison precedence, no chaining\n a and b / a or b the connectives (\u2261 && / ||: same precedence, short-circuit, value: title or _title coalesces)\n boss: ^docs[_path == ^manager], bossName: boss._title a select item may use the items to its LEFT\n Reserved words (never a bare field name): from where select is not and or, true false null.\n Contextual words (limit order offset follow collect count first distinct desc by in frontier depth group having\n flatten as) parse as fields in expression position (select o: order from docs).\n\nResults are LEAN hits \u2014 { _id, _path } + whatever `select` projects (or `values`,\n`count`, `exists`, `none`). The envelope keys ARE the attributes: copy a hit's\n`_id` straight into `where _id == \"\u2026\"` (no translation); `select _id, _path` is\nsimply the envelope (no duplicate columns); an alias `_id:`/`_path:` on any OTHER\nexpression is refused \u2014 `select _path: _doc._path from blocks` fails (\"projection\n`_path` collides with the hit envelope \u2014 alias it (`p: _doc._path`)\"); a row\nsurfacing as a value (an empty `collect { }`) is the same `{ _id, _path }`. Every\nother tool spells a row the same way (`docs_read` \u2192 `{ _id, _path, rev, properties,\n_raw }`, `blocks_read` \u2192 `{ _id, _type, _text, _raw, _hash, \u2026 }`, `docs_list` \u2192 `{ _id,\n_path, _updated_at, blocks }`, a revision \u2192 `{ _id, _path, _hash }`): hydrate by `_id`\nvia blocks_read/docs_read.\n\n## Paths\n\nEvery path the surface returns is `/`-rooted \u2014 the form a reference is\nwritten in (`[x](/projects/oqx.md)`, `before: [/timeline/kickoff.md]`): a\nhit's `_path`, an edge's `_dst_path`, every tool's paths. Every path it\naccepts tolerates both forms (`/a.md` or `a.md`): tool arguments, `refs(x)`,\n`within(...)`, and a string LITERAL compared with `_path`/`_dst_path` (on the row,\nor through a row handle: `_doc._path`, `_dst._path`, `$it._path`) by `==`/`!=` or\npassed to their `.startsWith(...)` (it is rooted first, so `_path == \"a.md\"` and\n`_path == \"/a.md\"` both match). A property holding a reference compares directly \u2014\nand is never rooted: `where ^_path in list(after)`, `where customer == ^_path`,\n`where path == \"/x.md\"` (YOUR authored `path:` key \u2014 the bare name \u2014 compared as\nwritten) \u2014 no `\"/\" + ...` glue.\n\n## Targets & field namespaces\n\nTHE NAME RULE \u2014 one rule on every record kind: a BARE name is YOURS (an authored\nproperty: frontmatter + inline on a doc, an attrs key on a block or node), an\nUNDERSCORE-prefixed name is the ENGINE's (however derived: `_id`, `_path`,\n`_title`, `_updated_at`, `_doc`), and `$` belongs to OQX \u2014 $it, $key, $depth,\n$stop, $leaf, $frontier, $ordinal are values the LANGUAGE injects into a scope;\nomgbase adds NO $ names (a `$id` in a query is an unknown name, absent). Nothing\nbare is reserved: `title`, `tags`, `path`, `id`, `format`, `doc` are your keys\nwhen you wrote them. Collections stay bare (`from docs`, `^docs`, `docs count { \u2026 }`,\n`entries(^$it)`) \u2014 `from` names a collection, not a row property \u2014 and so do the\nrow functions in call position (`text(\"x\")`, `under(\u2026)`, `refs(x)`): a call is\nnever an entry read. There is NO self alias (the 4.x `doc`/`block`/`section`):\n`$it` is the self, and `_doc`/`_block`/`_dst` reach ANOTHER row. So `_path` is the\nengine's /-rooted location and `path` your `path:` key; `_tags` is your tags \u222A the\nbody #hashtags and `tags` the authored list alone; `_title` the computed title (the\nfirst H1) and `title` your `title:`; `_data.<k>` \u2261 `<k>` (the merged authored view \u2014\nkept for `entries(_data)` and to reach an authored key that itself starts with\n`_`), `_frontmatter.<k>` / `_inline.<k>` one source each. Same value, same name\neverywhere: a hit's `_id` is docs_read's `_id` and the `_id` argument of blocks_read.\n\n Engine names per kind (attributes | handles | relations):\n docs _id _path _raw _hash _updated_at _created_at _title _tags _format | _data _frontmatter _inline | _blocks _nodes _out_links _in_links _revisions\n blocks _id _path _doc_id _raw _hash _updated_at _created_at _type _text _attrs _depth _ordinal | _doc | _children _nodes _out_links _section\n nodes _id _path _doc_id _block_id _kind _name _value _attrs | _doc _block | _blocks _subsections _children\n links _id _path _doc_id _block_id _via_id _dst_id _dst_path _uri _field _dst_kind _predicate _provenance _anchor | _doc _dst _block | \u2014\n revisions _id _path _rev _seq _doc_id _commit _ts _origin _actor _reason _hash _is_current _archived | _doc | \u2014\n root _id (^_id, 0^_id) | \u2014 | docs blocks nodes links revisions\n Every bare name is yours; `_attrs.<k>` on a block or node is the same read as\n the bare attrs key `<k>`.\n UNKNOWN NAMES: a bare name that no object of the kind in this repository\n carries is a property read that yields absent \u2014 on docs/blocks/nodes the result\n gains `hints` (one line per name, the nearest match drawn from your keys and the\n engine names: \"no document in this repository carries a property `title`; did\n you mean `_title`?\") and the rows still come back; in a plan/apply/execute\n selector it is refused. On links and revisions (closed: no authored data reaches\n them) a bare name is a filter_invalid error. A `_x` that is no engine name of\n the kind is filter_invalid naming the kind's engine names. A property some\n objects carry is known (partial presence is normal).\n\ndocs:\n - bare identifier = a document PROPERTY (frontmatter + inline `key:: value`,\n unioned; nested via dots: meta.owner). YAML/JSON docs\n expose the parsed object's keys the same way. Nothing is\n reserved: `title`, `tags`, `path`, `id` are your keys.\n - source handles = _data.<k> (the merged authored view \u2014 the same read as the\n bare key) / _frontmatter.<k> / _inline.<k>; the whole bag\n as a collection via entries(_data) / entries(_frontmatter)\n / entries(_inline) (see entries()).\n - attributes = _id, _path (/-rooted, see Paths), _updated_at (ISO-8601,\n sorts chronologically), _created_at, _raw (the whole file,\n frontmatter included), _hash (its sha256, 64 hex), _title\n (the computed title: the first H1; `title` is the authored\n key), _tags (authored tags \u222A body #hashtags, deduped,\n authored first; `tags` is the authored list), _format (the\n doc's format \u2014 \"markdown\"/\"yaml\"/\"json\"; present on EVERY\n doc, so has(_format) is always true and `_format == \"yaml\"`\n is the filter).\n - relations = _nodes, _blocks, _out_links / _in_links (a doc's links as\n rows; the documents at their other end are\n _out_links collect distinct { from _dst } and\n _in_links collect distinct { from _doc } \u2014 there is no\n out / in relation), _revisions (the document's stored\n revisions, oldest first \u2014 see revisions below).\n\nblocks:\n - bare fields = every attrs key FLATTENED onto the row (bare `checked`;\n `_attrs.<k>` is the same read; absent \u21D2 false) \u2014 a block's\n authored side.\n - attributes = _id, _path (the owning doc's path), _doc_id (the owning\n doc), _type, _text (normalized), _ordinal (0-based among\n siblings), _depth (0 at the top level), _raw (the block's\n raw source \u2014 a container's raw holds its children), _hash\n (its sha256 \u2014 the value update/split expect as\n expect.content_hash), _updated_at, _created_at, _attrs.\n - reach-through = _doc.<key> / _doc._path / _doc._id / _doc._format = the\n CONTAINING doc (_doc._id and _doc._path cost nothing \u2014 they\n read the row's own _doc_id / joined path, no lookup).\n - relations = _children, _nodes, _out_links, _section (no self alias:\n the 4.x `block.children` is `_children`).\n NOTE: inside a `follow`, $depth/$ordinal are ALWAYS the walk's metadata; a\n block's own nesting is `_depth` / `_ordinal` everywhere.\n\nnodes:\n - bare fields = the attrs keys flattened onto the row (checked, level,\n first_ordinal; `_attrs.<k>` is the same read).\n - attributes = _id, _path, _doc_id, _block_id (null for a block-less\n node), _kind (md:task / md:link / md:section / yaml:\u2026),\n _name, _value, _attrs.\n - reach-through = _doc.<key> / _doc._id / _doc._path, _block._type /\n _block._text / _block._id / _block._path.\n - relations = _blocks, _children, _subsections (a section node's; the\n 4.x `section.blocks` is `_blocks`).\n\nlinks: (the authored link graph as rows \u2014 one per open link: the relation a link\n establishes; the md:link NODE is its occurrence in the text and _via_id\n points at it \u2014 a frontmatter link has a row and no node)\n - attributes = _id, _path (the SOURCE doc's path), _doc_id (the SOURCE\n doc), _block_id (the source block; null for a frontmatter\n edge), _via_id, _dst_id (the destination node id: a doc id,\n an external node id or phantom:<path>), _dst_path (target\n doc path, /-rooted; NULL when dangling/external), _uri\n (external URL; NULL otherwise), _predicate\n (\"references\"/\"embeds\"/freeform), _provenance\n (\"link\"/\"frontmatter\"/\"inline_field\"/\u2026), _dst_kind\n (\"document\"/\"external\"/\"collection\"), _anchor, _field (the\n frontmatter key / inline field it was authored in).\n - reach-through = _doc.<key> (the source document), _dst.<key> (the\n destination document; absent when dangling/external),\n _block.<key> (the source block). Closed: links carry no\n authored data, so a bare name is filter_invalid. There is\n no from_commit: a query sees open edges only (history is\n blocks_history/changes_since).\n - text()/semantic() are N/A on links.\n\nrevisions: (history as rows \u2014 one per stored revision of every document of the\n repository, deleted documents included: what docs_history lists, queryable)\n - attributes = _id (the revision id, r_\u2026), _rev (= _id), _seq (1, 2, \u2026 per\n document), _doc_id, _commit, _ts (the commit's time, sorts\n chronologically), _origin (\"observed\"/\"api\"/\"import\"/\n \"projection\"), _actor, _reason, _hash (the rendered file's\n sha256 \u2014 docs_history's _hash), _is_current (true for the\n document's current revision), _archived (true when the\n bodies moved to an archive segment: the row stays,\n docs_read { at } reads through the segment), _path (the\n path the revision was committed under \u2014 a move re-paths\n only the current revision, so earlier ones keep the old\n path).\n - reach-through = _doc.<key> reaches the document's CURRENT row; absent for\n a deleted document, so where _doc == null is the revisions\n of deleted documents. Closed: a bare name is filter_invalid.\n - on docs = _revisions is a relation (oldest first): a document with\n more than one version is docs { _revisions count { } > 1 }.\n - text()/semantic() are N/A on revisions. The body as of a revision is\n docs_read { _path, at: \"<rev>\" } (docs_read_at is the same call).\n\n## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL\n\n name the CURRENT row only \u2014 never falls through to an enclosing row\n or the repository; an absent field is simply absent.\n ^name one scope OUT (the enclosing row's fields/intrinsics/lifts);\n ^^name two scopes out. `^_path` = the enclosing row's path.\n ^docs ^nodes ^blocks ^links ^revisions\n THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root\n scope's row, one scope out from a top-level row, so ^docs there is\n the whole documents scan (uncorrelated until you add a ^ predicate);\n one caret per enclosing block (^^docs from depth two), or the\n absolute 0^docs from ANY depth. ^_id / 0^_id = the repository id.\n ^$it = the root object: ^$it.docs \u2261 ^docs, entries(^$it) names the\n five collections. At the root scope itself a bare target is the scan\n (docs count { \u2026 }, from docs, entries(docs)).\n ERRORS: a bare `docs` INSIDE a block reads a property of the current\n row, which has none \u2014 refused (\"did you mean ^docs\"); so is a bare\n `blocks`/`nodes`/`revisions` there (a doc's own relations are\n `_blocks`/`_nodes`/`_revisions`); `^docs` AT THE TOP LEVEL reaches\n past the root \u2014 refused (write the bare `docs`); so is `$repo`\n (surface < 2.0; the message names the replacement).\n $it the current item itself (a row, or each ELEMENT when the\n receiver is a list property); ^$it = the enclosing row, ^$it._id\n its id. $value is NOT an intrinsic (since oqx 0.15): it reads a\n property literally named $value, absent when there is none.\n $key the row's identity (0.20): a store row's id (doc/block/node/edge), an entries(x)\n entry's key, a follow occurrence's walk identity; project $key: <expr> to rekey the\n rows a block yields (a destination block's $key: is not a column).\n\n## Scalar semantics (@omgbase/oqx)\n\n comparisons == != < <= > >= strict & typed (5 == \"5\" is false); either\n side may be a field (field-vs-field OK).\n absence null and a missing field are the same \"absent\" value; two absents\n are EQUAL, so `absent != \"x\"` is TRUE and `absent == null` is TRUE.\n Relational (< <= > >=) on an absent operand \u2192 FALSE. Bare `f` in\n boolean position uses JS truthiness (absent/false/0/\"\" \u21D2 false;\n NOTE: \"false\", [] and {} are truthy). has(f) = explicit presence.\n boolean and or not grouping ( \u2026 ) \u2014 && || ! are accepted synonyms\n arithmetic + - * / % (+ concatenates if either side is a string)\n membership x in y array \u2192 typed membership; string \u2192 substring;\n object \u2192 key exists; range \u2192 coverage. Any list\n property works directly: \"pricing\" in tags. list(f)\n coerces scalar-or-list (absent \u2192 []) \u2014 use it when a\n key is sometimes scalar, sometimes a list.\n ranges lo..hi (inclusive) lo...hi (exclusive hi) ..hi lo..\n era in 800..1680; works over ISO dates. range(prop) reads a\n range-shaped STRING property as an interval: \"2026-01-15\" in range(window).\n strings x.contains(\"s\") x.startsWith(\"p\") x.endsWith(\"s\") x.matches(\"^re$\")\n (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE \u2014\n fold with .lower(): _title.lower().contains(\"aurora\").\n Infix regex: x ~ \"re\" is x.matches(\"re\"); x ~* \"re\" is the\n case-insensitive match; x !~ \"re\" and x !~* \"re\" negate them.\n Unanchored unless the pattern anchors; an absent x is false\n under ~ and TRUE under !~ (not SQL's NULL).\n functions has(f) size(x) list(x) entries(x) range(s)\n dates a date is an ISO string, a duration a NUMBER OF SECONDS; arithmetic\n is METHODS, never + or - (+ concatenates strings).\n date(\"today\"|\"now\"|\"yesterday\"|\"tomorrow\"|\"sow\"|\"eow\"|\"som\"|\"eom\"|\n \"soy\"|\"eoy\") \u2014 the host clock, UTC; date(\"2026-10-15\") canonical ISO\n (absent if unparseable). dur(\"1h30m\") dur(\"2 days, 3 hours\")\n dur(\"P1DT2H\") dur(90) \u2192 seconds (year = 365 d, month = 30 d).\n d.plus(dur(\"30 days\")) d.minus(86400) d.plus(\"1 month\") (calendar:\n Jan 31 \u2192 Feb 28) a.until(b) \u2192 signed seconds d.year() d.month()\n d.day() d.weekday() (1 = Mon) d.hour() d.minute() d.second()\n d.date() d.format(\"EEE dd MMM yyyy\") n.dur_text() \u2192 \"1h 30m\"\n n.dur_iso() \u2192 \"PT1H30M\". Total: a non-date is absent, so\n `where date(due) < date(\"today\").minus(dur(\"30 days\"))` just\n filters. A datetime is never < its own date: compare\n _updated_at.date() == date(\"today\"), or < date(\"tomorrow\").\n literals \"str\" 'str' 123 1.5 true false null\n determinism no random; the only clock is date(\"today\"/\"now\"/\u2026), read once per\n query. Lists/structs are not literals; ternary is not supported.\n Errors surface as filter_invalid.\n scalars vs lists a list-authored key never == a scalar (tags == \"x\" is false\n when tags is a list) \u2014 use `\"x\" in tags` / `in list(tags)`.\n\n## Domain predicates (omgbase)\n\n text(\"terms\") FTS5 PRUNING predicate (docs match via their blocks, nodes\n via the node index); composes anywhere, incl. nested scopes.\n semantic(\"phrase\") embedding cosine SCORE (docs/blocks): threshold it\n (semantic(\"x\") > 0.6), project it, or `order by semantic(\"x\")\n desc` for top-K. Needs a provider that has embedded the repository; else provider_unavailable.\n blocks target only:\n under(id_or_heading) block in the containment subtree / section range\n under_heading(\"Launch\") an ancestor section heading contains the text (ci)\n under_kind(type[, name]) an ancestor block has that type (name: its text\n contains / its key equals name)\n within(\"path\" | \"d_..\") containing doc = id, exact path, or path glob (*);\n either path form\n yaml_path(\"a.b\") YAML docs: the block at that key path\n json_pointer(\"#/a/b\") JSON docs: the block at that pointer\n has_edge(\"pred\"[, target]) an open authored edge with that predicate leaves here\n has_anchor() block carries an authored ^ref\n parent_type() == \"x\" parent block's type (must be compared)\n child_count() > 0 number of direct children (must be compared)\n refs(x) the live documents a property's document references name:\n x is a string, a list or absent; each \"/a/b.md\", \"a/b.md\"\n or \"d_\u2026\" element resolves to that doc's row; dangling and\n non-string elements are dropped. Yields docs rows, so it is\n a source, a receiver or a follow destination:\n follow refs(before), refs(after) walks a timeline both ways.\n The reverse direction needs no function: a document's\n _path is already the reference form, so\n ^docs { where ^_path in list(after) } is\n \"the documents whose `after` names me\".\n A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails\n (filter_invalid). `follow before` over a list of paths\n reaches the STRINGS \u2014 write `follow refs(before)`.\n\n## Nested queries & consumers\n\n <receiver> collect|exists|none|count|first|single { <block> }\n receiver = a relation (_nodes, _blocks, _doc._out_links, _section._blocks, \u2026), a\n reach-through row (_dst, _doc: one row \u2014 _dst { _title }), a\n list property (tags), entries(x), or a root collection (^docs; 0^docs at depth \u2265 2).\n block = [select \u2026] [from E] [where \u2026] [frontier \u2026] [follow \u2026] [flatten \u2026] [group by \u2026] [having \u2026] [order by \u2026] [limit N] [offset N]\n \u2014 the same fixed order as the top level (`from` optional; the\n receiver supplies the rows). A leading name/alias list is the\n projection (`select` dropped); a predicate always needs `where`.\n A `from E` inside a block RE-PROJECTS the rows through a relation\n (a typed flatMap): `docs collect { from _nodes where _kind == \"md:task\" }`\n starts at docs, projects each through `_nodes`, and queries the\n resulting nodes; a scalar relation (`from _doc`) contributes one\n row; a missing or non-navigable relation is a loud error, never a\n global scan.\n exists \u22651 row _nodes exists { where _kind == \"md:task\" and not checked }\n none 0 rows _nodes none { where _kind == \"md:task\" and not checked } (\"every task done\")\n count a number _nodes count { where _kind == \"md:task\" } >= 2\n collect rows (default) \u2014 in select for nested results; in where with a lift\n first / single zero-or-one (single errors on >1) \u2014 lookups in select\n distinct on any consumer: dedup by projected value \u2014 _nodes count distinct { select _kind }\n limit/offset bound the block's rows AFTER where/order/distinct, BEFORE the\n consumer reduces: _nodes exists { offset 1 } = at least two;\n first { \u2026 offset 1 } = the second.\n lift `^name:` in a where-collect binds values outward:\n select _path, open from docs where _nodes collect { ^open: _value where _kind == \"md:task\" and not checked }\n joins ^<t> exists { where slug == ^ref } semi-join; not \u2026 exists anti-join;\n ^nodes single { where _kind == \"person\" and _attrs.id == ^owner_id } lookup;\n x in ^keys membership over a lifted set.\n\n## select \u2014 projection\n\n select a, alias: expr, nested: _nodes collect { \u2026 }\n bare key \u2192 your property (scalar-authored \u2192 scalar; list \u2192 array); _name \u2192\n the engine's field (_id, _path, _title, \u2026); _data.<k> is the bare key's read;\n a call/arithmetic needs an alias (n: size(tags)) unless in values mode;\n an output key of _id/_path other than the attribute itself needs an alias.\n _raw whole file bytes (docs) \u2014 docs_read is cheaper for one doc.\n distinct select distinct _type (dedup hits by projected value)\n values select <ONE item> values \u2192 bare values instead of records:\n select distinct type values from docs \u2192 values: [\"hub\",\"lab-note\",\u2026]\n select _path values from docs \u2192 values: [\"/index.md\", \u2026]\n tags: tags collect { $it values } \u2192 a plain array\n h: _nodes first { _name values where _kind == \"md:section\" order by first_ordinal }\n entries(x) a record as a collection ($key / $it per entry), key order:\n select fm: entries(_data) collect { k: $key, v: $it }\n where entries(_frontmatter) exists { where $key == \"era\" and $it > 1600 }\n entries(_inline), entries(_attrs), a nested map, or a list (index keys)\n $semantic_score is not a field \u2014 project semantic(\"\u2026\") under an alias instead.\n\n## flatten (OQX 0.25)\n\n flatten <expr> [{ \u2026 }] [as <name>] after follow, before group by; may repeat\n Splits one value into rows. Every hit is still the PARENT row \u2014 its _id/_path\n repeat per element, as Dataview's TABLE \u2026 FLATTEN \u2014 with <name> bound beside\n its fields at the same depth: an array \u2192 one row per element (an empty list\n yields none), a missing field \u2192 one row with the name absent (never dropped),\n an object or a scalar \u2192 one row with the value. A plain navigation names\n itself (flatten tags rebinds tags to each element); anything else needs as.\n The block is the ordinary receiver block over the elements (where, a\n projection, order by, limit, distinct), applied first. Later clauses read the\n name \u2014 a later flatten (flatten jobs as j flatten j.tags as t), group by,\n having, select, order by; where runs before it. A flattened query pages by\n position (cursor null); as _id / as _path is refused (the envelope reads them).\n select _path, tags from docs where type == \"process\" flatten tags order by _path, tags\n select _path, t: b._text from docs flatten _blocks { where _type == \"task\" and not checked } as b\n select tags, n: count() from docs flatten tags group by tags order by count() desc\n\n## order by, limit/offset, pagination\n\n order by era desc, _path asc absent values sort LAST (asc); ties \u2192 (_path, _id)\n limit N / offset N define the result SET (integer literals);\n the tool's limit/cursor then page WITHIN it\n cursor opaque keyset on (_path, _id); a custom order by\n disables it (you get the top page + truncated)\n\n## Graph traversal (follow) and links\n\n The citation graph is the LINK ROWS walked through a destination block:\n _dst is a link's destination document (absent for an external or dangling\n link \u2014 it then contributes no successor), _doc its source.\n from docs where <seed> follow _out_links collect { from _dst } docs the seed links TO\n from docs where <seed> follow _in_links collect { from _doc } backlinks\n follow _out_links collect { from _dst depth 3 } bound (1..8, default 8) \u2014 a clause of the block\n follow _out_links collect { from _dst where layer == \"canon\" } keep only matching successors\n follow _in_links collect { from _doc where ^_path in list(before) } ^ = the row being expanded\n (^^ = the walk's enclosing scope)\n follow _out_links collect { from _dst where _predicate == \"depends_on\" } one predicate only\n follow _out_links collect { from _dst }, _in_links collect { from _doc }\n several destinations: their union\n (one step dedups by identity)\n follow ^docs { where _out_links exists { where _dst_path == ^^_path } }\n a destination block over the root's\n documents, re-read per frontier row\n (^ = that row) = the backlinks walk\n follow _out_links collect { from _dst frontier type == \"practitioner\" } cut, keeping the frontier row\n from docs where type == \"process\" frontier !verified follow _out_links collect { from _dst }\n the BODY's frontier applies to the seeds:\n an unverified seed is kept, not expanded\n follow distinct _out_links collect { from _dst } / \u2026 { $key: group from _dst } identity control ($key)\n follow refs(before), ^docs { where ^_path in list(after) } forward over `before`, backward\n over every doc whose `after` names me\n Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|\n depth|cycle), $leaf/$frontier, $ordinal (a deterministic 1..N rank over the\n walk, ordered by depth then path \u2014 `order by $ordinal` for walk order,\n `where $ordinal <= N` for a deterministic budget cut), $key \u2014 usable in\n select/order by and the top-level post-walk where (a where conjunct that\n reads one filters the walk's RESULT; the other conjuncts remain the seed\n predicate), NOT metadata of the candidates a destination block's where reads,\n nor valid inside a consumer directive. Cyclic graphs are safe: a revisited\n node is admitted once as a `$stop == \"cycle\"` occurrence and never\n re-expanded. The default keeps one occurrence per distinct walk path;\n `follow distinct` dedups reached rows by identity.\n Destination blocks (OQX 0.20): a brace after ANY destination opens that\n destination's block \u2014 _children { depth 2 }, refs(x) { depth 2 }, _out_links\n collect { from _dst depth 2 }, ^docs { where ^_path in list(after) } \u2014 with the\n 0.17 body rules (where-first, projections) and two clauses of its own after\n `where`: frontier <pred> (occurrences reached through this destination that\n match are frontiers; ^ there is the walk's enclosing scope) and depth <n>\n (this destination expands a frontier row only while $depth < n). The body\n that carries the follow may write frontier <pred> too, after its where and\n before follow: it applies to the SEEDS (read like the body's where) \u2014 a\n matching seed stays in the result with $stop == \"frontier\" and is not\n expanded; each destination's frontier is its own. There is no options block;\n the old `by` is $key. A bare follow ^docs (no brace, no consumer) is an\n error naming both block spellings.\n Type-preserving relations: a block's _children; a section node's _children and\n _subsections; the citation graph is _out_links collect { from _dst } / _in_links collect { from _doc }.\n from links where _dst_path == \"/notes/x.md\" inbound links to a doc, as rows\n select d: _dst._title, k: _dst_kind from links the destination document's title\n from docs where _out_links exists { where _dst.layer == \"draft\" } links into drafts\n select b: _in_links collect distinct { from _doc } from docs backlinks as { _id, _path }\n select _doc_id, _dst_path from links where _predicate == \"depends_on\"\n from docs where not _in_links exists { } orphans (nothing links in)\n (The `graph` tool is a convenience wrapper that compiles to a follow query.)\n\n## semantic grain\n\nfrom docs ranks whole DOCUMENTS (one hit per file); from blocks ranks PASSAGES.\ntext()/where prune the candidate set; only order by reweights.\n\n## Examples\n\n from docs where layer == \"working\"\n from docs where _path.startsWith(\"/guides/\") and _updated_at >= \"2026-08-01\"\n select layer, tags from docs where \"pricing\" in list(tags)\n from docs where _inline.owner == \"alice\"\n from docs where _title.lower().contains(\"q3 plan\")\n from docs where _format == \"yaml\"\n from docs where type == \"practitioner\" order by era desc limit 2\n from docs where _nodes none { where _kind == \"md:task\" and not checked }\n from docs where tags exists { where $it == \"tria-prima\" }\n select fm: entries(_data) collect { k: $key, v: $it } from docs where _path == \"/x.md\"\n select owner_id, owner: ^nodes single { where _kind == \"person\" and _attrs.id == ^owner_id } from docs\n from blocks where _type == \"task\" and not checked and under_heading(\"Launch\") and _doc.layer == \"working\"\n from blocks where _type == \"paragraph\" and has_edge(\"references\", \"d_92aaaaa\")\n select _path, $depth from docs where _path == \"/timeline/review.md\" follow refs(before), refs(after)\n select _path, next: ^docs collect { _path where ^_path in list(before) } from docs where type == \"milestone\"\n select _path, n: size(^docs), repo: ^_id from docs limit 1\n select path, title from docs where has(path)\n docs { type == \"substance\" and not verified }\n select subject, process: ^docs[slug == ^subject]!._path from docs where type == \"lab-note\"\n select _path, back: _nodes first { select p: 0^docs collect { _path where ^^_path in list(after) } values } from docs where type == \"milestone\"\n select _path, prior: refs(before) collect { _path, when } from docs where type == \"milestone\"\n from blocks order by semantic(\"identity preservation across edits\") desc limit 10\n select _uri from links where _dst_kind == \"external\"\n docs count { where layer == \"canon\" }\n select _seq, _origin, _ts, _hash from revisions where _path == \"/processes/calcination.md\" order by _seq desc\n from docs where _revisions count { where _origin == \"api\" } > 0\n select p: _path, d: _doc._path from revisions where _doc == null\n select layer, n: count(), titles: collect(_title), open: rows count { where _nodes exists { where _kind == \"md:task\" and not checked } } from docs group by layer having count() > 1 order by count() desc\n select _path, tag: tags from docs where \"process\" in tags flatten tags order by _path, tags\n\nCommon mistake: `$path`/`$id` \u2014 omgbase has no $ names; the engine's location and\nidentity are `_path`/`_id`. Another: the 4.x bare `path`/`id`/`title`/`tags` \u2014 a\nbare `path` is now YOUR `path:` key (absent when you wrote none, with a hint\npointing at `_path`); the engine's are `_path`/`_id`/`_title`/`_tags`; `select\ndata.path` is gone \u2014 write `path`. Another: `where tags == \"x\"` on a list-valued\nkey is false \u2014 write `\"x\" in tags`. Another: `select _path: _doc._path` \u2014 alias it\n(`p: _doc._path`).\n";
1
+ export declare const QUERY_SYNTAX = "# query \u2014 OQX syntax reference\n\nThe `query` tool takes ONE plain OQX string (+ optional `limit`/`cursor`, and\n`bare: true` to drop the automatic `_id`/`_path` envelope \u2014 needs a `select`).\nThere is no {from, filter, order} envelope: every concern is a clause of the\nstring. Two rules tell the two syntaxes apart: DOT navigation belongs to the\nhost object model (`_doc.layer`, `_section._blocks`, `_dst._title`); WHITESPACE\nquery directives belong to OQX \u2014 a nested query is `<receiver> collect|exists|\ncount|first|single { <block> }` (a postfix directive + a `{ \u2026 }` block), never a\nmethod. Its distinctive power is the receiver-constrained nested query that\ncorrelates to the current row: `docs { _nodes exists { _kind == \"md:task\" } }`\nreturns only the docs that themselves contain a matching node.\n\n <target> { [select <items>] [where <pred>] [frontier <pred>] [follow <dest> [{ \u2026 }], \u2026]\n [flatten <expr> [{ \u2026 }] [as <name>]]\u2026 [group by <key>, \u2026] [having <pred>]\n [order by <expr> [asc|desc], \u2026] [limit N] [offset N] }\n <target> count|exists|none|first|single { <block> } (a scalar or one row; <target> is docs, blocks, nodes, links or revisions)\n\nTHE COLLECTION HEADS THE BODY (OQX 0.26): `docs { \u2026 }` reads the documents and\nshapes them with the clauses inside the braces; a consumer word between the\ntarget and the brace (`docs count { \u2026 }`) shapes the whole result; nothing may\nfollow the closing brace. The pre-0.26 clause form `select \u2026 from docs where \u2026`\nis still read as the same query \u2014 write the braces.\nCLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset \u2014\neach at most once (`flatten` may repeat; `from` inside a body RE-PROJECTS the\nrows through a relation, see Nested \u2014 it never names the source); an\nout-of-order clause is a parse error naming the order.\nOnly `select` may drop its keyword, and only as the first clause\n(`docs { _path, era where \u2026 }`). A body may LEAD with a predicate \u2014 `docs { era\n> 1600 }` filters (see Sugar) \u2014 but a predicate after a projection still needs\nthe keyword (`{ _name where _kind == \"md:task\" }`, not `{ _name, _kind ==\n\"md:task\" }`); in the clause form a top-level predicate needs `where` (`era >\n1600 from docs` is the clause-order error), and `from docs count` is an error\n(write `docs count { \u2026 }`). `where` and later `select` items may use the same\nbody's `select` aliases (`docs { select _path, old: era < 1000 where old }`; an\nalias shadows a same-named field there). `and` / `or` / `not` are the connectives;\nthe symbols `&&` / `||` / `!` are accepted synonyms (the words are canonical and\nwhat `print` writes).\n\n## Sugar (OQX 0.17)\n\nEvery form here is shorthand for an explicit directive (the AST is the explicit form):\n nodes { _kind == \"md:task\" } \u2261 nodes collect { where _kind == \"md:task\" } \u2014 a receiver block with\n no consumer is collect; a block whose LEADING expression is a predicate\n (comparison, call, !/is/not, in, literal, (\u2026), a consumer test \u2014 anything\n but a bare name, a dotted path or a ^lift) is where-first. Names still\n project: _nodes { _name } \u2261 _nodes collect { _name }; filter a bare field with\n _nodes { is checked }. Wherever a value stands (0.20), call arguments included:\n size(_children { is checked }); x distinct { \u2026 } \u2261 x collect distinct { \u2026 }. After a\n follow destination too: follow _children { depth 2 } is that destination's block.\n (a, b) a tuple (0.20): two or more expressions, concatenated one level flat \u2014 a value\n (size((tags, aliases)), \"x\" in (tags, aliases)) and a receiver ((a, b) { \u2026 },\n follow (before, after) { depth 2 }); (a) alone is grouping\n refs(x)[0] \u2261 refs(x) first { offset 0 } positional (integer literal or ${binding});\n out of range \u21D2 absent; refs(x)[0]._path navigates the row\n ^docs[_path == ^company] \u2261 ^docs first { where _path == ^company } first match or absent\n ^docs[_path == ^company]! \u2261 \u2026 single { \u2026 } required: exactly one, else filter_invalid\n _title! required: _title, or an error naming the expression (and the row's $key \u2014 its _id)\n \u2014 never a filter, never a coercion (0!, \"\"! are values); tightest: a!.b vs a.b!\n is x / not x truthiness of x, and its negation (prefix; `not` \u2261 `!`)\n x is y / x is not y identity ($key: a store row's id, else structural) \u2014 compares rows, which == does not;\n x is null = absent; for scalars is \u2261 ==; comparison precedence, no chaining\n a and b / a or b the connectives (\u2261 && / ||: same precedence, short-circuit, value: title or _title coalesces)\n boss: ^docs[_path == ^manager], bossName: boss._title a select item may use the items to its LEFT\n Reserved words (never a bare field name): from where select is not and or, true false null.\n Contextual words (limit order offset follow collect count first distinct desc by in frontier depth group having\n flatten as) parse as fields in expression position (docs { select o: order }).\n\nResults are LEAN hits \u2014 { _id, _path } + whatever `select` projects (or `values`,\n`count`, `exists`, `none`). The envelope keys ARE the attributes: copy a hit's\n`_id` straight into `where _id == \"\u2026\"` (no translation); `select _id, _path` is\nsimply the envelope (no duplicate columns); an alias `_id:`/`_path:` on any OTHER\nexpression is refused \u2014 `blocks { select _path: _doc._path }` fails (\"projection\n`_path` collides with the hit envelope \u2014 alias it (`p: _doc._path`)\"); a row\nsurfacing as a value (an empty `collect { }`) is the same `{ _id, _path }`. Every\nother tool spells a row the same way (`docs_read` \u2192 `{ _id, _path, rev, properties,\n_raw }`, `blocks_read` \u2192 `{ _id, _type, _text, _raw, _hash, \u2026 }`, `docs_list` \u2192 `{ _id,\n_path, _updated_at, blocks }`, a revision \u2192 `{ _id, _path, _hash }`): hydrate by `_id`\nvia blocks_read/docs_read.\n\n## Paths\n\nEvery path the surface returns is `/`-rooted \u2014 the form a reference is\nwritten in (`[x](/projects/oqx.md)`, `before: [/timeline/kickoff.md]`): a\nhit's `_path`, an edge's `_dst_path`, every tool's paths. Every path it\naccepts tolerates both forms (`/a.md` or `a.md`): tool arguments, `refs(x)`,\n`within(...)`, and a string LITERAL compared with `_path`/`_dst_path` (on the row,\nor through a row handle: `_doc._path`, `_dst._path`, `$it._path`) by `==`/`!=` or\npassed to their `.startsWith(...)` (it is rooted first, so `_path == \"a.md\"` and\n`_path == \"/a.md\"` both match). A property holding a reference compares directly \u2014\nand is never rooted: `where ^_path in list(after)`, `where customer == ^_path`,\n`where path == \"/x.md\"` (YOUR authored `path:` key \u2014 the bare name \u2014 compared as\nwritten) \u2014 no `\"/\" + ...` glue.\n\n## Targets & field namespaces\n\nTHE NAME RULE \u2014 one rule on every record kind: a BARE name is YOURS (an authored\nproperty: frontmatter + inline on a doc, an attrs key on a block or node), an\nUNDERSCORE-prefixed name is the ENGINE's (however derived: `_id`, `_path`,\n`_title`, `_updated_at`, `_doc`), and `$` belongs to OQX \u2014 $it, $key, $depth,\n$stop, $leaf, $frontier, $ordinal are values the LANGUAGE injects into a scope;\nomgbase adds NO $ names (a `$id` in a query is an unknown name, absent). Nothing\nbare is reserved: `title`, `tags`, `path`, `id`, `format`, `doc` are your keys\nwhen you wrote them. Collections stay bare (`docs { \u2026 }`, `^docs`, `docs count { \u2026 }`,\n`entries(^$it)`) \u2014 the collection before a body is a root, not a row property \u2014 and so do the\nrow functions in call position (`text(\"x\")`, `under(\u2026)`, `refs(x)`): a call is\nnever an entry read. There is NO self alias (the 4.x `doc`/`block`/`section`):\n`$it` is the self, and `_doc`/`_block`/`_dst` reach ANOTHER row. So `_path` is the\nengine's /-rooted location and `path` your `path:` key; `_tags` is your tags \u222A the\nbody #hashtags and `tags` the authored list alone; `_title` the computed title (the\nfirst H1) and `title` your `title:`; `_data.<k>` \u2261 `<k>` (the merged authored view \u2014\nkept for `entries(_data)` and to reach an authored key that itself starts with\n`_`), `_frontmatter.<k>` / `_inline.<k>` one source each. Same value, same name\neverywhere: a hit's `_id` is docs_read's `_id` and the `_id` argument of blocks_read.\n\n Engine names per kind (attributes | handles | relations):\n docs _id _path _raw _hash _updated_at _created_at _title _tags _format | _data _frontmatter _inline | _blocks _nodes _out_links _in_links _revisions\n blocks _id _path _doc_id _raw _hash _updated_at _created_at _type _text _attrs _depth _ordinal | _doc | _children _nodes _out_links _section\n nodes _id _path _doc_id _block_id _kind _name _value _attrs | _doc _block | _blocks _subsections _children\n links _id _path _doc_id _block_id _via_id _dst_id _dst_path _uri _field _dst_kind _predicate _provenance _anchor | _doc _dst _block | \u2014\n revisions _id _path _rev _seq _doc_id _commit _ts _origin _actor _reason _hash _is_current _archived | _doc | \u2014\n root _id (^_id, 0^_id) | \u2014 | docs blocks nodes links revisions\n Every bare name is yours; `_attrs.<k>` on a block or node is the same read as\n the bare attrs key `<k>`.\n UNKNOWN NAMES: a bare name that no object of the kind in this repository\n carries is a property read that yields absent \u2014 on docs/blocks/nodes the result\n gains `hints` (one line per name, the nearest match drawn from your keys and the\n engine names: \"no document in this repository carries a property `title`; did\n you mean `_title`?\") and the rows still come back; in a plan/apply/execute\n selector it is refused. On links and revisions (closed: no authored data reaches\n them) a bare name is a filter_invalid error. A `_x` that is no engine name of\n the kind is filter_invalid naming the kind's engine names. A property some\n objects carry is known (partial presence is normal).\n\ndocs:\n - bare identifier = a document PROPERTY (frontmatter + inline `key:: value`,\n unioned; nested via dots: meta.owner). YAML/JSON docs\n expose the parsed object's keys the same way. Nothing is\n reserved: `title`, `tags`, `path`, `id` are your keys.\n - source handles = _data.<k> (the merged authored view \u2014 the same read as the\n bare key) / _frontmatter.<k> / _inline.<k>; the whole bag\n as a collection via entries(_data) / entries(_frontmatter)\n / entries(_inline) (see entries()).\n - attributes = _id, _path (/-rooted, see Paths), _updated_at (ISO-8601,\n sorts chronologically), _created_at, _raw (the whole file,\n frontmatter included), _hash (its sha256, 64 hex), _title\n (the computed title: the first H1; `title` is the authored\n key), _tags (authored tags \u222A body #hashtags, deduped,\n authored first; `tags` is the authored list), _format (the\n doc's format \u2014 \"markdown\"/\"yaml\"/\"json\"; present on EVERY\n doc, so has(_format) is always true and `_format == \"yaml\"`\n is the filter).\n - relations = _nodes, _blocks, _out_links / _in_links (a doc's links as\n rows; the documents at their other end are\n _out_links distinct { from _dst } and\n _in_links distinct { from _doc } \u2014 there is no\n out / in relation), _revisions (the document's stored\n revisions, oldest first \u2014 see revisions below).\n\nblocks:\n - bare fields = every attrs key FLATTENED onto the row (bare `checked`;\n `_attrs.<k>` is the same read; absent \u21D2 false) \u2014 a block's\n authored side.\n - attributes = _id, _path (the owning doc's path), _doc_id (the owning\n doc), _type, _text (normalized), _ordinal (0-based among\n siblings), _depth (0 at the top level), _raw (the block's\n raw source \u2014 a container's raw holds its children), _hash\n (its sha256 \u2014 the value update/split expect as\n expect.content_hash), _updated_at, _created_at, _attrs.\n - reach-through = _doc.<key> / _doc._path / _doc._id / _doc._format = the\n CONTAINING doc (_doc._id and _doc._path cost nothing \u2014 they\n read the row's own _doc_id / joined path, no lookup).\n - relations = _children, _nodes, _out_links, _section (no self alias:\n the 4.x `block.children` is `_children`).\n NOTE: inside a `follow`, $depth/$ordinal are ALWAYS the walk's metadata; a\n block's own nesting is `_depth` / `_ordinal` everywhere.\n\nnodes:\n - bare fields = the attrs keys flattened onto the row (checked, level,\n first_ordinal; `_attrs.<k>` is the same read).\n - attributes = _id, _path, _doc_id, _block_id (null for a block-less\n node), _kind (md:task / md:link / md:section / yaml:\u2026),\n _name, _value, _attrs.\n - reach-through = _doc.<key> / _doc._id / _doc._path, _block._type /\n _block._text / _block._id / _block._path.\n - relations = _blocks, _children, _subsections (a section node's; the\n 4.x `section.blocks` is `_blocks`).\n\nlinks: (the authored link graph as rows \u2014 one per open link: the relation a link\n establishes; the md:link NODE is its occurrence in the text and _via_id\n points at it \u2014 a frontmatter link has a row and no node)\n - attributes = _id, _path (the SOURCE doc's path), _doc_id (the SOURCE\n doc), _block_id (the source block; null for a frontmatter\n edge), _via_id, _dst_id (the destination node id: a doc id,\n an external node id or phantom:<path>), _dst_path (target\n doc path, /-rooted; NULL when dangling/external), _uri\n (external URL; NULL otherwise), _predicate\n (\"references\"/\"embeds\"/freeform), _provenance\n (\"link\"/\"frontmatter\"/\"inline_field\"/\u2026), _dst_kind\n (\"document\"/\"external\"/\"collection\"), _anchor, _field (the\n frontmatter key / inline field it was authored in).\n - reach-through = _doc.<key> (the source document), _dst.<key> (the\n destination document; absent when dangling/external),\n _block.<key> (the source block). Closed: links carry no\n authored data, so a bare name is filter_invalid. There is\n no from_commit: a query sees open edges only (history is\n blocks_history/changes_since).\n - text()/semantic() are N/A on links.\n\nrevisions: (history as rows \u2014 one per stored revision of every document of the\n repository, deleted documents included: what docs_history lists, queryable)\n - attributes = _id (the revision id, r_\u2026), _rev (= _id), _seq (1, 2, \u2026 per\n document), _doc_id, _commit, _ts (the commit's time, sorts\n chronologically), _origin (\"observed\"/\"api\"/\"import\"/\n \"projection\"), _actor, _reason, _hash (the rendered file's\n sha256 \u2014 docs_history's _hash), _is_current (true for the\n document's current revision), _archived (true when the\n bodies moved to an archive segment: the row stays,\n docs_read { at } reads through the segment), _path (the\n path the revision was committed under \u2014 a move re-paths\n only the current revision, so earlier ones keep the old\n path).\n - reach-through = _doc.<key> reaches the document's CURRENT row; absent for\n a deleted document, so where _doc == null is the revisions\n of deleted documents. Closed: a bare name is filter_invalid.\n - on docs = _revisions is a relation (oldest first): a document with\n more than one version is docs { _revisions count > 1 }.\n - text()/semantic() are N/A on revisions. The body as of a revision is\n docs_read { _path, at: \"<rev>\" } (docs_read_at is the same call).\n\n## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL\n\n name the CURRENT row only \u2014 never falls through to an enclosing row\n or the repository; an absent field is simply absent.\n ^name one scope OUT (the enclosing row's fields/intrinsics/lifts);\n ^^name two scopes out. `^_path` = the enclosing row's path.\n ^docs ^nodes ^blocks ^links ^revisions\n THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root\n scope's row, one scope out from a top-level row, so ^docs there is\n the whole documents scan (uncorrelated until you add a ^ predicate);\n one caret per enclosing block (^^docs from depth two), or the\n absolute 0^docs from ANY depth. ^_id / 0^_id = the repository id.\n ^$it = the root object: ^$it.docs \u2261 ^docs, entries(^$it) names the\n five collections. At the root scope itself a bare target is the scan\n (docs { \u2026 }, docs count { \u2026 }, entries(docs)).\n ERRORS: a bare `docs` INSIDE a block reads a property of the current\n row, which has none \u2014 refused (\"did you mean ^docs\"); so is a bare\n `blocks`/`nodes`/`revisions` there (a doc's own relations are\n `_blocks`/`_nodes`/`_revisions`); `^docs` AT THE TOP LEVEL reaches\n past the root \u2014 refused (write the bare `docs`); so is `$repo`\n (surface < 2.0; the message names the replacement).\n $it the current item itself (a row, or each ELEMENT when the\n receiver is a list property); ^$it = the enclosing row, ^$it._id\n its id. $value is NOT an intrinsic (since oqx 0.15): it reads a\n property literally named $value, absent when there is none.\n $key the row's identity (0.20): a store row's id (doc/block/node/edge), an entries(x)\n entry's key, a follow occurrence's walk identity; project $key: <expr> to rekey the\n rows a block yields (a destination block's $key: is not a column).\n\n## Scalar semantics (@omgbase/oqx)\n\n comparisons == != < <= > >= strict & typed (5 == \"5\" is false); either\n side may be a field (field-vs-field OK).\n absence null and a missing field are the same \"absent\" value; two absents\n are EQUAL, so `absent != \"x\"` is TRUE and `absent == null` is TRUE.\n Relational (< <= > >=) on an absent operand \u2192 FALSE. Bare `f` in\n boolean position uses JS truthiness (absent/false/0/\"\" \u21D2 false;\n NOTE: \"false\", [] and {} are truthy). has(f) = explicit presence.\n boolean and or not grouping ( \u2026 ) \u2014 && || ! are accepted synonyms\n arithmetic + - * / % (+ concatenates if either side is a string)\n membership x in y array \u2192 typed membership; string \u2192 substring;\n object \u2192 key exists; range \u2192 coverage. Any list\n property works directly: \"pricing\" in tags. list(f)\n coerces scalar-or-list (absent \u2192 []) \u2014 use it when a\n key is sometimes scalar, sometimes a list.\n ranges lo..hi (inclusive) lo...hi (exclusive hi) ..hi lo..\n era in 800..1680; works over ISO dates. range(prop) reads a\n range-shaped STRING property as an interval: \"2026-01-15\" in range(window).\n strings x.contains(\"s\") x.startsWith(\"p\") x.endsWith(\"s\") x.matches(\"^re$\")\n (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE \u2014\n fold with .lower(): _title.lower().contains(\"aurora\").\n Infix regex: x ~ \"re\" is x.matches(\"re\"); x ~* \"re\" is the\n case-insensitive match; x !~ \"re\" and x !~* \"re\" negate them.\n Unanchored unless the pattern anchors; an absent x is false\n under ~ and TRUE under !~ (not SQL's NULL).\n functions has(f) size(x) list(x) entries(x) range(s)\n dates a date is an ISO string, a duration a NUMBER OF SECONDS; arithmetic\n is METHODS, never + or - (+ concatenates strings).\n date(\"today\"|\"now\"|\"yesterday\"|\"tomorrow\"|\"sow\"|\"eow\"|\"som\"|\"eom\"|\n \"soy\"|\"eoy\") \u2014 the host clock, UTC; date(\"2026-10-15\") canonical ISO\n (absent if unparseable). dur(\"1h30m\") dur(\"2 days, 3 hours\")\n dur(\"P1DT2H\") dur(90) \u2192 seconds (year = 365 d, month = 30 d).\n d.plus(dur(\"30 days\")) d.minus(86400) d.plus(\"1 month\") (calendar:\n Jan 31 \u2192 Feb 28) a.until(b) \u2192 signed seconds d.year() d.month()\n d.day() d.weekday() (1 = Mon) d.hour() d.minute() d.second()\n d.date() d.format(\"EEE dd MMM yyyy\") n.dur_text() \u2192 \"1h 30m\"\n n.dur_iso() \u2192 \"PT1H30M\". Total: a non-date is absent, so\n `where date(due) < date(\"today\").minus(dur(\"30 days\"))` just\n filters. A datetime is never < its own date: compare\n _updated_at.date() == date(\"today\"), or < date(\"tomorrow\").\n literals \"str\" 'str' 123 1.5 true false null\n determinism no random; the only clock is date(\"today\"/\"now\"/\u2026), read once per\n query. Lists/structs are not literals; ternary is not supported.\n Errors surface as filter_invalid.\n scalars vs lists a list-authored key never == a scalar (tags == \"x\" is false\n when tags is a list) \u2014 use `\"x\" in tags` / `in list(tags)`.\n\n## Domain predicates (omgbase)\n\n text(\"terms\") FTS5 PRUNING predicate (docs match via their blocks, nodes\n via the node index); composes anywhere, incl. nested scopes.\n semantic(\"phrase\") embedding cosine SCORE (docs/blocks): threshold it\n (semantic(\"x\") > 0.6), project it, or `order by semantic(\"x\")\n desc` for top-K. Needs a provider that has embedded the repository; else provider_unavailable.\n blocks target only:\n under(id_or_heading) block in the containment subtree / section range\n under_heading(\"Launch\") an ancestor section heading contains the text (ci)\n under_kind(type[, name]) an ancestor block has that type (name: its text\n contains / its key equals name)\n within(\"path\" | \"d_..\") containing doc = id, exact path, or path glob (*);\n either path form\n yaml_path(\"a.b\") YAML docs: the block at that key path\n json_pointer(\"#/a/b\") JSON docs: the block at that pointer\n has_edge(\"pred\"[, target]) an open authored edge with that predicate leaves here\n has_anchor() block carries an authored ^ref\n parent_type() == \"x\" parent block's type (must be compared)\n child_count() > 0 number of direct children (must be compared)\n refs(x) the live documents a property's document references name:\n x is a string, a list or absent; each \"/a/b.md\", \"a/b.md\"\n or \"d_\u2026\" element resolves to that doc's row; dangling and\n non-string elements are dropped. Yields docs rows, so it is\n a source, a receiver or a follow destination:\n follow refs(before), refs(after) walks a timeline both ways.\n The reverse direction needs no function: a document's\n _path is already the reference form, so\n ^docs { ^_path in list(after) } is\n \"the documents whose `after` names me\".\n A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails\n (filter_invalid). `follow before` over a list of paths\n reaches the STRINGS \u2014 write `follow refs(before)`.\n\n## Nested queries & consumers\n\n <receiver> collect|exists|none|count|first|single { <block> }\n The block may be omitted after a consumer word (OQX 0.26): the omitted block is\n the empty block \u2014 `_nodes exists`, `_revisions count > 1`, `_blocks first`,\n `docs count` as a whole query; `x { }` keeps its braces (the brace is what\n makes a collect). A projection block over a lookup is the lookup's projection:\n `_blocks[0] { _text }` \u2261 `_blocks first { _text offset 0 }`.\n receiver = a relation (_nodes, _blocks, _doc._out_links, _section._blocks, \u2026), a\n reach-through row (_dst, _doc: one row \u2014 _dst { _title }), a\n list property (tags), entries(x), or a root collection (^docs; 0^docs at depth \u2265 2).\n block = [select \u2026] [from E] [where \u2026] [frontier \u2026] [follow \u2026] [flatten \u2026] [group by \u2026] [having \u2026] [order by \u2026] [limit N] [offset N]\n \u2014 the same fixed order as the whole query, whose body is a block\n too (the target is its receiver). A leading name/alias list is the\n projection (`select` dropped); a predicate needs `where` unless\n it LEADS the block (Sugar). A `from E` inside a block RE-PROJECTS\n the rows through a relation (a typed flatMap):\n `docs { from _nodes where _kind == \"md:task\" }` starts at docs,\n projects each through `_nodes`, and queries the resulting nodes;\n a scalar relation (`from _doc`) contributes one row; a missing or\n non-navigable relation is a loud error, never a global scan. `from`\n never names the source \u2014 the collection before the body does.\n exists \u22651 row _nodes exists { _kind == \"md:task\" and not checked }\n none 0 rows _nodes none { _kind == \"md:task\" and not checked } (\"every task done\")\n count a number _nodes count { _kind == \"md:task\" } >= 2\n collect rows (default) \u2014 in select for nested results; in where with a lift\n first / single zero-or-one (single errors on >1) \u2014 lookups in select\n distinct on any consumer: dedup by projected value \u2014 _nodes count distinct { select _kind }\n limit/offset bound the block's rows AFTER where/order/distinct, BEFORE the\n consumer reduces: _nodes exists { offset 1 } = at least two;\n first { \u2026 offset 1 } = the second.\n lift `^name:` in a where-collect binds values outward:\n docs { select _path, open where _nodes { ^open: _value where _kind == \"md:task\" and not checked } }\n joins ^<t> exists { slug == ^ref } semi-join; not \u2026 exists anti-join;\n ^nodes single { _kind == \"person\" and _attrs.id == ^owner_id } lookup;\n x in ^keys membership over a lifted set.\n\n## select \u2014 projection\n\n select a, alias: expr, nested: _nodes collect { \u2026 }\n bare key \u2192 your property (scalar-authored \u2192 scalar; list \u2192 array); _name \u2192\n the engine's field (_id, _path, _title, \u2026); _data.<k> is the bare key's read;\n a call/arithmetic needs an alias (n: size(tags)) unless in values mode;\n an output key of _id/_path other than the attribute itself needs an alias.\n _raw whole file bytes (docs) \u2014 docs_read is cheaper for one doc.\n distinct select distinct _type (dedup hits by projected value)\n values select <ONE item> values \u2192 bare values instead of records:\n docs { select distinct type values } \u2192 values: [\"hub\",\"lab-note\",\u2026]\n docs { select _path values } \u2192 values: [\"/index.md\", \u2026]\n tags: tags { $it values } \u2192 a plain array\n h: _nodes first { _name values where _kind == \"md:section\" order by first_ordinal }\n entries(x) a record as a collection ($key / $it per entry), key order:\n select fm: entries(_data) { k: $key, v: $it }\n where entries(_frontmatter) exists { $key == \"era\" and $it > 1600 }\n entries(_inline), entries(_attrs), a nested map, or a list (index keys)\n $semantic_score is not a field \u2014 project semantic(\"\u2026\") under an alias instead.\n\n## flatten (OQX 0.25)\n\n flatten <expr> [{ \u2026 }] [as <name>] after follow, before group by; may repeat\n Splits one value into rows. Every hit is still the PARENT row \u2014 its _id/_path\n repeat per element, as Dataview's TABLE \u2026 FLATTEN \u2014 with <name> bound beside\n its fields at the same depth: an array \u2192 one row per element (an empty list\n yields none), a missing field \u2192 one row with the name absent (never dropped),\n an object or a scalar \u2192 one row with the value. A plain navigation names\n itself (flatten tags rebinds tags to each element); anything else needs as.\n The block is the ordinary receiver block over the elements (where, a\n projection, order by, limit, distinct), applied first. Later clauses read the\n name \u2014 a later flatten (flatten jobs as j flatten j.tags as t), group by,\n having, select, order by; where runs before it. A flattened query pages by\n position (cursor null); as _id / as _path is refused (the envelope reads them).\n docs { select _path, tags where type == \"process\" flatten tags order by _path, tags }\n docs { select _path, t: b._text flatten _blocks { _type == \"task\" and not checked } as b }\n docs { select tags, n: count() flatten tags group by tags order by count() desc }\n\n## order by, limit/offset, pagination\n\n order by era desc, _path asc absent values sort LAST (asc); ties \u2192 (_path, _id)\n limit N / offset N define the result SET (integer literals);\n the tool's limit/cursor then page WITHIN it\n cursor opaque keyset on (_path, _id); a custom order by\n disables it (you get the top page + truncated)\n\n## Graph traversal (follow) and links\n\n The citation graph is the LINK ROWS walked through a destination block:\n _dst is a link's destination document (absent for an external or dangling\n link \u2014 it then contributes no successor), _doc its source.\n docs { where <seed> follow _out_links { from _dst } } docs the seed links TO\n docs { where <seed> follow _in_links { from _doc } } backlinks\n follow _out_links { from _dst depth 3 } bound (1..8, default 8) \u2014 a clause of the block\n follow _out_links { from _dst where layer == \"canon\" } keep only matching successors\n follow _in_links { from _doc where ^_path in list(before) } ^ = the row being expanded\n (^^ = the walk's enclosing scope)\n follow _out_links { from _dst where _predicate == \"depends_on\" } one predicate only\n follow _out_links { from _dst }, _in_links { from _doc }\n several destinations: their union\n (one step dedups by identity)\n follow ^docs { _out_links exists { _dst_path == ^^_path } }\n a destination block over the root's\n documents, re-read per frontier row\n (^ = that row) = the backlinks walk\n follow _out_links { from _dst frontier type == \"practitioner\" } cut, keeping the frontier row\n docs { type == \"process\" frontier !verified follow _out_links { from _dst } }\n the BODY's frontier applies to the seeds:\n an unverified seed is kept, not expanded\n follow distinct _out_links { from _dst } / \u2026 { $key: group from _dst } identity control ($key)\n follow refs(before), ^docs { ^_path in list(after) } forward over `before`, backward\n over every doc whose `after` names me\n Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|\n depth|cycle), $leaf/$frontier, $ordinal (a deterministic 1..N rank over the\n walk, ordered by depth then path \u2014 `order by $ordinal` for walk order,\n `where $ordinal <= N` for a deterministic budget cut), $key \u2014 usable in\n select/order by and the top-level post-walk where (a where conjunct that\n reads one filters the walk's RESULT; the other conjuncts remain the seed\n predicate), NOT metadata of the candidates a destination block's where reads;\n a consumer directive may carry a follow of its own (its block yields the\n walk: `_children count { follow _children } > 5`). Cyclic graphs are safe: a revisited\n node is admitted once as a `$stop == \"cycle\"` occurrence and never\n re-expanded. The default keeps one occurrence per distinct walk path;\n `follow distinct` dedups reached rows by identity.\n Destination blocks (OQX 0.20): a brace after ANY destination opens that\n destination's block \u2014 _children { depth 2 }, refs(x) { depth 2 }, _out_links\n { from _dst depth 2 }, ^docs { ^_path in list(after) } \u2014 with the\n 0.17 body rules (where-first, projections) and two clauses of its own after\n `where`: frontier <pred> (occurrences reached through this destination that\n match are frontiers; ^ there is the walk's enclosing scope) and depth <n>\n (this destination expands a frontier row only while $depth < n). The body\n that carries the follow may write frontier <pred> too, after its where and\n before follow: it applies to the SEEDS (read like the body's where) \u2014 a\n matching seed stays in the result with $stop == \"frontier\" and is not\n expanded; each destination's frontier is its own. There is no options block;\n the old `by` is $key. A bare follow ^docs (no brace, no consumer) is an\n error naming both block spellings.\n Type-preserving relations: a block's _children; a section node's _children and\n _subsections; the citation graph is _out_links { from _dst } / _in_links { from _doc }.\n links { _dst_path == \"/notes/x.md\" } inbound links to a doc, as rows\n links { select d: _dst._title, k: _dst_kind } the destination document's title\n docs { _out_links exists { _dst.layer == \"draft\" } } links into drafts\n docs { select b: _in_links distinct { from _doc } } backlinks as { _id, _path }\n links { select _doc_id, _dst_path where _predicate == \"depends_on\" }\n docs { not _in_links exists } orphans (nothing links in)\n (The `graph` tool is a convenience wrapper that compiles to a follow query.)\n\n## semantic grain\n\ndocs { \u2026 } ranks whole DOCUMENTS (one hit per file); blocks { \u2026 } ranks PASSAGES.\ntext()/where prune the candidate set; only order by reweights.\n\n## Examples\n\n docs { layer == \"working\" }\n docs { _path.startsWith(\"/guides/\") and _updated_at >= \"2026-08-01\" }\n docs { select layer, tags where \"pricing\" in list(tags) }\n docs { _inline.owner == \"alice\" }\n docs { _title.lower().contains(\"q3 plan\") }\n docs { _format == \"yaml\" }\n docs { type == \"practitioner\" order by era desc limit 2 }\n docs { _nodes none { _kind == \"md:task\" and not checked } }\n docs { tags exists { $it == \"tria-prima\" } }\n docs { select fm: entries(_data) { k: $key, v: $it } where _path == \"/x.md\" }\n docs { select owner_id, owner: ^nodes single { _kind == \"person\" and _attrs.id == ^owner_id } }\n blocks { _type == \"task\" and not checked and under_heading(\"Launch\") and _doc.layer == \"working\" }\n blocks { _type == \"paragraph\" and has_edge(\"references\", \"d_92aaaaa\") }\n docs { select _path, $depth where _path == \"/timeline/review.md\" follow refs(before), refs(after) }\n docs { select _path, next: ^docs { _path where ^_path in list(before) } where type == \"milestone\" }\n docs { select _path, n: size(^docs), repo: ^_id limit 1 }\n docs { select path, title where has(path) }\n docs { type == \"substance\" and not verified }\n docs { select subject, process: ^docs[slug == ^subject]!._path where type == \"lab-note\" }\n docs { select _path, back: _nodes first { select p: 0^docs { _path where ^^_path in list(after) } values } where type == \"milestone\" }\n docs { select _path, prior: refs(before) { _path, when } where type == \"milestone\" }\n blocks { order by semantic(\"identity preservation across edits\") desc limit 10 }\n links { select _uri where _dst_kind == \"external\" }\n docs count { layer == \"canon\" }\n revisions { select _seq, _origin, _ts, _hash where _path == \"/processes/calcination.md\" order by _seq desc }\n docs { _revisions count { _origin == \"api\" } > 0 }\n revisions { select p: _path, d: _doc._path where _doc == null }\n docs { select layer, n: count(), titles: collect(_title), open: rows count { _nodes exists { _kind == \"md:task\" and not checked } } group by layer having count() > 1 order by count() desc }\n docs { select _path, tag: tags where \"process\" in tags flatten tags order by _path, tags }\n\nCommon mistake: `$path`/`$id` \u2014 omgbase has no $ names; the engine's location and\nidentity are `_path`/`_id`. Another: the 4.x bare `path`/`id`/`title`/`tags` \u2014 a\nbare `path` is now YOUR `path:` key (absent when you wrote none, with a hint\npointing at `_path`); the engine's are `_path`/`_id`/`_title`/`_tags`; `select\ndata.path` is gone \u2014 write `path`. Another: `where tags == \"x\"` on a list-valued\nkey is false \u2014 write `\"x\" in tags`. Another: `select _path: _doc._path` \u2014 alias it\n(`p: _doc._path`).\n";
2
2
  /**
3
3
  * The manual's chapters (spec/surface §4 `query_syntax`, 5.1): one entry per
4
4
  * `## ` heading of `QUERY_SYNTAX`, in order — the `section` argument's names.
@@ -26,4 +26,4 @@ export declare function querySyntaxSection(slug: string): string | null;
26
26
  * extracts the manual's). Keep every line earning its place: this is the most
27
27
  * read text on the surface.
28
28
  */
29
- export declare const QUERY_SYNTAX_CARD = "# query \u2014 OQX card. Chapters: query_syntax { section: \"<name>\" }; the manual: { section: \"all\" }\n\nOne OQX string; limit/cursor page the hits; bare: true drops the { _id, _path } envelope.\n [select <items>] from <target> [where <pred>] [follow <dest> { \u2026 }] [order by <expr> [desc]] [limit N] [offset N]\n <target> count|exists|none|first|single { <block> } a scalar or one row\nCLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset.\nand / or / not. Comparisons strict and typed; null == a missing field. A list key: \"x\" in tags, never tags == \"x\".\nStrings .contains(\"s\") .startsWith(\"p\") .lower(); x ~* \"re\" (ci regex), x !~ \"re\". Dates date(\"today\"),\ndate(due) < date(\"today\").minus(dur(\"30 days\")), _updated_at >= \"2026-08-01\".\n\nNAMES: a BARE name is YOURS (frontmatter, inline key:: value, a block's attrs); an UNDERSCORE name is the ENGINE's;\n$ is OQX's ($it $key $depth $stop $ordinal). title tags path id are your keys; _title _tags _path _id the engine's.\n docs _id _path _raw _hash _updated_at _created_at _title _tags _format | _data _frontmatter _inline | _blocks _nodes _out_links _in_links _revisions\n blocks _id _path _doc_id _raw _hash _type _text _attrs _depth _ordinal | _doc | _children _nodes _out_links _section\n nodes _id _path _doc_id _block_id _kind _name _value _attrs | _doc _block | _blocks _subsections _children\n links _id _path _doc_id _block_id _via_id _dst_id _dst_path _uri _dst_kind _predicate _provenance _anchor _field | _doc _dst _block\n revisions _id _path _rev _seq _doc_id _commit _ts _origin _actor _reason _hash _is_current _archived | _doc\nHandles _data.<k> \u2261 <k>, _frontmatter.<k> / _inline.<k>; entries(_data) the bag as rows ($key, $it). Reach-through\n_doc.<k> the owning document, _dst.<k> a link's target. Scoping: a bare name is the CURRENT row; ^name one scope\nout; ^docs ^blocks ^nodes ^links the repository from a row. An unknown bare name is absent plus a hints line.\n\nNESTED: <receiver> collect|exists|none|count|first|single { [select \u2026] [where \u2026] [order by \u2026] [limit N] } over a\nrelation, a list key, entries(x) or ^docs; no consumer = collect, a leading predicate = where: docs { type == \"x\" }.\nFOLLOW: from docs where <seed> follow _out_links collect { from _dst depth 3 } links out; _in_links { from _doc }\nbacklinks; refs(before) a property's documents; $depth per row. SEARCH: text(\"words\") prunes; order by semantic(\"\u2026\") desc.\n\n from docs where layer == \"working\"\n select _title, stage from docs where \"process\" in tags order by _path limit 5\n select _type, _text from blocks where _path == \"/processes/calcination.md\"\n select _path, _value from nodes where _kind == \"md:task\" and not checked\n select _path, _block_id from links where _dst_path == \"/processes/calcination.md\"\n from docs where _title.lower().contains(\"salt\") and _updated_at >= \"2026-01-01\"\n from docs where _nodes exists { where _kind == \"md:task\" and not checked }\n select _path, $depth from docs where _path == \"/timeline/review.md\" follow refs(before), refs(after)\n docs count { where layer == \"canon\" }\n select _seq, _ts, _hash from revisions where _path == \"/processes/calcination.md\" order by _seq desc\n select tags, n: count() from docs flatten tags group by tags order by count() desc\n\nEvery error is filter_invalid naming what to change; when the engine can correct the call, data.fix IS the call.\nChapters: sugar paths targets scoping scalars predicates nested select flatten order follow semantic examples; all.\n";
29
+ export declare const QUERY_SYNTAX_CARD = "# query \u2014 OQX card. Chapters: query_syntax { section: \"<name>\" }; the manual: { section: \"all\" }\n\nOne OQX string; limit/cursor page the hits; bare: true drops the { _id, _path } envelope.\n <target> { [select <items>] [where <pred>] [follow <dest> { \u2026 }] [order by <expr> [desc]] [limit N] [offset N] }\n <target> count|exists|none|first|single [{ <block> }] a scalar or one row; no block = the empty block (docs count)\nThe collection (docs blocks nodes links revisions) heads the body; nothing follows its closing brace.\nCLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset.\nand / or / not. Comparisons strict and typed; null == a missing field. A list key: \"x\" in tags, never tags == \"x\".\nStrings .contains(\"s\") .startsWith(\"p\") .lower(); x ~* \"re\" (ci regex), x !~ \"re\". Dates date(\"today\"),\ndate(due) < date(\"today\").minus(dur(\"30 days\")), _updated_at >= \"2026-08-01\".\n\nNAMES: a BARE name is YOURS (frontmatter, inline key:: value, a block's attrs); an UNDERSCORE name is the ENGINE's;\n$ is OQX's ($it $key $depth $stop $ordinal). title tags path id are your keys; _title _tags _path _id the engine's.\n docs _id _path _raw _hash _updated_at _created_at _title _tags _format | _data _frontmatter _inline | _blocks _nodes _out_links _in_links _revisions\n blocks _id _path _doc_id _raw _hash _type _text _attrs _depth _ordinal | _doc | _children _nodes _out_links _section\n nodes _id _path _doc_id _block_id _kind _name _value _attrs | _doc _block | _blocks _subsections _children\n links _id _path _doc_id _block_id _via_id _dst_id _dst_path _uri _dst_kind _predicate _provenance _anchor _field | _doc _dst _block\n revisions _id _path _rev _seq _doc_id _commit _ts _origin _actor _reason _hash _is_current _archived | _doc\nHandles _data.<k> \u2261 <k>, _frontmatter.<k> / _inline.<k>; entries(_data) the bag as rows ($key, $it). Reach-through\n_doc.<k> the owning document, _dst.<k> a link's target. Scoping: a bare name is the CURRENT row; ^name one scope\nout; ^docs ^blocks ^nodes ^links the repository from a row. An unknown bare name is absent plus a hints line.\n\nNESTED: <receiver> collect|exists|none|count|first|single { [select \u2026] [where \u2026] [order by \u2026] [limit N] } over a\nrelation, a list key, entries(x) or ^docs; no consumer = collect, a leading predicate = where: docs { type == \"x\" }.\nFOLLOW: docs { where <seed> follow _out_links { from _dst depth 3 } } links out; _in_links { from _doc }\nbacklinks; refs(before) a property's documents; $depth per row. SEARCH: text(\"words\") prunes; order by semantic(\"\u2026\") desc.\n\n docs { layer == \"working\" }\n docs { select _title, stage where \"process\" in tags order by _path limit 5 }\n blocks { select _type, _text where _path == \"/processes/calcination.md\" }\n nodes { select _path, _value where _kind == \"md:task\" and not checked }\n links { select _path, _block_id where _dst_path == \"/processes/calcination.md\" }\n docs { _title.lower().contains(\"salt\") and _updated_at >= \"2026-01-01\" }\n docs { _nodes exists { _kind == \"md:task\" and not checked } }\n docs { select _path, $depth where _path == \"/timeline/review.md\" follow refs(before), refs(after) }\n docs count { layer == \"canon\" }\n revisions { select _seq, _ts, _hash where _path == \"/processes/calcination.md\" order by _seq desc }\n docs { select tags, n: count() flatten tags group by tags order by count() desc }\n\nEvery error is filter_invalid naming what to change; when the engine can correct the call, data.fix IS the call.\nChapters: sugar paths targets scoping scalars predicates nested select flatten order follow semantic examples; all.\n";
@@ -24,25 +24,32 @@ host object model (\`_doc.layer\`, \`_section._blocks\`, \`_dst._title\`); WHITE
24
24
  query directives belong to OQX — a nested query is \`<receiver> collect|exists|
25
25
  count|first|single { <block> }\` (a postfix directive + a \`{ … }\` block), never a
26
26
  method. Its distinctive power is the receiver-constrained nested query that
27
- correlates to the current row: \`from docs where _nodes exists { where _kind ==
28
- "md:task" }\` returns only the docs that themselves contain a matching node.
29
-
30
- [select <items>] from <target> [where <pred>] [frontier <pred>] [follow <dest> [{ … }], …]
31
- [flatten <expr> [{ … }] [as <name>]]… [group by <key>, …] [having <pred>]
32
- [order by <expr> [asc|desc], …] [limit N] [offset N]
33
- <target> count|exists|none|first|single { <block> } (scalar/one-row form: a bare target at the root scope)
34
-
27
+ correlates to the current row: \`docs { _nodes exists { _kind == "md:task" } }\`
28
+ returns only the docs that themselves contain a matching node.
29
+
30
+ <target> { [select <items>] [where <pred>] [frontier <pred>] [follow <dest> [{ … }], …]
31
+ [flatten <expr> [{ … }] [as <name>]]… [group by <key>, …] [having <pred>]
32
+ [order by <expr> [asc|desc], …] [limit N] [offset N] }
33
+ <target> count|exists|none|first|single { <block> } (a scalar or one row; <target> is docs, blocks, nodes, links or revisions)
34
+
35
+ THE COLLECTION HEADS THE BODY (OQX 0.26): \`docs { … }\` reads the documents and
36
+ shapes them with the clauses inside the braces; a consumer word between the
37
+ target and the brace (\`docs count { … }\`) shapes the whole result; nothing may
38
+ follow the closing brace. The pre-0.26 clause form \`select … from docs where …\`
39
+ is still read as the same query — write the braces.
35
40
  CLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset —
36
- each at most once (\`flatten\` may repeat); an out-of-order clause is a parse error naming the order.
41
+ each at most once (\`flatten\` may repeat; \`from\` inside a body RE-PROJECTS the
42
+ rows through a relation, see Nested — it never names the source); an
43
+ out-of-order clause is a parse error naming the order.
37
44
  Only \`select\` may drop its keyword, and only as the first clause
38
- (\`_path, era from docs where …\`). A top-level predicate needs \`where\`, because
39
- \`where\` follows \`from\` (\`era > 1600 from docs\` is the clause-order error); a
40
- block may LEAD with a predicate (see Sugar), but a predicate after a projection
41
- still needs the keyword (\`{ _name where _kind == "md:task" }\`, not
42
- \`{ _name, _kind == "md:task" }\`); \`from docs count\` is an error (write
43
- \`docs count { … }\`). \`where\` and later \`select\` items may use the same body's
44
- \`select\` aliases (\`select _path, old: era < 1000 from docs where old\`; an alias
45
- shadows a same-named field there). \`and\` / \`or\` / \`not\` are the connectives;
45
+ (\`docs { _path, era where … }\`). A body may LEAD with a predicate — \`docs { era
46
+ > 1600 }\` filters (see Sugar) — but a predicate after a projection still needs
47
+ the keyword (\`{ _name where _kind == "md:task" }\`, not \`{ _name, _kind ==
48
+ "md:task" }\`); in the clause form a top-level predicate needs \`where\` (\`era >
49
+ 1600 from docs\` is the clause-order error), and \`from docs count\` is an error
50
+ (write \`docs count { … }\`). \`where\` and later \`select\` items may use the same
51
+ body's \`select\` aliases (\`docs { select _path, old: era < 1000 where old }\`; an
52
+ alias shadows a same-named field there). \`and\` / \`or\` / \`not\` are the connectives;
46
53
  the symbols \`&&\` / \`||\` / \`!\` are accepted synonyms (the words are canonical and
47
54
  what \`print\` writes).
48
55
 
@@ -73,13 +80,13 @@ Every form here is shorthand for an explicit directive (the AST is the explicit
73
80
  boss: ^docs[_path == ^manager], bossName: boss._title a select item may use the items to its LEFT
74
81
  Reserved words (never a bare field name): from where select is not and or, true false null.
75
82
  Contextual words (limit order offset follow collect count first distinct desc by in frontier depth group having
76
- flatten as) parse as fields in expression position (select o: order from docs).
83
+ flatten as) parse as fields in expression position (docs { select o: order }).
77
84
 
78
85
  Results are LEAN hits — { _id, _path } + whatever \`select\` projects (or \`values\`,
79
86
  \`count\`, \`exists\`, \`none\`). The envelope keys ARE the attributes: copy a hit's
80
87
  \`_id\` straight into \`where _id == "…"\` (no translation); \`select _id, _path\` is
81
88
  simply the envelope (no duplicate columns); an alias \`_id:\`/\`_path:\` on any OTHER
82
- expression is refused — \`select _path: _doc._path from blocks\` fails ("projection
89
+ expression is refused — \`blocks { select _path: _doc._path }\` fails ("projection
83
90
  \`_path\` collides with the hit envelope — alias it (\`p: _doc._path\`)"); a row
84
91
  surfacing as a value (an empty \`collect { }\`) is the same \`{ _id, _path }\`. Every
85
92
  other tool spells a row the same way (\`docs_read\` → \`{ _id, _path, rev, properties,
@@ -110,8 +117,8 @@ UNDERSCORE-prefixed name is the ENGINE's (however derived: \`_id\`, \`_path\`,
110
117
  $stop, $leaf, $frontier, $ordinal are values the LANGUAGE injects into a scope;
111
118
  omgbase adds NO $ names (a \`$id\` in a query is an unknown name, absent). Nothing
112
119
  bare is reserved: \`title\`, \`tags\`, \`path\`, \`id\`, \`format\`, \`doc\` are your keys
113
- when you wrote them. Collections stay bare (\`from docs\`, \`^docs\`, \`docs count { … }\`,
114
- \`entries(^$it)\`) — \`from\` names a collection, not a row property — and so do the
120
+ when you wrote them. Collections stay bare (\`docs { … }\`, \`^docs\`, \`docs count { … }\`,
121
+ \`entries(^$it)\`) — the collection before a body is a root, not a row property — and so do the
115
122
  row functions in call position (\`text("x")\`, \`under(…)\`, \`refs(x)\`): a call is
116
123
  never an entry read. There is NO self alias (the 4.x \`doc\`/\`block\`/\`section\`):
117
124
  \`$it\` is the self, and \`_doc\`/\`_block\`/\`_dst\` reach ANOTHER row. So \`_path\` is the
@@ -161,8 +168,8 @@ docs:
161
168
  is the filter).
162
169
  - relations = _nodes, _blocks, _out_links / _in_links (a doc's links as
163
170
  rows; the documents at their other end are
164
- _out_links collect distinct { from _dst } and
165
- _in_links collect distinct { from _doc } — there is no
171
+ _out_links distinct { from _dst } and
172
+ _in_links distinct { from _doc } — there is no
166
173
  out / in relation), _revisions (the document's stored
167
174
  revisions, oldest first — see revisions below).
168
175
 
@@ -233,7 +240,7 @@ revisions: (history as rows — one per stored revision of every document of th
233
240
  a deleted document, so where _doc == null is the revisions
234
241
  of deleted documents. Closed: a bare name is filter_invalid.
235
242
  - on docs = _revisions is a relation (oldest first): a document with
236
- more than one version is docs { _revisions count { } > 1 }.
243
+ more than one version is docs { _revisions count > 1 }.
237
244
  - text()/semantic() are N/A on revisions. The body as of a revision is
238
245
  docs_read { _path, at: "<rev>" } (docs_read_at is the same call).
239
246
 
@@ -251,7 +258,7 @@ revisions: (history as rows — one per stored revision of every document of th
251
258
  absolute 0^docs from ANY depth. ^_id / 0^_id = the repository id.
252
259
  ^$it = the root object: ^$it.docs ≡ ^docs, entries(^$it) names the
253
260
  five collections. At the root scope itself a bare target is the scan
254
- (docs count { … }, from docs, entries(docs)).
261
+ (docs { … }, docs count { … }, entries(docs)).
255
262
  ERRORS: a bare \`docs\` INSIDE a block reads a property of the current
256
263
  row, which has none — refused ("did you mean ^docs"); so is a bare
257
264
  \`blocks\`/\`nodes\`/\`revisions\` there (a doc's own relations are
@@ -342,7 +349,7 @@ revisions: (history as rows — one per stored revision of every document of th
342
349
  follow refs(before), refs(after) walks a timeline both ways.
343
350
  The reverse direction needs no function: a document's
344
351
  _path is already the reference form, so
345
- ^docs { where ^_path in list(after) } is
352
+ ^docs { ^_path in list(after) } is
346
353
  "the documents whose \`after\` names me".
347
354
  A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails
348
355
  (filter_invalid). \`follow before\` over a list of paths
@@ -351,22 +358,28 @@ revisions: (history as rows — one per stored revision of every document of th
351
358
  ## Nested queries & consumers
352
359
 
353
360
  <receiver> collect|exists|none|count|first|single { <block> }
361
+ The block may be omitted after a consumer word (OQX 0.26): the omitted block is
362
+ the empty block — \`_nodes exists\`, \`_revisions count > 1\`, \`_blocks first\`,
363
+ \`docs count\` as a whole query; \`x { }\` keeps its braces (the brace is what
364
+ makes a collect). A projection block over a lookup is the lookup's projection:
365
+ \`_blocks[0] { _text }\` ≡ \`_blocks first { _text offset 0 }\`.
354
366
  receiver = a relation (_nodes, _blocks, _doc._out_links, _section._blocks, …), a
355
367
  reach-through row (_dst, _doc: one row — _dst { _title }), a
356
368
  list property (tags), entries(x), or a root collection (^docs; 0^docs at depth ≥ 2).
357
369
  block = [select …] [from E] [where …] [frontier …] [follow …] [flatten …] [group by …] [having …] [order by …] [limit N] [offset N]
358
- — the same fixed order as the top level (\`from\` optional; the
359
- receiver supplies the rows). A leading name/alias list is the
360
- projection (\`select\` dropped); a predicate always needs \`where\`.
361
- A \`from E\` inside a block RE-PROJECTS the rows through a relation
362
- (a typed flatMap): \`docs collect { from _nodes where _kind == "md:task" }\`
363
- starts at docs, projects each through \`_nodes\`, and queries the
364
- resulting nodes; a scalar relation (\`from _doc\`) contributes one
365
- row; a missing or non-navigable relation is a loud error, never a
366
- global scan.
367
- exists ≥1 row _nodes exists { where _kind == "md:task" and not checked }
368
- none 0 rows _nodes none { where _kind == "md:task" and not checked } ("every task done")
369
- count a number _nodes count { where _kind == "md:task" } >= 2
370
+ — the same fixed order as the whole query, whose body is a block
371
+ too (the target is its receiver). A leading name/alias list is the
372
+ projection (\`select\` dropped); a predicate needs \`where\` unless
373
+ it LEADS the block (Sugar). A \`from E\` inside a block RE-PROJECTS
374
+ the rows through a relation (a typed flatMap):
375
+ \`docs { from _nodes where _kind == "md:task" }\` starts at docs,
376
+ projects each through \`_nodes\`, and queries the resulting nodes;
377
+ a scalar relation (\`from _doc\`) contributes one row; a missing or
378
+ non-navigable relation is a loud error, never a global scan. \`from\`
379
+ never names the source — the collection before the body does.
380
+ exists ≥1 row _nodes exists { _kind == "md:task" and not checked }
381
+ none 0 rows _nodes none { _kind == "md:task" and not checked } ("every task done")
382
+ count a number _nodes count { _kind == "md:task" } >= 2
370
383
  collect rows (default) — in select for nested results; in where with a lift
371
384
  first / single zero-or-one (single errors on >1) — lookups in select
372
385
  distinct on any consumer: dedup by projected value — _nodes count distinct { select _kind }
@@ -374,9 +387,9 @@ revisions: (history as rows — one per stored revision of every document of th
374
387
  consumer reduces: _nodes exists { offset 1 } = at least two;
375
388
  first { … offset 1 } = the second.
376
389
  lift \`^name:\` in a where-collect binds values outward:
377
- select _path, open from docs where _nodes collect { ^open: _value where _kind == "md:task" and not checked }
378
- joins ^<t> exists { where slug == ^ref } semi-join; not … exists anti-join;
379
- ^nodes single { where _kind == "person" and _attrs.id == ^owner_id } lookup;
390
+ docs { select _path, open where _nodes { ^open: _value where _kind == "md:task" and not checked } }
391
+ joins ^<t> exists { slug == ^ref } semi-join; not … exists anti-join;
392
+ ^nodes single { _kind == "person" and _attrs.id == ^owner_id } lookup;
380
393
  x in ^keys membership over a lifted set.
381
394
 
382
395
  ## select — projection
@@ -389,13 +402,13 @@ revisions: (history as rows — one per stored revision of every document of th
389
402
  _raw whole file bytes (docs) — docs_read is cheaper for one doc.
390
403
  distinct select distinct _type (dedup hits by projected value)
391
404
  values select <ONE item> values → bare values instead of records:
392
- select distinct type values from docs → values: ["hub","lab-note",…]
393
- select _path values from docs → values: ["/index.md", …]
394
- tags: tags collect { $it values } → a plain array
405
+ docs { select distinct type values } → values: ["hub","lab-note",…]
406
+ docs { select _path values } → values: ["/index.md", …]
407
+ tags: tags { $it values } → a plain array
395
408
  h: _nodes first { _name values where _kind == "md:section" order by first_ordinal }
396
409
  entries(x) a record as a collection ($key / $it per entry), key order:
397
- select fm: entries(_data) collect { k: $key, v: $it }
398
- where entries(_frontmatter) exists { where $key == "era" and $it > 1600 }
410
+ select fm: entries(_data) { k: $key, v: $it }
411
+ where entries(_frontmatter) exists { $key == "era" and $it > 1600 }
399
412
  entries(_inline), entries(_attrs), a nested map, or a list (index keys)
400
413
  $semantic_score is not a field — project semantic("…") under an alias instead.
401
414
 
@@ -413,9 +426,9 @@ revisions: (history as rows — one per stored revision of every document of th
413
426
  name — a later flatten (flatten jobs as j flatten j.tags as t), group by,
414
427
  having, select, order by; where runs before it. A flattened query pages by
415
428
  position (cursor null); as _id / as _path is refused (the envelope reads them).
416
- select _path, tags from docs where type == "process" flatten tags order by _path, tags
417
- select _path, t: b._text from docs flatten _blocks { where _type == "task" and not checked } as b
418
- select tags, n: count() from docs flatten tags group by tags order by count() desc
429
+ docs { select _path, tags where type == "process" flatten tags order by _path, tags }
430
+ docs { select _path, t: b._text flatten _blocks { _type == "task" and not checked } as b }
431
+ docs { select tags, n: count() flatten tags group by tags order by count() desc }
419
432
 
420
433
  ## order by, limit/offset, pagination
421
434
 
@@ -430,26 +443,26 @@ revisions: (history as rows — one per stored revision of every document of th
430
443
  The citation graph is the LINK ROWS walked through a destination block:
431
444
  _dst is a link's destination document (absent for an external or dangling
432
445
  link — it then contributes no successor), _doc its source.
433
- from docs where <seed> follow _out_links collect { from _dst } docs the seed links TO
434
- from docs where <seed> follow _in_links collect { from _doc } backlinks
435
- follow _out_links collect { from _dst depth 3 } bound (1..8, default 8) — a clause of the block
436
- follow _out_links collect { from _dst where layer == "canon" } keep only matching successors
437
- follow _in_links collect { from _doc where ^_path in list(before) } ^ = the row being expanded
446
+ docs { where <seed> follow _out_links { from _dst } } docs the seed links TO
447
+ docs { where <seed> follow _in_links { from _doc } } backlinks
448
+ follow _out_links { from _dst depth 3 } bound (1..8, default 8) — a clause of the block
449
+ follow _out_links { from _dst where layer == "canon" } keep only matching successors
450
+ follow _in_links { from _doc where ^_path in list(before) } ^ = the row being expanded
438
451
  (^^ = the walk's enclosing scope)
439
- follow _out_links collect { from _dst where _predicate == "depends_on" } one predicate only
440
- follow _out_links collect { from _dst }, _in_links collect { from _doc }
452
+ follow _out_links { from _dst where _predicate == "depends_on" } one predicate only
453
+ follow _out_links { from _dst }, _in_links { from _doc }
441
454
  several destinations: their union
442
455
  (one step dedups by identity)
443
- follow ^docs { where _out_links exists { where _dst_path == ^^_path } }
456
+ follow ^docs { _out_links exists { _dst_path == ^^_path } }
444
457
  a destination block over the root's
445
458
  documents, re-read per frontier row
446
459
  (^ = that row) = the backlinks walk
447
- follow _out_links collect { from _dst frontier type == "practitioner" } cut, keeping the frontier row
448
- from docs where type == "process" frontier !verified follow _out_links collect { from _dst }
460
+ follow _out_links { from _dst frontier type == "practitioner" } cut, keeping the frontier row
461
+ docs { type == "process" frontier !verified follow _out_links { from _dst } }
449
462
  the BODY's frontier applies to the seeds:
450
463
  an unverified seed is kept, not expanded
451
- follow distinct _out_links collect { from _dst } / … { $key: group from _dst } identity control ($key)
452
- follow refs(before), ^docs { where ^_path in list(after) } forward over \`before\`, backward
464
+ follow distinct _out_links { from _dst } / … { $key: group from _dst } identity control ($key)
465
+ follow refs(before), ^docs { ^_path in list(after) } forward over \`before\`, backward
453
466
  over every doc whose \`after\` names me
454
467
  Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|
455
468
  depth|cycle), $leaf/$frontier, $ordinal (a deterministic 1..N rank over the
@@ -457,14 +470,15 @@ revisions: (history as rows — one per stored revision of every document of th
457
470
  \`where $ordinal <= N\` for a deterministic budget cut), $key — usable in
458
471
  select/order by and the top-level post-walk where (a where conjunct that
459
472
  reads one filters the walk's RESULT; the other conjuncts remain the seed
460
- predicate), NOT metadata of the candidates a destination block's where reads,
461
- nor valid inside a consumer directive. Cyclic graphs are safe: a revisited
473
+ predicate), NOT metadata of the candidates a destination block's where reads;
474
+ a consumer directive may carry a follow of its own (its block yields the
475
+ walk: \`_children count { follow _children } > 5\`). Cyclic graphs are safe: a revisited
462
476
  node is admitted once as a \`$stop == "cycle"\` occurrence and never
463
477
  re-expanded. The default keeps one occurrence per distinct walk path;
464
478
  \`follow distinct\` dedups reached rows by identity.
465
479
  Destination blocks (OQX 0.20): a brace after ANY destination opens that
466
480
  destination's block — _children { depth 2 }, refs(x) { depth 2 }, _out_links
467
- collect { from _dst depth 2 }, ^docs { where ^_path in list(after) } — with the
481
+ { from _dst depth 2 }, ^docs { ^_path in list(after) } — with the
468
482
  0.17 body rules (where-first, projections) and two clauses of its own after
469
483
  \`where\`: frontier <pred> (occurrences reached through this destination that
470
484
  match are frontiers; ^ there is the walk's enclosing scope) and depth <n>
@@ -476,51 +490,51 @@ revisions: (history as rows — one per stored revision of every document of th
476
490
  the old \`by\` is $key. A bare follow ^docs (no brace, no consumer) is an
477
491
  error naming both block spellings.
478
492
  Type-preserving relations: a block's _children; a section node's _children and
479
- _subsections; the citation graph is _out_links collect { from _dst } / _in_links collect { from _doc }.
480
- from links where _dst_path == "/notes/x.md" inbound links to a doc, as rows
481
- select d: _dst._title, k: _dst_kind from links the destination document's title
482
- from docs where _out_links exists { where _dst.layer == "draft" } links into drafts
483
- select b: _in_links collect distinct { from _doc } from docs backlinks as { _id, _path }
484
- select _doc_id, _dst_path from links where _predicate == "depends_on"
485
- from docs where not _in_links exists { } orphans (nothing links in)
493
+ _subsections; the citation graph is _out_links { from _dst } / _in_links { from _doc }.
494
+ links { _dst_path == "/notes/x.md" } inbound links to a doc, as rows
495
+ links { select d: _dst._title, k: _dst_kind } the destination document's title
496
+ docs { _out_links exists { _dst.layer == "draft" } } links into drafts
497
+ docs { select b: _in_links distinct { from _doc } } backlinks as { _id, _path }
498
+ links { select _doc_id, _dst_path where _predicate == "depends_on" }
499
+ docs { not _in_links exists } orphans (nothing links in)
486
500
  (The \`graph\` tool is a convenience wrapper that compiles to a follow query.)
487
501
 
488
502
  ## semantic grain
489
503
 
490
- from docs ranks whole DOCUMENTS (one hit per file); from blocks ranks PASSAGES.
504
+ docs { … } ranks whole DOCUMENTS (one hit per file); blocks { … } ranks PASSAGES.
491
505
  text()/where prune the candidate set; only order by reweights.
492
506
 
493
507
  ## Examples
494
508
 
495
- from docs where layer == "working"
496
- from docs where _path.startsWith("/guides/") and _updated_at >= "2026-08-01"
497
- select layer, tags from docs where "pricing" in list(tags)
498
- from docs where _inline.owner == "alice"
499
- from docs where _title.lower().contains("q3 plan")
500
- from docs where _format == "yaml"
501
- from docs where type == "practitioner" order by era desc limit 2
502
- from docs where _nodes none { where _kind == "md:task" and not checked }
503
- from docs where tags exists { where $it == "tria-prima" }
504
- select fm: entries(_data) collect { k: $key, v: $it } from docs where _path == "/x.md"
505
- select owner_id, owner: ^nodes single { where _kind == "person" and _attrs.id == ^owner_id } from docs
506
- from blocks where _type == "task" and not checked and under_heading("Launch") and _doc.layer == "working"
507
- from blocks where _type == "paragraph" and has_edge("references", "d_92aaaaa")
508
- select _path, $depth from docs where _path == "/timeline/review.md" follow refs(before), refs(after)
509
- select _path, next: ^docs collect { _path where ^_path in list(before) } from docs where type == "milestone"
510
- select _path, n: size(^docs), repo: ^_id from docs limit 1
511
- select path, title from docs where has(path)
509
+ docs { layer == "working" }
510
+ docs { _path.startsWith("/guides/") and _updated_at >= "2026-08-01" }
511
+ docs { select layer, tags where "pricing" in list(tags) }
512
+ docs { _inline.owner == "alice" }
513
+ docs { _title.lower().contains("q3 plan") }
514
+ docs { _format == "yaml" }
515
+ docs { type == "practitioner" order by era desc limit 2 }
516
+ docs { _nodes none { _kind == "md:task" and not checked } }
517
+ docs { tags exists { $it == "tria-prima" } }
518
+ docs { select fm: entries(_data) { k: $key, v: $it } where _path == "/x.md" }
519
+ docs { select owner_id, owner: ^nodes single { _kind == "person" and _attrs.id == ^owner_id } }
520
+ blocks { _type == "task" and not checked and under_heading("Launch") and _doc.layer == "working" }
521
+ blocks { _type == "paragraph" and has_edge("references", "d_92aaaaa") }
522
+ docs { select _path, $depth where _path == "/timeline/review.md" follow refs(before), refs(after) }
523
+ docs { select _path, next: ^docs { _path where ^_path in list(before) } where type == "milestone" }
524
+ docs { select _path, n: size(^docs), repo: ^_id limit 1 }
525
+ docs { select path, title where has(path) }
512
526
  docs { type == "substance" and not verified }
513
- select subject, process: ^docs[slug == ^subject]!._path from docs where type == "lab-note"
514
- select _path, back: _nodes first { select p: 0^docs collect { _path where ^^_path in list(after) } values } from docs where type == "milestone"
515
- select _path, prior: refs(before) collect { _path, when } from docs where type == "milestone"
516
- from blocks order by semantic("identity preservation across edits") desc limit 10
517
- select _uri from links where _dst_kind == "external"
518
- docs count { where layer == "canon" }
519
- select _seq, _origin, _ts, _hash from revisions where _path == "/processes/calcination.md" order by _seq desc
520
- from docs where _revisions count { where _origin == "api" } > 0
521
- select p: _path, d: _doc._path from revisions where _doc == null
522
- select layer, n: count(), titles: collect(_title), open: rows count { where _nodes exists { where _kind == "md:task" and not checked } } from docs group by layer having count() > 1 order by count() desc
523
- select _path, tag: tags from docs where "process" in tags flatten tags order by _path, tags
527
+ docs { select subject, process: ^docs[slug == ^subject]!._path where type == "lab-note" }
528
+ docs { select _path, back: _nodes first { select p: 0^docs { _path where ^^_path in list(after) } values } where type == "milestone" }
529
+ docs { select _path, prior: refs(before) { _path, when } where type == "milestone" }
530
+ blocks { order by semantic("identity preservation across edits") desc limit 10 }
531
+ links { select _uri where _dst_kind == "external" }
532
+ docs count { layer == "canon" }
533
+ revisions { select _seq, _origin, _ts, _hash where _path == "/processes/calcination.md" order by _seq desc }
534
+ docs { _revisions count { _origin == "api" } > 0 }
535
+ revisions { select p: _path, d: _doc._path where _doc == null }
536
+ docs { select layer, n: count(), titles: collect(_title), open: rows count { _nodes exists { _kind == "md:task" and not checked } } group by layer having count() > 1 order by count() desc }
537
+ docs { select _path, tag: tags where "process" in tags flatten tags order by _path, tags }
524
538
 
525
539
  Common mistake: \`$path\`/\`$id\` — omgbase has no $ names; the engine's location and
526
540
  identity are \`_path\`/\`_id\`. Another: the 4.x bare \`path\`/\`id\`/\`title\`/\`tags\` — a
@@ -587,8 +601,9 @@ export function querySyntaxSection(slug) {
587
601
  export const QUERY_SYNTAX_CARD = `# query — OQX card. Chapters: query_syntax { section: "<name>" }; the manual: { section: "all" }
588
602
 
589
603
  One OQX string; limit/cursor page the hits; bare: true drops the { _id, _path } envelope.
590
- [select <items>] from <target> [where <pred>] [follow <dest> { … }] [order by <expr> [desc]] [limit N] [offset N]
591
- <target> count|exists|none|first|single { <block> } a scalar or one row
604
+ <target> { [select <items>] [where <pred>] [follow <dest> { … }] [order by <expr> [desc]] [limit N] [offset N] }
605
+ <target> count|exists|none|first|single [{ <block> }] a scalar or one row; no block = the empty block (docs count)
606
+ The collection (docs blocks nodes links revisions) heads the body; nothing follows its closing brace.
592
607
  CLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset.
593
608
  and / or / not. Comparisons strict and typed; null == a missing field. A list key: "x" in tags, never tags == "x".
594
609
  Strings .contains("s") .startsWith("p") .lower(); x ~* "re" (ci regex), x !~ "re". Dates date("today"),
@@ -607,20 +622,20 @@ out; ^docs ^blocks ^nodes ^links the repository from a row. An unknown bare name
607
622
 
608
623
  NESTED: <receiver> collect|exists|none|count|first|single { [select …] [where …] [order by …] [limit N] } over a
609
624
  relation, a list key, entries(x) or ^docs; no consumer = collect, a leading predicate = where: docs { type == "x" }.
610
- FOLLOW: from docs where <seed> follow _out_links collect { from _dst depth 3 } links out; _in_links { from _doc }
625
+ FOLLOW: docs { where <seed> follow _out_links { from _dst depth 3 } } links out; _in_links { from _doc }
611
626
  backlinks; refs(before) a property's documents; $depth per row. SEARCH: text("words") prunes; order by semantic("…") desc.
612
627
 
613
- from docs where layer == "working"
614
- select _title, stage from docs where "process" in tags order by _path limit 5
615
- select _type, _text from blocks where _path == "/processes/calcination.md"
616
- select _path, _value from nodes where _kind == "md:task" and not checked
617
- select _path, _block_id from links where _dst_path == "/processes/calcination.md"
618
- from docs where _title.lower().contains("salt") and _updated_at >= "2026-01-01"
619
- from docs where _nodes exists { where _kind == "md:task" and not checked }
620
- select _path, $depth from docs where _path == "/timeline/review.md" follow refs(before), refs(after)
621
- docs count { where layer == "canon" }
622
- select _seq, _ts, _hash from revisions where _path == "/processes/calcination.md" order by _seq desc
623
- select tags, n: count() from docs flatten tags group by tags order by count() desc
628
+ docs { layer == "working" }
629
+ docs { select _title, stage where "process" in tags order by _path limit 5 }
630
+ blocks { select _type, _text where _path == "/processes/calcination.md" }
631
+ nodes { select _path, _value where _kind == "md:task" and not checked }
632
+ links { select _path, _block_id where _dst_path == "/processes/calcination.md" }
633
+ docs { _title.lower().contains("salt") and _updated_at >= "2026-01-01" }
634
+ docs { _nodes exists { _kind == "md:task" and not checked } }
635
+ docs { select _path, $depth where _path == "/timeline/review.md" follow refs(before), refs(after) }
636
+ docs count { layer == "canon" }
637
+ revisions { select _seq, _ts, _hash where _path == "/processes/calcination.md" order by _seq desc }
638
+ docs { select tags, n: count() flatten tags group by tags order by count() desc }
624
639
 
625
640
  Every error is filter_invalid naming what to change; when the engine can correct the call, data.fix IS the call.
626
641
  Chapters: sugar paths targets scoping scalars predicates nested select flatten order follow semantic examples; all.
@@ -102,7 +102,7 @@ const ladder = (...lines) => lines.join("\n");
102
102
  // The `docs_read` ladder's time-travel rung (4.7): the revision id is the one
103
103
  // the spec minter gives `/processes/calcination.md`'s ingest revision over the
104
104
  // alchemy corpus (`examples.json` runs the rung before any write).
105
- const REVISION_RUNG = "{\"_path\":\"/processes/calcination.md\",\"at\":\"r_7\"} as of a revision (rev ids: from revisions where _path == \"/processes/calcination.md\")";
105
+ const REVISION_RUNG = "{\"_path\":\"/processes/calcination.md\",\"at\":\"r_7\"} as of a revision (rev ids: revisions { _rev where _path == \"/processes/calcination.md\" })";
106
106
  function ok(payload) {
107
107
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
108
108
  }
@@ -534,7 +534,7 @@ export function buildServer(ctx) {
534
534
  }
535
535
  });
536
536
  tool("nodes_block_read", {
537
- description: "Read the block a NODE was projected from: `_id` is the node's id (`n_…`, from `query` over `from nodes`) and the result is exactly what blocks_read returns for that block — the same resolutions (skeleton|outline|text|raw|full, default full), the same `_id`/`_type`/`_raw`/`_hash`/`_text`/`_attrs`/`_ordinal`/`_depth`/`parent_id`. The hop from an occurrence (an `md:link`, `md:task`, `md:section`, … node) to the editable block that holds it, in one call; blocks_read itself takes a block id only. An unknown node, or a node that names no live block (a block-less node, such as one derived from the frontmatter), is block_missing.",
537
+ description: "Read the block a NODE was projected from: `_id` is the node's id (`n_…`, from `query` over `nodes { … }`) and the result is exactly what blocks_read returns for that block — the same resolutions (skeleton|outline|text|raw|full, default full), the same `_id`/`_type`/`_raw`/`_hash`/`_text`/`_attrs`/`_ordinal`/`_depth`/`parent_id`. The hop from an occurrence (an `md:link`, `md:task`, `md:section`, … node) to the editable block that holds it, in one call; blocks_read itself takes a block id only. An unknown node, or a node that names no live block (a block-less node, such as one derived from the frontmatter), is block_missing.",
538
538
  inputSchema: {
539
539
  _id: z.string(),
540
540
  resolution: z.enum(["skeleton", "outline", "text", "raw", "full"]).optional(),
@@ -575,7 +575,7 @@ export function buildServer(ctx) {
575
575
  }
576
576
  });
577
577
  tool("docs_tree", {
578
- description: ladder("docs_tree — Orientation: the repo's directories with document and block counts, one small page (tree -L depth -d + du).", "{} the root: one row per directory ({ _path ending in /, docs, blocks }), files_here = documents at the root itself, total", "{\"path\":\"/processes/\",\"files\":true} descend (path is the prefix) and list its documents too ({ _id, _path, blocks })", "{\"depth\":2,\"updated\":true} nested directories to depth 2; updated adds _updated_at; limit/cursor page the rows", "Find documents by attribute, not by path: query (select _path from docs where ...). Grammar: query_syntax."),
578
+ description: ladder("docs_tree — Orientation: the repo's directories with document and block counts, one small page (tree -L depth -d + du).", "{} the root: one row per directory ({ _path ending in /, docs, blocks }), files_here = documents at the root itself, total", "{\"path\":\"/processes/\",\"files\":true} descend (path is the prefix) and list its documents too ({ _id, _path, blocks })", "{\"depth\":2,\"updated\":true} nested directories to depth 2; updated adds _updated_at; limit/cursor page the rows", "Find documents by attribute, not by path: query (docs { select _path where ... }). Grammar: query_syntax."),
579
579
  inputSchema: {
580
580
  path: z.string().optional(),
581
581
  depth: z.number().int().optional(),
@@ -648,7 +648,7 @@ export function buildServer(ctx) {
648
648
  }
649
649
  });
650
650
  tool("query", {
651
- description: ladder("query — rows (_id, _path, projection) from docs, blocks, nodes or links.", "{\"query\":\"select _title, stage from docs where \\\"process\\\" in tags order by _path limit 5\"} frontmatter; in; order, limit", "{\"query\":\"select _type, _text from blocks where _path == \\\"/processes/calcination.md\\\"\"} a document's blocks in order", "{\"query\":\"select _block_id from nodes where _kind == \\\"md:section\\\" and _name == \\\"Tasks\\\" and _path == \\\"/processes/calcination.md\\\"\"} a heading's block id", "{\"query\":\"select _path, _value from nodes where _kind == \\\"md:task\\\" and not checked\"} open tasks", "{\"query\":\"select _path, _block_id from links where _dst_path == \\\"/processes/calcination.md\\\"\"} inbound links (_path = source)", "{\"query\":\"select _path from docs where _title ~* \\\"salt\\\"\"} regex (~* ci, !~ not); substring: .contains(\\\"s\\\")", "Also docs count { where … }, follow, ^docs joins, text()/semantic(): query_syntax."),
651
+ description: ladder("query — rows (_id, _path, projection) from docs, blocks, nodes or links.", "{\"query\":\"docs { select _title, stage where \\\"process\\\" in tags order by _path limit 5 }\"} frontmatter; in; order, limit", "{\"query\":\"blocks { select _type, _text where _path == \\\"/processes/calcination.md\\\" }\"} a document's blocks in order", "{\"query\":\"nodes { select _block_id where _kind == \\\"md:section\\\" and _name == \\\"Tasks\\\" and _path == \\\"/processes/calcination.md\\\" }\"} a heading's block id", "{\"query\":\"nodes { select _path, _value where _kind == \\\"md:task\\\" and not checked }\"} open tasks", "{\"query\":\"links { select _path, _block_id where _dst_path == \\\"/processes/calcination.md\\\" }\"} inbound links (_path = source)", "{\"query\":\"docs { select _path where _title ~* \\\"salt\\\" }\"} regex (~* ci, !~ not); substring: .contains(\\\"s\\\")", "Also docs count { where … }, follow, ^docs joins, text()/semantic(): query_syntax."),
652
652
  inputSchema: {
653
653
  query: z.string(),
654
654
  limit: z.number().int().optional(),
@@ -676,7 +676,7 @@ export function buildServer(ctx) {
676
676
  }
677
677
  });
678
678
  tool("graph", {
679
- description: "Neighborhood macro: the bounded graph AROUND one or more root documents in ONE call — { documents, links, frontier } — so you can orient without hand-writing an OQX `follow`. This is a CONVENIENCE WRAPPER, not a new engine: it compiles its args into an OQX `follow _out_links collect { from _dst }` / `follow _in_links collect { from _doc }` query and runs it through the same `query` path (the returned `queries` field is the exact follow query it generated). It does NOT replace `follow` or `from links` — reach for the `query` tool directly when you need successor/frontier predicates, `$key`-keyed identity, `$ordinal` budgets, cross-document correlation, or the links scan with its own projections. `roots` are one or more doc refs (paths and/or ids); they are depth 0. `degrees` is the max hop distance (root = 0; maps to `follow … collect { … depth degrees+1 }`, capped so degrees+1 ≤ 8; default 1). `direction` is `out` (outgoing links), `in` (backlinks), or `both` (default). `predicate` restricts the neighborhood to documents reachable from a root via edges of that predicate (both the documents and the edges are filtered). `select` adds document projections (OQX select expressions, e.g. \"layer\", \"_title\"). `max_documents` caps the distinct document set (default 200) with a `truncated` flag. Returns: `documents` (each with `_id`/`_path`/`degree` = min hops from a root/`frontier` bool + your projections — a name selection such as \"layer\" or \"_title\" is its own key, `_id`/`_path` land on the envelope keys, and any other expression is `sel_<i>`), `links` (the traversed links as rows of the `links` target under its own field names — `_id`, `_doc_id`/`_block_id` (the source), `_via_id`, `_dst_id`, `_dst_path`, `_uri`, `_dst_kind`, `_predicate`, `_provenance`, `_anchor`, `_field`; external `x_…` and dangling phantom endpoints are preserved as edge stubs, exactly as `from links` surfaces them), and `frontier` (the documents on the outer boundary — `{ _id, _path, degree }`, min degree == degrees; at degrees 0 that is the roots themselves).",
679
+ description: "Neighborhood macro: the bounded graph AROUND one or more root documents in ONE call — { documents, links, frontier } — so you can orient without hand-writing an OQX `follow`. This is a CONVENIENCE WRAPPER, not a new engine: it compiles its args into an OQX `follow _out_links { from _dst }` / `follow _in_links { from _doc }` query and runs it through the same `query` path (the returned `queries` field is the exact follow query it generated). It does NOT replace `follow` or `from links` — reach for the `query` tool directly when you need successor/frontier predicates, `$key`-keyed identity, `$ordinal` budgets, cross-document correlation, or the links scan with its own projections. `roots` are one or more doc refs (paths and/or ids); they are depth 0. `degrees` is the max hop distance (root = 0; maps to `follow … collect { … depth degrees+1 }`, capped so degrees+1 ≤ 8; default 1). `direction` is `out` (outgoing links), `in` (backlinks), or `both` (default). `predicate` restricts the neighborhood to documents reachable from a root via edges of that predicate (both the documents and the edges are filtered). `select` adds document projections (OQX select expressions, e.g. \"layer\", \"_title\"). `max_documents` caps the distinct document set (default 200) with a `truncated` flag. Returns: `documents` (each with `_id`/`_path`/`degree` = min hops from a root/`frontier` bool + your projections — a name selection such as \"layer\" or \"_title\" is its own key, `_id`/`_path` land on the envelope keys, and any other expression is `sel_<i>`), `links` (the traversed links as rows of the `links` target under its own field names — `_id`, `_doc_id`/`_block_id` (the source), `_via_id`, `_dst_id`, `_dst_path`, `_uri`, `_dst_kind`, `_predicate`, `_provenance`, `_anchor`, `_field`; external `x_…` and dangling phantom endpoints are preserved as edge stubs, exactly as `from links` surfaces them), and `frontier` (the documents on the outer boundary — `{ _id, _path, degree }`, min degree == degrees; at degrees 0 that is the roots themselves).",
680
680
  inputSchema: {
681
681
  roots: stringList({ min: 1 }),
682
682
  degrees: z.number().int().optional(),
@@ -691,7 +691,7 @@ export function buildServer(ctx) {
691
691
  const { repoId } = repoScope(args.repo);
692
692
  // A `select` using semantic(...) needs a provider — surface the specific
693
693
  // code, mirroring the `query` tool.
694
- if (!ctx.embedQuery && (args.select ?? []).some((s) => collectSemanticPhrases(`from docs select x: ${s}`).length > 0))
694
+ if (!ctx.embedQuery && (args.select ?? []).some((s) => collectSemanticPhrases(`docs { select x: ${s} }`).length > 0))
695
695
  throw providerUnavailable();
696
696
  return ok(await graphNeighborhood(store, repoId, args, rankableEmbed(repoId)));
697
697
  }
@@ -729,7 +729,7 @@ export function buildServer(ctx) {
729
729
  }
730
730
  });
731
731
  tool("apply", {
732
- description: ladder("apply — plan and commit in one call; returns the Receipt { plan_id, commit, revisions, new_ids, committed, summary }.", "{\"effects\":[{\"op\":\"set\",\"target\":\"/substances/salt.md\",\"field\":\"_frontmatter.verified\",\"value\":false}],\"reason\":\"re-check salt\"} one scalar; a bare path is a document target", "{\"effects\":[{\"op\":\"insert\",\"into\":\"/lab/2026-02-notes.md\",\"at\":\"end\",\"markdown\":\"## Follow-ups\\n\\n- [ ] re-run cycle 4\"}]} append at a document's end", "{\"select\":\"from blocks where _type == \\\"task\\\" and not checked and _path == \\\"/processes/calcination.md\\\"\",\"effects\":[{\"op\":\"set\",\"field\":\"_attrs.checked\",\"value\":true}],\"expect\":{\"max\":10}} bulk check-off; expect.max is REQUIRED with select", "Plan first for anything you cannot undo by hand; every error's data.fix is a corrected call to copy. Grammar: query_syntax."),
732
+ description: ladder("apply — plan and commit in one call; returns the Receipt { plan_id, commit, revisions, new_ids, committed, summary }.", "{\"effects\":[{\"op\":\"set\",\"target\":\"/substances/salt.md\",\"field\":\"_frontmatter.verified\",\"value\":false}],\"reason\":\"re-check salt\"} one scalar; a bare path is a document target", "{\"effects\":[{\"op\":\"insert\",\"into\":\"/lab/2026-02-notes.md\",\"at\":\"end\",\"markdown\":\"## Follow-ups\\n\\n- [ ] re-run cycle 4\"}]} append at a document's end", "{\"select\":\"blocks { _type == \\\"task\\\" and not checked and _path == \\\"/processes/calcination.md\\\" }\",\"effects\":[{\"op\":\"set\",\"field\":\"_attrs.checked\",\"value\":true}],\"expect\":{\"max\":10}} bulk check-off; expect.max is REQUIRED with select", "Plan first for anything you cannot undo by hand; every error's data.fix is a corrected call to copy. Grammar: query_syntax."),
733
733
  inputSchema: {
734
734
  effects: z.array(effectSchema),
735
735
  select: z.string().optional(),
@@ -755,7 +755,7 @@ export function buildServer(ctx) {
755
755
  // commits exactly a plan (the plan form, or the digest form `plan_id` +
756
756
  // `effects`); `apply { effects }` above is plan + execute in one call.
757
757
  tool("plan", {
758
- description: ladder("plan — Preview: the Plan (summary, $new.n ids) burns no id. Commit: execute { plan_id }. Bodies: diffs: true.", "{\"effects\":[{\"op\":\"set\",\"target\":\"/processes/calcination.md\",\"field\":\"_frontmatter.verified\",\"value\":false}]} path = doc", "{\"effects\":[{\"op\":\"insert\",\"into\":{\"kind\":\"section\",\"heading_id\":\"b_153\"},\"markdown\":\"- [ ] Weigh the calx\"}]} in a section", "{\"effects\":[{\"op\":\"create\",\"path\":\"lab/march.md\",\"markdown\":\"# March\\n\"},{\"op\":\"insert\",\"into\":\"$new.0\",\"markdown\":\"Hi.\"}]} fill it: $new.0", "{\"select\":\"from docs where stage == \\\"nigredo\\\"\",\"effects\":[{\"op\":\"set\",\"field\":\"_frontmatter.ok\",\"value\":true}]} no target: the hit", "{\"select\":\"from docs where type == \\\"lab-note\\\"\",\"effects\":[{\"op\":\"insert\",\"into\":\"-> `/processes/${subject}.md`\",\"markdown\":\"-> `[${_title}](${_path})`\"}]} -> reads the row", "Ops: create insert set unset move remove split merge retarget; grammar: query_syntax."),
758
+ description: ladder("plan — Preview: the Plan (summary, $new.n ids) burns no id. Commit: execute { plan_id }. Bodies: diffs: true.", "{\"effects\":[{\"op\":\"set\",\"target\":\"/processes/calcination.md\",\"field\":\"_frontmatter.verified\",\"value\":false}]} path = doc", "{\"effects\":[{\"op\":\"insert\",\"into\":{\"kind\":\"section\",\"heading_id\":\"b_153\"},\"markdown\":\"- [ ] Weigh the calx\"}]} in a section", "{\"effects\":[{\"op\":\"create\",\"path\":\"lab/march.md\",\"markdown\":\"# March\\n\"},{\"op\":\"insert\",\"into\":\"$new.0\",\"markdown\":\"Hi.\"}]} fill it: $new.0", "{\"select\":\"docs { stage == \\\"nigredo\\\" }\",\"effects\":[{\"op\":\"set\",\"field\":\"_frontmatter.ok\",\"value\":true}]} no target: the hit", "{\"select\":\"docs { type == \\\"lab-note\\\" }\",\"effects\":[{\"op\":\"insert\",\"into\":\"-> `/processes/${subject}.md`\",\"markdown\":\"-> `[${_title}](${_path})`\"}]} -> reads the row", "Ops: create insert set unset move remove split merge retarget; grammar: query_syntax."),
759
759
  inputSchema: { effects: z.array(effectSchema), select: z.string().optional(), expect: selectionExpectSchema.optional(), diffs: z.boolean().optional(), ...REPO_ARG },
760
760
  }, async (args) => {
761
761
  try {
@@ -837,7 +837,7 @@ export function buildServer(ctx) {
837
837
  }
838
838
  });
839
839
  tool("blocks_update", {
840
- description: "Replace a block's markdown and/or set a task's `checked` state, with compare-and-swap. `_id` is the block's id (or any ref resolve accepts); `expect.content_hash` is pinned to the block's current bytes server-side when omitted (protects against a concurrent edit). `markdown` may parse to SEVERAL sibling blocks (e.g. a paragraph followed by a list): the target keeps its id and takes the first, the rest are inserted right after it with fresh ids — the Receipt's `new_ids` lists them (`$new.0`, …). A list item may likewise be replaced by a multi-item list (`- a\\n- b`), yielding sibling items. Task state is settable flat as `checked` (the same name reads flatten to — `from blocks where checked`), synced to the `[ ]`/`[x]` in the raw; every OTHER block attr (heading `level`, code `lang`, list `ordered`) is derived from the markdown, so set those by editing `markdown` (the 3.x `attrs` bag is retired and refused by name). Sugar for `apply { effects: [{ op: \"set\", target: { kind: \"block\", _id }, field: \"markdown\" | \"_attrs.checked\", value, expect? }] }` (both when both are given) — returns the Receipt; preview with `plan`.",
840
+ description: "Replace a block's markdown and/or set a task's `checked` state, with compare-and-swap. `_id` is the block's id (or any ref resolve accepts); `expect.content_hash` is pinned to the block's current bytes server-side when omitted (protects against a concurrent edit). `markdown` may parse to SEVERAL sibling blocks (e.g. a paragraph followed by a list): the target keeps its id and takes the first, the rest are inserted right after it with fresh ids — the Receipt's `new_ids` lists them (`$new.0`, …). A list item may likewise be replaced by a multi-item list (`- a\\n- b`), yielding sibling items. Task state is settable flat as `checked` (the same name reads flatten to — `blocks { checked }`), synced to the `[ ]`/`[x]` in the raw; every OTHER block attr (heading `level`, code `lang`, list `ordered`) is derived from the markdown, so set those by editing `markdown` (the 3.x `attrs` bag is retired and refused by name). Sugar for `apply { effects: [{ op: \"set\", target: { kind: \"block\", _id }, field: \"markdown\" | \"_attrs.checked\", value, expect? }] }` (both when both are given) — returns the Receipt; preview with `plan`.",
841
841
  inputSchema: { _id: z.string(), markdown: z.string().optional(), checked: z.boolean().optional(), expect: expectSchema.optional(), ...REPO_ARG },
842
842
  }, async (args) => {
843
843
  try {
@@ -938,7 +938,7 @@ export function buildServer(ctx) {
938
938
  }
939
939
  });
940
940
  tool("nodes_set", {
941
- description: "Macro: surgically set one editable property of a projected node (e.g. a link's `name` text or `value` target, a task's `checked`). `_id` is the node's id (from `query` over `from nodes` — the one tool that addresses `nodes` rows), `prop` the property, `value` its new value. Resolves the node to its block and rewrites only that node's span. Nodes are read-only projections — this is the affordance to edit what a node represents without hand-rewriting the block. Errors node_not_editable (with the editable prop list) when the kind/prop has no editor. Sugar for `apply { effects: [{ op: \"set\", target: { kind: \"node\", _id }, field: prop, value }] }` — returns the Receipt; preview with `plan` (node_set before surface 3.0).",
941
+ description: "Macro: surgically set one editable property of a projected node (e.g. a link's `name` text or `value` target, a task's `checked`). `_id` is the node's id (from `query` over `nodes { … }` — the one tool that addresses `nodes` rows), `prop` the property, `value` its new value. Resolves the node to its block and rewrites only that node's span. Nodes are read-only projections — this is the affordance to edit what a node represents without hand-rewriting the block. Errors node_not_editable (with the editable prop list) when the kind/prop has no editor. Sugar for `apply { effects: [{ op: \"set\", target: { kind: \"node\", _id }, field: prop, value }] }` — returns the Receipt; preview with `plan` (node_set before surface 3.0).",
942
942
  inputSchema: { _id: z.string(), prop: z.string(), value: z.string(), ...REPO_ARG },
943
943
  }, async (args) => {
944
944
  try {
@@ -1266,7 +1266,7 @@ export function buildServer(ctx) {
1266
1266
  }
1267
1267
  });
1268
1268
  tool("docs_read_at", {
1269
- description: "TIME-TRAVEL read: the whole file bytes of a document AS OF a past revision — `_raw` is the reconstructed source at that `rev` (fences/tables/list markers preserved), plus `_id`/`_path` (the path as of that revision)/`rev`. Sugar for `docs_read { _id | _path, at: rev }` (surface 4.7) — the same result, byte for byte. Use this to audit or answer 'what did this doc say at revision N', or to fetch the exact prior text before reverting. Get a `rev` from `query` over the `revisions` target (`from revisions where _path == … order by _seq`), docs_diff, blocks_history, docs_history or changes_since. Contrast: docs_read = current bytes; docs_diff = block-grain changes BETWEEN two revisions. `renderedHashMatch` is true when the reconstruction is byte-for-byte the file at that revision; it can be false for an old revision ONLY when the document's leading/frontmatter trivia (separator bytes, not block content) changed since — block content always reconstructs faithfully. No `properties`: the property table is current-state only (through 4.6 the current bag rode along flagged propertiesAreCurrent). Address the document by `_id` or `_path` plus a `rev` id. A revision whose bodies were moved to an archive segment (`_archived` in docs_history / the revisions target) reads through the segment when it is attached; otherwise the call fails `revision_archived` with `data: { rev, archive, path? }` — an operator attaches the file with `omg history attach`.",
1269
+ description: "TIME-TRAVEL read: the whole file bytes of a document AS OF a past revision — `_raw` is the reconstructed source at that `rev` (fences/tables/list markers preserved), plus `_id`/`_path` (the path as of that revision)/`rev`. Sugar for `docs_read { _id | _path, at: rev }` (surface 4.7) — the same result, byte for byte. Use this to audit or answer 'what did this doc say at revision N', or to fetch the exact prior text before reverting. Get a `rev` from `query` over the `revisions` target (`revisions { _path == … order by _seq }`), docs_diff, blocks_history, docs_history or changes_since. Contrast: docs_read = current bytes; docs_diff = block-grain changes BETWEEN two revisions. `renderedHashMatch` is true when the reconstruction is byte-for-byte the file at that revision; it can be false for an old revision ONLY when the document's leading/frontmatter trivia (separator bytes, not block content) changed since — block content always reconstructs faithfully. No `properties`: the property table is current-state only (through 4.6 the current bag rode along flagged propertiesAreCurrent). Address the document by `_id` or `_path` plus a `rev` id. A revision whose bodies were moved to an archive segment (`_archived` in docs_history / the revisions target) reads through the segment when it is attached; otherwise the call fails `revision_archived` with `data: { rev, archive, path? }` — an operator attaches the file with `omg history attach`.",
1270
1270
  inputSchema: { ...DOC_ARGS, rev: z.string(), ...REPO_ARG },
1271
1271
  }, async (args) => {
1272
1272
  try {
@@ -63,8 +63,8 @@ function describeValue(v) {
63
63
  // OQX has no expression entry point: each `{ oqx: e }` is parsed through this
64
64
  // wrapper and the one select item taken. A top-level item is scope depth 1 —
65
65
  // exactly the hit's scope — so `^` and `N^` validate as they will evaluate.
66
- const WRAPPER_PREFIX = "select __omg_x: ";
67
- const WRAPPER_SUFFIX = " from docs";
66
+ const WRAPPER_PREFIX = "docs { select __omg_x: ";
67
+ const WRAPPER_SUFFIX = " }";
68
68
  const OFFSET_RE = /\(at offset (\d+)\)/;
69
69
  /** Parse one expression; a parse error's offset is shifted back by the wrapper and forward by `shift` (the `-> ` leader's 3). */
70
70
  function parseExpression(src, here, shift = 0) {
@@ -80,7 +80,8 @@ function parseExpression(src, here, shift = 0) {
80
80
  throw selectionInvalid(`${here}: ${message}`);
81
81
  }
82
82
  // The whole source must have been one expression: a query smuggled into the
83
- // slot (`x from blocks where …`) parses, but not as one item over `docs`.
83
+ // slot (`x where … }` closing the wrapper early does not; `x }` + `{ … }`
84
+ // cannot) parses, but not as one item over `docs`.
84
85
  const one = q.select.length === 1 && q.source.kind === "ident" && q.source.name === "docs" && q.from.length === 0 && q.where === null
85
86
  && q.orderBy === null && q.follow === null && q.frontier === null && q.limit === null && q.offset === null && !q.distinct && !q.values && q.consumer === "collect";
86
87
  if (!one)
@@ -570,7 +571,7 @@ function admitSelector(select, request) {
570
571
  throw selectionInvalid("select: `revisions` rows have no effect target — select docs, blocks or nodes", rootFix(select, raw.source, request));
571
572
  }
572
573
  if (root === null)
573
- throw selectionInvalid("select: a selector scans a root collection — `from docs`, `from blocks` or `from nodes`", rootFix(select, raw.source, request));
574
+ throw selectionInvalid("select: a selector scans a root collection — `docs { … }`, `blocks { … }` or `nodes { … }`", rootFix(select, raw.source, request));
574
575
  if (root === "links")
575
576
  throw selectionInvalid("select: `links` rows have no effect target — select docs, blocks or nodes", rootFix(select, raw.source, request));
576
577
  if (raw.follow !== null)
@@ -11,6 +11,16 @@ export interface ExternalProvider {
11
11
  * failure. Callers must call close() when done.
12
12
  */
13
13
  export declare function createExternalProvider(settings: EmbeddingSettings): Promise<ExternalProvider | null>;
14
+ /**
15
+ * The hint appended to a spawn failure when the command names a JavaScript file
16
+ * (`.js`/`.mjs`/`.cjs`) and the OS refused it with EACCES or ENOENT: a built
17
+ * `bin.js` carries a `#!/usr/bin/env node` shebang but not always the executable
18
+ * bit, and the engine spawns the provider directly, so the fix is to run it through
19
+ * node. Empty for any other command or error. The Rust twin
20
+ * (`crates/omgbase-search/src/external.rs`, `spawn_hint`) emits the same text
21
+ * byte for byte; the tests of both pin it.
22
+ */
23
+ export declare function spawnHint(provider: string, code: string | undefined): string;
14
24
  /**
15
25
  * The environment a stdio embedder is spawned with: our own env, with the
16
26
  * repo's explicit `embedding.model` / `dim` / `maxInputTokens` settings laid over
@@ -70,7 +70,7 @@ async function connectStdio(settings) {
70
70
  child = spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"], env: embedderEnv(settings) });
71
71
  }
72
72
  catch (err) {
73
- throw new Error(`embedding command '${settings.provider}' failed to spawn: ${err.message}`);
73
+ throw new Error(`embedding command '${settings.provider}' failed to spawn: ${err.message}${spawnHint(settings.provider, err.code)}`);
74
74
  }
75
75
  child.stderr.pipe(process.stderr);
76
76
  // Don't let a still-running embedder keep our own event loop alive on exit.
@@ -78,7 +78,7 @@ async function connectStdio(settings) {
78
78
  const rl = createInterface({ input: child.stdout });
79
79
  const lines = lineQueue(rl);
80
80
  const spawnFailed = new Promise((_, reject) => {
81
- child.once("error", (err) => reject(new Error(`embedding command '${cmd}' failed to spawn: ${err.message}`)));
81
+ child.once("error", (err) => reject(new Error(`embedding command '${cmd}' failed to spawn: ${err.message}${spawnHint(settings.provider, err.code)}`)));
82
82
  child.once("exit", (code) => reject(new Error(`embedding command '${cmd}' exited early (code ${code ?? "?"})`)));
83
83
  });
84
84
  // The race consumers below observe this rejection; guard the reference itself
@@ -153,6 +153,24 @@ async function connectStdio(settings) {
153
153
  };
154
154
  return { provider, close };
155
155
  }
156
+ /**
157
+ * The hint appended to a spawn failure when the command names a JavaScript file
158
+ * (`.js`/`.mjs`/`.cjs`) and the OS refused it with EACCES or ENOENT: a built
159
+ * `bin.js` carries a `#!/usr/bin/env node` shebang but not always the executable
160
+ * bit, and the engine spawns the provider directly, so the fix is to run it through
161
+ * node. Empty for any other command or error. The Rust twin
162
+ * (`crates/omgbase-search/src/external.rs`, `spawn_hint`) emits the same text
163
+ * byte for byte; the tests of both pin it.
164
+ */
165
+ export function spawnHint(provider, code) {
166
+ const spec = provider.trim();
167
+ const cmd = spec.split(/\s+/)[0] ?? "";
168
+ if (!/\.[cm]?js$/i.test(cmd))
169
+ return "";
170
+ if (code !== "EACCES" && code !== "ENOENT")
171
+ return "";
172
+ return ` Hint: the command names a JavaScript file; run it through node — set embedding.provider to \`node ${spec}\` — or give the file the executable bit`;
173
+ }
156
174
  /**
157
175
  * The environment a stdio embedder is spawned with: our own env, with the
158
176
  * repo's explicit `embedding.model` / `dim` / `maxInputTokens` settings laid over
@@ -1,5 +1,5 @@
1
1
  export declare const SPEC_VERSIONS: {
2
- readonly oqx: "0.25";
2
+ readonly oqx: "0.26";
3
3
  readonly format: "0.2";
4
4
  readonly reconcile: "2.3";
5
5
  readonly store: "16.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/core",
3
- "version": "0.29.0",
3
+ "version": "0.29.2",
4
4
  "description": "Open Markdown Graph Base — embedded engine",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@modelcontextprotocol/sdk": "^1.30.0",
58
- "@omgbase/oqx": "^0.25.0",
58
+ "@omgbase/oqx": "^0.26.0",
59
59
  "better-sqlite3": "^13.0.3",
60
60
  "mdast-util-from-markdown": "^2.0.2",
61
61
  "mdast-util-frontmatter": "^2.0.1",