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

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,14 @@ 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;
17898
+ -- Live prior-tag edges by recency: the liveness corroboration check reads the
17899
+ -- newest 100 of ~300, and walking all live edges newest-first to find them read
17900
+ -- 69 MB. Its predicate is the one recentEdgeAttrCoverage uses for that marker.
17901
+ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1;
17864
17902
  -- Expression index on the per-node relPath (stored in attrs_json) so the
17865
17903
  -- incremental reindex can fetch just the changed files' symbols instead of
17866
17904
  -- scanning every versioned node. Partial: only live rows are ever queried.
@@ -17951,6 +17989,7 @@ function rowToEdge(r) {
17951
17989
  }
17952
17990
  var FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
17953
17991
  var NODE_LABELS_CHUNK = 256;
17992
+ var PRIOR_TAG_MARKER = '"priorTag":true';
17954
17993
  var SqliteGraphStore = class {
17955
17994
  db;
17956
17995
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -18557,6 +18596,27 @@ var SqliteGraphStore = class {
18557
18596
  cloud: r.cloud === 1
18558
18597
  }));
18559
18598
  }
18599
+ findNodeStampsByLabelAttrs(label, attrKeys) {
18600
+ for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
18601
+ const keys = [...new Set(attrKeys)];
18602
+ const rows = this.db.prepare(
18603
+ `SELECT id, created_at, created_at_seq,
18604
+ ${keys.map((k, i) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i}`).join(", ")}${keys.length ? "," : ""}
18605
+ COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
18606
+ FROM nodes WHERE label = ? AND valid_to IS NULL`
18607
+ ).all(label);
18608
+ const out = new Map(keys.map((k) => [k, []]));
18609
+ for (const r of rows) {
18610
+ keys.forEach((k, i) => out.get(k).push({
18611
+ id: r.id,
18612
+ createdAt: r.created_at,
18613
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
18614
+ has: r[`h${i}`] === 1,
18615
+ cloud: r.cloud === 1
18616
+ }));
18617
+ }
18618
+ return out;
18619
+ }
18560
18620
  findNodeAttrsByLabel(label) {
18561
18621
  const rows = this.stmts.attrsByLabel.all(label);
18562
18622
  return rows.map((r) => ({
@@ -18798,6 +18858,9 @@ var SqliteGraphStore = class {
18798
18858
  lineageChangedFiles(sinceSeq) {
18799
18859
  return readLineageChangedFiles(this.db, sinceSeq);
18800
18860
  }
18861
+ changedNodeIds(sinceSeq, labels) {
18862
+ return readChangedNodeIds(this.db, sinceSeq, labels);
18863
+ }
18801
18864
  pruneChangeLog() {
18802
18865
  return pruneChangeLog(this.db);
18803
18866
  }
@@ -18988,11 +19051,26 @@ var SqliteGraphStore = class {
18988
19051
  ).get(...edgeTypes, label, cloudOnly ? 1 : 0);
18989
19052
  return { count: Number(r.count), total: Number(r.total) };
18990
19053
  }
19054
+ liveEdgeTargets(edgeTypes) {
19055
+ if (edgeTypes.length === 0) return [];
19056
+ const rows = this.db.prepare(`SELECT DISTINCT to_id FROM edges INDEXED BY edges_type WHERE type IN (${edgeTypes.map(() => "?").join(",")}) AND valid_to IS NULL`).all(...edgeTypes);
19057
+ return rows.map((r) => r.to_id);
19058
+ }
18991
19059
  recentEdgeAttrCoverage(marker, attr, limit) {
19060
+ if (marker === PRIOR_TAG_MARKER) {
19061
+ const r2 = this.db.prepare(
19062
+ `SELECT COUNT(*) AS total,
19063
+ COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
19064
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_prior_tag
19065
+ WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1
19066
+ ORDER BY created_at DESC LIMIT ?)`
19067
+ ).get(attr, limit);
19068
+ return { count: Number(r2.count), total: Number(r2.total) };
19069
+ }
18992
19070
  const r = this.db.prepare(
18993
19071
  `SELECT COUNT(*) AS total,
18994
19072
  COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
18995
- FROM (SELECT attrs_json FROM edges
19073
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
18996
19074
  WHERE valid_to IS NULL AND attrs_json LIKE ?
18997
19075
  ORDER BY created_at DESC LIMIT ?)`
18998
19076
  ).get(attr, `%${marker}%`, limit);
@@ -19140,8 +19218,9 @@ var SqliteGraphStore = class {
19140
19218
  * so it must be a plain identifier. */
19141
19219
  nodesAsOfWhereTrue(t, attr) {
19142
19220
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
19221
+ const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
19143
19222
  const rows = this.db.prepare(
19144
- `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
19223
+ `SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
19145
19224
  AND json_type(attrs_json, '$.${attr}') = 'true'
19146
19225
  ORDER BY rowid`
19147
19226
  ).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([
@@ -20611,7 +20628,7 @@ function rowToEdge(r) {
20611
20628
  function openGraphStore(opts) {
20612
20629
  return new SqliteGraphStore(opts);
20613
20630
  }
20614
- var PAGE_RANK_SCALE_KEY, TEXT_SEARCH_COLUMNS, LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SESSION_REPAIR_KEY, SESSION_EVIDENCE_PREDICATE, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, NODE_LABELS_CHUNK, SqliteGraphStore;
20631
+ var PAGE_RANK_SCALE_KEY, TEXT_SEARCH_COLUMNS, LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SESSION_REPAIR_KEY, SESSION_EVIDENCE_PREDICATE, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, NODE_LABELS_CHUNK, PRIOR_TAG_MARKER, SqliteGraphStore;
20615
20632
  var init_store = __esm({
20616
20633
  "../../packages/local-graph/src/store.ts"() {
20617
20634
  "use strict";
@@ -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,14 @@ 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;
20807
+ -- Live prior-tag edges by recency: the liveness corroboration check reads the
20808
+ -- newest 100 of ~300, and walking all live edges newest-first to find them read
20809
+ -- 69 MB. Its predicate is the one recentEdgeAttrCoverage uses for that marker.
20810
+ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1;
20773
20811
  -- Expression index on the per-node relPath (stored in attrs_json) so the
20774
20812
  -- incremental reindex can fetch just the changed files' symbols instead of
20775
20813
  -- scanning every versioned node. Partial: only live rows are ever queried.
@@ -20803,6 +20841,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
20803
20841
  `;
20804
20842
  FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
20805
20843
  NODE_LABELS_CHUNK = 256;
20844
+ PRIOR_TAG_MARKER = '"priorTag":true';
20806
20845
  SqliteGraphStore = class {
20807
20846
  db;
20808
20847
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -21409,6 +21448,27 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21409
21448
  cloud: r.cloud === 1
21410
21449
  }));
21411
21450
  }
21451
+ findNodeStampsByLabelAttrs(label, attrKeys) {
21452
+ for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
21453
+ const keys = [...new Set(attrKeys)];
21454
+ const rows = this.db.prepare(
21455
+ `SELECT id, created_at, created_at_seq,
21456
+ ${keys.map((k, i2) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i2}`).join(", ")}${keys.length ? "," : ""}
21457
+ COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
21458
+ FROM nodes WHERE label = ? AND valid_to IS NULL`
21459
+ ).all(label);
21460
+ const out2 = new Map(keys.map((k) => [k, []]));
21461
+ for (const r of rows) {
21462
+ keys.forEach((k, i2) => out2.get(k).push({
21463
+ id: r.id,
21464
+ createdAt: r.created_at,
21465
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
21466
+ has: r[`h${i2}`] === 1,
21467
+ cloud: r.cloud === 1
21468
+ }));
21469
+ }
21470
+ return out2;
21471
+ }
21412
21472
  findNodeAttrsByLabel(label) {
21413
21473
  const rows = this.stmts.attrsByLabel.all(label);
21414
21474
  return rows.map((r) => ({
@@ -21650,6 +21710,9 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21650
21710
  lineageChangedFiles(sinceSeq) {
21651
21711
  return readLineageChangedFiles(this.db, sinceSeq);
21652
21712
  }
21713
+ changedNodeIds(sinceSeq, labels) {
21714
+ return readChangedNodeIds(this.db, sinceSeq, labels);
21715
+ }
21653
21716
  pruneChangeLog() {
21654
21717
  return pruneChangeLog(this.db);
21655
21718
  }
@@ -21840,11 +21903,26 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21840
21903
  ).get(...edgeTypes, label, cloudOnly ? 1 : 0);
21841
21904
  return { count: Number(r.count), total: Number(r.total) };
21842
21905
  }
21906
+ liveEdgeTargets(edgeTypes) {
21907
+ if (edgeTypes.length === 0) return [];
21908
+ const rows = this.db.prepare(`SELECT DISTINCT to_id FROM edges INDEXED BY edges_type WHERE type IN (${edgeTypes.map(() => "?").join(",")}) AND valid_to IS NULL`).all(...edgeTypes);
21909
+ return rows.map((r) => r.to_id);
21910
+ }
21843
21911
  recentEdgeAttrCoverage(marker, attr, limit) {
21912
+ if (marker === PRIOR_TAG_MARKER) {
21913
+ const r2 = this.db.prepare(
21914
+ `SELECT COUNT(*) AS total,
21915
+ COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
21916
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_prior_tag
21917
+ WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1
21918
+ ORDER BY created_at DESC LIMIT ?)`
21919
+ ).get(attr, limit);
21920
+ return { count: Number(r2.count), total: Number(r2.total) };
21921
+ }
21844
21922
  const r = this.db.prepare(
21845
21923
  `SELECT COUNT(*) AS total,
21846
21924
  COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
21847
- FROM (SELECT attrs_json FROM edges
21925
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
21848
21926
  WHERE valid_to IS NULL AND attrs_json LIKE ?
21849
21927
  ORDER BY created_at DESC LIMIT ?)`
21850
21928
  ).get(attr, `%${marker}%`, limit);
@@ -21992,8 +22070,9 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21992
22070
  * so it must be a plain identifier. */
21993
22071
  nodesAsOfWhereTrue(t, attr) {
21994
22072
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
22073
+ const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
21995
22074
  const rows = this.db.prepare(
21996
- `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
22075
+ `SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
21997
22076
  AND json_type(attrs_json, '$.${attr}') = 'true'
21998
22077
  ORDER BY rowid`
21999
22078
  ).all({ t });
@@ -22373,19 +22452,16 @@ function conditionOf(node2) {
22373
22452
  }
22374
22453
  function clearAnsweredRevisits(store, ts) {
22375
22454
  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++;
22455
+ const semantic = new Set(SEMANTIC_NODE_LABELS);
22456
+ for (const node2 of store.nodesAsOfWhereTrue(ts, "revisit")) {
22457
+ if (!semantic.has(node2.label) || node2.validTo != null) continue;
22458
+ const condition = conditionOf(node2);
22459
+ if (condition === void 0 || !isRevisitAnswered(store, node2, condition)) {
22460
+ report.standing++;
22461
+ continue;
22388
22462
  }
22463
+ clearRevisit(store, node2.id, ts);
22464
+ report.cleared++;
22389
22465
  }
22390
22466
  return report;
22391
22467
  }
@@ -26253,7 +26329,10 @@ function communityMembers(store) {
26253
26329
  return store.selectNodes({
26254
26330
  labels: JOINT_COMMUNITY_LABELS,
26255
26331
  attrs: ["localCommunity", "cloudNodeId", "source"],
26256
- where: [{ attr: "localCommunity", notNullish: true }]
26332
+ where: [{ attr: "localCommunity", notNullish: true }],
26333
+ // ~15% of semantic nodes are members; the ordered select otherwise walks
26334
+ // nodes_live and reads every live semantic row (168 MB on a 1.1 GB store).
26335
+ indexedBy: "nodes_live_local_community"
26257
26336
  });
26258
26337
  }
26259
26338
  function communitySeeds(store, opts = {}) {
@@ -26451,7 +26530,10 @@ async function resolveFoldCandidates(store, opts) {
26451
26530
  { attr: "resolvedAt", nullish: true },
26452
26531
  { attr: "mergedInto", absent: true },
26453
26532
  { attr: "foldCandidate", present: true }
26454
- ]
26533
+ ],
26534
+ // Only parked Problems are in it; the rowid-ordered select otherwise walks
26535
+ // nodes_live and reads every live Problem (56 MB per settle boundary).
26536
+ indexedBy: "nodes_live_fold_candidate"
26455
26537
  });
26456
26538
  const markOf = (n) => n.attrs["foldCandidate"];
26457
26539
  report.pending = pool.length;
@@ -26783,7 +26865,14 @@ var init_principle_sync = __esm({
26783
26865
  });
26784
26866
 
26785
26867
  // ../../packages/local-graph/src/mechanism-liveness.ts
26786
- function evaluateFed(store, d) {
26868
+ function evaluateFed(store, d, stamps) {
26869
+ const incomingRows = d.fedIncoming && store.liveEdgeTargets ? stamps?.(d.fedIncoming.label, "source") : void 0;
26870
+ if (d.fedIncoming && incomingRows) {
26871
+ const targets = new Set(store.liveEdgeTargets(d.fedIncoming.edgeTypes));
26872
+ const rows = d.fedIncoming.cloudOnly ? incomingRows.filter((r) => r.cloud) : incomingRows;
26873
+ const count2 = rows.filter((r) => targets.has(r.id)).length;
26874
+ return { count: count2, total: rows.length, fraction: rows.length === 0 ? 0 : count2 / rows.length };
26875
+ }
26787
26876
  if (d.fedIncoming) {
26788
26877
  const { count: count2, total } = store.incomingEdgeCoverage?.(
26789
26878
  d.fedIncoming.label,
@@ -26803,7 +26892,7 @@ function evaluateFed(store, d) {
26803
26892
  if (!d.fed) return { count: 0, total: 0, fraction: 0 };
26804
26893
  const column2 = COLUMN_INPUTS[d.fed.attr];
26805
26894
  const fed = d.fed;
26806
- const all = fed.labels.flatMap((label) => store.findNodeStampsByLabel ? store.findNodeStampsByLabel(label, fed.attr) : store.findNodeAttrsByLabel(label));
26895
+ const all = fed.labels.flatMap((label) => stamps?.(label, fed.attr) ?? (store.findNodeStampsByLabel ? store.findNodeStampsByLabel(label, fed.attr) : store.findNodeAttrsByLabel(label)));
26807
26896
  const isCloudMirror = (n) => n.cloud ?? n.attrs?.["source"] === "cloud";
26808
26897
  const nodes = fed.localOnly ? all.filter((n) => !isCloudMirror(n)) : all;
26809
26898
  const has = (n) => column2 ? column2(n) : n.has ?? n.attrs?.[fed.attr] !== void 0;
@@ -26826,8 +26915,16 @@ function evaluateMechanisms(store, invocations = /* @__PURE__ */ new Map(), mech
26826
26915
  if (!counts.has(label)) counts.set(label, store.countNodesByLabel(label));
26827
26916
  return counts.get(label);
26828
26917
  };
26918
+ const wanted = /* @__PURE__ */ new Map();
26919
+ 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));
26920
+ for (const d of mechanisms) if (d.fedIncoming) wanted.set(d.fedIncoming.label, (wanted.get(d.fedIncoming.label) ?? /* @__PURE__ */ new Set()).add("source"));
26921
+ const byLabel = /* @__PURE__ */ new Map();
26922
+ const stamps = store.findNodeStampsByLabelAttrs ? (label, attr) => {
26923
+ if (!byLabel.has(label)) byLabel.set(label, store.findNodeStampsByLabelAttrs(label, [...wanted.get(label) ?? [attr]]));
26924
+ return byLabel.get(label).get(attr);
26925
+ } : void 0;
26829
26926
  return mechanisms.map((d) => {
26830
- const fed = evaluateFed(store, d);
26927
+ const fed = evaluateFed(store, d, stamps);
26831
26928
  const inv = invocations.get(d.pass);
26832
26929
  const effectCount = d.effectIsLevel ? Math.max(
26833
26930
  0,
@@ -34598,7 +34695,10 @@ function embedCodeNodes(store, opts = {}) {
34598
34695
  return embedNodesByLabels(store, CODE_LABELS, opts);
34599
34696
  }
34600
34697
  function embedSemanticNodes(store, opts = {}) {
34601
- return embedNodesByLabels(store, SEMANTIC_LABELS, opts);
34698
+ return embedNodesByLabels(store, SEMANTIC_LABELS, {
34699
+ incrementalKey: `embed-scan:semantic:${opts.mode === "hash" ? "hash" : "model"}`,
34700
+ ...opts
34701
+ });
34602
34702
  }
34603
34703
  async function ensureNodeEmbedded(store, nodeId, opts = {}) {
34604
34704
  const node2 = store.getNode(nodeId);
@@ -34671,14 +34771,32 @@ async function embedNodesByLabels(store, labels, opts = {}) {
34671
34771
  };
34672
34772
  const hashOnly = opts.mode === "hash";
34673
34773
  const candidates = [];
34674
- for (const label of labels) {
34675
- for (const node2 of store.selectNodes({
34676
- labels: [label],
34677
- attrs: EMBED_SCAN_ATTRS
34678
- })) {
34774
+ const watermarkKey = opts.incrementalKey;
34775
+ const seqBefore = watermarkKey ? store.changeSeq?.() : void 0;
34776
+ let held = null;
34777
+ try {
34778
+ held = watermarkKey ? JSON.parse(store.getMeta?.(watermarkKey) ?? "null") : null;
34779
+ } catch {
34780
+ held = null;
34781
+ }
34782
+ const changedIds = !opts.force && held && seqBefore !== void 0 ? store.changedNodeIds?.(held.seq, labels) ?? null : null;
34783
+ const scan2 = function* () {
34784
+ if (changedIds === null) {
34785
+ for (const label of labels) yield* store.selectNodes({ labels: [label], attrs: EMBED_SCAN_ATTRS });
34786
+ return;
34787
+ }
34788
+ for (let i2 = 0; i2 < changedIds.length; i2 += ID_CHUNK) {
34789
+ yield* store.selectNodes({ labels, attrs: EMBED_SCAN_ATTRS, where: [{ idIn: changedIds.slice(i2, i2 + ID_CHUNK) }] });
34790
+ }
34791
+ };
34792
+ const complete = (version3) => {
34793
+ if (watermarkKey && seqBefore !== void 0 && report.failed === 0) store.setMeta?.(watermarkKey, JSON.stringify({ seq: seqBefore, version: version3 }));
34794
+ };
34795
+ {
34796
+ for (const node2 of scan2()) {
34679
34797
  report.scanned++;
34680
34798
  if (!eligibleForEmbedding(store, node2)) {
34681
- clearWithdrawnVector(store, node2.id);
34799
+ if (node2.embeddingDim > 0 || node2.attrs["embeddingVersion"] !== void 0) clearWithdrawnVector(store, node2.id);
34682
34800
  continue;
34683
34801
  }
34684
34802
  const existingVersion = node2.attrs["embeddingVersion"] ?? null;
@@ -34693,10 +34811,18 @@ async function embedNodesByLabels(store, labels, opts = {}) {
34693
34811
  }
34694
34812
  }
34695
34813
  if (candidates.length === 0) {
34814
+ complete(held?.version ?? (hashOnly ? EMBEDDING_VERSION : null));
34696
34815
  report.durationMs = Date.now() - started2;
34697
34816
  return report;
34698
34817
  }
34699
34818
  const currentVersion = hashOnly ? EMBEDDING_VERSION : (await embedBatchWithModelOrHash(["probe"]))[0]?.version ?? "unknown";
34819
+ if (changedIds !== null && held?.version && held.version !== currentVersion) {
34820
+ const { incrementalKey: _incremental, ...fullOpts } = opts;
34821
+ return embedNodesByLabels(store, labels, fullOpts).then((full) => {
34822
+ if (full.failed === 0 && watermarkKey && seqBefore !== void 0) store.setMeta?.(watermarkKey, JSON.stringify({ seq: seqBefore, version: currentVersion }));
34823
+ return full;
34824
+ });
34825
+ }
34700
34826
  const todo = [];
34701
34827
  for (const { node: node2, existingVersion } of candidates) {
34702
34828
  if (!opts.force && existingVersion === currentVersion && node2.embeddingDim > 0) {
@@ -34732,6 +34858,7 @@ async function embedNodesByLabels(store, labels, opts = {}) {
34732
34858
  });
34733
34859
  if (opts.progress) opts.progress(report.embedded, todo.length);
34734
34860
  }
34861
+ complete(currentVersion);
34735
34862
  report.durationMs = Date.now() - started2;
34736
34863
  return report;
34737
34864
  }
@@ -34752,7 +34879,7 @@ function findDocstring(store, nodeId) {
34752
34879
  }
34753
34880
  return null;
34754
34881
  }
34755
- var CODE_LABELS, SEMANTIC_LABELS, BATCH_SIZE, EMBED_SCAN_ATTRS;
34882
+ var CODE_LABELS, SEMANTIC_LABELS, BATCH_SIZE, ID_CHUNK, EMBED_SCAN_ATTRS;
34756
34883
  var init_embed_code = __esm({
34757
34884
  "../../packages/generalizer/src/embed-code.ts"() {
34758
34885
  "use strict";
@@ -34761,6 +34888,7 @@ var init_embed_code = __esm({
34761
34888
  CODE_LABELS = ["Function", "Method", "Class", "Const"];
34762
34889
  SEMANTIC_LABELS = ["Problem", "RootCause", "Solution", "Pattern", "Claim"];
34763
34890
  BATCH_SIZE = 32;
34891
+ ID_CHUNK = 500;
34764
34892
  EMBED_SCAN_ATTRS = [
34765
34893
  "cloudNodeId",
34766
34894
  "source",
@@ -86568,6 +86696,8 @@ function createLivenessWatch(deps) {
86568
86696
  // src/workspace-maintenance.ts
86569
86697
  init_src8();
86570
86698
  init_src5();
86699
+ var WM_CALIBRATION_AT_KEY = "wm-calibration-at";
86700
+ var WM_CALIBRATION_INTERVAL_MS = 30 * 6e4;
86571
86701
  function runWorkspaceMaintenance(store, workspaceId2) {
86572
86702
  const seqBefore = store.currentIngestSeq();
86573
86703
  const report = runSemanticMaintenance(store);
@@ -86578,9 +86708,13 @@ function runWorkspaceMaintenance(store, workspaceId2) {
86578
86708
  }
86579
86709
  let calibration = null;
86580
86710
  const calibrationStarted = Date.now();
86581
- try {
86582
- calibration = wmCalibrationCells(store);
86583
- } catch {
86711
+ const lastCalibration = Number(store.getMeta?.(WM_CALIBRATION_AT_KEY) ?? 0);
86712
+ if (!(calibrationStarted - lastCalibration < WM_CALIBRATION_INTERVAL_MS)) {
86713
+ try {
86714
+ calibration = wmCalibrationCells(store);
86715
+ store.setMeta?.(WM_CALIBRATION_AT_KEY, String(calibrationStarted));
86716
+ } catch {
86717
+ }
86584
86718
  }
86585
86719
  return {
86586
86720
  report,
@@ -86711,7 +86845,7 @@ function createWatchBreaker(deps) {
86711
86845
  }
86712
86846
 
86713
86847
  // src/engine.ts
86714
- var DAEMON_VERSION = true ? "2.0.22-dev.4617" : "2.0.0-alpha.0";
86848
+ var DAEMON_VERSION = true ? "2.0.22-dev.4629" : "2.0.0-alpha.0";
86715
86849
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
86716
86850
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
86717
86851
  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,14 @@ 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;
16295
+ -- Live prior-tag edges by recency: the liveness corroboration check reads the
16296
+ -- newest 100 of ~300, and walking all live edges newest-first to find them read
16297
+ -- 69 MB. Its predicate is the one recentEdgeAttrCoverage uses for that marker.
16298
+ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1;
16261
16299
  -- Expression index on the per-node relPath (stored in attrs_json) so the
16262
16300
  -- incremental reindex can fetch just the changed files' symbols instead of
16263
16301
  -- scanning every versioned node. Partial: only live rows are ever queried.
@@ -16348,6 +16386,7 @@ function rowToEdge(r) {
16348
16386
  }
16349
16387
  var FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
16350
16388
  var NODE_LABELS_CHUNK = 256;
16389
+ var PRIOR_TAG_MARKER = '"priorTag":true';
16351
16390
  var SqliteGraphStore = class {
16352
16391
  db;
16353
16392
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -16954,6 +16993,27 @@ var SqliteGraphStore = class {
16954
16993
  cloud: r.cloud === 1
16955
16994
  }));
16956
16995
  }
16996
+ findNodeStampsByLabelAttrs(label, attrKeys) {
16997
+ for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
16998
+ const keys = [...new Set(attrKeys)];
16999
+ const rows = this.db.prepare(
17000
+ `SELECT id, created_at, created_at_seq,
17001
+ ${keys.map((k, i) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i}`).join(", ")}${keys.length ? "," : ""}
17002
+ COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
17003
+ FROM nodes WHERE label = ? AND valid_to IS NULL`
17004
+ ).all(label);
17005
+ const out = new Map(keys.map((k) => [k, []]));
17006
+ for (const r of rows) {
17007
+ keys.forEach((k, i) => out.get(k).push({
17008
+ id: r.id,
17009
+ createdAt: r.created_at,
17010
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
17011
+ has: r[`h${i}`] === 1,
17012
+ cloud: r.cloud === 1
17013
+ }));
17014
+ }
17015
+ return out;
17016
+ }
16957
17017
  findNodeAttrsByLabel(label) {
16958
17018
  const rows = this.stmts.attrsByLabel.all(label);
16959
17019
  return rows.map((r) => ({
@@ -17195,6 +17255,9 @@ var SqliteGraphStore = class {
17195
17255
  lineageChangedFiles(sinceSeq) {
17196
17256
  return readLineageChangedFiles(this.db, sinceSeq);
17197
17257
  }
17258
+ changedNodeIds(sinceSeq, labels) {
17259
+ return readChangedNodeIds(this.db, sinceSeq, labels);
17260
+ }
17198
17261
  pruneChangeLog() {
17199
17262
  return pruneChangeLog(this.db);
17200
17263
  }
@@ -17385,11 +17448,26 @@ var SqliteGraphStore = class {
17385
17448
  ).get(...edgeTypes, label, cloudOnly ? 1 : 0);
17386
17449
  return { count: Number(r.count), total: Number(r.total) };
17387
17450
  }
17451
+ liveEdgeTargets(edgeTypes) {
17452
+ if (edgeTypes.length === 0) return [];
17453
+ const rows = this.db.prepare(`SELECT DISTINCT to_id FROM edges INDEXED BY edges_type WHERE type IN (${edgeTypes.map(() => "?").join(",")}) AND valid_to IS NULL`).all(...edgeTypes);
17454
+ return rows.map((r) => r.to_id);
17455
+ }
17388
17456
  recentEdgeAttrCoverage(marker, attr, limit) {
17457
+ if (marker === PRIOR_TAG_MARKER) {
17458
+ const r2 = this.db.prepare(
17459
+ `SELECT COUNT(*) AS total,
17460
+ COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
17461
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_prior_tag
17462
+ WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1
17463
+ ORDER BY created_at DESC LIMIT ?)`
17464
+ ).get(attr, limit);
17465
+ return { count: Number(r2.count), total: Number(r2.total) };
17466
+ }
17389
17467
  const r = this.db.prepare(
17390
17468
  `SELECT COUNT(*) AS total,
17391
17469
  COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
17392
- FROM (SELECT attrs_json FROM edges
17470
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
17393
17471
  WHERE valid_to IS NULL AND attrs_json LIKE ?
17394
17472
  ORDER BY created_at DESC LIMIT ?)`
17395
17473
  ).get(attr, `%${marker}%`, limit);
@@ -17537,8 +17615,9 @@ var SqliteGraphStore = class {
17537
17615
  * so it must be a plain identifier. */
17538
17616
  nodesAsOfWhereTrue(t, attr) {
17539
17617
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
17618
+ const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
17540
17619
  const rows = this.db.prepare(
17541
- `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
17620
+ `SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
17542
17621
  AND json_type(attrs_json, '$.${attr}') = 'true'
17543
17622
  ORDER BY rowid`
17544
17623
  ).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.4629",
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,14 @@ 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;
27163
+ -- Live prior-tag edges by recency: the liveness corroboration check reads the
27164
+ -- newest 100 of ~300, and walking all live edges newest-first to find them read
27165
+ -- 69 MB. Its predicate is the one recentEdgeAttrCoverage uses for that marker.
27166
+ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1;
27129
27167
  -- Expression index on the per-node relPath (stored in attrs_json) so the
27130
27168
  -- incremental reindex can fetch just the changed files' symbols instead of
27131
27169
  -- scanning every versioned node. Partial: only live rows are ever queried.
@@ -27216,6 +27254,7 @@ function rowToEdge(r) {
27216
27254
  }
27217
27255
  var FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
27218
27256
  var NODE_LABELS_CHUNK = 256;
27257
+ var PRIOR_TAG_MARKER = '"priorTag":true';
27219
27258
  var SqliteGraphStore = class {
27220
27259
  db;
27221
27260
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -27822,6 +27861,27 @@ var SqliteGraphStore = class {
27822
27861
  cloud: r.cloud === 1
27823
27862
  }));
27824
27863
  }
27864
+ findNodeStampsByLabelAttrs(label, attrKeys) {
27865
+ for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
27866
+ const keys = [...new Set(attrKeys)];
27867
+ const rows = this.db.prepare(
27868
+ `SELECT id, created_at, created_at_seq,
27869
+ ${keys.map((k, i2) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i2}`).join(", ")}${keys.length ? "," : ""}
27870
+ COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
27871
+ FROM nodes WHERE label = ? AND valid_to IS NULL`
27872
+ ).all(label);
27873
+ const out2 = new Map(keys.map((k) => [k, []]));
27874
+ for (const r of rows) {
27875
+ keys.forEach((k, i2) => out2.get(k).push({
27876
+ id: r.id,
27877
+ createdAt: r.created_at,
27878
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
27879
+ has: r[`h${i2}`] === 1,
27880
+ cloud: r.cloud === 1
27881
+ }));
27882
+ }
27883
+ return out2;
27884
+ }
27825
27885
  findNodeAttrsByLabel(label) {
27826
27886
  const rows = this.stmts.attrsByLabel.all(label);
27827
27887
  return rows.map((r) => ({
@@ -28063,6 +28123,9 @@ var SqliteGraphStore = class {
28063
28123
  lineageChangedFiles(sinceSeq) {
28064
28124
  return readLineageChangedFiles(this.db, sinceSeq);
28065
28125
  }
28126
+ changedNodeIds(sinceSeq, labels) {
28127
+ return readChangedNodeIds(this.db, sinceSeq, labels);
28128
+ }
28066
28129
  pruneChangeLog() {
28067
28130
  return pruneChangeLog(this.db);
28068
28131
  }
@@ -28253,11 +28316,26 @@ var SqliteGraphStore = class {
28253
28316
  ).get(...edgeTypes, label, cloudOnly ? 1 : 0);
28254
28317
  return { count: Number(r.count), total: Number(r.total) };
28255
28318
  }
28319
+ liveEdgeTargets(edgeTypes) {
28320
+ if (edgeTypes.length === 0) return [];
28321
+ const rows = this.db.prepare(`SELECT DISTINCT to_id FROM edges INDEXED BY edges_type WHERE type IN (${edgeTypes.map(() => "?").join(",")}) AND valid_to IS NULL`).all(...edgeTypes);
28322
+ return rows.map((r) => r.to_id);
28323
+ }
28256
28324
  recentEdgeAttrCoverage(marker, attr, limit) {
28325
+ if (marker === PRIOR_TAG_MARKER) {
28326
+ const r2 = this.db.prepare(
28327
+ `SELECT COUNT(*) AS total,
28328
+ COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
28329
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_prior_tag
28330
+ WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1
28331
+ ORDER BY created_at DESC LIMIT ?)`
28332
+ ).get(attr, limit);
28333
+ return { count: Number(r2.count), total: Number(r2.total) };
28334
+ }
28257
28335
  const r = this.db.prepare(
28258
28336
  `SELECT COUNT(*) AS total,
28259
28337
  COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
28260
- FROM (SELECT attrs_json FROM edges
28338
+ FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
28261
28339
  WHERE valid_to IS NULL AND attrs_json LIKE ?
28262
28340
  ORDER BY created_at DESC LIMIT ?)`
28263
28341
  ).get(attr, `%${marker}%`, limit);
@@ -28405,8 +28483,9 @@ var SqliteGraphStore = class {
28405
28483
  * so it must be a plain identifier. */
28406
28484
  nodesAsOfWhereTrue(t, attr) {
28407
28485
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
28486
+ const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
28408
28487
  const rows = this.db.prepare(
28409
- `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
28488
+ `SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
28410
28489
  AND json_type(attrs_json, '$.${attr}') = 'true'
28411
28490
  ORDER BY rowid`
28412
28491
  ).all({ t });
@@ -28492,19 +28571,16 @@ function conditionOf(node) {
28492
28571
  }
28493
28572
  function clearAnsweredRevisits(store2, ts) {
28494
28573
  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++;
28574
+ const semantic = new Set(SEMANTIC_NODE_LABELS);
28575
+ for (const node of store2.nodesAsOfWhereTrue(ts, "revisit")) {
28576
+ if (!semantic.has(node.label) || node.validTo != null) continue;
28577
+ const condition = conditionOf(node);
28578
+ if (condition === void 0 || !isRevisitAnswered(store2, node, condition)) {
28579
+ report.standing++;
28580
+ continue;
28507
28581
  }
28582
+ clearRevisit(store2, node.id, ts);
28583
+ report.cleared++;
28508
28584
  }
28509
28585
  return report;
28510
28586
  }
@@ -30107,7 +30183,14 @@ var MECHANISMS = [
30107
30183
  var COLUMN_INPUTS = {
30108
30184
  __createdAtSeq: (n) => n.createdAtSeq !== void 0 && n.createdAtSeq !== null
30109
30185
  };
30110
- function evaluateFed(store2, d) {
30186
+ function evaluateFed(store2, d, stamps) {
30187
+ const incomingRows = d.fedIncoming && store2.liveEdgeTargets ? stamps?.(d.fedIncoming.label, "source") : void 0;
30188
+ if (d.fedIncoming && incomingRows) {
30189
+ const targets = new Set(store2.liveEdgeTargets(d.fedIncoming.edgeTypes));
30190
+ const rows = d.fedIncoming.cloudOnly ? incomingRows.filter((r) => r.cloud) : incomingRows;
30191
+ const count2 = rows.filter((r) => targets.has(r.id)).length;
30192
+ return { count: count2, total: rows.length, fraction: rows.length === 0 ? 0 : count2 / rows.length };
30193
+ }
30111
30194
  if (d.fedIncoming) {
30112
30195
  const { count: count2, total } = store2.incomingEdgeCoverage?.(
30113
30196
  d.fedIncoming.label,
@@ -30127,7 +30210,7 @@ function evaluateFed(store2, d) {
30127
30210
  if (!d.fed) return { count: 0, total: 0, fraction: 0 };
30128
30211
  const column2 = COLUMN_INPUTS[d.fed.attr];
30129
30212
  const fed = d.fed;
30130
- const all = fed.labels.flatMap((label) => store2.findNodeStampsByLabel ? store2.findNodeStampsByLabel(label, fed.attr) : store2.findNodeAttrsByLabel(label));
30213
+ const all = fed.labels.flatMap((label) => stamps?.(label, fed.attr) ?? (store2.findNodeStampsByLabel ? store2.findNodeStampsByLabel(label, fed.attr) : store2.findNodeAttrsByLabel(label)));
30131
30214
  const isCloudMirror = (n) => n.cloud ?? n.attrs?.["source"] === "cloud";
30132
30215
  const nodes = fed.localOnly ? all.filter((n) => !isCloudMirror(n)) : all;
30133
30216
  const has = (n) => column2 ? column2(n) : n.has ?? n.attrs?.[fed.attr] !== void 0;
@@ -30150,8 +30233,16 @@ function evaluateMechanisms(store2, invocations = /* @__PURE__ */ new Map(), mec
30150
30233
  if (!counts.has(label)) counts.set(label, store2.countNodesByLabel(label));
30151
30234
  return counts.get(label);
30152
30235
  };
30236
+ const wanted = /* @__PURE__ */ new Map();
30237
+ 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));
30238
+ for (const d of mechanisms) if (d.fedIncoming) wanted.set(d.fedIncoming.label, (wanted.get(d.fedIncoming.label) ?? /* @__PURE__ */ new Set()).add("source"));
30239
+ const byLabel = /* @__PURE__ */ new Map();
30240
+ const stamps = store2.findNodeStampsByLabelAttrs ? (label, attr) => {
30241
+ if (!byLabel.has(label)) byLabel.set(label, store2.findNodeStampsByLabelAttrs(label, [...wanted.get(label) ?? [attr]]));
30242
+ return byLabel.get(label).get(attr);
30243
+ } : void 0;
30153
30244
  return mechanisms.map((d) => {
30154
- const fed = evaluateFed(store2, d);
30245
+ const fed = evaluateFed(store2, d, stamps);
30155
30246
  const inv = invocations.get(d.pass);
30156
30247
  const effectCount = d.effectIsLevel ? Math.max(
30157
30248
  0,
@@ -31204,6 +31295,8 @@ async function reconcileStaleFiles(store2, rootPath, workspaceId) {
31204
31295
  }
31205
31296
 
31206
31297
  // src/workspace-maintenance.ts
31298
+ var WM_CALIBRATION_AT_KEY = "wm-calibration-at";
31299
+ var WM_CALIBRATION_INTERVAL_MS = 30 * 6e4;
31207
31300
  function runWorkspaceMaintenance(store2, workspaceId) {
31208
31301
  const seqBefore = store2.currentIngestSeq();
31209
31302
  const report = runSemanticMaintenance(store2);
@@ -31214,9 +31307,13 @@ function runWorkspaceMaintenance(store2, workspaceId) {
31214
31307
  }
31215
31308
  let calibration = null;
31216
31309
  const calibrationStarted = Date.now();
31217
- try {
31218
- calibration = wmCalibrationCells(store2);
31219
- } catch {
31310
+ const lastCalibration = Number(store2.getMeta?.(WM_CALIBRATION_AT_KEY) ?? 0);
31311
+ if (!(calibrationStarted - lastCalibration < WM_CALIBRATION_INTERVAL_MS)) {
31312
+ try {
31313
+ calibration = wmCalibrationCells(store2);
31314
+ store2.setMeta?.(WM_CALIBRATION_AT_KEY, String(calibrationStarted));
31315
+ } catch {
31316
+ }
31220
31317
  }
31221
31318
  return {
31222
31319
  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.4629",
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
+ 6116713b2c7fa083cb68622899bbfe5c62167d3c1b8d6d62ec2c81e4cc391baa .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js