@inerrata-corporation/errata 2.0.22-dev.4617 → 2.0.22-dev.4624

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.
@@ -17179,6 +17179,12 @@ function changeLogCovers(db, sinceSeq) {
17179
17179
  const prunedTo = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? NaN);
17180
17180
  return Number.isFinite(prunedTo) && sinceSeq >= prunedTo;
17181
17181
  }
17182
+ function readChangedNodeIds(db, sinceSeq, labels) {
17183
+ if (!changeLogCovers(db, sinceSeq)) return null;
17184
+ if (!labels.length) return [];
17185
+ const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND label IN (${labels.map(() => "?").join(",")})`).all(sinceSeq, ...labels);
17186
+ return rows.map((r) => r.id);
17187
+ }
17182
17188
  function readLineageChangedFiles(db, sinceSeq) {
17183
17189
  if (!changeLogCovers(db, sinceSeq)) return null;
17184
17190
  const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
@@ -17563,6 +17569,7 @@ var DEMAND_HOT = Math.log1p(10);
17563
17569
  var DEMAND_WARM = Math.log1p(5);
17564
17570
 
17565
17571
  // ../../packages/local-graph/src/node-select.ts
17572
+ var SELECT_INDEXES = ["nodes_live_local_community", "nodes_live_fold_candidate"];
17566
17573
  var EMBEDDING_DIM_SQL = "(CASE WHEN length(embedding_blob) >= 4 THEN length(embedding_blob) / 4 ELSE COALESCE(json_array_length(embedding_json), 0) END)";
17567
17574
  var KEY = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
17568
17575
  var COLUMNS = /* @__PURE__ */ new Set([
@@ -17578,6 +17585,10 @@ function path(key) {
17578
17585
  if (!KEY.test(key)) throw new Error(`selectNodes: bad attr key ${key}`);
17579
17586
  return `'$.${key}'`;
17580
17587
  }
17588
+ function selectIndex(i) {
17589
+ if (!SELECT_INDEXES.includes(i)) throw new Error(`selectNodes: bad index ${i}`);
17590
+ return i;
17591
+ }
17581
17592
  function column(c) {
17582
17593
  if (!COLUMNS.has(c)) throw new Error(`selectNodes: bad column ${c}`);
17583
17594
  return c;
@@ -17593,6 +17604,12 @@ function compileNodeWhere(q) {
17593
17604
  } else if ("idPrefix" in w) {
17594
17605
  conds.push("substr(id, 1, ?) = ?");
17595
17606
  params.push(w.idPrefix.length, w.idPrefix);
17607
+ } else if ("idIn" in w) {
17608
+ if (w.idIn.length === 0) conds.push("0");
17609
+ else {
17610
+ conds.push(`id IN (${w.idIn.map(() => "?").join(", ")})`);
17611
+ params.push(...w.idIn);
17612
+ }
17596
17613
  } else if ("descriptionIn" in w) {
17597
17614
  if (w.descriptionIn.length === 0) conds.push("0");
17598
17615
  else {
@@ -17649,7 +17666,7 @@ function compileNodeSelect(q) {
17649
17666
  }
17650
17667
  const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
17651
17668
  const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
17652
- 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 WHERE ${where} ORDER BY ${order}${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}`;
17653
17670
  return { sql, params: [...params, ...orderParams] };
17654
17671
  }
17655
17672
  function decodeAttr(value, type) {
@@ -17847,6 +17864,19 @@ var TEMPORAL_INDEX_SQL = `
17847
17864
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
17848
17865
  CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
17849
17866
  CREATE INDEX IF NOT EXISTS nodes_live_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL;
17867
+ -- "Anything created since the watermark?" guards (the fold, regression and
17868
+ -- canonical-bind sweeps run one per session boundary). Without it the planner
17869
+ -- walks nodes_live and reads every live row of the label to test created_at:
17870
+ -- 56 MB per guard on a 1.1 GB store, to learn there was nothing to do. valid_to
17871
+ -- is a key column so the planner prefers this over nodes_live (one more term).
17872
+ CREATE INDEX IF NOT EXISTS nodes_live_created ON nodes(label, valid_to, created_at) WHERE valid_to IS NULL;
17873
+ -- Community members (the skills pull reads them twice per session boundary):
17874
+ -- ~15% of semantic nodes carry a localCommunity, and without this every live
17875
+ -- semantic row was read to find them (168 MB \u2192 18 MB per read).
17876
+ CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to, json_extract(attrs_json, '$.localCommunity')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.localCommunity') IS NOT NULL;
17877
+ -- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
17878
+ -- Problems; without it every live Problem was read to find them (56 MB).
17879
+ CREATE INDEX IF NOT EXISTS nodes_live_fold_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.foldCandidate') IS NOT NULL;
17850
17880
  CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
17851
17881
  CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
17852
17882
  -- Covering + partial index for labelFingerprint over the instance labels: the
@@ -17861,6 +17891,10 @@ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint_v2 ON nodes(label, last_up
17861
17891
  CREATE INDEX IF NOT EXISTS nodes_live_page_rank ON nodes(valid_to, page_rank);
17862
17892
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
17863
17893
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
17894
+ -- Newest live edges first, so a "recent N matching edges" read stops early instead
17895
+ -- of matching and sorting every live edge (the hourly liveness check: 163 MB \u2192
17896
+ -- 69 MB). Named by recentEdgeAttrCoverage; the planner keeps edges_live otherwise.
17897
+ CREATE INDEX IF NOT EXISTS edges_live_created ON edges(created_at) WHERE valid_to IS NULL;
17864
17898
  -- Expression index on the per-node relPath (stored in attrs_json) so the
17865
17899
  -- incremental reindex can fetch just the changed files' symbols instead of
17866
17900
  -- scanning every versioned node. Partial: only live rows are ever queried.
@@ -18557,6 +18591,27 @@ var SqliteGraphStore = class {
18557
18591
  cloud: r.cloud === 1
18558
18592
  }));
18559
18593
  }
18594
+ findNodeStampsByLabelAttrs(label, attrKeys) {
18595
+ for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
18596
+ const keys = [...new Set(attrKeys)];
18597
+ const rows = this.db.prepare(
18598
+ `SELECT id, created_at, created_at_seq,
18599
+ ${keys.map((k, i) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i}`).join(", ")}${keys.length ? "," : ""}
18600
+ COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
18601
+ FROM nodes WHERE label = ? AND valid_to IS NULL`
18602
+ ).all(label);
18603
+ const out = new Map(keys.map((k) => [k, []]));
18604
+ for (const r of rows) {
18605
+ keys.forEach((k, i) => out.get(k).push({
18606
+ id: r.id,
18607
+ createdAt: r.created_at,
18608
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
18609
+ has: r[`h${i}`] === 1,
18610
+ cloud: r.cloud === 1
18611
+ }));
18612
+ }
18613
+ return out;
18614
+ }
18560
18615
  findNodeAttrsByLabel(label) {
18561
18616
  const rows = this.stmts.attrsByLabel.all(label);
18562
18617
  return rows.map((r) => ({
@@ -18798,6 +18853,9 @@ var SqliteGraphStore = class {
18798
18853
  lineageChangedFiles(sinceSeq) {
18799
18854
  return readLineageChangedFiles(this.db, sinceSeq);
18800
18855
  }
18856
+ changedNodeIds(sinceSeq, labels) {
18857
+ return readChangedNodeIds(this.db, sinceSeq, labels);
18858
+ }
18801
18859
  pruneChangeLog() {
18802
18860
  return pruneChangeLog(this.db);
18803
18861
  }
@@ -18992,7 +19050,7 @@ var SqliteGraphStore = class {
18992
19050
  const r = this.db.prepare(
18993
19051
  `SELECT COUNT(*) AS total,
18994
19052
  COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
18995
- FROM (SELECT attrs_json FROM edges
19053
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
18996
19054
  WHERE valid_to IS NULL AND attrs_json LIKE ?
18997
19055
  ORDER BY created_at DESC LIMIT ?)`
18998
19056
  ).get(attr, `%${marker}%`, limit);
@@ -19140,8 +19198,9 @@ var SqliteGraphStore = class {
19140
19198
  * so it must be a plain identifier. */
19141
19199
  nodesAsOfWhereTrue(t, attr) {
19142
19200
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
19201
+ const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
19143
19202
  const rows = this.db.prepare(
19144
- `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
19203
+ `SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
19145
19204
  AND json_type(attrs_json, '$.${attr}') = 'true'
19146
19205
  ORDER BY rowid`
19147
19206
  ).all({ t });
package/errata.mjs CHANGED
@@ -19243,6 +19243,12 @@ function changeLogCovers(db, sinceSeq) {
19243
19243
  const prunedTo = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? NaN);
19244
19244
  return Number.isFinite(prunedTo) && sinceSeq >= prunedTo;
19245
19245
  }
19246
+ function readChangedNodeIds(db, sinceSeq, labels) {
19247
+ if (!changeLogCovers(db, sinceSeq)) return null;
19248
+ if (!labels.length) return [];
19249
+ const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND label IN (${labels.map(() => "?").join(",")})`).all(sinceSeq, ...labels);
19250
+ return rows.map((r) => r.id);
19251
+ }
19246
19252
  function readLineageChangedFiles(db, sinceSeq) {
19247
19253
  if (!changeLogCovers(db, sinceSeq)) return null;
19248
19254
  const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
@@ -20376,6 +20382,10 @@ function path(key) {
20376
20382
  if (!KEY.test(key)) throw new Error(`selectNodes: bad attr key ${key}`);
20377
20383
  return `'$.${key}'`;
20378
20384
  }
20385
+ function selectIndex(i2) {
20386
+ if (!SELECT_INDEXES.includes(i2)) throw new Error(`selectNodes: bad index ${i2}`);
20387
+ return i2;
20388
+ }
20379
20389
  function column(c) {
20380
20390
  if (!COLUMNS.has(c)) throw new Error(`selectNodes: bad column ${c}`);
20381
20391
  return c;
@@ -20391,6 +20401,12 @@ function compileNodeWhere(q) {
20391
20401
  } else if ("idPrefix" in w) {
20392
20402
  conds.push("substr(id, 1, ?) = ?");
20393
20403
  params.push(w.idPrefix.length, w.idPrefix);
20404
+ } else if ("idIn" in w) {
20405
+ if (w.idIn.length === 0) conds.push("0");
20406
+ else {
20407
+ conds.push(`id IN (${w.idIn.map(() => "?").join(", ")})`);
20408
+ params.push(...w.idIn);
20409
+ }
20394
20410
  } else if ("descriptionIn" in w) {
20395
20411
  if (w.descriptionIn.length === 0) conds.push("0");
20396
20412
  else {
@@ -20447,7 +20463,7 @@ function compileNodeSelect(q) {
20447
20463
  }
20448
20464
  const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
20449
20465
  const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
20450
- 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 WHERE ${where} ORDER BY ${order}${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}`;
20451
20467
  return { sql, params: [...params, ...orderParams] };
20452
20468
  }
20453
20469
  function decodeAttr(value, type) {
@@ -20495,10 +20511,11 @@ function decodeSelectedRow(r, attrs, decodeVector) {
20495
20511
  attrs: out2
20496
20512
  };
20497
20513
  }
20498
- var EMBEDDING_DIM_SQL, KEY, COLUMNS;
20514
+ var SELECT_INDEXES, EMBEDDING_DIM_SQL, KEY, COLUMNS;
20499
20515
  var init_node_select = __esm({
20500
20516
  "../../packages/local-graph/src/node-select.ts"() {
20501
20517
  "use strict";
20518
+ SELECT_INDEXES = ["nodes_live_local_community", "nodes_live_fold_candidate"];
20502
20519
  EMBEDDING_DIM_SQL = "(CASE WHEN length(embedding_blob) >= 4 THEN length(embedding_blob) / 4 ELSE COALESCE(json_array_length(embedding_json), 0) END)";
20503
20520
  KEY = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
20504
20521
  COLUMNS = /* @__PURE__ */ new Set([
@@ -20756,6 +20773,19 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
20756
20773
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
20757
20774
  CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
20758
20775
  CREATE INDEX IF NOT EXISTS nodes_live_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL;
20776
+ -- "Anything created since the watermark?" guards (the fold, regression and
20777
+ -- canonical-bind sweeps run one per session boundary). Without it the planner
20778
+ -- walks nodes_live and reads every live row of the label to test created_at:
20779
+ -- 56 MB per guard on a 1.1 GB store, to learn there was nothing to do. valid_to
20780
+ -- is a key column so the planner prefers this over nodes_live (one more term).
20781
+ CREATE INDEX IF NOT EXISTS nodes_live_created ON nodes(label, valid_to, created_at) WHERE valid_to IS NULL;
20782
+ -- Community members (the skills pull reads them twice per session boundary):
20783
+ -- ~15% of semantic nodes carry a localCommunity, and without this every live
20784
+ -- semantic row was read to find them (168 MB \u2192 18 MB per read).
20785
+ CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to, json_extract(attrs_json, '$.localCommunity')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.localCommunity') IS NOT NULL;
20786
+ -- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
20787
+ -- Problems; without it every live Problem was read to find them (56 MB).
20788
+ CREATE INDEX IF NOT EXISTS nodes_live_fold_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.foldCandidate') IS NOT NULL;
20759
20789
  CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
20760
20790
  CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
20761
20791
  -- Covering + partial index for labelFingerprint over the instance labels: the
@@ -20770,6 +20800,10 @@ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint_v2 ON nodes(label, last_up
20770
20800
  CREATE INDEX IF NOT EXISTS nodes_live_page_rank ON nodes(valid_to, page_rank);
20771
20801
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
20772
20802
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
20803
+ -- Newest live edges first, so a "recent N matching edges" read stops early instead
20804
+ -- of matching and sorting every live edge (the hourly liveness check: 163 MB \u2192
20805
+ -- 69 MB). Named by recentEdgeAttrCoverage; the planner keeps edges_live otherwise.
20806
+ CREATE INDEX IF NOT EXISTS edges_live_created ON edges(created_at) WHERE valid_to IS NULL;
20773
20807
  -- Expression index on the per-node relPath (stored in attrs_json) so the
20774
20808
  -- incremental reindex can fetch just the changed files' symbols instead of
20775
20809
  -- scanning every versioned node. Partial: only live rows are ever queried.
@@ -21409,6 +21443,27 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21409
21443
  cloud: r.cloud === 1
21410
21444
  }));
21411
21445
  }
21446
+ findNodeStampsByLabelAttrs(label, attrKeys) {
21447
+ for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
21448
+ const keys = [...new Set(attrKeys)];
21449
+ const rows = this.db.prepare(
21450
+ `SELECT id, created_at, created_at_seq,
21451
+ ${keys.map((k, i2) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i2}`).join(", ")}${keys.length ? "," : ""}
21452
+ COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
21453
+ FROM nodes WHERE label = ? AND valid_to IS NULL`
21454
+ ).all(label);
21455
+ const out2 = new Map(keys.map((k) => [k, []]));
21456
+ for (const r of rows) {
21457
+ keys.forEach((k, i2) => out2.get(k).push({
21458
+ id: r.id,
21459
+ createdAt: r.created_at,
21460
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
21461
+ has: r[`h${i2}`] === 1,
21462
+ cloud: r.cloud === 1
21463
+ }));
21464
+ }
21465
+ return out2;
21466
+ }
21412
21467
  findNodeAttrsByLabel(label) {
21413
21468
  const rows = this.stmts.attrsByLabel.all(label);
21414
21469
  return rows.map((r) => ({
@@ -21650,6 +21705,9 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21650
21705
  lineageChangedFiles(sinceSeq) {
21651
21706
  return readLineageChangedFiles(this.db, sinceSeq);
21652
21707
  }
21708
+ changedNodeIds(sinceSeq, labels) {
21709
+ return readChangedNodeIds(this.db, sinceSeq, labels);
21710
+ }
21653
21711
  pruneChangeLog() {
21654
21712
  return pruneChangeLog(this.db);
21655
21713
  }
@@ -21844,7 +21902,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21844
21902
  const r = this.db.prepare(
21845
21903
  `SELECT COUNT(*) AS total,
21846
21904
  COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
21847
- FROM (SELECT attrs_json FROM edges
21905
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
21848
21906
  WHERE valid_to IS NULL AND attrs_json LIKE ?
21849
21907
  ORDER BY created_at DESC LIMIT ?)`
21850
21908
  ).get(attr, `%${marker}%`, limit);
@@ -21992,8 +22050,9 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21992
22050
  * so it must be a plain identifier. */
21993
22051
  nodesAsOfWhereTrue(t, attr) {
21994
22052
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
22053
+ const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
21995
22054
  const rows = this.db.prepare(
21996
- `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
22055
+ `SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
21997
22056
  AND json_type(attrs_json, '$.${attr}') = 'true'
21998
22057
  ORDER BY rowid`
21999
22058
  ).all({ t });
@@ -22373,19 +22432,16 @@ function conditionOf(node2) {
22373
22432
  }
22374
22433
  function clearAnsweredRevisits(store, ts) {
22375
22434
  const report = { cleared: 0, standing: 0 };
22376
- for (const label of SEMANTIC_NODE_LABELS) {
22377
- for (const row of store.findNodeFieldsByLabel(label, ["revisit"])) {
22378
- if (row.attrs["revisit"] !== 1 && row.attrs["revisit"] !== true) continue;
22379
- const node2 = store.getNode(row.id);
22380
- if (!node2 || node2.attrs["revisit"] !== true) continue;
22381
- const condition = conditionOf(node2);
22382
- if (condition === void 0 || !isRevisitAnswered(store, node2, condition)) {
22383
- report.standing++;
22384
- continue;
22385
- }
22386
- clearRevisit(store, node2.id, ts);
22387
- report.cleared++;
22435
+ const semantic = new Set(SEMANTIC_NODE_LABELS);
22436
+ for (const node2 of store.nodesAsOfWhereTrue(ts, "revisit")) {
22437
+ if (!semantic.has(node2.label) || node2.validTo != null) continue;
22438
+ const condition = conditionOf(node2);
22439
+ if (condition === void 0 || !isRevisitAnswered(store, node2, condition)) {
22440
+ report.standing++;
22441
+ continue;
22388
22442
  }
22443
+ clearRevisit(store, node2.id, ts);
22444
+ report.cleared++;
22389
22445
  }
22390
22446
  return report;
22391
22447
  }
@@ -26253,7 +26309,10 @@ function communityMembers(store) {
26253
26309
  return store.selectNodes({
26254
26310
  labels: JOINT_COMMUNITY_LABELS,
26255
26311
  attrs: ["localCommunity", "cloudNodeId", "source"],
26256
- where: [{ attr: "localCommunity", notNullish: true }]
26312
+ where: [{ attr: "localCommunity", notNullish: true }],
26313
+ // ~15% of semantic nodes are members; the ordered select otherwise walks
26314
+ // nodes_live and reads every live semantic row (168 MB on a 1.1 GB store).
26315
+ indexedBy: "nodes_live_local_community"
26257
26316
  });
26258
26317
  }
26259
26318
  function communitySeeds(store, opts = {}) {
@@ -26451,7 +26510,10 @@ async function resolveFoldCandidates(store, opts) {
26451
26510
  { attr: "resolvedAt", nullish: true },
26452
26511
  { attr: "mergedInto", absent: true },
26453
26512
  { attr: "foldCandidate", present: true }
26454
- ]
26513
+ ],
26514
+ // Only parked Problems are in it; the rowid-ordered select otherwise walks
26515
+ // nodes_live and reads every live Problem (56 MB per settle boundary).
26516
+ indexedBy: "nodes_live_fold_candidate"
26455
26517
  });
26456
26518
  const markOf = (n) => n.attrs["foldCandidate"];
26457
26519
  report.pending = pool.length;
@@ -26783,7 +26845,7 @@ var init_principle_sync = __esm({
26783
26845
  });
26784
26846
 
26785
26847
  // ../../packages/local-graph/src/mechanism-liveness.ts
26786
- function evaluateFed(store, d) {
26848
+ function evaluateFed(store, d, stamps) {
26787
26849
  if (d.fedIncoming) {
26788
26850
  const { count: count2, total } = store.incomingEdgeCoverage?.(
26789
26851
  d.fedIncoming.label,
@@ -26803,7 +26865,7 @@ function evaluateFed(store, d) {
26803
26865
  if (!d.fed) return { count: 0, total: 0, fraction: 0 };
26804
26866
  const column2 = COLUMN_INPUTS[d.fed.attr];
26805
26867
  const fed = d.fed;
26806
- const all = fed.labels.flatMap((label) => store.findNodeStampsByLabel ? store.findNodeStampsByLabel(label, fed.attr) : store.findNodeAttrsByLabel(label));
26868
+ const all = fed.labels.flatMap((label) => stamps?.(label, fed.attr) ?? (store.findNodeStampsByLabel ? store.findNodeStampsByLabel(label, fed.attr) : store.findNodeAttrsByLabel(label)));
26807
26869
  const isCloudMirror = (n) => n.cloud ?? n.attrs?.["source"] === "cloud";
26808
26870
  const nodes = fed.localOnly ? all.filter((n) => !isCloudMirror(n)) : all;
26809
26871
  const has = (n) => column2 ? column2(n) : n.has ?? n.attrs?.[fed.attr] !== void 0;
@@ -26826,8 +26888,15 @@ function evaluateMechanisms(store, invocations = /* @__PURE__ */ new Map(), mech
26826
26888
  if (!counts.has(label)) counts.set(label, store.countNodesByLabel(label));
26827
26889
  return counts.get(label);
26828
26890
  };
26891
+ const wanted = /* @__PURE__ */ new Map();
26892
+ for (const d of mechanisms) for (const l of d.fed?.labels ?? []) wanted.set(l, (wanted.get(l) ?? /* @__PURE__ */ new Set()).add(d.fed.attr));
26893
+ const byLabel = /* @__PURE__ */ new Map();
26894
+ const stamps = store.findNodeStampsByLabelAttrs ? (label, attr) => {
26895
+ if (!byLabel.has(label)) byLabel.set(label, store.findNodeStampsByLabelAttrs(label, [...wanted.get(label) ?? [attr]]));
26896
+ return byLabel.get(label).get(attr);
26897
+ } : void 0;
26829
26898
  return mechanisms.map((d) => {
26830
- const fed = evaluateFed(store, d);
26899
+ const fed = evaluateFed(store, d, stamps);
26831
26900
  const inv = invocations.get(d.pass);
26832
26901
  const effectCount = d.effectIsLevel ? Math.max(
26833
26902
  0,
@@ -34598,7 +34667,10 @@ function embedCodeNodes(store, opts = {}) {
34598
34667
  return embedNodesByLabels(store, CODE_LABELS, opts);
34599
34668
  }
34600
34669
  function embedSemanticNodes(store, opts = {}) {
34601
- return embedNodesByLabels(store, SEMANTIC_LABELS, opts);
34670
+ return embedNodesByLabels(store, SEMANTIC_LABELS, {
34671
+ incrementalKey: `embed-scan:semantic:${opts.mode === "hash" ? "hash" : "model"}`,
34672
+ ...opts
34673
+ });
34602
34674
  }
34603
34675
  async function ensureNodeEmbedded(store, nodeId, opts = {}) {
34604
34676
  const node2 = store.getNode(nodeId);
@@ -34671,14 +34743,32 @@ async function embedNodesByLabels(store, labels, opts = {}) {
34671
34743
  };
34672
34744
  const hashOnly = opts.mode === "hash";
34673
34745
  const candidates = [];
34674
- for (const label of labels) {
34675
- for (const node2 of store.selectNodes({
34676
- labels: [label],
34677
- attrs: EMBED_SCAN_ATTRS
34678
- })) {
34746
+ const watermarkKey = opts.incrementalKey;
34747
+ const seqBefore = watermarkKey ? store.changeSeq?.() : void 0;
34748
+ let held = null;
34749
+ try {
34750
+ held = watermarkKey ? JSON.parse(store.getMeta?.(watermarkKey) ?? "null") : null;
34751
+ } catch {
34752
+ held = null;
34753
+ }
34754
+ const changedIds = !opts.force && held && seqBefore !== void 0 ? store.changedNodeIds?.(held.seq, labels) ?? null : null;
34755
+ const scan2 = function* () {
34756
+ if (changedIds === null) {
34757
+ for (const label of labels) yield* store.selectNodes({ labels: [label], attrs: EMBED_SCAN_ATTRS });
34758
+ return;
34759
+ }
34760
+ for (let i2 = 0; i2 < changedIds.length; i2 += ID_CHUNK) {
34761
+ yield* store.selectNodes({ labels, attrs: EMBED_SCAN_ATTRS, where: [{ idIn: changedIds.slice(i2, i2 + ID_CHUNK) }] });
34762
+ }
34763
+ };
34764
+ const complete = (version3) => {
34765
+ if (watermarkKey && seqBefore !== void 0 && report.failed === 0) store.setMeta?.(watermarkKey, JSON.stringify({ seq: seqBefore, version: version3 }));
34766
+ };
34767
+ {
34768
+ for (const node2 of scan2()) {
34679
34769
  report.scanned++;
34680
34770
  if (!eligibleForEmbedding(store, node2)) {
34681
- clearWithdrawnVector(store, node2.id);
34771
+ if (node2.embeddingDim > 0 || node2.attrs["embeddingVersion"] !== void 0) clearWithdrawnVector(store, node2.id);
34682
34772
  continue;
34683
34773
  }
34684
34774
  const existingVersion = node2.attrs["embeddingVersion"] ?? null;
@@ -34693,10 +34783,18 @@ async function embedNodesByLabels(store, labels, opts = {}) {
34693
34783
  }
34694
34784
  }
34695
34785
  if (candidates.length === 0) {
34786
+ complete(held?.version ?? (hashOnly ? EMBEDDING_VERSION : null));
34696
34787
  report.durationMs = Date.now() - started2;
34697
34788
  return report;
34698
34789
  }
34699
34790
  const currentVersion = hashOnly ? EMBEDDING_VERSION : (await embedBatchWithModelOrHash(["probe"]))[0]?.version ?? "unknown";
34791
+ if (changedIds !== null && held?.version && held.version !== currentVersion) {
34792
+ const { incrementalKey: _incremental, ...fullOpts } = opts;
34793
+ return embedNodesByLabels(store, labels, fullOpts).then((full) => {
34794
+ if (full.failed === 0 && watermarkKey && seqBefore !== void 0) store.setMeta?.(watermarkKey, JSON.stringify({ seq: seqBefore, version: currentVersion }));
34795
+ return full;
34796
+ });
34797
+ }
34700
34798
  const todo = [];
34701
34799
  for (const { node: node2, existingVersion } of candidates) {
34702
34800
  if (!opts.force && existingVersion === currentVersion && node2.embeddingDim > 0) {
@@ -34732,6 +34830,7 @@ async function embedNodesByLabels(store, labels, opts = {}) {
34732
34830
  });
34733
34831
  if (opts.progress) opts.progress(report.embedded, todo.length);
34734
34832
  }
34833
+ complete(currentVersion);
34735
34834
  report.durationMs = Date.now() - started2;
34736
34835
  return report;
34737
34836
  }
@@ -34752,7 +34851,7 @@ function findDocstring(store, nodeId) {
34752
34851
  }
34753
34852
  return null;
34754
34853
  }
34755
- var CODE_LABELS, SEMANTIC_LABELS, BATCH_SIZE, EMBED_SCAN_ATTRS;
34854
+ var CODE_LABELS, SEMANTIC_LABELS, BATCH_SIZE, ID_CHUNK, EMBED_SCAN_ATTRS;
34756
34855
  var init_embed_code = __esm({
34757
34856
  "../../packages/generalizer/src/embed-code.ts"() {
34758
34857
  "use strict";
@@ -34761,6 +34860,7 @@ var init_embed_code = __esm({
34761
34860
  CODE_LABELS = ["Function", "Method", "Class", "Const"];
34762
34861
  SEMANTIC_LABELS = ["Problem", "RootCause", "Solution", "Pattern", "Claim"];
34763
34862
  BATCH_SIZE = 32;
34863
+ ID_CHUNK = 500;
34764
34864
  EMBED_SCAN_ATTRS = [
34765
34865
  "cloudNodeId",
34766
34866
  "source",
@@ -86568,6 +86668,8 @@ function createLivenessWatch(deps) {
86568
86668
  // src/workspace-maintenance.ts
86569
86669
  init_src8();
86570
86670
  init_src5();
86671
+ var WM_CALIBRATION_AT_KEY = "wm-calibration-at";
86672
+ var WM_CALIBRATION_INTERVAL_MS = 30 * 6e4;
86571
86673
  function runWorkspaceMaintenance(store, workspaceId2) {
86572
86674
  const seqBefore = store.currentIngestSeq();
86573
86675
  const report = runSemanticMaintenance(store);
@@ -86578,9 +86680,13 @@ function runWorkspaceMaintenance(store, workspaceId2) {
86578
86680
  }
86579
86681
  let calibration = null;
86580
86682
  const calibrationStarted = Date.now();
86581
- try {
86582
- calibration = wmCalibrationCells(store);
86583
- } catch {
86683
+ const lastCalibration = Number(store.getMeta?.(WM_CALIBRATION_AT_KEY) ?? 0);
86684
+ if (!(calibrationStarted - lastCalibration < WM_CALIBRATION_INTERVAL_MS)) {
86685
+ try {
86686
+ calibration = wmCalibrationCells(store);
86687
+ store.setMeta?.(WM_CALIBRATION_AT_KEY, String(calibrationStarted));
86688
+ } catch {
86689
+ }
86584
86690
  }
86585
86691
  return {
86586
86692
  report,
@@ -86711,7 +86817,7 @@ function createWatchBreaker(deps) {
86711
86817
  }
86712
86818
 
86713
86819
  // src/engine.ts
86714
- var DAEMON_VERSION = true ? "2.0.22-dev.4617" : "2.0.0-alpha.0";
86820
+ var DAEMON_VERSION = true ? "2.0.22-dev.4624" : "2.0.0-alpha.0";
86715
86821
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
86716
86822
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
86717
86823
  var GIT_OP_MUTE_MS = 4e3;
@@ -15753,6 +15753,12 @@ function changeLogCovers(db, sinceSeq) {
15753
15753
  const prunedTo = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? NaN);
15754
15754
  return Number.isFinite(prunedTo) && sinceSeq >= prunedTo;
15755
15755
  }
15756
+ function readChangedNodeIds(db, sinceSeq, labels) {
15757
+ if (!changeLogCovers(db, sinceSeq)) return null;
15758
+ if (!labels.length) return [];
15759
+ const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND label IN (${labels.map(() => "?").join(",")})`).all(sinceSeq, ...labels);
15760
+ return rows.map((r) => r.id);
15761
+ }
15756
15762
  function readLineageChangedFiles(db, sinceSeq) {
15757
15763
  if (!changeLogCovers(db, sinceSeq)) return null;
15758
15764
  const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
@@ -15960,6 +15966,7 @@ var DEMAND_HOT = Math.log1p(10);
15960
15966
  var DEMAND_WARM = Math.log1p(5);
15961
15967
 
15962
15968
  // ../../packages/local-graph/src/node-select.ts
15969
+ var SELECT_INDEXES = ["nodes_live_local_community", "nodes_live_fold_candidate"];
15963
15970
  var EMBEDDING_DIM_SQL = "(CASE WHEN length(embedding_blob) >= 4 THEN length(embedding_blob) / 4 ELSE COALESCE(json_array_length(embedding_json), 0) END)";
15964
15971
  var KEY = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
15965
15972
  var COLUMNS = /* @__PURE__ */ new Set([
@@ -15975,6 +15982,10 @@ function path(key) {
15975
15982
  if (!KEY.test(key)) throw new Error(`selectNodes: bad attr key ${key}`);
15976
15983
  return `'$.${key}'`;
15977
15984
  }
15985
+ function selectIndex(i) {
15986
+ if (!SELECT_INDEXES.includes(i)) throw new Error(`selectNodes: bad index ${i}`);
15987
+ return i;
15988
+ }
15978
15989
  function column(c) {
15979
15990
  if (!COLUMNS.has(c)) throw new Error(`selectNodes: bad column ${c}`);
15980
15991
  return c;
@@ -15990,6 +16001,12 @@ function compileNodeWhere(q) {
15990
16001
  } else if ("idPrefix" in w) {
15991
16002
  conds.push("substr(id, 1, ?) = ?");
15992
16003
  params.push(w.idPrefix.length, w.idPrefix);
16004
+ } else if ("idIn" in w) {
16005
+ if (w.idIn.length === 0) conds.push("0");
16006
+ else {
16007
+ conds.push(`id IN (${w.idIn.map(() => "?").join(", ")})`);
16008
+ params.push(...w.idIn);
16009
+ }
15993
16010
  } else if ("descriptionIn" in w) {
15994
16011
  if (w.descriptionIn.length === 0) conds.push("0");
15995
16012
  else {
@@ -16046,7 +16063,7 @@ function compileNodeSelect(q) {
16046
16063
  }
16047
16064
  const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
16048
16065
  const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
16049
- 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 WHERE ${where} ORDER BY ${order}${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}`;
16050
16067
  return { sql, params: [...params, ...orderParams] };
16051
16068
  }
16052
16069
  function decodeAttr(value, type) {
@@ -16244,6 +16261,19 @@ var TEMPORAL_INDEX_SQL = `
16244
16261
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
16245
16262
  CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
16246
16263
  CREATE INDEX IF NOT EXISTS nodes_live_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL;
16264
+ -- "Anything created since the watermark?" guards (the fold, regression and
16265
+ -- canonical-bind sweeps run one per session boundary). Without it the planner
16266
+ -- walks nodes_live and reads every live row of the label to test created_at:
16267
+ -- 56 MB per guard on a 1.1 GB store, to learn there was nothing to do. valid_to
16268
+ -- is a key column so the planner prefers this over nodes_live (one more term).
16269
+ CREATE INDEX IF NOT EXISTS nodes_live_created ON nodes(label, valid_to, created_at) WHERE valid_to IS NULL;
16270
+ -- Community members (the skills pull reads them twice per session boundary):
16271
+ -- ~15% of semantic nodes carry a localCommunity, and without this every live
16272
+ -- semantic row was read to find them (168 MB \u2192 18 MB per read).
16273
+ CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to, json_extract(attrs_json, '$.localCommunity')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.localCommunity') IS NOT NULL;
16274
+ -- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
16275
+ -- Problems; without it every live Problem was read to find them (56 MB).
16276
+ CREATE INDEX IF NOT EXISTS nodes_live_fold_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.foldCandidate') IS NOT NULL;
16247
16277
  CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
16248
16278
  CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
16249
16279
  -- Covering + partial index for labelFingerprint over the instance labels: the
@@ -16258,6 +16288,10 @@ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint_v2 ON nodes(label, last_up
16258
16288
  CREATE INDEX IF NOT EXISTS nodes_live_page_rank ON nodes(valid_to, page_rank);
16259
16289
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
16260
16290
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
16291
+ -- Newest live edges first, so a "recent N matching edges" read stops early instead
16292
+ -- of matching and sorting every live edge (the hourly liveness check: 163 MB \u2192
16293
+ -- 69 MB). Named by recentEdgeAttrCoverage; the planner keeps edges_live otherwise.
16294
+ CREATE INDEX IF NOT EXISTS edges_live_created ON edges(created_at) WHERE valid_to IS NULL;
16261
16295
  -- Expression index on the per-node relPath (stored in attrs_json) so the
16262
16296
  -- incremental reindex can fetch just the changed files' symbols instead of
16263
16297
  -- scanning every versioned node. Partial: only live rows are ever queried.
@@ -16954,6 +16988,27 @@ var SqliteGraphStore = class {
16954
16988
  cloud: r.cloud === 1
16955
16989
  }));
16956
16990
  }
16991
+ findNodeStampsByLabelAttrs(label, attrKeys) {
16992
+ for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
16993
+ const keys = [...new Set(attrKeys)];
16994
+ const rows = this.db.prepare(
16995
+ `SELECT id, created_at, created_at_seq,
16996
+ ${keys.map((k, i) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i}`).join(", ")}${keys.length ? "," : ""}
16997
+ COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
16998
+ FROM nodes WHERE label = ? AND valid_to IS NULL`
16999
+ ).all(label);
17000
+ const out = new Map(keys.map((k) => [k, []]));
17001
+ for (const r of rows) {
17002
+ keys.forEach((k, i) => out.get(k).push({
17003
+ id: r.id,
17004
+ createdAt: r.created_at,
17005
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
17006
+ has: r[`h${i}`] === 1,
17007
+ cloud: r.cloud === 1
17008
+ }));
17009
+ }
17010
+ return out;
17011
+ }
16957
17012
  findNodeAttrsByLabel(label) {
16958
17013
  const rows = this.stmts.attrsByLabel.all(label);
16959
17014
  return rows.map((r) => ({
@@ -17195,6 +17250,9 @@ var SqliteGraphStore = class {
17195
17250
  lineageChangedFiles(sinceSeq) {
17196
17251
  return readLineageChangedFiles(this.db, sinceSeq);
17197
17252
  }
17253
+ changedNodeIds(sinceSeq, labels) {
17254
+ return readChangedNodeIds(this.db, sinceSeq, labels);
17255
+ }
17198
17256
  pruneChangeLog() {
17199
17257
  return pruneChangeLog(this.db);
17200
17258
  }
@@ -17389,7 +17447,7 @@ var SqliteGraphStore = class {
17389
17447
  const r = this.db.prepare(
17390
17448
  `SELECT COUNT(*) AS total,
17391
17449
  COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
17392
- FROM (SELECT attrs_json FROM edges
17450
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
17393
17451
  WHERE valid_to IS NULL AND attrs_json LIKE ?
17394
17452
  ORDER BY created_at DESC LIMIT ?)`
17395
17453
  ).get(attr, `%${marker}%`, limit);
@@ -17537,8 +17595,9 @@ var SqliteGraphStore = class {
17537
17595
  * so it must be a plain identifier. */
17538
17596
  nodesAsOfWhereTrue(t, attr) {
17539
17597
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
17598
+ const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
17540
17599
  const rows = this.db.prepare(
17541
- `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
17600
+ `SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
17542
17601
  AND json_type(attrs_json, '$.${attr}') = 'true'
17543
17602
  ORDER BY rowid`
17544
17603
  ).all({ t });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.22-dev.4617",
3
+ "version": "2.0.22-dev.4624",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -26185,6 +26185,12 @@ function changeLogCovers(db, sinceSeq) {
26185
26185
  const prunedTo = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? NaN);
26186
26186
  return Number.isFinite(prunedTo) && sinceSeq >= prunedTo;
26187
26187
  }
26188
+ function readChangedNodeIds(db, sinceSeq, labels) {
26189
+ if (!changeLogCovers(db, sinceSeq)) return null;
26190
+ if (!labels.length) return [];
26191
+ const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND label IN (${labels.map(() => "?").join(",")})`).all(sinceSeq, ...labels);
26192
+ return rows.map((r) => r.id);
26193
+ }
26188
26194
  function readLineageChangedFiles(db, sinceSeq) {
26189
26195
  if (!changeLogCovers(db, sinceSeq)) return null;
26190
26196
  const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
@@ -26828,6 +26834,7 @@ function pagerankLocal(input) {
26828
26834
  }
26829
26835
 
26830
26836
  // ../../packages/local-graph/src/node-select.ts
26837
+ var SELECT_INDEXES = ["nodes_live_local_community", "nodes_live_fold_candidate"];
26831
26838
  var EMBEDDING_DIM_SQL = "(CASE WHEN length(embedding_blob) >= 4 THEN length(embedding_blob) / 4 ELSE COALESCE(json_array_length(embedding_json), 0) END)";
26832
26839
  var KEY = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
26833
26840
  var COLUMNS = /* @__PURE__ */ new Set([
@@ -26843,6 +26850,10 @@ function path(key) {
26843
26850
  if (!KEY.test(key)) throw new Error(`selectNodes: bad attr key ${key}`);
26844
26851
  return `'$.${key}'`;
26845
26852
  }
26853
+ function selectIndex(i2) {
26854
+ if (!SELECT_INDEXES.includes(i2)) throw new Error(`selectNodes: bad index ${i2}`);
26855
+ return i2;
26856
+ }
26846
26857
  function column(c) {
26847
26858
  if (!COLUMNS.has(c)) throw new Error(`selectNodes: bad column ${c}`);
26848
26859
  return c;
@@ -26858,6 +26869,12 @@ function compileNodeWhere(q) {
26858
26869
  } else if ("idPrefix" in w) {
26859
26870
  conds.push("substr(id, 1, ?) = ?");
26860
26871
  params.push(w.idPrefix.length, w.idPrefix);
26872
+ } else if ("idIn" in w) {
26873
+ if (w.idIn.length === 0) conds.push("0");
26874
+ else {
26875
+ conds.push(`id IN (${w.idIn.map(() => "?").join(", ")})`);
26876
+ params.push(...w.idIn);
26877
+ }
26861
26878
  } else if ("descriptionIn" in w) {
26862
26879
  if (w.descriptionIn.length === 0) conds.push("0");
26863
26880
  else {
@@ -26914,7 +26931,7 @@ function compileNodeSelect(q) {
26914
26931
  }
26915
26932
  const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
26916
26933
  const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
26917
- 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 WHERE ${where} ORDER BY ${order}${limit}`;
26934
+ 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}`;
26918
26935
  return { sql, params: [...params, ...orderParams] };
26919
26936
  }
26920
26937
  function decodeAttr(value, type) {
@@ -27112,6 +27129,19 @@ var TEMPORAL_INDEX_SQL = `
27112
27129
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
27113
27130
  CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
27114
27131
  CREATE INDEX IF NOT EXISTS nodes_live_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL;
27132
+ -- "Anything created since the watermark?" guards (the fold, regression and
27133
+ -- canonical-bind sweeps run one per session boundary). Without it the planner
27134
+ -- walks nodes_live and reads every live row of the label to test created_at:
27135
+ -- 56 MB per guard on a 1.1 GB store, to learn there was nothing to do. valid_to
27136
+ -- is a key column so the planner prefers this over nodes_live (one more term).
27137
+ CREATE INDEX IF NOT EXISTS nodes_live_created ON nodes(label, valid_to, created_at) WHERE valid_to IS NULL;
27138
+ -- Community members (the skills pull reads them twice per session boundary):
27139
+ -- ~15% of semantic nodes carry a localCommunity, and without this every live
27140
+ -- semantic row was read to find them (168 MB \u2192 18 MB per read).
27141
+ CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to, json_extract(attrs_json, '$.localCommunity')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.localCommunity') IS NOT NULL;
27142
+ -- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
27143
+ -- Problems; without it every live Problem was read to find them (56 MB).
27144
+ CREATE INDEX IF NOT EXISTS nodes_live_fold_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.foldCandidate') IS NOT NULL;
27115
27145
  CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
27116
27146
  CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
27117
27147
  -- Covering + partial index for labelFingerprint over the instance labels: the
@@ -27126,6 +27156,10 @@ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint_v2 ON nodes(label, last_up
27126
27156
  CREATE INDEX IF NOT EXISTS nodes_live_page_rank ON nodes(valid_to, page_rank);
27127
27157
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
27128
27158
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
27159
+ -- Newest live edges first, so a "recent N matching edges" read stops early instead
27160
+ -- of matching and sorting every live edge (the hourly liveness check: 163 MB \u2192
27161
+ -- 69 MB). Named by recentEdgeAttrCoverage; the planner keeps edges_live otherwise.
27162
+ CREATE INDEX IF NOT EXISTS edges_live_created ON edges(created_at) WHERE valid_to IS NULL;
27129
27163
  -- Expression index on the per-node relPath (stored in attrs_json) so the
27130
27164
  -- incremental reindex can fetch just the changed files' symbols instead of
27131
27165
  -- scanning every versioned node. Partial: only live rows are ever queried.
@@ -27822,6 +27856,27 @@ var SqliteGraphStore = class {
27822
27856
  cloud: r.cloud === 1
27823
27857
  }));
27824
27858
  }
27859
+ findNodeStampsByLabelAttrs(label, attrKeys) {
27860
+ for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
27861
+ const keys = [...new Set(attrKeys)];
27862
+ const rows = this.db.prepare(
27863
+ `SELECT id, created_at, created_at_seq,
27864
+ ${keys.map((k, i2) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i2}`).join(", ")}${keys.length ? "," : ""}
27865
+ COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
27866
+ FROM nodes WHERE label = ? AND valid_to IS NULL`
27867
+ ).all(label);
27868
+ const out2 = new Map(keys.map((k) => [k, []]));
27869
+ for (const r of rows) {
27870
+ keys.forEach((k, i2) => out2.get(k).push({
27871
+ id: r.id,
27872
+ createdAt: r.created_at,
27873
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
27874
+ has: r[`h${i2}`] === 1,
27875
+ cloud: r.cloud === 1
27876
+ }));
27877
+ }
27878
+ return out2;
27879
+ }
27825
27880
  findNodeAttrsByLabel(label) {
27826
27881
  const rows = this.stmts.attrsByLabel.all(label);
27827
27882
  return rows.map((r) => ({
@@ -28063,6 +28118,9 @@ var SqliteGraphStore = class {
28063
28118
  lineageChangedFiles(sinceSeq) {
28064
28119
  return readLineageChangedFiles(this.db, sinceSeq);
28065
28120
  }
28121
+ changedNodeIds(sinceSeq, labels) {
28122
+ return readChangedNodeIds(this.db, sinceSeq, labels);
28123
+ }
28066
28124
  pruneChangeLog() {
28067
28125
  return pruneChangeLog(this.db);
28068
28126
  }
@@ -28257,7 +28315,7 @@ var SqliteGraphStore = class {
28257
28315
  const r = this.db.prepare(
28258
28316
  `SELECT COUNT(*) AS total,
28259
28317
  COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
28260
- FROM (SELECT attrs_json FROM edges
28318
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
28261
28319
  WHERE valid_to IS NULL AND attrs_json LIKE ?
28262
28320
  ORDER BY created_at DESC LIMIT ?)`
28263
28321
  ).get(attr, `%${marker}%`, limit);
@@ -28405,8 +28463,9 @@ var SqliteGraphStore = class {
28405
28463
  * so it must be a plain identifier. */
28406
28464
  nodesAsOfWhereTrue(t, attr) {
28407
28465
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
28466
+ const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
28408
28467
  const rows = this.db.prepare(
28409
- `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
28468
+ `SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
28410
28469
  AND json_type(attrs_json, '$.${attr}') = 'true'
28411
28470
  ORDER BY rowid`
28412
28471
  ).all({ t });
@@ -28492,19 +28551,16 @@ function conditionOf(node) {
28492
28551
  }
28493
28552
  function clearAnsweredRevisits(store2, ts) {
28494
28553
  const report = { cleared: 0, standing: 0 };
28495
- for (const label of SEMANTIC_NODE_LABELS) {
28496
- for (const row of store2.findNodeFieldsByLabel(label, ["revisit"])) {
28497
- if (row.attrs["revisit"] !== 1 && row.attrs["revisit"] !== true) continue;
28498
- const node = store2.getNode(row.id);
28499
- if (!node || node.attrs["revisit"] !== true) continue;
28500
- const condition = conditionOf(node);
28501
- if (condition === void 0 || !isRevisitAnswered(store2, node, condition)) {
28502
- report.standing++;
28503
- continue;
28504
- }
28505
- clearRevisit(store2, node.id, ts);
28506
- report.cleared++;
28554
+ const semantic = new Set(SEMANTIC_NODE_LABELS);
28555
+ for (const node of store2.nodesAsOfWhereTrue(ts, "revisit")) {
28556
+ if (!semantic.has(node.label) || node.validTo != null) continue;
28557
+ const condition = conditionOf(node);
28558
+ if (condition === void 0 || !isRevisitAnswered(store2, node, condition)) {
28559
+ report.standing++;
28560
+ continue;
28507
28561
  }
28562
+ clearRevisit(store2, node.id, ts);
28563
+ report.cleared++;
28508
28564
  }
28509
28565
  return report;
28510
28566
  }
@@ -30107,7 +30163,7 @@ var MECHANISMS = [
30107
30163
  var COLUMN_INPUTS = {
30108
30164
  __createdAtSeq: (n) => n.createdAtSeq !== void 0 && n.createdAtSeq !== null
30109
30165
  };
30110
- function evaluateFed(store2, d) {
30166
+ function evaluateFed(store2, d, stamps) {
30111
30167
  if (d.fedIncoming) {
30112
30168
  const { count: count2, total } = store2.incomingEdgeCoverage?.(
30113
30169
  d.fedIncoming.label,
@@ -30127,7 +30183,7 @@ function evaluateFed(store2, d) {
30127
30183
  if (!d.fed) return { count: 0, total: 0, fraction: 0 };
30128
30184
  const column2 = COLUMN_INPUTS[d.fed.attr];
30129
30185
  const fed = d.fed;
30130
- const all = fed.labels.flatMap((label) => store2.findNodeStampsByLabel ? store2.findNodeStampsByLabel(label, fed.attr) : store2.findNodeAttrsByLabel(label));
30186
+ const all = fed.labels.flatMap((label) => stamps?.(label, fed.attr) ?? (store2.findNodeStampsByLabel ? store2.findNodeStampsByLabel(label, fed.attr) : store2.findNodeAttrsByLabel(label)));
30131
30187
  const isCloudMirror = (n) => n.cloud ?? n.attrs?.["source"] === "cloud";
30132
30188
  const nodes = fed.localOnly ? all.filter((n) => !isCloudMirror(n)) : all;
30133
30189
  const has = (n) => column2 ? column2(n) : n.has ?? n.attrs?.[fed.attr] !== void 0;
@@ -30150,8 +30206,15 @@ function evaluateMechanisms(store2, invocations = /* @__PURE__ */ new Map(), mec
30150
30206
  if (!counts.has(label)) counts.set(label, store2.countNodesByLabel(label));
30151
30207
  return counts.get(label);
30152
30208
  };
30209
+ const wanted = /* @__PURE__ */ new Map();
30210
+ for (const d of mechanisms) for (const l of d.fed?.labels ?? []) wanted.set(l, (wanted.get(l) ?? /* @__PURE__ */ new Set()).add(d.fed.attr));
30211
+ const byLabel = /* @__PURE__ */ new Map();
30212
+ const stamps = store2.findNodeStampsByLabelAttrs ? (label, attr) => {
30213
+ if (!byLabel.has(label)) byLabel.set(label, store2.findNodeStampsByLabelAttrs(label, [...wanted.get(label) ?? [attr]]));
30214
+ return byLabel.get(label).get(attr);
30215
+ } : void 0;
30153
30216
  return mechanisms.map((d) => {
30154
- const fed = evaluateFed(store2, d);
30217
+ const fed = evaluateFed(store2, d, stamps);
30155
30218
  const inv = invocations.get(d.pass);
30156
30219
  const effectCount = d.effectIsLevel ? Math.max(
30157
30220
  0,
@@ -31204,6 +31267,8 @@ async function reconcileStaleFiles(store2, rootPath, workspaceId) {
31204
31267
  }
31205
31268
 
31206
31269
  // src/workspace-maintenance.ts
31270
+ var WM_CALIBRATION_AT_KEY = "wm-calibration-at";
31271
+ var WM_CALIBRATION_INTERVAL_MS = 30 * 6e4;
31207
31272
  function runWorkspaceMaintenance(store2, workspaceId) {
31208
31273
  const seqBefore = store2.currentIngestSeq();
31209
31274
  const report = runSemanticMaintenance(store2);
@@ -31214,9 +31279,13 @@ function runWorkspaceMaintenance(store2, workspaceId) {
31214
31279
  }
31215
31280
  let calibration = null;
31216
31281
  const calibrationStarted = Date.now();
31217
- try {
31218
- calibration = wmCalibrationCells(store2);
31219
- } catch {
31282
+ const lastCalibration = Number(store2.getMeta?.(WM_CALIBRATION_AT_KEY) ?? 0);
31283
+ if (!(calibrationStarted - lastCalibration < WM_CALIBRATION_INTERVAL_MS)) {
31284
+ try {
31285
+ calibration = wmCalibrationCells(store2);
31286
+ store2.setMeta?.(WM_CALIBRATION_AT_KEY, String(calibrationStarted));
31287
+ } catch {
31288
+ }
31220
31289
  }
31221
31290
  return {
31222
31291
  report,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.22-dev.4617",
3
+ "version": "2.0.22-dev.4624",
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
- 81a5a7875eb137fa180465a687ecfd2fd76f67967cec97962a20f6e695c81d2f .codex-plugin/plugin.json
1
+ 2f2ed72ed3ac78912e2f6d5afbfd43bb55b67b91e397c9c559d045d0b93f1bde .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js