@omgbase/core 0.28.0 → 0.29.0

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.
Files changed (71) hide show
  1. package/dist/src/core/read/document.d.ts +11 -10
  2. package/dist/src/core/read/document.js +12 -11
  3. package/dist/src/core/read/nodes.d.ts +10 -10
  4. package/dist/src/core/read/nodes.js +11 -11
  5. package/dist/src/core/read/reader.d.ts +37 -24
  6. package/dist/src/core/read/reader.js +44 -31
  7. package/dist/src/graph/history.d.ts +25 -22
  8. package/dist/src/graph/history.js +17 -17
  9. package/dist/src/graph/inbound-links.d.ts +7 -7
  10. package/dist/src/graph/inbound-links.js +7 -7
  11. package/dist/src/graph/link-health.d.ts +9 -9
  12. package/dist/src/graph/link-health.js +9 -9
  13. package/dist/src/graph/links.d.ts +3 -3
  14. package/dist/src/graph/links.js +4 -4
  15. package/dist/src/mcp/arguments.d.ts +10 -3
  16. package/dist/src/mcp/arguments.js +32 -9
  17. package/dist/src/mcp/errors.d.ts +1 -1
  18. package/dist/src/mcp/graph.d.ts +22 -22
  19. package/dist/src/mcp/graph.js +53 -46
  20. package/dist/src/mcp/reference.d.ts +29 -1
  21. package/dist/src/mcp/reference.js +350 -222
  22. package/dist/src/mcp/server.d.ts +9 -0
  23. package/dist/src/mcp/server.js +169 -107
  24. package/dist/src/mutate/apply.d.ts +6 -6
  25. package/dist/src/mutate/apply.js +1 -1
  26. package/dist/src/mutate/canonical.d.ts +1 -1
  27. package/dist/src/mutate/canonical.js +2 -2
  28. package/dist/src/mutate/commit.d.ts +3 -3
  29. package/dist/src/mutate/commit.js +4 -4
  30. package/dist/src/mutate/docs.d.ts +1 -1
  31. package/dist/src/mutate/docs.js +8 -8
  32. package/dist/src/mutate/effects.d.ts +17 -17
  33. package/dist/src/mutate/effects.js +113 -113
  34. package/dist/src/mutate/macros.d.ts +5 -5
  35. package/dist/src/mutate/macros.js +1 -1
  36. package/dist/src/mutate/materialize.js +2 -2
  37. package/dist/src/mutate/ops.js +2 -2
  38. package/dist/src/mutate/opset.d.ts +7 -7
  39. package/dist/src/mutate/plan-update.js +2 -2
  40. package/dist/src/mutate/plan.d.ts +8 -8
  41. package/dist/src/mutate/plan.js +25 -25
  42. package/dist/src/mutate/select.d.ts +4 -4
  43. package/dist/src/mutate/select.js +52 -52
  44. package/dist/src/mutate/sugar.d.ts +10 -10
  45. package/dist/src/mutate/sugar.js +21 -21
  46. package/dist/src/oqx-js/context.d.ts +8 -4
  47. package/dist/src/oqx-js/context.js +206 -189
  48. package/dist/src/oqx-js/names.d.ts +28 -17
  49. package/dist/src/oqx-js/names.js +115 -78
  50. package/dist/src/oqx-js/planner.d.ts +4 -8
  51. package/dist/src/oqx-js/planner.js +6 -10
  52. package/dist/src/oqx-js/root-row.d.ts +6 -5
  53. package/dist/src/oqx-js/root-row.js +15 -16
  54. package/dist/src/oqx-js/run.d.ts +18 -17
  55. package/dist/src/oqx-js/run.js +57 -54
  56. package/dist/src/oqx-js/sql/translate.d.ts +5 -5
  57. package/dist/src/oqx-js/sql/translate.js +162 -161
  58. package/dist/src/oqx-js/store-index.d.ts +1 -1
  59. package/dist/src/oqx-js/store-index.js +28 -30
  60. package/dist/src/search/resolve.d.ts +2 -2
  61. package/dist/src/search/resolve.js +1 -1
  62. package/dist/src/search/rrf.js +2 -2
  63. package/dist/src/search/text.d.ts +5 -5
  64. package/dist/src/search/text.js +2 -2
  65. package/dist/src/spec-versions.d.ts +6 -6
  66. package/dist/src/spec-versions.js +6 -6
  67. package/dist/src/surface-paths.d.ts +18 -17
  68. package/dist/src/surface-paths.js +23 -21
  69. package/dist/src/sync/admin.d.ts +2 -2
  70. package/dist/src/sync/admin.js +1 -1
  71. package/package.json +1 -1
@@ -5,17 +5,26 @@
5
5
  // tool handler stays thin. Semantics are the @omgbase/oqx contract (ADR-013);
6
6
  // when this text and the corpus (corpus/oqx/alchemy.test.ts) disagree, the
7
7
  // corpus wins.
8
+ //
9
+ // Three texts (spec/surface §4 `query_syntax`, 5.1): `QUERY_SYNTAX_CARD` is
10
+ // what `query_syntax {}` returns — a ~3 kB card an agent can hold in context
11
+ // (the 2026-10-21 agent-bench fetched the 38 kB manual twelve times and
12
+ // replayed it as cached prefix on every later turn); `QUERY_SYNTAX` is the
13
+ // whole manual (`section: "all"`); `querySyntaxSection(slug)` is one chapter of
14
+ // it, by the slugs of `QUERY_SYNTAX_SECTIONS` (the manual's `##` headings). The
15
+ // Rust twin (`crates/omgbase-surface/src/reference.rs`) mirrors all three byte
16
+ // for byte; its tests compare against this file.
8
17
  export const QUERY_SYNTAX = `# query — OQX syntax reference
9
18
 
10
19
  The \`query\` tool takes ONE plain OQX string (+ optional \`limit\`/\`cursor\`, and
11
- \`bare: true\` to drop the automatic \`id\`/\`path\` envelope — needs a \`select\`).
20
+ \`bare: true\` to drop the automatic \`_id\`/\`_path\` envelope — needs a \`select\`).
12
21
  There is no {from, filter, order} envelope: every concern is a clause of the
13
22
  string. Two rules tell the two syntaxes apart: DOT navigation belongs to the
14
- host object model (\`doc.layer\`, \`section.blocks\`, \`dst.title\`); WHITESPACE
23
+ host object model (\`_doc.layer\`, \`_section._blocks\`, \`_dst._title\`); WHITESPACE
15
24
  query directives belong to OQX — a nested query is \`<receiver> collect|exists|
16
25
  count|first|single { <block> }\` (a postfix directive + a \`{ … }\` block), never a
17
26
  method. Its distinctive power is the receiver-constrained nested query that
18
- correlates to the current row: \`from docs where nodes exists { where kind ==
27
+ correlates to the current row: \`from docs where _nodes exists { where _kind ==
19
28
  "md:task" }\` returns only the docs that themselves contain a matching node.
20
29
 
21
30
  [select <items>] from <target> [where <pred>] [frontier <pred>] [follow <dest> [{ … }], …]
@@ -26,13 +35,13 @@ correlates to the current row: \`from docs where nodes exists { where kind ==
26
35
  CLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset —
27
36
  each at most once (\`flatten\` may repeat); an out-of-order clause is a parse error naming the order.
28
37
  Only \`select\` may drop its keyword, and only as the first clause
29
- (\`path, era from docs where …\`). A top-level predicate needs \`where\`, because
38
+ (\`_path, era from docs where …\`). A top-level predicate needs \`where\`, because
30
39
  \`where\` follows \`from\` (\`era > 1600 from docs\` is the clause-order error); a
31
40
  block may LEAD with a predicate (see Sugar), but a predicate after a projection
32
- still needs the keyword (\`{ name where kind == "md:task" }\`, not
33
- \`{ name, kind == "md:task" }\`); \`from docs count\` is an error (write
41
+ still needs the keyword (\`{ _name where _kind == "md:task" }\`, not
42
+ \`{ _name, _kind == "md:task" }\`); \`from docs count\` is an error (write
34
43
  \`docs count { … }\`). \`where\` and later \`select\` items may use the same body's
35
- \`select\` aliases (\`select path, old: era < 1000 from docs where old\`; an alias
44
+ \`select\` aliases (\`select _path, old: era < 1000 from docs where old\`; an alias
36
45
  shadows a same-named field there). \`and\` / \`or\` / \`not\` are the connectives;
37
46
  the symbols \`&&\` / \`||\` / \`!\` are accepted synonyms (the words are canonical and
38
47
  what \`print\` writes).
@@ -40,198 +49,218 @@ what \`print\` writes).
40
49
  ## Sugar (OQX 0.17)
41
50
 
42
51
  Every form here is shorthand for an explicit directive (the AST is the explicit form):
43
- nodes { kind == "md:task" } ≡ nodes collect { where kind == "md:task" } — a receiver block with
52
+ nodes { _kind == "md:task" } ≡ nodes collect { where _kind == "md:task" } — a receiver block with
44
53
  no consumer is collect; a block whose LEADING expression is a predicate
45
54
  (comparison, call, !/is/not, in, literal, (…), a consumer test — anything
46
- but a bare name, a dotted path or a ^lift) is where-first. Bare names still
47
- project: nodes { name } ≡ nodes collect { name }; filter a bare field with
48
- nodes { is checked }. Wherever a value stands (0.20), call arguments included:
49
- size(children { is checked }); x distinct { … } ≡ x collect distinct { … }. After a
50
- follow destination too: follow children { depth 2 } is that destination's block.
55
+ but a bare name, a dotted path or a ^lift) is where-first. Names still
56
+ project: _nodes { _name } ≡ _nodes collect { _name }; filter a bare field with
57
+ _nodes { is checked }. Wherever a value stands (0.20), call arguments included:
58
+ size(_children { is checked }); x distinct { … } ≡ x collect distinct { … }. After a
59
+ follow destination too: follow _children { depth 2 } is that destination's block.
51
60
  (a, b) a tuple (0.20): two or more expressions, concatenated one level flat — a value
52
61
  (size((tags, aliases)), "x" in (tags, aliases)) and a receiver ((a, b) { … },
53
62
  follow (before, after) { depth 2 }); (a) alone is grouping
54
63
  refs(x)[0] ≡ refs(x) first { offset 0 } positional (integer literal or \${binding});
55
- out of range ⇒ absent; refs(x)[0].path navigates the row
56
- ^docs[path == ^company] ≡ ^docs first { where path == ^company } first match or absent
57
- ^docs[path == ^company]! ≡ … single { … } required: exactly one, else filter_invalid
58
- title! required: title, or an error naming the expression (and the row's $key — its id)
64
+ out of range ⇒ absent; refs(x)[0]._path navigates the row
65
+ ^docs[_path == ^company] ≡ ^docs first { where _path == ^company } first match or absent
66
+ ^docs[_path == ^company]! ≡ … single { … } required: exactly one, else filter_invalid
67
+ _title! required: _title, or an error naming the expression (and the row's $key — its _id)
59
68
  — never a filter, never a coercion (0!, ""! are values); tightest: a!.b vs a.b!
60
69
  is x / not x truthiness of x, and its negation (prefix; \`not\` ≡ \`!\`)
61
70
  x is y / x is not y identity ($key: a store row's id, else structural) — compares rows, which == does not;
62
71
  x is null = absent; for scalars is ≡ ==; comparison precedence, no chaining
63
- a and b / a or b the connectives (≡ && / ||: same precedence, short-circuit, value: data.title or path coalesces)
64
- boss: ^docs[path == ^manager], bossName: boss.title a select item may use the items to its LEFT
72
+ a and b / a or b the connectives (≡ && / ||: same precedence, short-circuit, value: title or _title coalesces)
73
+ boss: ^docs[_path == ^manager], bossName: boss._title a select item may use the items to its LEFT
65
74
  Reserved words (never a bare field name): from where select is not and or, true false null.
66
75
  Contextual words (limit order offset follow collect count first distinct desc by in frontier depth group having
67
76
  flatten as) parse as fields in expression position (select o: order from docs).
68
77
 
69
- Results are LEAN hits — { id, path } + whatever \`select\` projects (or \`values\`,
78
+ Results are LEAN hits — { _id, _path } + whatever \`select\` projects (or \`values\`,
70
79
  \`count\`, \`exists\`, \`none\`). The envelope keys ARE the attributes: copy a hit's
71
- \`id\` straight into \`where id == "…"\` (no translation); \`select id, path\` is simply
72
- the envelope (no duplicate columns); any OTHER projection whose output key would
73
- be \`id\` or \`path\` must be aliased — \`select data.path\` is refused ("projection
74
- \`path\` collides with the hit envelope — alias it (\`p: data.path\`)"); a row
75
- surfacing as a value (an empty \`collect { }\`) is the same \`{ id, path }\`. Every
76
- other tool spells a row the same way (\`docs_read\` → \`{ id, path, rev, properties,
77
- raw }\`, \`blocks_read\` → \`{ id, type, text, raw, hash, … }\`, \`docs_list\` → \`{ id,
78
- path, updated_at, blocks }\`, a revision → \`{ id, path, hash }\`): hydrate by id via
79
- blocks_read/docs_read.
80
+ \`_id\` straight into \`where _id == "…"\` (no translation); \`select _id, _path\` is
81
+ 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
83
+ \`_path\` collides with the hit envelope — alias it (\`p: _doc._path\`)"); a row
84
+ surfacing as a value (an empty \`collect { }\`) is the same \`{ _id, _path }\`. Every
85
+ other tool spells a row the same way (\`docs_read\` → \`{ _id, _path, rev, properties,
86
+ _raw }\`, \`blocks_read\` → \`{ _id, _type, _text, _raw, _hash, … }\`, \`docs_list\` → \`{ _id,
87
+ _path, _updated_at, blocks }\`, a revision → \`{ _id, _path, _hash }\`): hydrate by \`_id\`
88
+ via blocks_read/docs_read.
80
89
 
81
90
  ## Paths
82
91
 
83
92
  Every path the surface returns is \`/\`-rooted — the form a reference is
84
93
  written in (\`[x](/projects/oqx.md)\`, \`before: [/timeline/kickoff.md]\`): a
85
- hit's \`path\`, an edge's \`dst_path\`, every tool's paths. Every path it
94
+ hit's \`_path\`, an edge's \`_dst_path\`, every tool's paths. Every path it
86
95
  accepts tolerates both forms (\`/a.md\` or \`a.md\`): tool arguments, \`refs(x)\`,
87
- \`within(...)\`, and a string LITERAL compared with \`path\`/\`dst_path\` (bare, or
88
- through a row handle: \`doc.path\`) by \`==\`/\`!=\` or passed to their
89
- \`.startsWith(...)\` (it is rooted first, so \`path == "a.md"\` and \`path == "/a.md"\`
90
- both match). A property holding a reference compares directly — and is never
91
- rooted: \`where ^path in list(after)\`, \`where customer == ^path\`,
92
- \`where data.path == "/x.md"\` (the author's \`path:\` key, as written) — no
93
- \`"/" + ...\` glue.
96
+ \`within(...)\`, and a string LITERAL compared with \`_path\`/\`_dst_path\` (on the row,
97
+ or through a row handle: \`_doc._path\`, \`_dst._path\`, \`$it._path\`) by \`==\`/\`!=\` or
98
+ passed to their \`.startsWith(...)\` (it is rooted first, so \`_path == "a.md"\` and
99
+ \`_path == "/a.md"\` both match). A property holding a reference compares directly —
100
+ and is never rooted: \`where ^_path in list(after)\`, \`where customer == ^_path\`,
101
+ \`where path == "/x.md"\` (YOUR authored \`path:\` key — the bare name — compared as
102
+ written) — no \`"/" + ...\` glue.
94
103
 
95
104
  ## Targets & field namespaces
96
105
 
97
- THE NAME RULE: \`$\` belongs to OQX — $it, $key, $depth, $stop, $leaf, $frontier,
98
- $ordinal are values the LANGUAGE injects into a scope; omgbase adds NO $ names
99
- (a \`$id\` in a query is an unknown name, absent). Every engine attribute is a BARE
100
- snake_case name that WINS on collision — the convention the relations (children,
101
- blocks, out, in, doc, frontmatter, …) always followed: a bare name that is an
102
- engine attribute reads the engine's value, never yours. Your data is bare too;
103
- where a key of yours collides with an engine name, reach it through a source
104
- handle: \`data.<k>\` (the merged authored view — frontmatter + inline, exactly the
105
- read a bare non-colliding key performs), \`frontmatter.<k>\`, \`inline.<k>\`. So
106
- \`path\` is the engine's /-rooted location and \`data.path\` your \`path:\` key;
107
- \`tags\` is your tags ∪ the body #hashtags and \`data.tags\` the authored list alone;
108
- \`title\` the computed title (the first H1) and \`data.title\` your \`title:\`;
109
- \`entries(data)\` lists every authored top-level key. Same value, same name
110
- everywhere: a hit's \`id\` is docs_read's \`id\` and the \`id\` argument of blocks_read.
111
-
112
- Reserved bare names per kind (engine attributes; handles; self; relations):
113
- docs id path raw hash updated_at created_at title tags format | data frontmatter inline | doc | blocks nodes out_links in_links revisions
114
- blocks id path doc_id raw hash updated_at created_at type text attrs depth ordinal | block doc | children nodes out_links section
115
- nodes id path doc_id block_id kind name value attrs | section doc block | blocks subsections children
116
- links id path doc_id block_id via_id dst_id dst_path uri field dst_kind predicate provenance anchor | doc dst block
117
- revisions id path rev seq doc_id commit ts origin actor reason hash is_current archived | doc
118
- A key of yours with any of these names is \`data.<k>\` (or frontmatter.<k> /
119
- inline.<k> by source; attrs.<k> on a block or node).
120
- UNKNOWN NAMES: a name that is none of the above AND that no object of the
121
- kind in this repository carries is reported before any row is read — on
122
- links as a filter_invalid error naming the fields; on docs/blocks/nodes the
123
- result gains \`hints\` (one line per name, nearest match when there is one)
124
- and the rows still come back; in a plan/apply/execute selector it is refused.
125
- A property some objects carry is known (partial presence is normal).
106
+ THE NAME RULE — one rule on every record kind: a BARE name is YOURS (an authored
107
+ property: frontmatter + inline on a doc, an attrs key on a block or node), an
108
+ UNDERSCORE-prefixed name is the ENGINE's (however derived: \`_id\`, \`_path\`,
109
+ \`_title\`, \`_updated_at\`, \`_doc\`), and \`$\` belongs to OQX — $it, $key, $depth,
110
+ $stop, $leaf, $frontier, $ordinal are values the LANGUAGE injects into a scope;
111
+ omgbase adds NO $ names (a \`$id\` in a query is an unknown name, absent). Nothing
112
+ 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
115
+ row functions in call position (\`text("x")\`, \`under(…)\`, \`refs(x)\`): a call is
116
+ never an entry read. There is NO self alias (the 4.x \`doc\`/\`block\`/\`section\`):
117
+ \`$it\` is the self, and \`_doc\`/\`_block\`/\`_dst\` reach ANOTHER row. So \`_path\` is the
118
+ engine's /-rooted location and \`path\` your \`path:\` key; \`_tags\` is your tags ∪ the
119
+ body #hashtags and \`tags\` the authored list alone; \`_title\` the computed title (the
120
+ first H1) and \`title\` your \`title:\`; \`_data.<k>\` ≡ \`<k>\` (the merged authored view —
121
+ kept for \`entries(_data)\` and to reach an authored key that itself starts with
122
+ \`_\`), \`_frontmatter.<k>\` / \`_inline.<k>\` one source each. Same value, same name
123
+ everywhere: a hit's \`_id\` is docs_read's \`_id\` and the \`_id\` argument of blocks_read.
124
+
125
+ Engine names per kind (attributes | handles | relations):
126
+ docs _id _path _raw _hash _updated_at _created_at _title _tags _format | _data _frontmatter _inline | _blocks _nodes _out_links _in_links _revisions
127
+ blocks _id _path _doc_id _raw _hash _updated_at _created_at _type _text _attrs _depth _ordinal | _doc | _children _nodes _out_links _section
128
+ nodes _id _path _doc_id _block_id _kind _name _value _attrs | _doc _block | _blocks _subsections _children
129
+ links _id _path _doc_id _block_id _via_id _dst_id _dst_path _uri _field _dst_kind _predicate _provenance _anchor | _doc _dst _block | —
130
+ revisions _id _path _rev _seq _doc_id _commit _ts _origin _actor _reason _hash _is_current _archived | _doc | —
131
+ root _id (^_id, 0^_id) | — | docs blocks nodes links revisions
132
+ Every bare name is yours; \`_attrs.<k>\` on a block or node is the same read as
133
+ the bare attrs key \`<k>\`.
134
+ UNKNOWN NAMES: a bare name that no object of the kind in this repository
135
+ carries is a property read that yields absent — on docs/blocks/nodes the result
136
+ gains \`hints\` (one line per name, the nearest match drawn from your keys and the
137
+ engine names: "no document in this repository carries a property \`title\`; did
138
+ you mean \`_title\`?") and the rows still come back; in a plan/apply/execute
139
+ selector it is refused. On links and revisions (closed: no authored data reaches
140
+ them) a bare name is a filter_invalid error. A \`_x\` that is no engine name of
141
+ the kind is filter_invalid naming the kind's engine names. A property some
142
+ objects carry is known (partial presence is normal).
126
143
 
127
144
  docs:
128
145
  - bare identifier = a document PROPERTY (frontmatter + inline \`key:: value\`,
129
146
  unioned; nested via dots: meta.owner). YAML/JSON docs
130
- expose the parsed object's keys the same way.
131
- - source handles = data.<k> (merged authored view) / frontmatter.<k> /
132
- inline.<k>; the whole bag as a collection via entries(data)
133
- / entries(frontmatter) / entries(inline) (see entries()).
134
- - attributes = id, path (/-rooted, see Paths), updated_at (ISO-8601,
135
- sorts chronologically), raw (the whole file, frontmatter
136
- included), hash (its sha256, 64 hex), title (the computed
137
- title: the first H1; data.title is the authored key), tags
138
- (authored tags ∪ body #hashtags, deduped, authored first;
139
- data.tags is the authored list), format (the doc's format —
140
- "markdown"/"yaml"/"json"; present on EVERY doc, so
141
- has(format) is always true and \`format == "yaml"\` is the
142
- filter).
143
- - relations = nodes, blocks, doc.out_links / doc.in_links (a doc's
144
- links as rows; the documents at their other end are
145
- out_links collect distinct { from dst } and
146
- in_links collect distinct { from doc } — there is no
147
- out / in relation), revisions (the document's stored
147
+ expose the parsed object's keys the same way. Nothing is
148
+ reserved: \`title\`, \`tags\`, \`path\`, \`id\` are your keys.
149
+ - source handles = _data.<k> (the merged authored view — the same read as the
150
+ bare key) / _frontmatter.<k> / _inline.<k>; the whole bag
151
+ as a collection via entries(_data) / entries(_frontmatter)
152
+ / entries(_inline) (see entries()).
153
+ - attributes = _id, _path (/-rooted, see Paths), _updated_at (ISO-8601,
154
+ sorts chronologically), _created_at, _raw (the whole file,
155
+ frontmatter included), _hash (its sha256, 64 hex), _title
156
+ (the computed title: the first H1; \`title\` is the authored
157
+ key), _tags (authored tags ∪ body #hashtags, deduped,
158
+ authored first; \`tags\` is the authored list), _format (the
159
+ doc's format — "markdown"/"yaml"/"json"; present on EVERY
160
+ doc, so has(_format) is always true and \`_format == "yaml"\`
161
+ is the filter).
162
+ - relations = _nodes, _blocks, _out_links / _in_links (a doc's links as
163
+ 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
166
+ out / in relation), _revisions (the document's stored
148
167
  revisions, oldest first — see revisions below).
149
168
 
150
169
  blocks:
151
- - bare fields = doc_id (the owning doc), type, text (normalized), ordinal
152
- (0-based among siblings), depth (0 at the top level), and
153
- every attrs key FLATTENED onto the row (bare \`checked\`
154
- reads attrs.checked; \`attrs.<k>\` still works; the
155
- structural fields win on collision; absent ⇒ false).
156
- - attributes = id, path (the owning doc's path), raw (the block's raw
157
- source — a container's raw holds its children), hash
170
+ - bare fields = every attrs key FLATTENED onto the row (bare \`checked\`;
171
+ \`_attrs.<k>\` is the same read; absent ⇒ false) — a block's
172
+ authored side.
173
+ - attributes = _id, _path (the owning doc's path), _doc_id (the owning
174
+ doc), _type, _text (normalized), _ordinal (0-based among
175
+ siblings), _depth (0 at the top level), _raw (the block's
176
+ raw source — a container's raw holds its children), _hash
158
177
  (its sha256 — the value update/split expect as
159
- expect.content_hash), updated_at.
160
- - reach-through = doc.<key> / doc.path / doc.id / doc.format = the
161
- CONTAINING doc (doc.id and doc.path cost nothing — they
162
- read the row's own doc_id / joined path, no lookup).
163
- - relations = block.children, block.nodes, block.out_links, section.
178
+ expect.content_hash), _updated_at, _created_at, _attrs.
179
+ - reach-through = _doc.<key> / _doc._path / _doc._id / _doc._format = the
180
+ CONTAINING doc (_doc._id and _doc._path cost nothing — they
181
+ read the row's own _doc_id / joined path, no lookup).
182
+ - relations = _children, _nodes, _out_links, _section (no self alias:
183
+ the 4.x \`block.children\` is \`_children\`).
164
184
  NOTE: inside a \`follow\`, $depth/$ordinal are ALWAYS the walk's metadata; a
165
- block's own nesting is \`depth\` / \`ordinal\` (no $) everywhere.
185
+ block's own nesting is \`_depth\` / \`_ordinal\` everywhere.
166
186
 
167
187
  nodes:
168
- - bare fields = doc_id, block_id (null for a block-less node), kind
169
- (md:task / md:link / md:section / yaml:…), name, value,
170
- + attrs keys flattened (checked, level).
171
- - attributes = id, path.
172
- - reach-through = doc.<key> / doc.id / doc.path, block.type / block.text
173
- / block.id / block.path.
174
- - relations = section.blocks, section.children, section.subsections.
188
+ - bare fields = the attrs keys flattened onto the row (checked, level,
189
+ first_ordinal; \`_attrs.<k>\` is the same read).
190
+ - attributes = _id, _path, _doc_id, _block_id (null for a block-less
191
+ node), _kind (md:task / md:link / md:section / yaml:…),
192
+ _name, _value, _attrs.
193
+ - reach-through = _doc.<key> / _doc._id / _doc._path, _block._type /
194
+ _block._text / _block._id / _block._path.
195
+ - relations = _blocks, _children, _subsections (a section node's; the
196
+ 4.x \`section.blocks\` is \`_blocks\`).
175
197
 
176
198
  links: (the authored link graph as rows — one per open link: the relation a link
177
- establishes; the md:link NODE is its occurrence in the text and via_id
199
+ establishes; the md:link NODE is its occurrence in the text and _via_id
178
200
  points at it — a frontmatter link has a row and no node)
179
- - bare fields = doc_id (the SOURCE doc), block_id (the source block; null
180
- for a frontmatter edge), via_id, dst_id (the destination
181
- node id: a doc id, an external node id or phantom:<path>),
182
- dst_path (target doc path, /-rooted; NULL when
183
- dangling/external), uri (external URL; NULL otherwise),
184
- predicate ("references"/"embeds"/freeform), provenance
185
- ("link"/"frontmatter"/"inline_field"/…), dst_kind
186
- ("document"/"external"/"collection"), anchor, field (the
201
+ - attributes = _id, _path (the SOURCE doc's path), _doc_id (the SOURCE
202
+ doc), _block_id (the source block; null for a frontmatter
203
+ edge), _via_id, _dst_id (the destination node id: a doc id,
204
+ an external node id or phantom:<path>), _dst_path (target
205
+ doc path, /-rooted; NULL when dangling/external), _uri
206
+ (external URL; NULL otherwise), _predicate
207
+ ("references"/"embeds"/freeform), _provenance
208
+ ("link"/"frontmatter"/"inline_field"/…), _dst_kind
209
+ ("document"/"external"/"collection"), _anchor, _field (the
187
210
  frontmatter key / inline field it was authored in).
188
- - attributes = id, path (the SOURCE doc's path); doc.<key> reaches the
189
- source document. There is no from_commit: a query sees
190
- open edges only (history is blocks_history/changes_since).
211
+ - reach-through = _doc.<key> (the source document), _dst.<key> (the
212
+ destination document; absent when dangling/external),
213
+ _block.<key> (the source block). Closed: links carry no
214
+ authored data, so a bare name is filter_invalid. There is
215
+ no from_commit: a query sees open edges only (history is
216
+ blocks_history/changes_since).
191
217
  - text()/semantic() are N/A on links.
192
218
 
193
219
  revisions: (history as rows — one per stored revision of every document of the
194
220
  repository, deleted documents included: what docs_history lists, queryable)
195
- - bare fields = rev (= id), seq (1, 2, … per document), doc_id, commit,
196
- ts (the commit's time, sorts chronologically), origin
197
- ("observed"/"api"/"import"/"projection"), actor, reason,
198
- hash (the rendered file's sha256 — docs_history's hash),
199
- is_current (true for the document's current revision),
200
- archived (true when the bodies moved to an archive segment:
201
- the row stays, docs_read { at } reads through the segment).
202
- - attributes = id (the revision id, r_…), path (the path the revision
203
- was committed under — a move re-paths only the current
204
- revision, so earlier ones keep the old path). doc.<key>
205
- reaches the document's CURRENT row; absent for a deleted
206
- document, so where doc == null is the revisions of deleted documents.
207
- - on docs = revisions is a relation (oldest first): a document with
208
- more than one version is docs { revisions count { } > 1 }.
221
+ - attributes = _id (the revision id, r_…), _rev (= _id), _seq (1, 2, … per
222
+ document), _doc_id, _commit, _ts (the commit's time, sorts
223
+ chronologically), _origin ("observed"/"api"/"import"/
224
+ "projection"), _actor, _reason, _hash (the rendered file's
225
+ sha256 — docs_history's _hash), _is_current (true for the
226
+ document's current revision), _archived (true when the
227
+ bodies moved to an archive segment: the row stays,
228
+ docs_read { at } reads through the segment), _path (the
229
+ path the revision was committed under — a move re-paths
230
+ only the current revision, so earlier ones keep the old
231
+ path).
232
+ - reach-through = _doc.<key> reaches the document's CURRENT row; absent for
233
+ a deleted document, so where _doc == null is the revisions
234
+ of deleted documents. Closed: a bare name is filter_invalid.
235
+ - on docs = _revisions is a relation (oldest first): a document with
236
+ more than one version is docs { _revisions count { } > 1 }.
209
237
  - text()/semantic() are N/A on revisions. The body as of a revision is
210
- docs_read { path, at: "<rev>" } (docs_read_at is the same call).
238
+ docs_read { _path, at: "<rev>" } (docs_read_at is the same call).
211
239
 
212
240
  ## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL
213
241
 
214
242
  name the CURRENT row only — never falls through to an enclosing row
215
243
  or the repository; an absent field is simply absent.
216
244
  ^name one scope OUT (the enclosing row's fields/intrinsics/lifts);
217
- ^^name two scopes out. \`^path\` = the enclosing row's path.
245
+ ^^name two scopes out. \`^_path\` = the enclosing row's path.
218
246
  ^docs ^nodes ^blocks ^links ^revisions
219
247
  THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root
220
248
  scope's row, one scope out from a top-level row, so ^docs there is
221
249
  the whole documents scan (uncorrelated until you add a ^ predicate);
222
250
  one caret per enclosing block (^^docs from depth two), or the
223
- absolute 0^docs from ANY depth. ^id / 0^id = the repository id.
251
+ absolute 0^docs from ANY depth. ^_id / 0^_id = the repository id.
224
252
  ^$it = the root object: ^$it.docs ≡ ^docs, entries(^$it) names the
225
253
  five collections. At the root scope itself a bare target is the scan
226
254
  (docs count { … }, from docs, entries(docs)).
227
255
  ERRORS: a bare \`docs\` INSIDE a block reads a property of the current
228
- row, which has none — refused ("did you mean ^docs"); \`^docs\` AT THE
229
- TOP LEVEL reaches past the root — refused (write the bare \`docs\`);
230
- so is \`$repo\` (surface < 2.0; the message names the replacement).
231
- nodes/blocks on a doc row are that doc's RELATIONS, not the root.
256
+ row, which has none — refused ("did you mean ^docs"); so is a bare
257
+ \`blocks\`/\`nodes\`/\`revisions\` there (a doc's own relations are
258
+ \`_blocks\`/\`_nodes\`/\`_revisions\`); \`^docs\` AT THE TOP LEVEL reaches
259
+ past the root — refused (write the bare \`docs\`); so is \`$repo\`
260
+ (surface < 2.0; the message names the replacement).
232
261
  $it the current item itself (a row, or each ELEMENT when the
233
- receiver is a list property); ^$it = the enclosing row.
234
- $value is NOT an intrinsic (since oqx 0.15): it reads a
262
+ receiver is a list property); ^$it = the enclosing row, ^$it._id
263
+ its id. $value is NOT an intrinsic (since oqx 0.15): it reads a
235
264
  property literally named $value, absent when there is none.
236
265
  $key the row's identity (0.20): a store row's id (doc/block/node/edge), an entries(x)
237
266
  entry's key, a follow occurrence's walk identity; project $key: <expr> to rekey the
@@ -258,7 +287,7 @@ revisions: (history as rows — one per stored revision of every document of th
258
287
  range-shaped STRING property as an interval: "2026-01-15" in range(window).
259
288
  strings x.contains("s") x.startsWith("p") x.endsWith("s") x.matches("^re$")
260
289
  (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE —
261
- fold with .lower(): title.lower().contains("aurora").
290
+ fold with .lower(): _title.lower().contains("aurora").
262
291
  Infix regex: x ~ "re" is x.matches("re"); x ~* "re" is the
263
292
  case-insensitive match; x !~ "re" and x !~* "re" negate them.
264
293
  Unanchored unless the pattern anchors; an absent x is false
@@ -277,7 +306,7 @@ revisions: (history as rows — one per stored revision of every document of th
277
306
  n.dur_iso() → "PT1H30M". Total: a non-date is absent, so
278
307
  \`where date(due) < date("today").minus(dur("30 days"))\` just
279
308
  filters. A datetime is never < its own date: compare
280
- updated_at.date() == date("today"), or < date("tomorrow").
309
+ _updated_at.date() == date("today"), or < date("tomorrow").
281
310
  literals "str" 'str' 123 1.5 true false null
282
311
  determinism no random; the only clock is date("today"/"now"/…), read once per
283
312
  query. Lists/structs are not literals; ternary is not supported.
@@ -312,8 +341,8 @@ revisions: (history as rows — one per stored revision of every document of th
312
341
  a source, a receiver or a follow destination:
313
342
  follow refs(before), refs(after) walks a timeline both ways.
314
343
  The reverse direction needs no function: a document's
315
- path is already the reference form, so
316
- ^docs { where ^path in list(after) } is
344
+ _path is already the reference form, so
345
+ ^docs { where ^_path in list(after) } is
317
346
  "the documents whose \`after\` names me".
318
347
  A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails
319
348
  (filter_invalid). \`follow before\` over a list of paths
@@ -322,58 +351,58 @@ revisions: (history as rows — one per stored revision of every document of th
322
351
  ## Nested queries & consumers
323
352
 
324
353
  <receiver> collect|exists|none|count|first|single { <block> }
325
- receiver = a relation (nodes, blocks, doc.out_links, section.blocks, …), a
326
- reach-through row (dst, doc: one row — dst { title }), a
354
+ receiver = a relation (_nodes, _blocks, _doc._out_links, _section._blocks, …), a
355
+ reach-through row (_dst, _doc: one row — _dst { _title }), a
327
356
  list property (tags), entries(x), or a root collection (^docs; 0^docs at depth ≥ 2).
328
357
  block = [select …] [from E] [where …] [frontier …] [follow …] [flatten …] [group by …] [having …] [order by …] [limit N] [offset N]
329
358
  — the same fixed order as the top level (\`from\` optional; the
330
359
  receiver supplies the rows). A leading name/alias list is the
331
360
  projection (\`select\` dropped); a predicate always needs \`where\`.
332
361
  A \`from E\` inside a block RE-PROJECTS the rows through a relation
333
- (a typed flatMap): \`docs collect { from nodes where kind == "md:task" }\`
334
- starts at docs, projects each through \`nodes\`, and queries the
335
- resulting nodes; a scalar relation (\`from doc\`) contributes one
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
336
365
  row; a missing or non-navigable relation is a loud error, never a
337
366
  global scan.
338
- exists ≥1 row nodes exists { where kind == "md:task" and not checked }
339
- none 0 rows nodes none { where kind == "md:task" and not checked } ("every task done")
340
- count a number nodes count { where kind == "md:task" } >= 2
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
341
370
  collect rows (default) — in select for nested results; in where with a lift
342
371
  first / single zero-or-one (single errors on >1) — lookups in select
343
- distinct on any consumer: dedup by projected value — nodes count distinct { select kind }
372
+ distinct on any consumer: dedup by projected value — _nodes count distinct { select _kind }
344
373
  limit/offset bound the block's rows AFTER where/order/distinct, BEFORE the
345
- consumer reduces: nodes exists { offset 1 } = at least two;
374
+ consumer reduces: _nodes exists { offset 1 } = at least two;
346
375
  first { … offset 1 } = the second.
347
376
  lift \`^name:\` in a where-collect binds values outward:
348
- select path, open from docs where nodes collect { ^open: value where kind == "md:task" and not checked }
377
+ select _path, open from docs where _nodes collect { ^open: _value where _kind == "md:task" and not checked }
349
378
  joins ^<t> exists { where slug == ^ref } semi-join; not … exists anti-join;
350
- ^nodes single { where kind == "person" and attrs.id == ^owner_id } lookup;
379
+ ^nodes single { where _kind == "person" and _attrs.id == ^owner_id } lookup;
351
380
  x in ^keys membership over a lifted set.
352
381
 
353
382
  ## select — projection
354
383
 
355
- select a, alias: expr, nested: nodes collect { … }
356
- bare key → the attribute when it is one (id, path, title, …), else the property
357
- (scalar-authored → scalar; list → array); data.<k> forces the property;
384
+ select a, alias: expr, nested: _nodes collect { … }
385
+ bare key → your property (scalar-authored → scalar; list → array); _name →
386
+ the engine's field (_id, _path, _title, …); _data.<k> is the bare key's read;
358
387
  a call/arithmetic needs an alias (n: size(tags)) unless in values mode;
359
- an output key of id/path other than the attribute itself needs an alias.
360
- raw whole file bytes (docs) — docs_read is cheaper for one doc.
361
- distinct select distinct type (dedup hits by projected value)
388
+ an output key of _id/_path other than the attribute itself needs an alias.
389
+ _raw whole file bytes (docs) — docs_read is cheaper for one doc.
390
+ distinct select distinct _type (dedup hits by projected value)
362
391
  values select <ONE item> values → bare values instead of records:
363
392
  select distinct type values from docs → values: ["hub","lab-note",…]
364
- select path values from docs → values: ["/index.md", …]
393
+ select _path values from docs → values: ["/index.md", …]
365
394
  tags: tags collect { $it values } → a plain array
366
- h: nodes first { name values where kind == "md:section" order by first_ordinal }
395
+ h: _nodes first { _name values where _kind == "md:section" order by first_ordinal }
367
396
  entries(x) a record as a collection ($key / $it per entry), key order:
368
- select fm: entries(data) collect { k: $key, v: $it }
369
- where entries(frontmatter) exists { where $key == "era" and $it > 1600 }
370
- entries(inline), entries(attrs), a nested map, or a list (index keys)
397
+ select fm: entries(_data) collect { k: $key, v: $it }
398
+ where entries(_frontmatter) exists { where $key == "era" and $it > 1600 }
399
+ entries(_inline), entries(_attrs), a nested map, or a list (index keys)
371
400
  $semantic_score is not a field — project semantic("…") under an alias instead.
372
401
 
373
402
  ## flatten (OQX 0.25)
374
403
 
375
404
  flatten <expr> [{ … }] [as <name>] after follow, before group by; may repeat
376
- Splits one value into rows. Every hit is still the PARENT row — its id/path
405
+ Splits one value into rows. Every hit is still the PARENT row — its _id/_path
377
406
  repeat per element, as Dataview's TABLE … FLATTEN — with <name> bound beside
378
407
  its fields at the same depth: an array → one row per element (an empty list
379
408
  yields none), a missing field → one row with the name absent (never dropped),
@@ -383,44 +412,44 @@ revisions: (history as rows — one per stored revision of every document of th
383
412
  projection, order by, limit, distinct), applied first. Later clauses read the
384
413
  name — a later flatten (flatten jobs as j flatten j.tags as t), group by,
385
414
  having, select, order by; where runs before it. A flattened query pages by
386
- position (cursor null); as id / as path is refused (the envelope reads them).
387
- select path, tags from docs where type == "process" flatten tags order by path, tags
388
- select path, t: b.text from docs flatten blocks { where type == "task" and not checked } as b
415
+ 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
389
418
  select tags, n: count() from docs flatten tags group by tags order by count() desc
390
419
 
391
420
  ## order by, limit/offset, pagination
392
421
 
393
- order by era desc, path asc absent values sort LAST (asc); ties → (path, id)
422
+ order by era desc, _path asc absent values sort LAST (asc); ties → (_path, _id)
394
423
  limit N / offset N define the result SET (integer literals);
395
424
  the tool's limit/cursor then page WITHIN it
396
- cursor opaque keyset on (path,id); a custom order by
425
+ cursor opaque keyset on (_path, _id); a custom order by
397
426
  disables it (you get the top page + truncated)
398
427
 
399
428
  ## Graph traversal (follow) and links
400
429
 
401
430
  The citation graph is the LINK ROWS walked through a destination block:
402
- dst is a link's destination document (absent for an external or dangling
403
- link — it then contributes no successor), doc its source.
404
- from docs where <seed> follow out_links collect { from dst } docs the seed links TO
405
- from docs where <seed> follow in_links collect { from doc } backlinks
406
- follow out_links collect { from dst depth 3 } bound (1..8, default 8) — a clause of the block
407
- follow out_links collect { from dst where layer == "canon" } keep only matching successors
408
- follow in_links collect { from doc where ^path in list(before) } ^ = the row being expanded
431
+ _dst is a link's destination document (absent for an external or dangling
432
+ 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
409
438
  (^^ = the walk's enclosing scope)
410
- follow out_links collect { from dst where predicate == "depends_on" } one predicate only
411
- follow out_links collect { from dst }, in_links collect { from doc }
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 }
412
441
  several destinations: their union
413
442
  (one step dedups by identity)
414
- follow ^docs { where out_links exists { where dst_path == ^^path } }
443
+ follow ^docs { where _out_links exists { where _dst_path == ^^_path } }
415
444
  a destination block over the root's
416
445
  documents, re-read per frontier row
417
446
  (^ = that row) = the backlinks walk
418
- follow out_links collect { from dst frontier type == "practitioner" } cut, keeping the frontier row
419
- from docs where type == "process" frontier !verified follow out_links collect { from dst }
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 }
420
449
  the BODY's frontier applies to the seeds:
421
450
  an unverified seed is kept, not expanded
422
- follow distinct out_links collect { from dst } / … { $key: group from dst } identity control ($key)
423
- follow refs(before), ^docs { where ^path in list(after) } forward over \`before\`, backward
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
424
453
  over every doc whose \`after\` names me
425
454
  Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|
426
455
  depth|cycle), $leaf/$frontier, $ordinal (a deterministic 1..N rank over the
@@ -434,8 +463,8 @@ revisions: (history as rows — one per stored revision of every document of th
434
463
  re-expanded. The default keeps one occurrence per distinct walk path;
435
464
  \`follow distinct\` dedups reached rows by identity.
436
465
  Destination blocks (OQX 0.20): a brace after ANY destination opens that
437
- destination's block — children { depth 2 }, refs(x) { depth 2 }, out_links
438
- collect { from dst depth 2 }, ^docs { where ^path in list(after) } — with the
466
+ 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
439
468
  0.17 body rules (where-first, projections) and two clauses of its own after
440
469
  \`where\`: frontier <pred> (occurrences reached through this destination that
441
470
  match are frontiers; ^ there is the walk's enclosing scope) and depth <n>
@@ -446,14 +475,14 @@ revisions: (history as rows — one per stored revision of every document of th
446
475
  expanded; each destination's frontier is its own. There is no options block;
447
476
  the old \`by\` is $key. A bare follow ^docs (no brace, no consumer) is an
448
477
  error naming both block spellings.
449
- Type-preserving relations: block.children, section.children, section.subsections;
450
- the citation graph is out_links collect { from dst } / in_links collect { from doc }.
451
- from links where dst_path == "/notes/x.md" inbound links to a doc, as rows
452
- select d: dst.title, k: dst_kind from links the destination document's title
453
- from docs where out_links exists { where dst.layer == "draft" } links into drafts
454
- select b: in_links collect distinct { from doc } from docs backlinks as { id, path }
455
- select doc_id, dst_path from links where predicate == "depends_on"
456
- from docs where not doc.in_links exists { } orphans (nothing links in)
478
+ 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)
457
486
  (The \`graph\` tool is a convenience wrapper that compiles to a follow query.)
458
487
 
459
488
  ## semantic grain
@@ -464,37 +493,136 @@ text()/where prune the candidate set; only order by reweights.
464
493
  ## Examples
465
494
 
466
495
  from docs where layer == "working"
467
- from docs where path.startsWith("/guides/") and updated_at >= "2026-08-01"
496
+ from docs where _path.startsWith("/guides/") and _updated_at >= "2026-08-01"
468
497
  select layer, tags from docs where "pricing" in list(tags)
469
- from docs where inline.owner == "alice"
470
- from docs where title.lower().contains("q3 plan")
471
- from docs where format == "yaml"
498
+ from docs where _inline.owner == "alice"
499
+ from docs where _title.lower().contains("q3 plan")
500
+ from docs where _format == "yaml"
472
501
  from docs where type == "practitioner" order by era desc limit 2
473
- from docs where nodes none { where kind == "md:task" and not checked }
502
+ from docs where _nodes none { where _kind == "md:task" and not checked }
474
503
  from docs where tags exists { where $it == "tria-prima" }
475
- select fm: entries(data) collect { k: $key, v: $it } from docs where path == "/x.md"
476
- select owner_id, owner: ^nodes single { where kind == "person" and attrs.id == ^owner_id } from docs
477
- from blocks where type == "task" and not checked and under_heading("Launch") and doc.layer == "working"
478
- from blocks where type == "paragraph" and has_edge("references", "d_92aaaaa")
479
- select path, $depth from docs where path == "/timeline/review.md" follow refs(before), refs(after)
480
- select path, next: ^docs collect { path where ^path in list(before) } from docs where type == "milestone"
481
- select path, n: size(^docs), repo: ^id from docs limit 1
482
- select p: data.path, t: data.title from docs where has(data.path)
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)
483
512
  docs { type == "substance" and not verified }
484
- select subject, process: ^docs[slug == ^subject]!.path from docs where type == "lab-note"
485
- select path, back: nodes first { select p: 0^docs collect { path where ^^path in list(after) } values } from docs where type == "milestone"
486
- select path, prior: refs(before) collect { path, when } from docs where type == "milestone"
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"
487
516
  from blocks order by semantic("identity preservation across edits") desc limit 10
488
- select uri from links where dst_kind == "external"
517
+ select _uri from links where _dst_kind == "external"
489
518
  docs count { where layer == "canon" }
490
- select seq, origin, ts, hash from revisions where path == "/processes/calcination.md" order by seq desc
491
- from docs where revisions count { where origin == "api" } > 0
492
- select p: path, d: doc.path from revisions where doc == null
493
- 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
494
- select path, tag: tags from docs where "process" in tags flatten tags order by path, tags
495
-
496
- Common mistake: \`$path\`/\`$id\` (the 2.x spelling) — omgbase has no $ names; write
497
- \`path\`/\`id\`. Another: \`where tags == "x"\` on a list-valued key is false — write
498
- \`"x" in tags\`. Another: \`select data.path\` — alias it (\`p: data.path\`).
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
524
+
525
+ Common mistake: \`$path\`/\`$id\` — omgbase has no $ names; the engine's location and
526
+ identity are \`_path\`/\`_id\`. Another: the 4.x bare \`path\`/\`id\`/\`title\`/\`tags\` — a
527
+ bare \`path\` is now YOUR \`path:\` key (absent when you wrote none, with a hint
528
+ pointing at \`_path\`); the engine's are \`_path\`/\`_id\`/\`_title\`/\`_tags\`; \`select
529
+ data.path\` is gone — write \`path\`. Another: \`where tags == "x"\` on a list-valued
530
+ key is false — write \`"x" in tags\`. Another: \`select _path: _doc._path\` — alias it
531
+ (\`p: _doc._path\`).
532
+ `;
533
+ /**
534
+ * The manual's chapters (spec/surface §4 `query_syntax`, 5.1): one entry per
535
+ * `## ` heading of `QUERY_SYNTAX`, in order — the `section` argument's names.
536
+ * `querySyntaxSection(slug)` is the text from that heading up to the next one.
537
+ * The Rust twin pins the same table (its test checks every pair against this
538
+ * source).
539
+ */
540
+ export const QUERY_SYNTAX_SECTIONS = [
541
+ { slug: "sugar", heading: "Sugar (OQX 0.17)" },
542
+ { slug: "paths", heading: "Paths" },
543
+ { slug: "targets", heading: "Targets & field namespaces" },
544
+ { slug: "scoping", heading: "Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL" },
545
+ { slug: "scalars", heading: "Scalar semantics (@omgbase/oqx)" },
546
+ { slug: "predicates", heading: "Domain predicates (omgbase)" },
547
+ { slug: "nested", heading: "Nested queries & consumers" },
548
+ { slug: "select", heading: "select — projection" },
549
+ { slug: "flatten", heading: "flatten (OQX 0.25)" },
550
+ { slug: "order", heading: "order by, limit/offset, pagination" },
551
+ { slug: "follow", heading: "Graph traversal (follow) and links" },
552
+ { slug: "semantic", heading: "semantic grain" },
553
+ { slug: "examples", heading: "Examples" },
554
+ ];
555
+ /** The `section` names a caller may pass: every slug, then `all` (the whole manual). */
556
+ export const QUERY_SYNTAX_SECTION_NAMES = [...QUERY_SYNTAX_SECTIONS.map((s) => s.slug), "all"];
557
+ /**
558
+ * One chapter of the manual: the lines from `## <heading>` up to (not
559
+ * including) the next `## ` heading or the end, trailing blank lines dropped,
560
+ * one trailing newline. `"all"` is the whole manual; an unknown slug is `null`.
561
+ */
562
+ export function querySyntaxSection(slug) {
563
+ if (slug === "all")
564
+ return QUERY_SYNTAX;
565
+ const entry = QUERY_SYNTAX_SECTIONS.find((s) => s.slug === slug);
566
+ if (!entry)
567
+ return null;
568
+ const lines = QUERY_SYNTAX.split("\n");
569
+ const start = lines.indexOf(`## ${entry.heading}`);
570
+ if (start < 0)
571
+ return null;
572
+ let end = start + 1;
573
+ while (end < lines.length && !lines[end].startsWith("## "))
574
+ end++;
575
+ while (end > start + 1 && lines[end - 1].trim() === "")
576
+ end--;
577
+ return lines.slice(start, end).join("\n") + "\n";
578
+ }
579
+ /**
580
+ * The card `query_syntax {}` returns (5.1): the grammar skeleton, the names
581
+ * rule and the engine names per kind, the handles, the consumers, follow in
582
+ * one line, the examples agents reach for first, and where the chapters are.
583
+ * Every indented example parses (reference.test.ts extracts them exactly as it
584
+ * extracts the manual's). Keep every line earning its place: this is the most
585
+ * read text on the surface.
586
+ */
587
+ export const QUERY_SYNTAX_CARD = `# query — OQX card. Chapters: query_syntax { section: "<name>" }; the manual: { section: "all" }
588
+
589
+ 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
592
+ CLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset.
593
+ and / or / not. Comparisons strict and typed; null == a missing field. A list key: "x" in tags, never tags == "x".
594
+ Strings .contains("s") .startsWith("p") .lower(); x ~* "re" (ci regex), x !~ "re". Dates date("today"),
595
+ date(due) < date("today").minus(dur("30 days")), _updated_at >= "2026-08-01".
596
+
597
+ NAMES: a BARE name is YOURS (frontmatter, inline key:: value, a block's attrs); an UNDERSCORE name is the ENGINE's;
598
+ $ is OQX's ($it $key $depth $stop $ordinal). title tags path id are your keys; _title _tags _path _id the engine's.
599
+ docs _id _path _raw _hash _updated_at _created_at _title _tags _format | _data _frontmatter _inline | _blocks _nodes _out_links _in_links _revisions
600
+ blocks _id _path _doc_id _raw _hash _type _text _attrs _depth _ordinal | _doc | _children _nodes _out_links _section
601
+ nodes _id _path _doc_id _block_id _kind _name _value _attrs | _doc _block | _blocks _subsections _children
602
+ links _id _path _doc_id _block_id _via_id _dst_id _dst_path _uri _dst_kind _predicate _provenance _anchor _field | _doc _dst _block
603
+ revisions _id _path _rev _seq _doc_id _commit _ts _origin _actor _reason _hash _is_current _archived | _doc
604
+ Handles _data.<k> ≡ <k>, _frontmatter.<k> / _inline.<k>; entries(_data) the bag as rows ($key, $it). Reach-through
605
+ _doc.<k> the owning document, _dst.<k> a link's target. Scoping: a bare name is the CURRENT row; ^name one scope
606
+ out; ^docs ^blocks ^nodes ^links the repository from a row. An unknown bare name is absent plus a hints line.
607
+
608
+ NESTED: <receiver> collect|exists|none|count|first|single { [select …] [where …] [order by …] [limit N] } over a
609
+ 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 }
611
+ backlinks; refs(before) a property's documents; $depth per row. SEARCH: text("words") prunes; order by semantic("…") desc.
612
+
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
624
+
625
+ Every error is filter_invalid naming what to change; when the engine can correct the call, data.fix IS the call.
626
+ Chapters: sugar paths targets scoping scalars predicates nested select flatten order follow semantic examples; all.
499
627
  `;
500
628
  //# sourceMappingURL=reference.js.map