@omgbase/core 0.27.1 → 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
@@ -2,14 +2,14 @@ import type { Store } from "../core/store/store.js";
2
2
  export type StaleReason = "dangling_doc" | "dangling_block" | "broken_anchor";
3
3
  export interface StaleLink {
4
4
  /** source document id */
5
- doc_id: string;
5
+ _doc_id: string;
6
6
  /** source document path in the reference form */
7
- path: string;
7
+ _path: string;
8
8
  /** source block id (null for frontmatter-level edges) */
9
- block_id: string | null;
10
- predicate: string;
11
- provenance: string;
12
- dst_kind: string;
9
+ _block_id: string | null;
10
+ _predicate: string;
11
+ _provenance: string;
12
+ _dst_kind: string;
13
13
  /** the canonical target path in the reference form (phantom prefix stripped,
14
14
  * rooted), e.g. "/guides/old.md" — spec/surface §1 "Paths" */
15
15
  target: string;
@@ -17,7 +17,7 @@ export interface StaleLink {
17
17
  * — paste-ready as `links_repair.from`. null when it can't be recovered (frontmatter
18
18
  * edges have no block; a projected node no longer matches). */
19
19
  authored: string | null;
20
- anchor: string | null;
20
+ _anchor: string | null;
21
21
  reason: StaleReason;
22
22
  }
23
23
  /** links_stale summary mode: counts only, no per-row output. */
@@ -29,9 +29,9 @@ export interface LinkHealthSummary {
29
29
  target: string;
30
30
  count: number;
31
31
  }[];
32
- /** source documents carrying dangling links, most first (ties by path); `path` the reference form */
32
+ /** source documents carrying dangling links, most first (ties by path); `_path` the reference form */
33
33
  bySource: {
34
- path: string;
34
+ _path: string;
35
35
  count: number;
36
36
  }[];
37
37
  externalCount: number;
@@ -88,18 +88,18 @@ export function linksStale(store, repoId, opts = {}) {
88
88
  const stale = page.map((r) => {
89
89
  // Strip the "phantom:" prefix to expose the canonical path.
90
90
  const target = r.dstNode.slice("phantom:".length);
91
- // The surface speaks the reference form: `path` and `target` rooted;
91
+ // The surface speaks the reference form: `_path` and `target` rooted;
92
92
  // `authored` stays exactly as written in the source.
93
93
  return {
94
- doc_id: r.srcDoc,
95
- path: referencePath(r.srcPath),
96
- block_id: r.srcBlock,
97
- predicate: r.predicate,
98
- provenance: r.provenance,
99
- dst_kind: r.dstKind,
94
+ _doc_id: r.srcDoc,
95
+ _path: referencePath(r.srcPath),
96
+ _block_id: r.srcBlock,
97
+ _predicate: r.predicate,
98
+ _provenance: r.provenance,
99
+ _dst_kind: r.dstKind,
100
100
  target: referencePath(target),
101
101
  authored: authoredFor(r.srcBlock, r.srcPath, target, r.anchor),
102
- anchor: r.anchor,
102
+ _anchor: r.anchor,
103
103
  // All phantom targets are dangling DOCUMENTS in this data model (cross-doc
104
104
  // block refs collapse to document phantoms carrying an anchor). We tag rows
105
105
  // that also carry an anchor as such, but the miss is still a missing doc.
@@ -140,7 +140,7 @@ export function linksStaleSummary(store, repoId, opts = {}) {
140
140
  const bySource = store.db
141
141
  .prepare(`SELECT d.path AS srcPath, count(*) AS count ${phantom} GROUP BY d.path ORDER BY count DESC, d.path`)
142
142
  .all(...params)
143
- .map((r) => ({ path: referencePath(r.srcPath), count: r.count }));
143
+ .map((r) => ({ _path: referencePath(r.srcPath), count: r.count }));
144
144
  return {
145
145
  staleCount: byTarget.reduce((n, t) => n + t.count, 0),
146
146
  byTarget,
@@ -1,15 +1,15 @@
1
1
  import type { Store } from "../core/store/store.js";
2
2
  export interface LinkEdge {
3
- predicate: string;
3
+ _predicate: string;
4
4
  /** the other endpoint (dst for out-edges, src for in-edges): a doc id, an external node id or `phantom:<path>` */
5
- doc_id: string;
5
+ _doc_id: string;
6
6
  kind: string;
7
7
  /** how many underlying block-grain edges this doc-grain row rolls up */
8
8
  count: number;
9
9
  /** sample source block ids (out-edges only) */
10
10
  samples?: string[];
11
11
  /** block-grain (`blocks: true`): the source block of this edge (null for a frontmatter edge) */
12
- block_id?: string | null;
12
+ _block_id?: string | null;
13
13
  }
14
14
  export interface LinksResult {
15
15
  out: LinkEdge[];
@@ -13,7 +13,7 @@ export function docLinks(store, docId, opts = {}) {
13
13
  .all(docId);
14
14
  for (const r of rows)
15
15
  if (inPred(r.predicate))
16
- out.push({ predicate: r.predicate, doc_id: r.dst_node, kind: r.dst_kind, count: 1, block_id: r.src_block });
16
+ out.push({ _predicate: r.predicate, _doc_id: r.dst_node, kind: r.dst_kind, count: 1, _block_id: r.src_block });
17
17
  }
18
18
  else {
19
19
  const rows = store.db
@@ -23,7 +23,7 @@ export function docLinks(store, docId, opts = {}) {
23
23
  for (const r of rows) {
24
24
  if (!inPred(r.predicate))
25
25
  continue;
26
- out.push({ predicate: r.predicate, doc_id: r.dst_node, kind: r.dst_kind, count: r.count, samples: JSON.parse(r.samples) });
26
+ out.push({ _predicate: r.predicate, _doc_id: r.dst_node, kind: r.dst_kind, count: r.count, samples: JSON.parse(r.samples) });
27
27
  }
28
28
  }
29
29
  }
@@ -35,7 +35,7 @@ export function docLinks(store, docId, opts = {}) {
35
35
  .all(docId);
36
36
  for (const r of rows)
37
37
  if (inPred(r.predicate))
38
- inbound.push({ predicate: r.predicate, doc_id: r.src_doc, kind: "document", count: 1, block_id: r.src_block });
38
+ inbound.push({ _predicate: r.predicate, _doc_id: r.src_doc, kind: "document", count: 1, _block_id: r.src_block });
39
39
  }
40
40
  else {
41
41
  const rows = store.db
@@ -45,7 +45,7 @@ export function docLinks(store, docId, opts = {}) {
45
45
  .all(docId);
46
46
  for (const r of rows)
47
47
  if (inPred(r.predicate))
48
- inbound.push({ predicate: r.predicate, doc_id: r.src_doc, kind: "document", count: r.cnt });
48
+ inbound.push({ _predicate: r.predicate, _doc_id: r.src_doc, kind: "document", count: r.cnt });
49
49
  }
50
50
  }
51
51
  return { out, in: inbound };
@@ -31,10 +31,17 @@ export declare function jsonArrayString(s: string): {
31
31
  * arguments with any `undefined` members dropped; throws the first failure.
32
32
  */
33
33
  export declare function validateArguments(tool: string, shape: ArgShape, raw: unknown): Record<string, unknown>;
34
+ /** Whether `key` is a bare engine-field name whose `_` spelling the tool takes. */
35
+ export declare function bareEngineArgument(key: string, shape: ArgShape): boolean;
36
+ /** The call with every bare engine-field argument respelled to its `_` form (the `fix` of the no-tolerance error). */
37
+ export declare function respellEngineArguments(args: Record<string, unknown>, shape: ArgShape): Record<string, unknown>;
34
38
  /**
35
- * What to say about an argument name the tool does not take. The names 3.0
36
- * retired are called out with their replacement (`doc` → `id`/`path`,
37
- * `block`/`node` → `id`, `heading` → `section`, `markdown` → `text`) when the
39
+ * What to say about an argument name the tool does not take. A bare engine
40
+ * field (`id`, `path`, `ids`) where the tool takes its `_` spelling is the
41
+ * no-tolerance error of 5.0 (spec/surface §4 "Arguments": the bare namespace is
42
+ * the user's, an engine field is matched by its exact name). The names 3.0
43
+ * retired are called out with their replacement (`doc` → `_id`/`_path`,
44
+ * `block`/`node` → `_id`, `heading` → `section`, `markdown` → `text`) when the
38
45
  * tool has the replacement, and so are the inputs 4.0 retired (spec/surface
39
46
  * §4; ADR-021's retirement release): `ops` on a tool that takes `effects`
40
47
  * (the kernel ops are a library/fixture contract), `dry_run` on any tool (the
@@ -77,8 +77,13 @@ export function validateArguments(tool, shape, raw) {
77
77
  throw argumentError(tool, "arguments must be an object", shape);
78
78
  const args = raw;
79
79
  for (const key of Object.keys(args).sort(cmpBytes)) {
80
- if (!(key in shape))
81
- throw argumentError(tool, unknownArgumentMessage(key, shape), shape);
80
+ if (key in shape)
81
+ continue;
82
+ // (5.0) A bare engine-field name where the tool takes its `_` spelling:
83
+ // the error carries the corrected call (every bare `id`/`path`/`ids` the
84
+ // call holds respelled at once), never a silent acceptance.
85
+ const fix = bareEngineArgument(key, shape) ? canonicalJson(respellEngineArguments(args, shape)) : undefined;
86
+ throw argumentError(tool, unknownArgumentMessage(key, shape), shape, fix);
82
87
  }
83
88
  const out = {};
84
89
  for (const key of Object.keys(shape).sort(cmpBytes)) {
@@ -135,10 +140,26 @@ function issueMessage(key, schema, issues) {
135
140
  const expected = { string: "a string", number: "an integer", boolean: "a boolean", object: "an object", array: "an array" };
136
141
  return `\`${at}\` must be ${issue.expected !== undefined ? (expected[issue.expected] ?? issue.expected) : "valid"}`;
137
142
  }
143
+ /** The bare engine-field names a tool may be called with by mistake (spec/surface §4, 5.0): each has its `_` spelling in the shape. */
144
+ const BARE_ENGINE_ARGUMENTS = ["id", "path", "ids", "paths"];
145
+ /** Whether `key` is a bare engine-field name whose `_` spelling the tool takes. */
146
+ export function bareEngineArgument(key, shape) {
147
+ return BARE_ENGINE_ARGUMENTS.includes(key) && `_${key}` in shape;
148
+ }
149
+ /** The call with every bare engine-field argument respelled to its `_` form (the `fix` of the no-tolerance error). */
150
+ export function respellEngineArguments(args, shape) {
151
+ const out = {};
152
+ for (const [k, v] of Object.entries(args))
153
+ out[bareEngineArgument(k, shape) ? `_${k}` : k] = v;
154
+ return out;
155
+ }
138
156
  /**
139
- * What to say about an argument name the tool does not take. The names 3.0
140
- * retired are called out with their replacement (`doc` → `id`/`path`,
141
- * `block`/`node` → `id`, `heading` → `section`, `markdown` → `text`) when the
157
+ * What to say about an argument name the tool does not take. A bare engine
158
+ * field (`id`, `path`, `ids`) where the tool takes its `_` spelling is the
159
+ * no-tolerance error of 5.0 (spec/surface §4 "Arguments": the bare namespace is
160
+ * the user's, an engine field is matched by its exact name). The names 3.0
161
+ * retired are called out with their replacement (`doc` → `_id`/`_path`,
162
+ * `block`/`node` → `_id`, `heading` → `section`, `markdown` → `text`) when the
142
163
  * tool has the replacement, and so are the inputs 4.0 retired (spec/surface
143
164
  * §4; ADR-021's retirement release): `ops` on a tool that takes `effects`
144
165
  * (the kernel ops are a library/fixture contract), `dry_run` on any tool (the
@@ -148,10 +169,12 @@ function issueMessage(key, schema, issues) {
148
169
  export function unknownArgumentMessage(key, shape) {
149
170
  const has = (n) => n in shape;
150
171
  const retired = (replacement) => `\`${key}\` is no longer an argument — pass ${replacement}`;
151
- if (key === "doc" && has("id") && has("path"))
152
- return retired("`id` or `path`");
153
- if ((key === "block" || key === "node") && has("id"))
154
- return retired("`id`");
172
+ if (bareEngineArgument(key, shape))
173
+ return `\`${key}\` is not an argument — the engine field is \`_${key}\` (the bare namespace is the user's); pass \`_${key}\``;
174
+ if (key === "doc" && has("_id") && has("_path"))
175
+ return retired("`_id` or `_path`");
176
+ if ((key === "block" || key === "node") && has("_id"))
177
+ return retired("`_id`");
155
178
  if (key === "heading" && has("section"))
156
179
  return retired("`section` (the heading's text), or blocks_append with the heading's `id`");
157
180
  if (key === "markdown" && has("text"))
@@ -1,4 +1,4 @@
1
- export type ErrorCode = "stale_expectation" | "parent_missing" | "target_missing" | "block_missing" | "doc_missing" | "cycle_move" | "opaque_block" | "not_contiguous" | "type_mismatch" | "conflicted_document" | "path_taken" | "create_conflict" | "ambiguous_locator" | "ambiguous_heading" | "filter_invalid" | "budget_exceeded" | "provider_unavailable" | "embedder_failed" | "sync_conflict" | "repo_not_found" | "node_not_editable" | "seed_unresolved" | "block_not_container" | "stale_plan" | "plan_not_convergent" | "conflicting_effects" | "unsupported_plan_version" | "plan_mismatch" | "ambiguous_target" | "selection_too_large" | "revision_archived";
1
+ export type ErrorCode = "stale_expectation" | "parent_missing" | "target_missing" | "block_missing" | "doc_missing" | "cycle_move" | "opaque_block" | "not_contiguous" | "type_mismatch" | "conflicted_document" | "path_taken" | "create_conflict" | "ambiguous_locator" | "ambiguous_heading" | "filter_invalid" | "budget_exceeded" | "provider_unavailable" | "embedder_failed" | "sync_conflict" | "repo_not_found" | "node_not_editable" | "seed_unresolved" | "block_not_container" | "stale_plan" | "plan_not_convergent" | "conflicting_effects" | "unsupported_plan_version" | "plan_mismatch" | "ambiguous_target" | "selection_too_large" | "revision_archived" | "plan_unknown";
2
2
  export interface EngineErrorBody {
3
3
  error: ErrorCode;
4
4
  message: string;
@@ -11,35 +11,35 @@ export interface GraphArgs {
11
11
  /** restrict the neighborhood to documents reachable from a root via edges of
12
12
  * this predicate, within `degrees` (both the docs and the edges are filtered). */
13
13
  predicate?: string | undefined;
14
- /** extra document projections (OQX select expressions, e.g. "layer", "path"). */
14
+ /** extra document projections (OQX select expressions, e.g. "layer", "_path"). */
15
15
  select?: string[] | undefined;
16
16
  /** cap on the distinct documents returned (default 200). */
17
17
  max_documents?: number | undefined;
18
18
  }
19
19
  /** A traversed edge: a row of the `links` target under its own field names
20
- * (spec/surface §1.2, 3.0 — `id` the edge, `doc_id`/`block_id` the source,
21
- * `via_id`, `dst_id` the destination node, `dst_path`/`uri` its path or URI,
22
- * `field` the authoring key). No rename table: what the walk projects is what
23
- * the result carries. */
20
+ * (spec/surface §1.2, 5.0 — `_id` the edge, `_doc_id`/`_block_id` the source,
21
+ * `_via_id`, `_dst_id` the destination node, `_dst_path`/`_uri` its path or
22
+ * URI, `_field` the authoring key). No rename table: what the walk projects is
23
+ * what the result carries. */
24
24
  export interface GraphEdge {
25
- id: string;
26
- doc_id: string;
27
- block_id: string | null;
28
- via_id: string | null;
29
- dst_id: string;
30
- dst_path: string | null;
31
- uri: string | null;
32
- dst_kind: string;
33
- predicate: string;
34
- provenance: string;
35
- anchor: string | null;
36
- field: string | null;
25
+ _id: string;
26
+ _doc_id: string;
27
+ _block_id: string | null;
28
+ _via_id: string | null;
29
+ _dst_id: string;
30
+ _dst_path: string | null;
31
+ _uri: string | null;
32
+ _dst_kind: string;
33
+ _predicate: string;
34
+ _provenance: string;
35
+ _anchor: string | null;
36
+ _field: string | null;
37
37
  }
38
- /** A reached document: `id`/`path` (the row's own names, as a `query` hit)
38
+ /** A reached document: `_id`/`_path` (the row's own names, as a `query` hit)
39
39
  * + walk metadata + any requested projections. */
40
40
  export interface GraphDoc {
41
- id: string;
42
- path: string;
41
+ _id: string;
42
+ _path: string;
43
43
  /** minimum hop distance from a root (root = 0). */
44
44
  degree: number;
45
45
  /** true when this node sits on the outer boundary (its min degree == degrees). */
@@ -53,8 +53,8 @@ export interface GraphResult {
53
53
  documents: GraphDoc[];
54
54
  links: GraphEdge[];
55
55
  frontier: {
56
- id: string;
57
- path: string;
56
+ _id: string;
57
+ _path: string;
58
58
  degree: number;
59
59
  }[];
60
60
  truncated: boolean;
@@ -2,8 +2,8 @@
2
2
  //
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
- // 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
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
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
@@ -11,17 +11,17 @@
11
11
  // bound is `follow`'s; nothing here re-implements BFS/DFS.
12
12
  //
13
13
  // Mapping (args → follow query):
14
- // roots → the seed `where id == … or …` (refs resolved to doc ids first; literals in the AST, never spliced text)
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 collect { from _dst }` (the linked documents)
17
+ // | `follow _in_links collect { 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)
21
21
  // select → extra doc projections spliced into the walk's `select`
22
22
  // max_documents → a post-walk cap on the distinct document set
23
23
  //
24
- // Edges are surfaced by projecting the reached doc's `doc.out_links`/`doc.in_links`
24
+ // Edges are surfaced by projecting the reached doc's `_out_links`/`_in_links`
25
25
  // in the SAME walk (a select-position collect), then keeping the induced-subgraph
26
26
  // edges (both endpoints in the neighborhood) plus dangling stubs to external/
27
27
  // phantom targets — so external (`x_…`) and phantom endpoints are never dropped,
@@ -41,8 +41,8 @@ function cmpBytes(a, b) {
41
41
  // Build the extra `select` items for caller projections. Each expression gets a
42
42
  // stable internal alias (_u0, _u1, …) so it can never collide with a reserved
43
43
  // word or the macro's own aliases; the clean output name is derived separately.
44
- // A bare-name selection (`layer`, `title`) is its own output key, exactly as a
45
- // `query` hit projects it (spec/surface §1.4, 3.0) — so `id`/`path` collapse
44
+ // A bare-name selection (`layer`, `_title`) is its own output key, exactly as a
45
+ // `query` hit projects it (spec/surface §1.4, 5.0) — so `_id`/`_path` collapse
46
46
  // onto the envelope keys (the engine reads the same value) and any other name
47
47
  // is an ordinary column, deduped by suffix. Anything else (an expression) is
48
48
  // `sel_<i>`, so a projection can never overwrite the envelope here.
@@ -51,16 +51,16 @@ function buildUserSelect(select) {
51
51
  const outNames = [];
52
52
  if (!select || select.length === 0)
53
53
  return { items, outNames };
54
- const used = new Set(["id", "path"]);
54
+ const used = new Set(["_id", "_path"]);
55
55
  select.forEach((expr, i) => {
56
56
  const trimmed = expr.trim();
57
57
  if (!trimmed)
58
58
  return;
59
59
  const m = /^(\$?[A-Za-z_][A-Za-z0-9_]*)$/.exec(trimmed);
60
60
  let name = m ? m[1] : `sel_${i}`;
61
- // The envelope keys are the row's own identity: an `id`/`path` selection
61
+ // The envelope keys are the row's own identity: an `_id`/`_path` selection
62
62
  // lands on them rather than beside them (the engine reads the same value).
63
- if (name !== "id" && name !== "path") {
63
+ if (name !== "_id" && name !== "_path") {
64
64
  while (used.has(name))
65
65
  name = `${name}_${i}`;
66
66
  used.add(name);
@@ -86,40 +86,47 @@ function userItems(src, alias) {
86
86
  }
87
87
  return q.select;
88
88
  }
89
- // The edge fields projected from doc.out_links/doc.in_links — the `links`
90
- // target's own names, projected under themselves (spec/surface §1.2, 3.0: no
89
+ // The edge fields projected from `_out_links`/`_in_links` — the `links`
90
+ // target's own names, projected under themselves (spec/surface §1.2, 5.0: no
91
91
  // rename between the language and the result) — identical for both directions
92
92
  // so a single filter handles either scan (an edge is a directional fact
93
93
  // regardless of which endpoint the walk reached it from).
94
94
  const EDGE_FIELDS = [
95
- "id", "doc_id", "block_id", "via_id", "dst_id", "dst_path", "uri",
96
- "dst_kind", "predicate", "provenance", "anchor", "field",
95
+ "_id", "_doc_id", "_block_id", "_via_id", "_dst_id", "_dst_path", "_uri",
96
+ "_dst_kind", "_predicate", "_provenance", "_anchor", "_field",
97
97
  ];
98
+ // The macro's own aliases: `__oqx_`/`__omg_` (two underscores) are the runner's
99
+ // and the selector compiler's reserved prefixes; a single underscore is the
100
+ // engine prefix of 5.0, which an ALIAS may use freely (an alias is never a row
101
+ // read), and keeps these off any authored key.
102
+ const DEPTH_ALIAS = "_depth";
103
+ const STOP_ALIAS = "_stop";
104
+ const LINKS_ALIAS = "_links";
98
105
  // The walk, as an AST (spec/oqx/AST.md §7) printed canonically — never spliced
99
106
  // strings: the seed ids are literals, the caller's projections parsed items.
100
107
  //
101
- // select _depth: $depth, _stop: $stop, _links: doc.<dir>_links collect { <edge fields> } <user items>
102
- // from docs where id == <root> or … follow distinct <dir>_links collect { from <dst | doc> depth <depth> }
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> }
103
110
  //
104
111
  // The destination block walks the link rows to the documents at their other
105
112
  // end (spec/surface §1.2, 3.0 follow-up: there are no `out`/`in` relations —
106
- // `dst` is a link's destination document, `doc` its source; a `from` over an
107
- // absent `dst` — an external or phantom target — contributes no successor).
113
+ // `_dst` is a link's destination document, `_doc` its source; a `from` over an
114
+ // absent `_dst` — an external or phantom target — contributes no successor).
108
115
  function buildQuery(rootIds, dir, depth, userSelect) {
109
- const edges = build.op(build.path("doc", `${dir}_links`), "collect", build.subquery({
116
+ const edges = build.op(build.ident(`_${dir}_links`), "collect", build.subquery({
110
117
  select: EDGE_FIELDS.map((name) => build.field(name, build.ident(name))),
111
118
  }));
112
- const seed = rootIds.map((id) => build.scalar(build.binary("==", build.ident("id"), build.lit(id))));
119
+ const seed = rootIds.map((id) => build.scalar(build.binary("==", build.ident("_id"), build.lit(id))));
113
120
  const q = build.query(build.ident("docs"), {
114
121
  select: [
115
- build.field("_depth", build.ident("$depth")),
116
- build.field("_stop", build.ident("$stop")),
117
- build.collect("_links", edges),
122
+ build.field(DEPTH_ALIAS, build.ident("$depth")),
123
+ build.field(STOP_ALIAS, build.ident("$stop")),
124
+ build.collect(LINKS_ALIAS, edges),
118
125
  ...userSelect,
119
126
  ],
120
127
  where: seed.length === 1 ? seed[0] : build.or(seed),
121
128
  // `depth` is the destination block's own clause (oqx 0.20).
122
- follow: build.follow([build.op(build.ident(`${dir}_links`), "collect", build.subquery({ from: [build.ident(dir === "out" ? "dst" : "doc")], depth }))], { distinct: true }),
129
+ follow: build.follow([build.op(build.ident(`_${dir}_links`), "collect", build.subquery({ from: [build.ident(dir === "out" ? "_dst" : "_doc")], depth }))], { distinct: true }),
123
130
  });
124
131
  return print(q);
125
132
  }
@@ -163,26 +170,26 @@ export async function graphNeighborhood(store, repoId, args, embedQuery) {
163
170
  if (res.truncated)
164
171
  queryTruncated = true;
165
172
  for (const hit of res.hits) {
166
- const hopDepth = Number(hit._depth); // $depth: seed = 1
173
+ const hopDepth = Number(hit[DEPTH_ALIAS]); // $depth: seed = 1
167
174
  const degree = hopDepth - 1; // root = 0
168
- // The walk's hits carry `id`/`path` (spec/surface §1.4, 3.0) and so
175
+ // The walk's hits carry `_id`/`_path` (spec/surface §1.4, 5.0) and so
169
176
  // does `graph`'s document: one notation for a document everywhere.
170
- const prev = docMap.get(hit.id);
177
+ const prev = docMap.get(hit._id);
171
178
  if (!prev || degree < prev.degree) {
172
- const doc = { id: hit.id, path: hit.path, degree, frontier: false };
179
+ const doc = { _id: hit._id, _path: hit._path, degree, frontier: false };
173
180
  outNames.forEach((name, i) => {
174
- // An `id`/`path` selection is the envelope key itself (same value).
175
- if (name !== undefined && name !== "id" && name !== "path")
181
+ // An `_id`/`_path` selection is the envelope key itself (same value).
182
+ if (name !== undefined && name !== "_id" && name !== "_path")
176
183
  doc[name] = hit[`_u${i}`] ?? null;
177
184
  });
178
- docMap.set(hit.id, doc);
185
+ docMap.set(hit._id, doc);
179
186
  }
180
187
  // Gather this node's edges; dedup by edge id (an a→b edge is reachable as
181
188
  // a's out-edge AND b's in-edge in "both" mode).
182
- const raw = hit._links ?? [];
189
+ const raw = hit[LINKS_ALIAS] ?? [];
183
190
  for (const e of raw)
184
- if (!edgeMap.has(e.id))
185
- edgeMap.set(e.id, e);
191
+ if (!edgeMap.has(e._id))
192
+ edgeMap.set(e._id, e);
186
193
  }
187
194
  }
188
195
  // Predicate filter restricts the NEIGHBORHOOD, not just the edge list: a doc is
@@ -196,12 +203,12 @@ export async function graphNeighborhood(store, repoId, args, embedQuery) {
196
203
  const adj = new Map();
197
204
  const link = (from, to) => { (adj.get(from) ?? adj.set(from, []).get(from)).push(to); };
198
205
  for (const e of edgeMap.values()) {
199
- if (e.predicate !== P)
206
+ if (e._predicate !== P)
200
207
  continue;
201
208
  if (dirs.includes("out"))
202
- link(e.doc_id, e.dst_id); // forward
209
+ link(e._doc_id, e._dst_id); // forward
203
210
  if (dirs.includes("in"))
204
- link(e.dst_id, e.doc_id); // backward
211
+ link(e._dst_id, e._doc_id); // backward
205
212
  }
206
213
  const depthOf = new Map(rootIds.map((id) => [id, 0]));
207
214
  let wave = [...rootIds];
@@ -230,10 +237,10 @@ export async function graphNeighborhood(store, repoId, args, embedQuery) {
230
237
  docMap.set(id, d);
231
238
  }
232
239
  // Cap the distinct document set (nearest-first), then bound edges to it.
233
- const allDocs = [...docMap.values()].sort((a, b) => a.degree - b.degree || cmpBytes(a.path, b.path));
240
+ const allDocs = [...docMap.values()].sort((a, b) => a.degree - b.degree || cmpBytes(a._path, b._path));
234
241
  const capped = allDocs.length > maxDocuments;
235
242
  const documents = allDocs.slice(0, maxDocuments);
236
- const reached = new Set(documents.map((d) => d.id));
243
+ const reached = new Set(documents.map((d) => d._id));
237
244
  const truncated = queryTruncated || capped;
238
245
  // Mark the outer boundary: a node whose MIN degree equals the requested reach
239
246
  // was only ever touched at the edge of the walk (never expanded past). At
@@ -242,7 +249,7 @@ export async function graphNeighborhood(store, repoId, args, embedQuery) {
242
249
  for (const d of documents) {
243
250
  d.frontier = d.degree === effectiveDegrees;
244
251
  if (d.frontier)
245
- frontier.push({ id: d.id, path: d.path, degree: d.degree });
252
+ frontier.push({ _id: d._id, _path: d._path, degree: d.degree });
246
253
  }
247
254
  // Keep the induced-subgraph edges (both endpoints in the neighborhood) plus
248
255
  // dangling stubs to external/phantom targets, so external (x_…) and phantom
@@ -251,15 +258,15 @@ export async function graphNeighborhood(store, repoId, args, embedQuery) {
251
258
  // predicate-reachable docs above).
252
259
  const edges = [];
253
260
  for (const e of edgeMap.values()) {
254
- if (args.predicate && e.predicate !== args.predicate)
261
+ if (args.predicate && e._predicate !== args.predicate)
255
262
  continue;
256
- const srcIn = reached.has(e.doc_id);
257
- const dstDangling = e.dst_kind === "external" || (e.dst_kind === "document" && e.dst_path === null);
258
- const dstIn = reached.has(e.dst_id) || dstDangling;
263
+ const srcIn = reached.has(e._doc_id);
264
+ const dstDangling = e._dst_kind === "external" || (e._dst_kind === "document" && e._dst_path === null);
265
+ const dstIn = reached.has(e._dst_id) || dstDangling;
259
266
  if (srcIn && dstIn)
260
267
  edges.push(e);
261
268
  }
262
- edges.sort((a, b) => cmpBytes(a.doc_id, b.doc_id) || cmpBytes(a.id, b.id));
269
+ edges.sort((a, b) => cmpBytes(a._doc_id, b._doc_id) || cmpBytes(a._id, b._id));
263
270
  return {
264
271
  roots: rootIds,
265
272
  degrees: effectiveDegrees,