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

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.
@@ -15778,7 +15778,7 @@ var init_history_read = __esm({
15778
15778
  "../../packages/shared/src/history-read.ts"() {
15779
15779
  "use strict";
15780
15780
  init_history_dependencies();
15781
- CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding"];
15781
+ CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding", "structEmbedding", "structEmbeddedAt"];
15782
15782
  }
15783
15783
  });
15784
15784
 
@@ -17001,7 +17001,9 @@ function installEditPlaceholderRetirement(db) {
17001
17001
 
17002
17002
  // ../../packages/local-graph/src/cloud-attr-compaction.ts
17003
17003
  init_src2();
17004
- var CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-1";
17004
+ var CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-2";
17005
+ var V1_KEY = "cloud-attr-compaction-1";
17006
+ var STRUCT_LABELS = "('Problem', 'Solution', 'RootCause')";
17005
17007
  var DUPES = CLOUD_ATTR_DUPLICATE_KEYS.map((k) => `'${k}'`).join(",");
17006
17008
  function compacted(row) {
17007
17009
  const packed = `json_extract(${row}.attrs_json, '$.attrsJson')`;
@@ -17020,14 +17022,17 @@ function dirty(row) {
17020
17022
  return `json_extract(${row}.attrs_json, '$.source') = 'cloud' AND (${has})`;
17021
17023
  }
17022
17024
  function installCloudAttrCompaction(db) {
17023
- if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(CLOUD_ATTR_COMPACTION_KEY)) return;
17025
+ const marked = (key) => Boolean(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
17026
+ if (marked(CLOUD_ATTR_COMPACTION_KEY)) return;
17027
+ const scope = marked(V1_KEY) ? ` AND label IN ${STRUCT_LABELS}` : "";
17024
17028
  db.transaction(() => {
17025
17029
  for (const operation of ["INSERT", "UPDATE"]) {
17026
- db.exec(`CREATE TRIGGER IF NOT EXISTS cloud_attr_compact_${operation.toLowerCase()}
17030
+ db.exec(`DROP TRIGGER IF EXISTS cloud_attr_compact_${operation.toLowerCase()}`);
17031
+ db.exec(`CREATE TRIGGER cloud_attr_compact_${operation.toLowerCase()}
17027
17032
  AFTER ${operation} ON nodes WHEN ${dirty("NEW")}
17028
17033
  BEGIN UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE id = NEW.id; END;`);
17029
17034
  }
17030
- db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}`);
17035
+ db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}${scope}`);
17031
17036
  db.prepare("INSERT INTO store_meta(key,value) VALUES (?,?)").run(CLOUD_ATTR_COMPACTION_KEY, "installed");
17032
17037
  });
17033
17038
  }
@@ -17126,6 +17131,7 @@ var EXPOSURE_SCOPE = "exposure";
17126
17131
  // ../../packages/local-graph/src/change-feed.ts
17127
17132
  var CHANGE_FEED_KEY = "change-feed";
17128
17133
  var ANY_CHANGE = "*";
17134
+ var nodeScope = (label) => `node:${label}`;
17129
17135
  var CHANGE_LOG_KEEP = 25e4;
17130
17136
  var PRUNED_TO_KEY = "change-log-pruned-to";
17131
17137
  function installChangeFeed(db) {
@@ -18040,6 +18046,7 @@ var SqliteGraphStore = class {
18040
18046
  initialPageRankScale;
18041
18047
  constructor(opts) {
18042
18048
  this.db = openDatabase(opts.path);
18049
+ this.db.exec("PRAGMA cache_size = -32768");
18043
18050
  this.db.exec(SCHEMA_SQL);
18044
18051
  const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
18045
18052
  if (!versionRow) {
@@ -18104,6 +18111,7 @@ var SqliteGraphStore = class {
18104
18111
  attrs_json = excluded.attrs_json
18105
18112
  `),
18106
18113
  getNode: this.db.prepare("SELECT * FROM nodes WHERE id = ?"),
18114
+ labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
18107
18115
  // Single-column fast paths for the nightly pass — writing 100k pageRank
18108
18116
  // values through the 18-column COALESCE updateNode took ~30s; these are ~10x
18109
18117
  // cheaper (one bound column, no COALESCE evaluation per row).
@@ -18603,11 +18611,13 @@ var SqliteGraphStore = class {
18603
18611
  }
18604
18612
  }
18605
18613
  edgeRuleViolation(edge) {
18606
- return localEdgeViolation(
18607
- this.getNode(edge.from)?.label,
18608
- edge.type,
18609
- this.getNode(edge.to)?.label
18610
- );
18614
+ return localEdgeViolation(this.labelOf(edge.from), edge.type, this.labelOf(edge.to));
18615
+ }
18616
+ /** An endpoint's label, any version, as `getNode(id)?.label` reads it. Every
18617
+ * edge write checks both endpoints, and hydrating them (attrs + vector) to
18618
+ * read one leading column was most of an incremental reindex's reads. */
18619
+ labelOf(id) {
18620
+ return this.stmts.labelOf.get(id)?.label;
18611
18621
  }
18612
18622
  updateEdge(id, patch) {
18613
18623
  this.stmts.updateEdge.run({
@@ -18651,7 +18661,21 @@ var SqliteGraphStore = class {
18651
18661
  * per project per request; the materializing form held the daemon's event
18652
18662
  * loop for seconds at scale. */
18653
18663
  countNodesByLabel(label) {
18654
- return Number(this.stmts.countByLabel.get(label).n);
18664
+ return this.countAt(`label:${label}`, [nodeScope(label)], () => Number(this.stmts.countByLabel.get(label).n));
18665
+ }
18666
+ /** Counts asked for on every status poll (/health, `errata status`, the
18667
+ * dashboards): a live-node COUNT(*) reads the whole liveness index, ~3 MB per
18668
+ * call on a 1.1 GB store, to recount a set that moves only on a write. Each is
18669
+ * kept with the change-feed reading of the rows it counts — triggers bump it on
18670
+ * every write from any connection, so a hit is exact, not a TTL guess. */
18671
+ counts = /* @__PURE__ */ new Map();
18672
+ countAt(key, scopes, count) {
18673
+ const seq = readChangeSeq(this.db, scopes);
18674
+ const hit = this.counts.get(key);
18675
+ if (hit && hit.seq === seq) return hit.n;
18676
+ const n = count();
18677
+ this.counts.set(key, { seq, n });
18678
+ return n;
18655
18679
  }
18656
18680
  /** Live rows for a label with attrs and creation stamps only — for scans that
18657
18681
  * read attrs and never the vector. `findNodesByLabel` decodes every embedding
@@ -18918,7 +18942,7 @@ var SqliteGraphStore = class {
18918
18942
  return out;
18919
18943
  }
18920
18944
  nodeCount() {
18921
- return Number(this.stmts.nodeCount.get().n);
18945
+ return this.countAt("nodes", [ANY_CHANGE], () => Number(this.stmts.nodeCount.get().n));
18922
18946
  }
18923
18947
  mutationCount() {
18924
18948
  return this.mutations;
@@ -18962,7 +18986,7 @@ var SqliteGraphStore = class {
18962
18986
  return this.ingestSeq;
18963
18987
  }
18964
18988
  edgeCount() {
18965
- return Number(this.stmts.edgeCount.get().n);
18989
+ return this.countAt("edges", [ANY_CHANGE], () => Number(this.stmts.edgeCount.get().n));
18966
18990
  }
18967
18991
  // Lightweight whole-graph scans — select only the columns the caller needs so a
18968
18992
  // 100k-node pass skips the per-row attrs JSON.parse + embedding decode rowToNode
package/errata.mjs CHANGED
@@ -17382,7 +17382,7 @@ var init_history_read = __esm({
17382
17382
  "../../packages/shared/src/history-read.ts"() {
17383
17383
  "use strict";
17384
17384
  init_history_dependencies();
17385
- CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding"];
17385
+ CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding", "structEmbedding", "structEmbeddedAt"];
17386
17386
  }
17387
17387
  });
17388
17388
 
@@ -19061,23 +19061,28 @@ function dirty(row) {
19061
19061
  return `json_extract(${row}.attrs_json, '$.source') = 'cloud' AND (${has})`;
19062
19062
  }
19063
19063
  function installCloudAttrCompaction(db) {
19064
- if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(CLOUD_ATTR_COMPACTION_KEY)) return;
19064
+ const marked = (key) => Boolean(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
19065
+ if (marked(CLOUD_ATTR_COMPACTION_KEY)) return;
19066
+ const scope = marked(V1_KEY) ? ` AND label IN ${STRUCT_LABELS}` : "";
19065
19067
  db.transaction(() => {
19066
19068
  for (const operation of ["INSERT", "UPDATE"]) {
19067
- db.exec(`CREATE TRIGGER IF NOT EXISTS cloud_attr_compact_${operation.toLowerCase()}
19069
+ db.exec(`DROP TRIGGER IF EXISTS cloud_attr_compact_${operation.toLowerCase()}`);
19070
+ db.exec(`CREATE TRIGGER cloud_attr_compact_${operation.toLowerCase()}
19068
19071
  AFTER ${operation} ON nodes WHEN ${dirty("NEW")}
19069
19072
  BEGIN UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE id = NEW.id; END;`);
19070
19073
  }
19071
- db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}`);
19074
+ db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}${scope}`);
19072
19075
  db.prepare("INSERT INTO store_meta(key,value) VALUES (?,?)").run(CLOUD_ATTR_COMPACTION_KEY, "installed");
19073
19076
  });
19074
19077
  }
19075
- var CLOUD_ATTR_COMPACTION_KEY, DUPES;
19078
+ var CLOUD_ATTR_COMPACTION_KEY, V1_KEY, STRUCT_LABELS, DUPES;
19076
19079
  var init_cloud_attr_compaction = __esm({
19077
19080
  "../../packages/local-graph/src/cloud-attr-compaction.ts"() {
19078
19081
  "use strict";
19079
19082
  init_src2();
19080
- CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-1";
19083
+ CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-2";
19084
+ V1_KEY = "cloud-attr-compaction-1";
19085
+ STRUCT_LABELS = "('Problem', 'Solution', 'RootCause')";
19081
19086
  DUPES = CLOUD_ATTR_DUPLICATE_KEYS.map((k) => `'${k}'`).join(",");
19082
19087
  }
19083
19088
  });
@@ -20892,6 +20897,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
20892
20897
  initialPageRankScale;
20893
20898
  constructor(opts) {
20894
20899
  this.db = openDatabase(opts.path);
20900
+ this.db.exec("PRAGMA cache_size = -32768");
20895
20901
  this.db.exec(SCHEMA_SQL);
20896
20902
  const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
20897
20903
  if (!versionRow) {
@@ -20956,6 +20962,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
20956
20962
  attrs_json = excluded.attrs_json
20957
20963
  `),
20958
20964
  getNode: this.db.prepare("SELECT * FROM nodes WHERE id = ?"),
20965
+ labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
20959
20966
  // Single-column fast paths for the nightly pass — writing 100k pageRank
20960
20967
  // values through the 18-column COALESCE updateNode took ~30s; these are ~10x
20961
20968
  // cheaper (one bound column, no COALESCE evaluation per row).
@@ -21455,11 +21462,13 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21455
21462
  }
21456
21463
  }
21457
21464
  edgeRuleViolation(edge2) {
21458
- return localEdgeViolation(
21459
- this.getNode(edge2.from)?.label,
21460
- edge2.type,
21461
- this.getNode(edge2.to)?.label
21462
- );
21465
+ return localEdgeViolation(this.labelOf(edge2.from), edge2.type, this.labelOf(edge2.to));
21466
+ }
21467
+ /** An endpoint's label, any version, as `getNode(id)?.label` reads it. Every
21468
+ * edge write checks both endpoints, and hydrating them (attrs + vector) to
21469
+ * read one leading column was most of an incremental reindex's reads. */
21470
+ labelOf(id) {
21471
+ return this.stmts.labelOf.get(id)?.label;
21463
21472
  }
21464
21473
  updateEdge(id, patch) {
21465
21474
  this.stmts.updateEdge.run({
@@ -21503,7 +21512,21 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21503
21512
  * per project per request; the materializing form held the daemon's event
21504
21513
  * loop for seconds at scale. */
21505
21514
  countNodesByLabel(label) {
21506
- return Number(this.stmts.countByLabel.get(label).n);
21515
+ return this.countAt(`label:${label}`, [nodeScope(label)], () => Number(this.stmts.countByLabel.get(label).n));
21516
+ }
21517
+ /** Counts asked for on every status poll (/health, `errata status`, the
21518
+ * dashboards): a live-node COUNT(*) reads the whole liveness index, ~3 MB per
21519
+ * call on a 1.1 GB store, to recount a set that moves only on a write. Each is
21520
+ * kept with the change-feed reading of the rows it counts — triggers bump it on
21521
+ * every write from any connection, so a hit is exact, not a TTL guess. */
21522
+ counts = /* @__PURE__ */ new Map();
21523
+ countAt(key, scopes, count) {
21524
+ const seq = readChangeSeq(this.db, scopes);
21525
+ const hit = this.counts.get(key);
21526
+ if (hit && hit.seq === seq) return hit.n;
21527
+ const n = count();
21528
+ this.counts.set(key, { seq, n });
21529
+ return n;
21507
21530
  }
21508
21531
  /** Live rows for a label with attrs and creation stamps only — for scans that
21509
21532
  * read attrs and never the vector. `findNodesByLabel` decodes every embedding
@@ -21770,7 +21793,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21770
21793
  return out2;
21771
21794
  }
21772
21795
  nodeCount() {
21773
- return Number(this.stmts.nodeCount.get().n);
21796
+ return this.countAt("nodes", [ANY_CHANGE], () => Number(this.stmts.nodeCount.get().n));
21774
21797
  }
21775
21798
  mutationCount() {
21776
21799
  return this.mutations;
@@ -21814,7 +21837,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21814
21837
  return this.ingestSeq;
21815
21838
  }
21816
21839
  edgeCount() {
21817
- return Number(this.stmts.edgeCount.get().n);
21840
+ return this.countAt("edges", [ANY_CHANGE], () => Number(this.stmts.edgeCount.get().n));
21818
21841
  }
21819
21842
  // Lightweight whole-graph scans — select only the columns the caller needs so a
21820
21843
  // 100k-node pass skips the per-row attrs JSON.parse + embedding decode rowToNode
@@ -39603,9 +39626,10 @@ async function runIndexer(store, opts) {
39603
39626
  if (ei > 0) await new Promise((r) => setImmediate(r));
39604
39627
  const slice = pendingEdges.slice(ei, ei + EDGE_BATCH);
39605
39628
  store.transaction(() => {
39629
+ const present = store.nodeLabels([...new Set(slice.flatMap((e) => [e.from, e.to]))]);
39606
39630
  for (const e of slice) {
39607
39631
  if (e.from === e.to) continue;
39608
- if (!store.getNode(e.from) || !store.getNode(e.to)) continue;
39632
+ if (!present.has(e.from) || !present.has(e.to)) continue;
39609
39633
  upsertEdge(
39610
39634
  store,
39611
39635
  e.from,
@@ -87006,7 +87030,7 @@ function createWatchBreaker(deps) {
87006
87030
  }
87007
87031
 
87008
87032
  // src/engine.ts
87009
- var DAEMON_VERSION = true ? "2.0.22-dev.4652" : "2.0.0-alpha.0";
87033
+ var DAEMON_VERSION = true ? "2.0.22-dev.4660" : "2.0.0-alpha.0";
87010
87034
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
87011
87035
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
87012
87036
  var GIT_OP_MUTE_MS = 4e3;
@@ -14697,7 +14697,7 @@ var CastaliaIngestPayloadSchema = external_exports.object({
14697
14697
  });
14698
14698
 
14699
14699
  // ../../packages/shared/src/history-read.ts
14700
- var CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding"];
14700
+ var CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding", "structEmbedding", "structEmbeddedAt"];
14701
14701
 
14702
14702
  // ../../packages/shared/src/history-revision.ts
14703
14703
  var Sha = external_exports.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
@@ -15576,7 +15576,9 @@ function installEditPlaceholderRetirement(db) {
15576
15576
  }
15577
15577
 
15578
15578
  // ../../packages/local-graph/src/cloud-attr-compaction.ts
15579
- var CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-1";
15579
+ var CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-2";
15580
+ var V1_KEY = "cloud-attr-compaction-1";
15581
+ var STRUCT_LABELS = "('Problem', 'Solution', 'RootCause')";
15580
15582
  var DUPES = CLOUD_ATTR_DUPLICATE_KEYS.map((k) => `'${k}'`).join(",");
15581
15583
  function compacted(row) {
15582
15584
  const packed = `json_extract(${row}.attrs_json, '$.attrsJson')`;
@@ -15595,14 +15597,17 @@ function dirty(row) {
15595
15597
  return `json_extract(${row}.attrs_json, '$.source') = 'cloud' AND (${has})`;
15596
15598
  }
15597
15599
  function installCloudAttrCompaction(db) {
15598
- if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(CLOUD_ATTR_COMPACTION_KEY)) return;
15600
+ const marked = (key) => Boolean(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
15601
+ if (marked(CLOUD_ATTR_COMPACTION_KEY)) return;
15602
+ const scope = marked(V1_KEY) ? ` AND label IN ${STRUCT_LABELS}` : "";
15599
15603
  db.transaction(() => {
15600
15604
  for (const operation of ["INSERT", "UPDATE"]) {
15601
- db.exec(`CREATE TRIGGER IF NOT EXISTS cloud_attr_compact_${operation.toLowerCase()}
15605
+ db.exec(`DROP TRIGGER IF EXISTS cloud_attr_compact_${operation.toLowerCase()}`);
15606
+ db.exec(`CREATE TRIGGER cloud_attr_compact_${operation.toLowerCase()}
15602
15607
  AFTER ${operation} ON nodes WHEN ${dirty("NEW")}
15603
15608
  BEGIN UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE id = NEW.id; END;`);
15604
15609
  }
15605
- db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}`);
15610
+ db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}${scope}`);
15606
15611
  db.prepare("INSERT INTO store_meta(key,value) VALUES (?,?)").run(CLOUD_ATTR_COMPACTION_KEY, "installed");
15607
15612
  });
15608
15613
  }
@@ -15700,6 +15705,7 @@ var EXPOSURE_SCOPE = "exposure";
15700
15705
  // ../../packages/local-graph/src/change-feed.ts
15701
15706
  var CHANGE_FEED_KEY = "change-feed";
15702
15707
  var ANY_CHANGE = "*";
15708
+ var nodeScope = (label) => `node:${label}`;
15703
15709
  var CHANGE_LOG_KEEP = 25e4;
15704
15710
  var PRUNED_TO_KEY = "change-log-pruned-to";
15705
15711
  function installChangeFeed(db) {
@@ -16437,6 +16443,7 @@ var SqliteGraphStore = class {
16437
16443
  initialPageRankScale;
16438
16444
  constructor(opts) {
16439
16445
  this.db = openDatabase(opts.path);
16446
+ this.db.exec("PRAGMA cache_size = -32768");
16440
16447
  this.db.exec(SCHEMA_SQL);
16441
16448
  const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
16442
16449
  if (!versionRow) {
@@ -16501,6 +16508,7 @@ var SqliteGraphStore = class {
16501
16508
  attrs_json = excluded.attrs_json
16502
16509
  `),
16503
16510
  getNode: this.db.prepare("SELECT * FROM nodes WHERE id = ?"),
16511
+ labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
16504
16512
  // Single-column fast paths for the nightly pass — writing 100k pageRank
16505
16513
  // values through the 18-column COALESCE updateNode took ~30s; these are ~10x
16506
16514
  // cheaper (one bound column, no COALESCE evaluation per row).
@@ -17000,11 +17008,13 @@ var SqliteGraphStore = class {
17000
17008
  }
17001
17009
  }
17002
17010
  edgeRuleViolation(edge) {
17003
- return localEdgeViolation(
17004
- this.getNode(edge.from)?.label,
17005
- edge.type,
17006
- this.getNode(edge.to)?.label
17007
- );
17011
+ return localEdgeViolation(this.labelOf(edge.from), edge.type, this.labelOf(edge.to));
17012
+ }
17013
+ /** An endpoint's label, any version, as `getNode(id)?.label` reads it. Every
17014
+ * edge write checks both endpoints, and hydrating them (attrs + vector) to
17015
+ * read one leading column was most of an incremental reindex's reads. */
17016
+ labelOf(id) {
17017
+ return this.stmts.labelOf.get(id)?.label;
17008
17018
  }
17009
17019
  updateEdge(id, patch) {
17010
17020
  this.stmts.updateEdge.run({
@@ -17048,7 +17058,21 @@ var SqliteGraphStore = class {
17048
17058
  * per project per request; the materializing form held the daemon's event
17049
17059
  * loop for seconds at scale. */
17050
17060
  countNodesByLabel(label) {
17051
- return Number(this.stmts.countByLabel.get(label).n);
17061
+ return this.countAt(`label:${label}`, [nodeScope(label)], () => Number(this.stmts.countByLabel.get(label).n));
17062
+ }
17063
+ /** Counts asked for on every status poll (/health, `errata status`, the
17064
+ * dashboards): a live-node COUNT(*) reads the whole liveness index, ~3 MB per
17065
+ * call on a 1.1 GB store, to recount a set that moves only on a write. Each is
17066
+ * kept with the change-feed reading of the rows it counts — triggers bump it on
17067
+ * every write from any connection, so a hit is exact, not a TTL guess. */
17068
+ counts = /* @__PURE__ */ new Map();
17069
+ countAt(key, scopes, count) {
17070
+ const seq = readChangeSeq(this.db, scopes);
17071
+ const hit = this.counts.get(key);
17072
+ if (hit && hit.seq === seq) return hit.n;
17073
+ const n = count();
17074
+ this.counts.set(key, { seq, n });
17075
+ return n;
17052
17076
  }
17053
17077
  /** Live rows for a label with attrs and creation stamps only — for scans that
17054
17078
  * read attrs and never the vector. `findNodesByLabel` decodes every embedding
@@ -17315,7 +17339,7 @@ var SqliteGraphStore = class {
17315
17339
  return out;
17316
17340
  }
17317
17341
  nodeCount() {
17318
- return Number(this.stmts.nodeCount.get().n);
17342
+ return this.countAt("nodes", [ANY_CHANGE], () => Number(this.stmts.nodeCount.get().n));
17319
17343
  }
17320
17344
  mutationCount() {
17321
17345
  return this.mutations;
@@ -17359,7 +17383,7 @@ var SqliteGraphStore = class {
17359
17383
  return this.ingestSeq;
17360
17384
  }
17361
17385
  edgeCount() {
17362
- return Number(this.stmts.edgeCount.get().n);
17386
+ return this.countAt("edges", [ANY_CHANGE], () => Number(this.stmts.edgeCount.get().n));
17363
17387
  }
17364
17388
  // Lightweight whole-graph scans — select only the columns the caller needs so a
17365
17389
  // 100k-node pass skips the per-row attrs JSON.parse + embedding decode rowToNode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.22-dev.4652",
3
+ "version": "2.0.22-dev.4660",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -16172,7 +16172,7 @@ var init_history_read = __esm({
16172
16172
  "../../packages/shared/src/history-read.ts"() {
16173
16173
  "use strict";
16174
16174
  init_history_dependencies();
16175
- CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding"];
16175
+ CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding", "structEmbedding", "structEmbeddedAt"];
16176
16176
  }
16177
16177
  });
16178
16178
 
@@ -18280,9 +18280,10 @@ async function runIndexer(store2, opts) {
18280
18280
  if (ei > 0) await new Promise((r) => setImmediate(r));
18281
18281
  const slice = pendingEdges.slice(ei, ei + EDGE_BATCH);
18282
18282
  store2.transaction(() => {
18283
+ const present = store2.nodeLabels([...new Set(slice.flatMap((e) => [e.from, e.to]))]);
18283
18284
  for (const e of slice) {
18284
18285
  if (e.from === e.to) continue;
18285
- if (!store2.getNode(e.from) || !store2.getNode(e.to)) continue;
18286
+ if (!present.has(e.from) || !present.has(e.to)) continue;
18286
18287
  upsertEdge(
18287
18288
  store2,
18288
18289
  e.from,
@@ -26017,7 +26018,9 @@ function installEditPlaceholderRetirement(db) {
26017
26018
 
26018
26019
  // ../../packages/local-graph/src/cloud-attr-compaction.ts
26019
26020
  init_src2();
26020
- var CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-1";
26021
+ var CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-2";
26022
+ var V1_KEY = "cloud-attr-compaction-1";
26023
+ var STRUCT_LABELS = "('Problem', 'Solution', 'RootCause')";
26021
26024
  var DUPES = CLOUD_ATTR_DUPLICATE_KEYS.map((k) => `'${k}'`).join(",");
26022
26025
  function compacted(row) {
26023
26026
  const packed = `json_extract(${row}.attrs_json, '$.attrsJson')`;
@@ -26036,14 +26039,17 @@ function dirty(row) {
26036
26039
  return `json_extract(${row}.attrs_json, '$.source') = 'cloud' AND (${has})`;
26037
26040
  }
26038
26041
  function installCloudAttrCompaction(db) {
26039
- if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(CLOUD_ATTR_COMPACTION_KEY)) return;
26042
+ const marked = (key) => Boolean(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
26043
+ if (marked(CLOUD_ATTR_COMPACTION_KEY)) return;
26044
+ const scope = marked(V1_KEY) ? ` AND label IN ${STRUCT_LABELS}` : "";
26040
26045
  db.transaction(() => {
26041
26046
  for (const operation of ["INSERT", "UPDATE"]) {
26042
- db.exec(`CREATE TRIGGER IF NOT EXISTS cloud_attr_compact_${operation.toLowerCase()}
26047
+ db.exec(`DROP TRIGGER IF EXISTS cloud_attr_compact_${operation.toLowerCase()}`);
26048
+ db.exec(`CREATE TRIGGER cloud_attr_compact_${operation.toLowerCase()}
26043
26049
  AFTER ${operation} ON nodes WHEN ${dirty("NEW")}
26044
26050
  BEGIN UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE id = NEW.id; END;`);
26045
26051
  }
26046
- db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}`);
26052
+ db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}${scope}`);
26047
26053
  db.prepare("INSERT INTO store_meta(key,value) VALUES (?,?)").run(CLOUD_ATTR_COMPACTION_KEY, "installed");
26048
26054
  });
26049
26055
  }
@@ -26142,6 +26148,7 @@ var EXPOSURE_SCOPE = "exposure";
26142
26148
  // ../../packages/local-graph/src/change-feed.ts
26143
26149
  var CHANGE_FEED_KEY = "change-feed";
26144
26150
  var ANY_CHANGE = "*";
26151
+ var nodeScope = (label) => `node:${label}`;
26145
26152
  var CHANGE_LOG_KEEP = 25e4;
26146
26153
  var PRUNED_TO_KEY = "change-log-pruned-to";
26147
26154
  function installChangeFeed(db) {
@@ -27315,6 +27322,7 @@ var SqliteGraphStore = class {
27315
27322
  initialPageRankScale;
27316
27323
  constructor(opts) {
27317
27324
  this.db = openDatabase(opts.path);
27325
+ this.db.exec("PRAGMA cache_size = -32768");
27318
27326
  this.db.exec(SCHEMA_SQL);
27319
27327
  const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
27320
27328
  if (!versionRow) {
@@ -27379,6 +27387,7 @@ var SqliteGraphStore = class {
27379
27387
  attrs_json = excluded.attrs_json
27380
27388
  `),
27381
27389
  getNode: this.db.prepare("SELECT * FROM nodes WHERE id = ?"),
27390
+ labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
27382
27391
  // Single-column fast paths for the nightly pass — writing 100k pageRank
27383
27392
  // values through the 18-column COALESCE updateNode took ~30s; these are ~10x
27384
27393
  // cheaper (one bound column, no COALESCE evaluation per row).
@@ -27878,11 +27887,13 @@ var SqliteGraphStore = class {
27878
27887
  }
27879
27888
  }
27880
27889
  edgeRuleViolation(edge) {
27881
- return localEdgeViolation(
27882
- this.getNode(edge.from)?.label,
27883
- edge.type,
27884
- this.getNode(edge.to)?.label
27885
- );
27890
+ return localEdgeViolation(this.labelOf(edge.from), edge.type, this.labelOf(edge.to));
27891
+ }
27892
+ /** An endpoint's label, any version, as `getNode(id)?.label` reads it. Every
27893
+ * edge write checks both endpoints, and hydrating them (attrs + vector) to
27894
+ * read one leading column was most of an incremental reindex's reads. */
27895
+ labelOf(id) {
27896
+ return this.stmts.labelOf.get(id)?.label;
27886
27897
  }
27887
27898
  updateEdge(id, patch) {
27888
27899
  this.stmts.updateEdge.run({
@@ -27926,7 +27937,21 @@ var SqliteGraphStore = class {
27926
27937
  * per project per request; the materializing form held the daemon's event
27927
27938
  * loop for seconds at scale. */
27928
27939
  countNodesByLabel(label) {
27929
- return Number(this.stmts.countByLabel.get(label).n);
27940
+ return this.countAt(`label:${label}`, [nodeScope(label)], () => Number(this.stmts.countByLabel.get(label).n));
27941
+ }
27942
+ /** Counts asked for on every status poll (/health, `errata status`, the
27943
+ * dashboards): a live-node COUNT(*) reads the whole liveness index, ~3 MB per
27944
+ * call on a 1.1 GB store, to recount a set that moves only on a write. Each is
27945
+ * kept with the change-feed reading of the rows it counts — triggers bump it on
27946
+ * every write from any connection, so a hit is exact, not a TTL guess. */
27947
+ counts = /* @__PURE__ */ new Map();
27948
+ countAt(key, scopes, count) {
27949
+ const seq = readChangeSeq(this.db, scopes);
27950
+ const hit = this.counts.get(key);
27951
+ if (hit && hit.seq === seq) return hit.n;
27952
+ const n = count();
27953
+ this.counts.set(key, { seq, n });
27954
+ return n;
27930
27955
  }
27931
27956
  /** Live rows for a label with attrs and creation stamps only — for scans that
27932
27957
  * read attrs and never the vector. `findNodesByLabel` decodes every embedding
@@ -28193,7 +28218,7 @@ var SqliteGraphStore = class {
28193
28218
  return out2;
28194
28219
  }
28195
28220
  nodeCount() {
28196
- return Number(this.stmts.nodeCount.get().n);
28221
+ return this.countAt("nodes", [ANY_CHANGE], () => Number(this.stmts.nodeCount.get().n));
28197
28222
  }
28198
28223
  mutationCount() {
28199
28224
  return this.mutations;
@@ -28237,7 +28262,7 @@ var SqliteGraphStore = class {
28237
28262
  return this.ingestSeq;
28238
28263
  }
28239
28264
  edgeCount() {
28240
- return Number(this.stmts.edgeCount.get().n);
28265
+ return this.countAt("edges", [ANY_CHANGE], () => Number(this.stmts.edgeCount.get().n));
28241
28266
  }
28242
28267
  // Lightweight whole-graph scans — select only the columns the caller needs so a
28243
28268
  // 100k-node pass skips the per-row attrs JSON.parse + embedding decode rowToNode
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.22-dev.4652",
3
+ "version": "2.0.22-dev.4660",
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
- ba1065832fc364902b70117e8568ae7f0d76cd3af48ed4d0929f03d9d3d55c7f .codex-plugin/plugin.json
1
+ 3bcb422706463f9d29ee3751dd1036046ce56211386ec8d38b1cbe53b26ac809 .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js