@inerrata-corporation/errata 2.0.22-dev.4638 → 2.0.22-dev.4652

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.
@@ -17596,8 +17596,10 @@ function column(c) {
17596
17596
  function compileNodeWhere(q) {
17597
17597
  if (q.labels.length === 0) throw new Error("selectNodes: no labels");
17598
17598
  const params = [...q.labels];
17599
- const conds = [`label IN (${q.labels.map(() => "?").join(", ")})`];
17600
- if (!q.includeClosed) conds.push("valid_to IS NULL");
17599
+ const byId = (q.where ?? []).some((w) => "idIn" in w);
17600
+ const unindexed = byId ? "+" : "";
17601
+ const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
17602
+ if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
17601
17603
  for (const w of q.where ?? []) {
17602
17604
  if ("embedding" in w) {
17603
17605
  conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
@@ -17649,6 +17651,14 @@ function compileNodeWhere(q) {
17649
17651
  function attrValueSql(p) {
17650
17652
  return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
17651
17653
  }
17654
+ function nodeSelectFrom(q) {
17655
+ if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
17656
+ const where = q.where ?? [];
17657
+ if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
17658
+ return "nodes INDEXED BY nodes_live_description";
17659
+ }
17660
+ return "nodes";
17661
+ }
17652
17662
  function compileNodeSelect(q) {
17653
17663
  const { where, params } = compileNodeWhere(q);
17654
17664
  const attrs = q.attrs ?? [];
@@ -17666,7 +17676,7 @@ function compileNodeSelect(q) {
17666
17676
  }
17667
17677
  const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
17668
17678
  const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
17669
- const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM nodes${q.indexedBy ? ` INDEXED BY ${selectIndex(q.indexedBy)}` : ""} WHERE ${where} ORDER BY ${order}${limit}`;
17679
+ const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM ${nodeSelectFrom(q)} WHERE ${where} ORDER BY ${order}${limit}`;
17670
17680
  return { sql, params: [...params, ...orderParams] };
17671
17681
  }
17672
17682
  function decodeAttr(value, type) {
@@ -17903,6 +17913,18 @@ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid
17903
17913
  -- incremental reindex can fetch just the changed files' symbols instead of
17904
17914
  -- scanning every versioned node. Partial: only live rows are ever queried.
17905
17915
  CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
17916
+ -- Symbols by qname and doc comments by the symbol they attach to: the drain's
17917
+ -- symbol lexicon resolves the names in the text it ships one at a time, instead
17918
+ -- of reading every Function/Method/Class/Interface/Const and Comment row on each
17919
+ -- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
17920
+ CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
17921
+ CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
17922
+ -- The attrs the cloud drain gates every instance row on, so it reads them from
17923
+ -- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
17924
+ -- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
17925
+ -- two attrs read ~220 MB per push to find the few local rows that can ship.
17926
+ -- created_at leads so the index order is findNodeFieldsByLabel's.
17927
+ CREATE INDEX IF NOT EXISTS nodes_live_instance_gate ON nodes(label, created_at, json_extract(attrs_json, '$.source'), json_extract(attrs_json, '$.privateFinding'), json_extract(attrs_json, '$.resolvedAs'), id) WHERE valid_to IS NULL AND label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause');
17906
17928
  -- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
17907
17929
  -- live-only index above cannot serve it, so it scanned the whole table (~275 MB
17908
17930
  -- of a 1.1 GB store per reindex, 2026-10-20).
@@ -18253,6 +18275,52 @@ var SqliteGraphStore = class {
18253
18275
  AND (t.id IS NULL OR t.valid_to IS NOT NULL)
18254
18276
  `).all();
18255
18277
  }
18278
+ strandedSymbolsForFiles(fileIds) {
18279
+ const out = [];
18280
+ for (let i = 0; i < fileIds.length; i += 500) {
18281
+ const slice = fileIds.slice(i, i + 500);
18282
+ if (slice.length === 0) continue;
18283
+ out.push(...this.db.prepare(`
18284
+ SELECT f.id AS fileId, e.to_id AS symbolId
18285
+ FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
18286
+ LEFT JOIN nodes t ON t.id = e.to_id
18287
+ WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
18288
+ AND e.type IN ('DEFINES', 'CONTAINS')
18289
+ AND (t.id IS NULL OR t.valid_to IS NOT NULL)
18290
+ `).all(...slice));
18291
+ }
18292
+ return out;
18293
+ }
18294
+ firstLiveByNameOrQname(labels, name) {
18295
+ if (labels.length === 0) return null;
18296
+ const inLabels = labels.map(() => "?").join(",");
18297
+ const probe = (index, match) => this.db.prepare(
18298
+ `SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
18299
+ WHERE ${match} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
18300
+ ).all(name, ...labels);
18301
+ const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
18302
+ const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
18303
+ let best;
18304
+ for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
18305
+ if (!best || before(rank(r), rank(best))) best = r;
18306
+ }
18307
+ return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
18308
+ }
18309
+ instanceGateRows(label) {
18310
+ return this.db.prepare(
18311
+ `SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
18312
+ json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
18313
+ FROM nodes INDEXED BY nodes_live_instance_gate
18314
+ WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
18315
+ ORDER BY label, created_at, rowid`
18316
+ ).all(label);
18317
+ }
18318
+ liveCommentsAttachedTo(name) {
18319
+ return this.db.prepare(
18320
+ `SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
18321
+ WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
18322
+ ).all(name);
18323
+ }
18256
18324
  strandedEdges() {
18257
18325
  return countStrandedEdges(this.db);
18258
18326
  }
@@ -18431,7 +18499,11 @@ var SqliteGraphStore = class {
18431
18499
  if (!stmt2) {
18432
18500
  const cols = attrKeys.map((k, i) => `, ${attrValueSql(`'$.${k}'`)} AS a${i}, json_type(attrs_json, '$.${k}') AS t${i}`).join("");
18433
18501
  stmt2 = this.db.prepare(
18434
- `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL`
18502
+ // Ordered, not left to the planner: readers take "first row wins" (the
18503
+ // symbol lexicon's name winner and doc pick), and the order otherwise
18504
+ // followed whichever partial index the planner chose. nodes_live_created
18505
+ // serves this order without a sort.
18506
+ `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
18435
18507
  );
18436
18508
  this.fieldStmts.set(cacheKey, stmt2);
18437
18509
  }
@@ -18457,7 +18529,7 @@ var SqliteGraphStore = class {
18457
18529
  }
18458
18530
  countNodes(q) {
18459
18531
  const { where, params } = compileNodeWhere(q);
18460
- return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM nodes WHERE ${where}`).get(...params).n);
18532
+ return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
18461
18533
  }
18462
18534
  setCommunity(id, community) {
18463
18535
  this.stmts.setCommunity.run({ id, v: community });
@@ -20877,6 +20949,62 @@ function buildSymbolLexicon(store) {
20877
20949
  }
20878
20950
  return lex;
20879
20951
  }
20952
+ function lazySymbolLexicon(store) {
20953
+ if (!store.firstLiveByNameOrQname || !store.liveCommentsAttachedTo) return null;
20954
+ const labels = SYMBOL_LABELS.map(([label]) => label);
20955
+ const kindOf = new Map(SYMBOL_LABELS.map(([label, kind]) => [label, kind]));
20956
+ const members = /* @__PURE__ */ new Map();
20957
+ const docs = /* @__PURE__ */ new Map();
20958
+ const entries = /* @__PURE__ */ new Map();
20959
+ const member = (name) => {
20960
+ if (!members.has(name)) {
20961
+ const hit = isDistinctiveIdentifier(name) ? store.firstLiveByNameOrQname(labels, name) : null;
20962
+ members.set(name, hit ? { kind: kindOf.get(hit.label), description: hit.description, qname: hit.qname } : null);
20963
+ }
20964
+ return members.get(name);
20965
+ };
20966
+ const docFor = (name) => {
20967
+ if (!docs.has(name)) {
20968
+ let found;
20969
+ for (const c of store.liveCommentsAttachedTo(name)) {
20970
+ if (typeof c.kind !== "string" || !DOC_COMMENT_KINDS.has(c.kind)) continue;
20971
+ const text = typeof c.text === "string" ? c.text : c.description;
20972
+ if (!text?.trim()) continue;
20973
+ found = text.trim().slice(0, MAX_DOC_CHARS);
20974
+ break;
20975
+ }
20976
+ docs.set(name, found);
20977
+ }
20978
+ return docs.get(name);
20979
+ };
20980
+ const isKnown = (tok) => member(tok) !== null;
20981
+ let anySymbol;
20982
+ return {
20983
+ has: isKnown,
20984
+ get(name) {
20985
+ if (!entries.has(name)) {
20986
+ const m = member(name);
20987
+ let entry = null;
20988
+ if (m) {
20989
+ entry = { kind: m.kind };
20990
+ const doc = (m.description ? docFor(m.description) : void 0) ?? (m.qname ? docFor(m.qname) : void 0);
20991
+ for (const phrase of summaryCandidates(name, m.kind, doc)) {
20992
+ if (summaryRejectionReason(phrase, isKnown) === null) {
20993
+ entry.summary = phrase;
20994
+ break;
20995
+ }
20996
+ }
20997
+ }
20998
+ entries.set(name, entry);
20999
+ }
21000
+ return entries.get(name) ?? void 0;
21001
+ },
21002
+ get size() {
21003
+ anySymbol ??= labels.some((label) => store.countNodesByLabel(label) > 0);
21004
+ return anySymbol ? 1 : 0;
21005
+ }
21006
+ };
21007
+ }
20880
21008
  var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
20881
21009
  function codeGraphKey(store) {
20882
21010
  const files = store.labelFingerprint?.(["File"]);
@@ -20888,7 +21016,7 @@ function lexiconsFor(store) {
20888
21016
  const key = codeGraphKey(store);
20889
21017
  const hit = key !== null ? lexiconCache.get(store) : void 0;
20890
21018
  if (hit && hit.key === key) return hit;
20891
- const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
21019
+ const built = { symbols: lazySymbolLexicon(store) ?? buildSymbolLexicon(store), files: buildFileLexicon(store) };
20892
21020
  if (key !== null) lexiconCache.set(store, { key, ...built });
20893
21021
  return built;
20894
21022
  }
@@ -21127,6 +21255,38 @@ function stripCodebaseScope(scope) {
21127
21255
  delete s["codebase"];
21128
21256
  return s;
21129
21257
  }
21258
+ function instanceGateRows(store, label) {
21259
+ return store.instanceGateRows?.(label) ?? store.findNodeFieldsByLabel(label, ["source", "privateFinding", "resolvedAs"]).map((r) => ({
21260
+ id: r.id,
21261
+ source: r.attrs["source"],
21262
+ privateFinding: r.attrs["privateFinding"],
21263
+ resolvedAs: r.attrs["resolvedAs"]
21264
+ }));
21265
+ }
21266
+ var GATE_VIEW_ATTRS = [
21267
+ "privateFinding",
21268
+ "source",
21269
+ "sources",
21270
+ "resolvedAs",
21271
+ "instanceReceipts",
21272
+ "cloudWithdrawn",
21273
+ "withdrawnAtCorrectionSeq",
21274
+ "contributedAtSeq",
21275
+ "cloudNodeId"
21276
+ ];
21277
+ var GATE_VIEW_CHUNK = 500;
21278
+ var gateView = /* @__PURE__ */ new WeakSet();
21279
+ function gateViews(store, label, ids) {
21280
+ const byId = /* @__PURE__ */ new Map();
21281
+ for (let i = 0; i < ids.length; i += GATE_VIEW_CHUNK) {
21282
+ for (const r of store.selectNodes({ labels: [label], attrs: GATE_VIEW_ATTRS, where: [{ idIn: ids.slice(i, i + GATE_VIEW_CHUNK) }] })) {
21283
+ const view = { ...r, embedding: [] };
21284
+ gateView.add(view);
21285
+ byId.set(r.id, view);
21286
+ }
21287
+ }
21288
+ return ids.flatMap((id) => byId.get(id) ?? []);
21289
+ }
21130
21290
  function contributedWireId(n, destinationKey) {
21131
21291
  const cloudId = instanceReceipt(n, destinationKey)["cloudNodeId"];
21132
21292
  return typeof cloudId === "string" && cloudId ? cloudId : n.id;
@@ -21285,7 +21445,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
21285
21445
  edgesHeldDropped: 0
21286
21446
  };
21287
21447
  const sharedCauses = /* @__PURE__ */ new Map();
21288
- const causalQueue = store.findNodeFieldsByLabel("Problem", ["source", "resolvedAs"]).filter((r) => r.attrs["source"] !== "cloud" && r.attrs["resolvedAs"] !== PROBLEM_RESOLUTION.FALSE_POSITIVE).map((r) => r.id).filter((id) => ignorePatterns.length === 0 || !ignored(store.getNode(id)?.description ?? ""));
21448
+ const causalQueue = instanceGateRows(store, "Problem").filter((r) => r.source !== "cloud" && r.resolvedAs !== PROBLEM_RESOLUTION.FALSE_POSITIVE).map((r) => r.id).filter((id) => ignorePatterns.length === 0 || !ignored(store.getNode(id)?.description ?? ""));
21289
21449
  const causalSeen = new Set(causalQueue);
21290
21450
  for (let i = 0; i < causalQueue.length; i++) {
21291
21451
  const id = causalQueue[i];
@@ -21301,21 +21461,21 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
21301
21461
  for (const label of INSTANCE_LABELS) {
21302
21462
  const ref = [];
21303
21463
  includedByLabel.set(label, ref);
21304
- const local = [];
21305
- for (const row of store.findNodeFieldsByLabel(label, ["source", "privateFinding"])) {
21306
- if (row.attrs["source"] !== "cloud") {
21307
- const n = store.getNode(row.id);
21308
- if (n) local.push(n);
21464
+ const localIds = [];
21465
+ for (const row of instanceGateRows(store, label)) {
21466
+ if (row.source !== "cloud") {
21467
+ localIds.push(row.id);
21309
21468
  continue;
21310
21469
  }
21311
21470
  if (seen.has(row.id) || walked.has(row.id)) continue;
21312
21471
  walked.add(row.id);
21313
21472
  composition.candidates++;
21314
- if ((row.attrs["privateFinding"] || label === "Vulnerability") && !opts.project) composition.privateTargetMissing++;
21473
+ if ((row.privateFinding || label === "Vulnerability") && !opts.project) composition.privateTargetMissing++;
21315
21474
  else composition.cloudSourced++;
21316
21475
  }
21476
+ const local = gateViews(store, label, localIds);
21317
21477
  const population = label === "RootCause" && opts.sharedStore ? [...local, ...sharedCauses.values()] : local;
21318
- for (const n of population) {
21478
+ for (let n of population) {
21319
21479
  if (seen.has(n.id)) continue;
21320
21480
  if (walked.has(n.id)) continue;
21321
21481
  walked.add(n.id);
@@ -21351,6 +21511,11 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
21351
21511
  semanticEndpoints.set(n.id, contributedWireId(n, opts.destinationKey));
21352
21512
  continue;
21353
21513
  }
21514
+ if (gateView.has(n)) {
21515
+ const full = store.getNode(n.id);
21516
+ if (!full) continue;
21517
+ n = full;
21518
+ }
21354
21519
  if (isEditPlaceholderSolution(n)) {
21355
21520
  composition.autoMint++;
21356
21521
  continue;
@@ -21754,7 +21919,7 @@ function runPass(msg) {
21754
21919
  return revisitContradictedPrinciples(shared, msg.ts ?? Date.now());
21755
21920
  case "strandedCount": {
21756
21921
  if (!msg.castaliaPath) throw new Error("strandedCount: castaliaPath required");
21757
- return findStrandedSymbols(storeAt(msg.castaliaPath)).length;
21922
+ return findStrandedSymbols(storeAt(msg.castaliaPath));
21758
21923
  }
21759
21924
  case "drainContext": {
21760
21925
  if (!msg.castaliaPath) throw new Error("drainContext: castaliaPath required");
package/errata.mjs CHANGED
@@ -20393,8 +20393,10 @@ function column(c) {
20393
20393
  function compileNodeWhere(q) {
20394
20394
  if (q.labels.length === 0) throw new Error("selectNodes: no labels");
20395
20395
  const params = [...q.labels];
20396
- const conds = [`label IN (${q.labels.map(() => "?").join(", ")})`];
20397
- if (!q.includeClosed) conds.push("valid_to IS NULL");
20396
+ const byId = (q.where ?? []).some((w) => "idIn" in w);
20397
+ const unindexed = byId ? "+" : "";
20398
+ const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
20399
+ if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
20398
20400
  for (const w of q.where ?? []) {
20399
20401
  if ("embedding" in w) {
20400
20402
  conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
@@ -20446,6 +20448,14 @@ function compileNodeWhere(q) {
20446
20448
  function attrValueSql(p) {
20447
20449
  return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
20448
20450
  }
20451
+ function nodeSelectFrom(q) {
20452
+ if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
20453
+ const where = q.where ?? [];
20454
+ if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
20455
+ return "nodes INDEXED BY nodes_live_description";
20456
+ }
20457
+ return "nodes";
20458
+ }
20449
20459
  function compileNodeSelect(q) {
20450
20460
  const { where, params } = compileNodeWhere(q);
20451
20461
  const attrs = q.attrs ?? [];
@@ -20463,7 +20473,7 @@ function compileNodeSelect(q) {
20463
20473
  }
20464
20474
  const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
20465
20475
  const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
20466
- const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM nodes${q.indexedBy ? ` INDEXED BY ${selectIndex(q.indexedBy)}` : ""} WHERE ${where} ORDER BY ${order}${limit}`;
20476
+ const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM ${nodeSelectFrom(q)} WHERE ${where} ORDER BY ${order}${limit}`;
20467
20477
  return { sql, params: [...params, ...orderParams] };
20468
20478
  }
20469
20479
  function decodeAttr(value, type) {
@@ -20812,6 +20822,18 @@ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid
20812
20822
  -- incremental reindex can fetch just the changed files' symbols instead of
20813
20823
  -- scanning every versioned node. Partial: only live rows are ever queried.
20814
20824
  CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
20825
+ -- Symbols by qname and doc comments by the symbol they attach to: the drain's
20826
+ -- symbol lexicon resolves the names in the text it ships one at a time, instead
20827
+ -- of reading every Function/Method/Class/Interface/Const and Comment row on each
20828
+ -- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
20829
+ CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
20830
+ CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
20831
+ -- The attrs the cloud drain gates every instance row on, so it reads them from
20832
+ -- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
20833
+ -- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
20834
+ -- two attrs read ~220 MB per push to find the few local rows that can ship.
20835
+ -- created_at leads so the index order is findNodeFieldsByLabel's.
20836
+ CREATE INDEX IF NOT EXISTS nodes_live_instance_gate ON nodes(label, created_at, json_extract(attrs_json, '$.source'), json_extract(attrs_json, '$.privateFinding'), json_extract(attrs_json, '$.resolvedAs'), id) WHERE valid_to IS NULL AND label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause');
20815
20837
  -- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
20816
20838
  -- live-only index above cannot serve it, so it scanned the whole table (~275 MB
20817
20839
  -- of a 1.1 GB store per reindex, 2026-10-20).
@@ -21105,6 +21127,52 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21105
21127
  AND (t.id IS NULL OR t.valid_to IS NOT NULL)
21106
21128
  `).all();
21107
21129
  }
21130
+ strandedSymbolsForFiles(fileIds) {
21131
+ const out2 = [];
21132
+ for (let i2 = 0; i2 < fileIds.length; i2 += 500) {
21133
+ const slice = fileIds.slice(i2, i2 + 500);
21134
+ if (slice.length === 0) continue;
21135
+ out2.push(...this.db.prepare(`
21136
+ SELECT f.id AS fileId, e.to_id AS symbolId
21137
+ FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
21138
+ LEFT JOIN nodes t ON t.id = e.to_id
21139
+ WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
21140
+ AND e.type IN ('DEFINES', 'CONTAINS')
21141
+ AND (t.id IS NULL OR t.valid_to IS NOT NULL)
21142
+ `).all(...slice));
21143
+ }
21144
+ return out2;
21145
+ }
21146
+ firstLiveByNameOrQname(labels, name2) {
21147
+ if (labels.length === 0) return null;
21148
+ const inLabels = labels.map(() => "?").join(",");
21149
+ const probe = (index, match2) => this.db.prepare(
21150
+ `SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
21151
+ WHERE ${match2} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
21152
+ ).all(name2, ...labels);
21153
+ const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
21154
+ const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
21155
+ let best;
21156
+ for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
21157
+ if (!best || before(rank(r), rank(best))) best = r;
21158
+ }
21159
+ return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
21160
+ }
21161
+ instanceGateRows(label) {
21162
+ return this.db.prepare(
21163
+ `SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
21164
+ json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
21165
+ FROM nodes INDEXED BY nodes_live_instance_gate
21166
+ WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
21167
+ ORDER BY label, created_at, rowid`
21168
+ ).all(label);
21169
+ }
21170
+ liveCommentsAttachedTo(name2) {
21171
+ return this.db.prepare(
21172
+ `SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
21173
+ WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
21174
+ ).all(name2);
21175
+ }
21108
21176
  strandedEdges() {
21109
21177
  return countStrandedEdges(this.db);
21110
21178
  }
@@ -21283,7 +21351,11 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21283
21351
  if (!stmt2) {
21284
21352
  const cols = attrKeys.map((k, i2) => `, ${attrValueSql(`'$.${k}'`)} AS a${i2}, json_type(attrs_json, '$.${k}') AS t${i2}`).join("");
21285
21353
  stmt2 = this.db.prepare(
21286
- `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL`
21354
+ // Ordered, not left to the planner: readers take "first row wins" (the
21355
+ // symbol lexicon's name winner and doc pick), and the order otherwise
21356
+ // followed whichever partial index the planner chose. nodes_live_created
21357
+ // serves this order without a sort.
21358
+ `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
21287
21359
  );
21288
21360
  this.fieldStmts.set(cacheKey2, stmt2);
21289
21361
  }
@@ -21309,7 +21381,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21309
21381
  }
21310
21382
  countNodes(q) {
21311
21383
  const { where, params } = compileNodeWhere(q);
21312
- return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM nodes WHERE ${where}`).get(...params).n);
21384
+ return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
21313
21385
  }
21314
21386
  setCommunity(id, community) {
21315
21387
  this.stmts.setCommunity.run({ id, v: community });
@@ -37286,6 +37358,62 @@ function buildSymbolLexicon(store) {
37286
37358
  }
37287
37359
  return lex;
37288
37360
  }
37361
+ function lazySymbolLexicon(store) {
37362
+ if (!store.firstLiveByNameOrQname || !store.liveCommentsAttachedTo) return null;
37363
+ const labels = SYMBOL_LABELS2.map(([label]) => label);
37364
+ const kindOf = new Map(SYMBOL_LABELS2.map(([label, kind]) => [label, kind]));
37365
+ const members = /* @__PURE__ */ new Map();
37366
+ const docs = /* @__PURE__ */ new Map();
37367
+ const entries = /* @__PURE__ */ new Map();
37368
+ const member = (name2) => {
37369
+ if (!members.has(name2)) {
37370
+ const hit = isDistinctiveIdentifier(name2) ? store.firstLiveByNameOrQname(labels, name2) : null;
37371
+ members.set(name2, hit ? { kind: kindOf.get(hit.label), description: hit.description, qname: hit.qname } : null);
37372
+ }
37373
+ return members.get(name2);
37374
+ };
37375
+ const docFor = (name2) => {
37376
+ if (!docs.has(name2)) {
37377
+ let found;
37378
+ for (const c of store.liveCommentsAttachedTo(name2)) {
37379
+ if (typeof c.kind !== "string" || !DOC_COMMENT_KINDS.has(c.kind)) continue;
37380
+ const text = typeof c.text === "string" ? c.text : c.description;
37381
+ if (!text?.trim()) continue;
37382
+ found = text.trim().slice(0, MAX_DOC_CHARS);
37383
+ break;
37384
+ }
37385
+ docs.set(name2, found);
37386
+ }
37387
+ return docs.get(name2);
37388
+ };
37389
+ const isKnown = (tok) => member(tok) !== null;
37390
+ let anySymbol;
37391
+ return {
37392
+ has: isKnown,
37393
+ get(name2) {
37394
+ if (!entries.has(name2)) {
37395
+ const m = member(name2);
37396
+ let entry = null;
37397
+ if (m) {
37398
+ entry = { kind: m.kind };
37399
+ const doc = (m.description ? docFor(m.description) : void 0) ?? (m.qname ? docFor(m.qname) : void 0);
37400
+ for (const phrase2 of summaryCandidates(name2, m.kind, doc)) {
37401
+ if (summaryRejectionReason(phrase2, isKnown) === null) {
37402
+ entry.summary = phrase2;
37403
+ break;
37404
+ }
37405
+ }
37406
+ }
37407
+ entries.set(name2, entry);
37408
+ }
37409
+ return entries.get(name2) ?? void 0;
37410
+ },
37411
+ get size() {
37412
+ anySymbol ??= labels.some((label) => store.countNodesByLabel(label) > 0);
37413
+ return anySymbol ? 1 : 0;
37414
+ }
37415
+ };
37416
+ }
37289
37417
  function codeGraphKey(store) {
37290
37418
  const files = store.labelFingerprint?.(["File"]);
37291
37419
  if (files === void 0) return null;
@@ -37295,7 +37423,7 @@ function lexiconsFor(store) {
37295
37423
  const key = codeGraphKey(store);
37296
37424
  const hit = key !== null ? lexiconCache.get(store) : void 0;
37297
37425
  if (hit && hit.key === key) return hit;
37298
- const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
37426
+ const built = { symbols: lazySymbolLexicon(store) ?? buildSymbolLexicon(store), files: buildFileLexicon(store) };
37299
37427
  if (key !== null) lexiconCache.set(store, { key, ...built });
37300
37428
  return built;
37301
37429
  }
@@ -47122,14 +47250,31 @@ function standingStrandedCount(store) {
47122
47250
  try {
47123
47251
  const last = JSON.parse(store.getMeta?.(STRANDED_KEY) ?? "null");
47124
47252
  if (!last) return null;
47125
- if ((store.changeSeq?.() ?? Infinity) - last.seq > STRANDED_WINDOW_MAX) return null;
47126
- return store.lineageChangedFiles?.(last.seq)?.length === 0 ? last.count : null;
47253
+ const seq = store.changeSeq?.();
47254
+ if ((seq ?? Infinity) - last.seq > STRANDED_WINDOW_MAX) return null;
47255
+ const changed = store.lineageChangedFiles?.(last.seq);
47256
+ if (!changed) return null;
47257
+ if (changed.length === 0) return last.count;
47258
+ if (!last.byFile || !store.strandedSymbolsForFiles || seq === void 0) return null;
47259
+ const byFile = { ...last.byFile };
47260
+ for (const f of changed) delete byFile[f];
47261
+ for (const s of store.strandedSymbolsForFiles(changed)) byFile[s.fileId] = (byFile[s.fileId] ?? 0) + 1;
47262
+ const count = Object.values(byFile).reduce((a, b) => a + b, 0);
47263
+ store.setMeta?.(STRANDED_KEY, JSON.stringify({ seq, count, byFile }));
47264
+ return count;
47127
47265
  } catch {
47128
47266
  return null;
47129
47267
  }
47130
47268
  }
47131
- function recordStrandedCount(store, seq, count) {
47132
- if (seq !== void 0) store.setMeta?.(STRANDED_KEY, JSON.stringify({ seq, count }));
47269
+ function recordStrandedCount(store, seq, stranded) {
47270
+ if (seq === void 0) return;
47271
+ if (typeof stranded === "number") {
47272
+ store.setMeta?.(STRANDED_KEY, JSON.stringify({ seq, count: stranded }));
47273
+ return;
47274
+ }
47275
+ const byFile = {};
47276
+ for (const s of stranded) byFile[s.fileId] = (byFile[s.fileId] ?? 0) + 1;
47277
+ store.setMeta?.(STRANDED_KEY, JSON.stringify({ seq, count: stranded.length, byFile }));
47133
47278
  }
47134
47279
  function reapOrphanedFiles(store, rootPath, workspaceId2, ignoreDirs = []) {
47135
47280
  const ignores = /* @__PURE__ */ new Set([...DEFAULT_IGNORES, ...ignoreDirs]);
@@ -80199,7 +80344,7 @@ import { readFileSync as readFileSync18, renameSync as renameSync3, statSync as
80199
80344
  import { fileURLToPath as fileURLToPath2 } from "node:url";
80200
80345
  import { dirname as dirname11, join as join22 } from "node:path";
80201
80346
  function snapshotReusable(held, want) {
80202
- return held !== null && held.key === want.key && held.atCommit === want.atCommit && held.workingFile === want.workingFile;
80347
+ return held !== null && held.key === want.key && held.atCommit === want.atCommit;
80203
80348
  }
80204
80349
  var codeIdentityMemo;
80205
80350
  function snapshotCodeIdentity(version3) {
@@ -86861,7 +87006,7 @@ function createWatchBreaker(deps) {
86861
87006
  }
86862
87007
 
86863
87008
  // src/engine.ts
86864
- var DAEMON_VERSION = true ? "2.0.22-dev.4638" : "2.0.0-alpha.0";
87009
+ var DAEMON_VERSION = true ? "2.0.22-dev.4652" : "2.0.0-alpha.0";
86865
87010
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
86866
87011
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
86867
87012
  var GIT_OP_MUTE_MS = 4e3;
@@ -87374,10 +87519,18 @@ function createWorkspaceEngine(opts) {
87374
87519
  };
87375
87520
  const changes = store.changeSeq ? store.changeSeq() - store.changeSeq([EXPOSURE_SCOPE]) : store.changeStamp();
87376
87521
  const { id, name: name2, stack, languages, domains, goals } = snapOpts.profile;
87377
- const snapshotKey = `${snapshotCodeIdentity(DAEMON_VERSION)}|${changes}|${JSON.stringify({ ...snapOpts, profile: { id, name: name2, stack, languages, domains, goals } })}`;
87522
+ const { workingFile: _workingFile, ...keyedOpts } = snapOpts;
87523
+ const snapshotKey = `${snapshotCodeIdentity(DAEMON_VERSION)}|${changes}|${JSON.stringify({ ...keyedOpts, profile: { id, name: name2, stack, languages, domains, goals } })}`;
87378
87524
  lastWorkerSnapshot ??= loadHeldSnapshot(heldSnapshotPath(paths.configDir));
87379
87525
  const held = lastWorkerSnapshot;
87380
- const prebuilt = snapshotReusable(held, { key: snapshotKey, atCommit: snapOpts.atCommit, workingFile: snapOpts.workingFile }) ? structuredClone(held.snapshot) : passWorker ? await passWorker.snapshot(snapOpts).then((snapshot2) => {
87526
+ const withWorkingFile = (snap, builtFor, want, atCommit) => {
87527
+ if (builtFor === want) return snap;
87528
+ const slice = want ? sliceForFile(store, want, atCommit) : void 0;
87529
+ if (slice) snap.workingFile = slice;
87530
+ else delete snap.workingFile;
87531
+ return snap;
87532
+ };
87533
+ const prebuilt = snapshotReusable(held, { key: snapshotKey, atCommit: snapOpts.atCommit }) ? withWorkingFile(structuredClone(held.snapshot), held.workingFile, snapOpts.workingFile, snapOpts.atCommit) : passWorker ? await passWorker.snapshot(snapOpts).then((snapshot2) => {
87381
87534
  lastWorkerSnapshot = { key: snapshotKey, at: Date.now(), atCommit: snapOpts.atCommit, workingFile: snapOpts.workingFile, snapshot: structuredClone(snapshot2) };
87382
87535
  saveHeldSnapshot(heldSnapshotPath(paths.configDir), lastWorkerSnapshot);
87383
87536
  return snapshot2;
@@ -90691,6 +90844,38 @@ function compositionCounters(c) {
90691
90844
  ...Object.fromEntries(buckets.map((b) => [`sidecarDrop_${b}`, sidecarDropReasons[b] ?? 0]))
90692
90845
  };
90693
90846
  }
90847
+ function instanceGateRows(store, label) {
90848
+ return store.instanceGateRows?.(label) ?? store.findNodeFieldsByLabel(label, ["source", "privateFinding", "resolvedAs"]).map((r) => ({
90849
+ id: r.id,
90850
+ source: r.attrs["source"],
90851
+ privateFinding: r.attrs["privateFinding"],
90852
+ resolvedAs: r.attrs["resolvedAs"]
90853
+ }));
90854
+ }
90855
+ var GATE_VIEW_ATTRS = [
90856
+ "privateFinding",
90857
+ "source",
90858
+ "sources",
90859
+ "resolvedAs",
90860
+ "instanceReceipts",
90861
+ "cloudWithdrawn",
90862
+ "withdrawnAtCorrectionSeq",
90863
+ "contributedAtSeq",
90864
+ "cloudNodeId"
90865
+ ];
90866
+ var GATE_VIEW_CHUNK = 500;
90867
+ var gateView = /* @__PURE__ */ new WeakSet();
90868
+ function gateViews(store, label, ids) {
90869
+ const byId = /* @__PURE__ */ new Map();
90870
+ for (let i2 = 0; i2 < ids.length; i2 += GATE_VIEW_CHUNK) {
90871
+ for (const r of store.selectNodes({ labels: [label], attrs: GATE_VIEW_ATTRS, where: [{ idIn: ids.slice(i2, i2 + GATE_VIEW_CHUNK) }] })) {
90872
+ const view = { ...r, embedding: [] };
90873
+ gateView.add(view);
90874
+ byId.set(r.id, view);
90875
+ }
90876
+ }
90877
+ return ids.flatMap((id) => byId.get(id) ?? []);
90878
+ }
90694
90879
  function contributedWireId(n, destinationKey) {
90695
90880
  const cloudId = instanceReceipt(n, destinationKey)["cloudNodeId"];
90696
90881
  return typeof cloudId === "string" && cloudId ? cloudId : n.id;
@@ -90849,7 +91034,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
90849
91034
  edgesHeldDropped: 0
90850
91035
  };
90851
91036
  const sharedCauses = /* @__PURE__ */ new Map();
90852
- const causalQueue = store.findNodeFieldsByLabel("Problem", ["source", "resolvedAs"]).filter((r) => r.attrs["source"] !== "cloud" && r.attrs["resolvedAs"] !== PROBLEM_RESOLUTION.FALSE_POSITIVE).map((r) => r.id).filter((id) => ignorePatterns.length === 0 || !ignored(store.getNode(id)?.description ?? ""));
91037
+ const causalQueue = instanceGateRows(store, "Problem").filter((r) => r.source !== "cloud" && r.resolvedAs !== PROBLEM_RESOLUTION.FALSE_POSITIVE).map((r) => r.id).filter((id) => ignorePatterns.length === 0 || !ignored(store.getNode(id)?.description ?? ""));
90853
91038
  const causalSeen = new Set(causalQueue);
90854
91039
  for (let i2 = 0; i2 < causalQueue.length; i2++) {
90855
91040
  const id = causalQueue[i2];
@@ -90865,21 +91050,21 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
90865
91050
  for (const label of INSTANCE_LABELS) {
90866
91051
  const ref = [];
90867
91052
  includedByLabel.set(label, ref);
90868
- const local = [];
90869
- for (const row of store.findNodeFieldsByLabel(label, ["source", "privateFinding"])) {
90870
- if (row.attrs["source"] !== "cloud") {
90871
- const n = store.getNode(row.id);
90872
- if (n) local.push(n);
91053
+ const localIds = [];
91054
+ for (const row of instanceGateRows(store, label)) {
91055
+ if (row.source !== "cloud") {
91056
+ localIds.push(row.id);
90873
91057
  continue;
90874
91058
  }
90875
91059
  if (seen.has(row.id) || walked.has(row.id)) continue;
90876
91060
  walked.add(row.id);
90877
91061
  composition.candidates++;
90878
- if ((row.attrs["privateFinding"] || label === "Vulnerability") && !opts.project) composition.privateTargetMissing++;
91062
+ if ((row.privateFinding || label === "Vulnerability") && !opts.project) composition.privateTargetMissing++;
90879
91063
  else composition.cloudSourced++;
90880
91064
  }
91065
+ const local = gateViews(store, label, localIds);
90881
91066
  const population = label === "RootCause" && opts.sharedStore ? [...local, ...sharedCauses.values()] : local;
90882
- for (const n of population) {
91067
+ for (let n of population) {
90883
91068
  if (seen.has(n.id)) continue;
90884
91069
  if (walked.has(n.id)) continue;
90885
91070
  walked.add(n.id);
@@ -90915,6 +91100,11 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
90915
91100
  semanticEndpoints.set(n.id, contributedWireId(n, opts.destinationKey));
90916
91101
  continue;
90917
91102
  }
91103
+ if (gateView.has(n)) {
91104
+ const full = store.getNode(n.id);
91105
+ if (!full) continue;
91106
+ n = full;
91107
+ }
90918
91108
  if (isEditPlaceholderSolution(n)) {
90919
91109
  composition.autoMint++;
90920
91110
  continue;
@@ -95314,17 +95504,17 @@ async function startMultiDaemon(opts = {}) {
95314
95504
  if (!strandedRefreshing.has(r.id)) {
95315
95505
  strandedRefreshing.add(r.id);
95316
95506
  const seq = store.changeSeq?.();
95317
- const finish = (count) => {
95318
- strandedCache.set(r.id, { at: Date.now(), count });
95507
+ const finish = (stranded) => {
95508
+ strandedCache.set(r.id, { at: Date.now(), count: stranded.length });
95319
95509
  strandedRefreshing.delete(r.id);
95320
- recordStrandedCount(store, seq, count);
95510
+ recordStrandedCount(store, seq, stranded);
95321
95511
  };
95322
95512
  if (consolidateWorker && r.engine.paths.castalia !== ":memory:") {
95323
- consolidateWorker.run({ kind: "strandedCount", sharedDbPath, castaliaPath: r.engine.paths.castalia }).then((n) => finish(Number(n))).catch(() => {
95513
+ consolidateWorker.run({ kind: "strandedCount", sharedDbPath, castaliaPath: r.engine.paths.castalia }).then((stranded) => finish(Array.isArray(stranded) ? stranded : [])).catch(() => {
95324
95514
  setImmediate(() => {
95325
95515
  const done = markPass(`stranded-scan:${r.entry.name}`);
95326
95516
  try {
95327
- finish(findStrandedSymbols(r.engine.store).length);
95517
+ finish(findStrandedSymbols(r.engine.store));
95328
95518
  } catch {
95329
95519
  strandedRefreshing.delete(r.id);
95330
95520
  } finally {
@@ -95336,7 +95526,7 @@ async function startMultiDaemon(opts = {}) {
95336
95526
  setImmediate(() => {
95337
95527
  const done = markPass(`stranded-scan:${r.entry.name}`);
95338
95528
  try {
95339
- finish(findStrandedSymbols(r.engine.store).length);
95529
+ finish(findStrandedSymbols(r.engine.store));
95340
95530
  } catch {
95341
95531
  strandedRefreshing.delete(r.id);
95342
95532
  } finally {
@@ -15993,8 +15993,10 @@ function column(c) {
15993
15993
  function compileNodeWhere(q) {
15994
15994
  if (q.labels.length === 0) throw new Error("selectNodes: no labels");
15995
15995
  const params = [...q.labels];
15996
- const conds = [`label IN (${q.labels.map(() => "?").join(", ")})`];
15997
- if (!q.includeClosed) conds.push("valid_to IS NULL");
15996
+ const byId = (q.where ?? []).some((w) => "idIn" in w);
15997
+ const unindexed = byId ? "+" : "";
15998
+ const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
15999
+ if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
15998
16000
  for (const w of q.where ?? []) {
15999
16001
  if ("embedding" in w) {
16000
16002
  conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
@@ -16046,6 +16048,14 @@ function compileNodeWhere(q) {
16046
16048
  function attrValueSql(p) {
16047
16049
  return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
16048
16050
  }
16051
+ function nodeSelectFrom(q) {
16052
+ if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
16053
+ const where = q.where ?? [];
16054
+ if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
16055
+ return "nodes INDEXED BY nodes_live_description";
16056
+ }
16057
+ return "nodes";
16058
+ }
16049
16059
  function compileNodeSelect(q) {
16050
16060
  const { where, params } = compileNodeWhere(q);
16051
16061
  const attrs = q.attrs ?? [];
@@ -16063,7 +16073,7 @@ function compileNodeSelect(q) {
16063
16073
  }
16064
16074
  const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
16065
16075
  const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
16066
- const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM nodes${q.indexedBy ? ` INDEXED BY ${selectIndex(q.indexedBy)}` : ""} WHERE ${where} ORDER BY ${order}${limit}`;
16076
+ const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM ${nodeSelectFrom(q)} WHERE ${where} ORDER BY ${order}${limit}`;
16067
16077
  return { sql, params: [...params, ...orderParams] };
16068
16078
  }
16069
16079
  function decodeAttr(value, type) {
@@ -16300,6 +16310,18 @@ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid
16300
16310
  -- incremental reindex can fetch just the changed files' symbols instead of
16301
16311
  -- scanning every versioned node. Partial: only live rows are ever queried.
16302
16312
  CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
16313
+ -- Symbols by qname and doc comments by the symbol they attach to: the drain's
16314
+ -- symbol lexicon resolves the names in the text it ships one at a time, instead
16315
+ -- of reading every Function/Method/Class/Interface/Const and Comment row on each
16316
+ -- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
16317
+ CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
16318
+ CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
16319
+ -- The attrs the cloud drain gates every instance row on, so it reads them from
16320
+ -- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
16321
+ -- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
16322
+ -- two attrs read ~220 MB per push to find the few local rows that can ship.
16323
+ -- created_at leads so the index order is findNodeFieldsByLabel's.
16324
+ CREATE INDEX IF NOT EXISTS nodes_live_instance_gate ON nodes(label, created_at, json_extract(attrs_json, '$.source'), json_extract(attrs_json, '$.privateFinding'), json_extract(attrs_json, '$.resolvedAs'), id) WHERE valid_to IS NULL AND label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause');
16303
16325
  -- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
16304
16326
  -- live-only index above cannot serve it, so it scanned the whole table (~275 MB
16305
16327
  -- of a 1.1 GB store per reindex, 2026-10-20).
@@ -16650,6 +16672,52 @@ var SqliteGraphStore = class {
16650
16672
  AND (t.id IS NULL OR t.valid_to IS NOT NULL)
16651
16673
  `).all();
16652
16674
  }
16675
+ strandedSymbolsForFiles(fileIds) {
16676
+ const out = [];
16677
+ for (let i = 0; i < fileIds.length; i += 500) {
16678
+ const slice = fileIds.slice(i, i + 500);
16679
+ if (slice.length === 0) continue;
16680
+ out.push(...this.db.prepare(`
16681
+ SELECT f.id AS fileId, e.to_id AS symbolId
16682
+ FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
16683
+ LEFT JOIN nodes t ON t.id = e.to_id
16684
+ WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
16685
+ AND e.type IN ('DEFINES', 'CONTAINS')
16686
+ AND (t.id IS NULL OR t.valid_to IS NOT NULL)
16687
+ `).all(...slice));
16688
+ }
16689
+ return out;
16690
+ }
16691
+ firstLiveByNameOrQname(labels, name) {
16692
+ if (labels.length === 0) return null;
16693
+ const inLabels = labels.map(() => "?").join(",");
16694
+ const probe = (index, match) => this.db.prepare(
16695
+ `SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
16696
+ WHERE ${match} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
16697
+ ).all(name, ...labels);
16698
+ const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
16699
+ const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
16700
+ let best;
16701
+ for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
16702
+ if (!best || before(rank(r), rank(best))) best = r;
16703
+ }
16704
+ return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
16705
+ }
16706
+ instanceGateRows(label) {
16707
+ return this.db.prepare(
16708
+ `SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
16709
+ json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
16710
+ FROM nodes INDEXED BY nodes_live_instance_gate
16711
+ WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
16712
+ ORDER BY label, created_at, rowid`
16713
+ ).all(label);
16714
+ }
16715
+ liveCommentsAttachedTo(name) {
16716
+ return this.db.prepare(
16717
+ `SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
16718
+ WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
16719
+ ).all(name);
16720
+ }
16653
16721
  strandedEdges() {
16654
16722
  return countStrandedEdges(this.db);
16655
16723
  }
@@ -16828,7 +16896,11 @@ var SqliteGraphStore = class {
16828
16896
  if (!stmt2) {
16829
16897
  const cols = attrKeys.map((k, i) => `, ${attrValueSql(`'$.${k}'`)} AS a${i}, json_type(attrs_json, '$.${k}') AS t${i}`).join("");
16830
16898
  stmt2 = this.db.prepare(
16831
- `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL`
16899
+ // Ordered, not left to the planner: readers take "first row wins" (the
16900
+ // symbol lexicon's name winner and doc pick), and the order otherwise
16901
+ // followed whichever partial index the planner chose. nodes_live_created
16902
+ // serves this order without a sort.
16903
+ `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
16832
16904
  );
16833
16905
  this.fieldStmts.set(cacheKey, stmt2);
16834
16906
  }
@@ -16854,7 +16926,7 @@ var SqliteGraphStore = class {
16854
16926
  }
16855
16927
  countNodes(q) {
16856
16928
  const { where, params } = compileNodeWhere(q);
16857
- return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM nodes WHERE ${where}`).get(...params).n);
16929
+ return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
16858
16930
  }
16859
16931
  setCommunity(id, community) {
16860
16932
  this.stmts.setCommunity.run({ id, v: community });
@@ -18334,6 +18406,62 @@ function buildSymbolLexicon(store) {
18334
18406
  }
18335
18407
  return lex;
18336
18408
  }
18409
+ function lazySymbolLexicon(store) {
18410
+ if (!store.firstLiveByNameOrQname || !store.liveCommentsAttachedTo) return null;
18411
+ const labels = SYMBOL_LABELS.map(([label]) => label);
18412
+ const kindOf = new Map(SYMBOL_LABELS.map(([label, kind]) => [label, kind]));
18413
+ const members = /* @__PURE__ */ new Map();
18414
+ const docs = /* @__PURE__ */ new Map();
18415
+ const entries = /* @__PURE__ */ new Map();
18416
+ const member = (name) => {
18417
+ if (!members.has(name)) {
18418
+ const hit = isDistinctiveIdentifier(name) ? store.firstLiveByNameOrQname(labels, name) : null;
18419
+ members.set(name, hit ? { kind: kindOf.get(hit.label), description: hit.description, qname: hit.qname } : null);
18420
+ }
18421
+ return members.get(name);
18422
+ };
18423
+ const docFor = (name) => {
18424
+ if (!docs.has(name)) {
18425
+ let found;
18426
+ for (const c of store.liveCommentsAttachedTo(name)) {
18427
+ if (typeof c.kind !== "string" || !DOC_COMMENT_KINDS.has(c.kind)) continue;
18428
+ const text = typeof c.text === "string" ? c.text : c.description;
18429
+ if (!text?.trim()) continue;
18430
+ found = text.trim().slice(0, MAX_DOC_CHARS);
18431
+ break;
18432
+ }
18433
+ docs.set(name, found);
18434
+ }
18435
+ return docs.get(name);
18436
+ };
18437
+ const isKnown = (tok) => member(tok) !== null;
18438
+ let anySymbol;
18439
+ return {
18440
+ has: isKnown,
18441
+ get(name) {
18442
+ if (!entries.has(name)) {
18443
+ const m = member(name);
18444
+ let entry = null;
18445
+ if (m) {
18446
+ entry = { kind: m.kind };
18447
+ const doc = (m.description ? docFor(m.description) : void 0) ?? (m.qname ? docFor(m.qname) : void 0);
18448
+ for (const phrase of summaryCandidates(name, m.kind, doc)) {
18449
+ if (summaryRejectionReason(phrase, isKnown) === null) {
18450
+ entry.summary = phrase;
18451
+ break;
18452
+ }
18453
+ }
18454
+ }
18455
+ entries.set(name, entry);
18456
+ }
18457
+ return entries.get(name) ?? void 0;
18458
+ },
18459
+ get size() {
18460
+ anySymbol ??= labels.some((label) => store.countNodesByLabel(label) > 0);
18461
+ return anySymbol ? 1 : 0;
18462
+ }
18463
+ };
18464
+ }
18337
18465
  var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
18338
18466
  function codeGraphKey(store) {
18339
18467
  const files = store.labelFingerprint?.(["File"]);
@@ -18345,7 +18473,7 @@ function lexiconsFor(store) {
18345
18473
  const key = codeGraphKey(store);
18346
18474
  const hit = key !== null ? lexiconCache.get(store) : void 0;
18347
18475
  if (hit && hit.key === key) return hit;
18348
- const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
18476
+ const built = { symbols: lazySymbolLexicon(store) ?? buildSymbolLexicon(store), files: buildFileLexicon(store) };
18349
18477
  if (key !== null) lexiconCache.set(store, { key, ...built });
18350
18478
  return built;
18351
18479
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.22-dev.4638",
3
+ "version": "2.0.22-dev.4652",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -26871,8 +26871,10 @@ function column(c) {
26871
26871
  function compileNodeWhere(q) {
26872
26872
  if (q.labels.length === 0) throw new Error("selectNodes: no labels");
26873
26873
  const params = [...q.labels];
26874
- const conds = [`label IN (${q.labels.map(() => "?").join(", ")})`];
26875
- if (!q.includeClosed) conds.push("valid_to IS NULL");
26874
+ const byId = (q.where ?? []).some((w) => "idIn" in w);
26875
+ const unindexed = byId ? "+" : "";
26876
+ const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
26877
+ if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
26876
26878
  for (const w of q.where ?? []) {
26877
26879
  if ("embedding" in w) {
26878
26880
  conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
@@ -26924,6 +26926,14 @@ function compileNodeWhere(q) {
26924
26926
  function attrValueSql(p) {
26925
26927
  return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
26926
26928
  }
26929
+ function nodeSelectFrom(q) {
26930
+ if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
26931
+ const where = q.where ?? [];
26932
+ if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
26933
+ return "nodes INDEXED BY nodes_live_description";
26934
+ }
26935
+ return "nodes";
26936
+ }
26927
26937
  function compileNodeSelect(q) {
26928
26938
  const { where, params } = compileNodeWhere(q);
26929
26939
  const attrs = q.attrs ?? [];
@@ -26941,7 +26951,7 @@ function compileNodeSelect(q) {
26941
26951
  }
26942
26952
  const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
26943
26953
  const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
26944
- const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM nodes${q.indexedBy ? ` INDEXED BY ${selectIndex(q.indexedBy)}` : ""} WHERE ${where} ORDER BY ${order}${limit}`;
26954
+ const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM ${nodeSelectFrom(q)} WHERE ${where} ORDER BY ${order}${limit}`;
26945
26955
  return { sql, params: [...params, ...orderParams] };
26946
26956
  }
26947
26957
  function decodeAttr(value, type) {
@@ -27178,6 +27188,18 @@ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid
27178
27188
  -- incremental reindex can fetch just the changed files' symbols instead of
27179
27189
  -- scanning every versioned node. Partial: only live rows are ever queried.
27180
27190
  CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
27191
+ -- Symbols by qname and doc comments by the symbol they attach to: the drain's
27192
+ -- symbol lexicon resolves the names in the text it ships one at a time, instead
27193
+ -- of reading every Function/Method/Class/Interface/Const and Comment row on each
27194
+ -- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
27195
+ CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
27196
+ CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
27197
+ -- The attrs the cloud drain gates every instance row on, so it reads them from
27198
+ -- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
27199
+ -- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
27200
+ -- two attrs read ~220 MB per push to find the few local rows that can ship.
27201
+ -- created_at leads so the index order is findNodeFieldsByLabel's.
27202
+ CREATE INDEX IF NOT EXISTS nodes_live_instance_gate ON nodes(label, created_at, json_extract(attrs_json, '$.source'), json_extract(attrs_json, '$.privateFinding'), json_extract(attrs_json, '$.resolvedAs'), id) WHERE valid_to IS NULL AND label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause');
27181
27203
  -- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
27182
27204
  -- live-only index above cannot serve it, so it scanned the whole table (~275 MB
27183
27205
  -- of a 1.1 GB store per reindex, 2026-10-20).
@@ -27528,6 +27550,52 @@ var SqliteGraphStore = class {
27528
27550
  AND (t.id IS NULL OR t.valid_to IS NOT NULL)
27529
27551
  `).all();
27530
27552
  }
27553
+ strandedSymbolsForFiles(fileIds) {
27554
+ const out2 = [];
27555
+ for (let i2 = 0; i2 < fileIds.length; i2 += 500) {
27556
+ const slice = fileIds.slice(i2, i2 + 500);
27557
+ if (slice.length === 0) continue;
27558
+ out2.push(...this.db.prepare(`
27559
+ SELECT f.id AS fileId, e.to_id AS symbolId
27560
+ FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
27561
+ LEFT JOIN nodes t ON t.id = e.to_id
27562
+ WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
27563
+ AND e.type IN ('DEFINES', 'CONTAINS')
27564
+ AND (t.id IS NULL OR t.valid_to IS NOT NULL)
27565
+ `).all(...slice));
27566
+ }
27567
+ return out2;
27568
+ }
27569
+ firstLiveByNameOrQname(labels, name2) {
27570
+ if (labels.length === 0) return null;
27571
+ const inLabels = labels.map(() => "?").join(",");
27572
+ const probe = (index, match) => this.db.prepare(
27573
+ `SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
27574
+ WHERE ${match} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
27575
+ ).all(name2, ...labels);
27576
+ const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
27577
+ const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
27578
+ let best;
27579
+ for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
27580
+ if (!best || before(rank(r), rank(best))) best = r;
27581
+ }
27582
+ return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
27583
+ }
27584
+ instanceGateRows(label) {
27585
+ return this.db.prepare(
27586
+ `SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
27587
+ json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
27588
+ FROM nodes INDEXED BY nodes_live_instance_gate
27589
+ WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
27590
+ ORDER BY label, created_at, rowid`
27591
+ ).all(label);
27592
+ }
27593
+ liveCommentsAttachedTo(name2) {
27594
+ return this.db.prepare(
27595
+ `SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
27596
+ WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
27597
+ ).all(name2);
27598
+ }
27531
27599
  strandedEdges() {
27532
27600
  return countStrandedEdges(this.db);
27533
27601
  }
@@ -27706,7 +27774,11 @@ var SqliteGraphStore = class {
27706
27774
  if (!stmt2) {
27707
27775
  const cols = attrKeys.map((k, i2) => `, ${attrValueSql(`'$.${k}'`)} AS a${i2}, json_type(attrs_json, '$.${k}') AS t${i2}`).join("");
27708
27776
  stmt2 = this.db.prepare(
27709
- `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL`
27777
+ // Ordered, not left to the planner: readers take "first row wins" (the
27778
+ // symbol lexicon's name winner and doc pick), and the order otherwise
27779
+ // followed whichever partial index the planner chose. nodes_live_created
27780
+ // serves this order without a sort.
27781
+ `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
27710
27782
  );
27711
27783
  this.fieldStmts.set(cacheKey, stmt2);
27712
27784
  }
@@ -27732,7 +27804,7 @@ var SqliteGraphStore = class {
27732
27804
  }
27733
27805
  countNodes(q) {
27734
27806
  const { where, params } = compileNodeWhere(q);
27735
- return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM nodes WHERE ${where}`).get(...params).n);
27807
+ return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
27736
27808
  }
27737
27809
  setCommunity(id, community) {
27738
27810
  this.stmts.setCommunity.run({ id, v: community });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.22-dev.4638",
3
+ "version": "2.0.22-dev.4652",
4
4
  "description": "A minimal 3D view of loaded Errata priors and recent graph bursts.",
5
5
  "author": {
6
6
  "name": "inErrata"
@@ -1,4 +1,4 @@
1
- 16485de5505d71b8dbb138984d557208bdfde9d8bf52baff33f96a477df65145 .codex-plugin/plugin.json
1
+ ba1065832fc364902b70117e8568ae7f0d76cd3af48ed4d0929f03d9d3d55c7f .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js