@inerrata-corporation/errata 2.0.22-dev.4173 → 2.0.22-dev.4186

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.
@@ -14788,20 +14788,20 @@ function openDatabase(path2, opts) {
14788
14788
  } catch {
14789
14789
  }
14790
14790
  }
14791
- const wrapStmt = (stmt) => ({
14791
+ const wrapStmt = (stmt2) => ({
14792
14792
  run: (...params) => {
14793
- const r = stmt.run(...coerceParams(params));
14793
+ const r = stmt2.run(...coerceParams(params));
14794
14794
  return {
14795
14795
  changes: Number(r.changes),
14796
14796
  lastInsertRowid: Number(r.lastInsertRowid)
14797
14797
  };
14798
14798
  },
14799
14799
  get: (...params) => {
14800
- const r = stmt.get(...coerceParams(params));
14800
+ const r = stmt2.get(...coerceParams(params));
14801
14801
  return r;
14802
14802
  },
14803
14803
  all: (...params) => {
14804
- const r = stmt.all(...coerceParams(params));
14804
+ const r = stmt2.all(...coerceParams(params));
14805
14805
  return r;
14806
14806
  }
14807
14807
  });
@@ -15661,6 +15661,169 @@ function textIndexReady(db) {
15661
15661
  return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
15662
15662
  }
15663
15663
 
15664
+ // ../../packages/local-graph/src/lineage-integrity.ts
15665
+ var statements = /* @__PURE__ */ new WeakMap();
15666
+ function stmt(db, sql) {
15667
+ let byDb = statements.get(db);
15668
+ if (!byDb) statements.set(db, byDb = /* @__PURE__ */ new Map());
15669
+ let s = byDb.get(sql);
15670
+ if (!s) byDb.set(sql, s = db.prepare(sql));
15671
+ return s;
15672
+ }
15673
+ var HISTORY_EDGE_TYPES = [
15674
+ "SUPERSEDED_BY",
15675
+ "CONTINUES",
15676
+ "FORKED_FROM",
15677
+ "REGRESSION_OF",
15678
+ "SPLIT_INTO",
15679
+ "REVEALED_BY"
15680
+ ];
15681
+ var LINEAGE_INTEGRITY_KEY = "lineage-integrity-v1";
15682
+ var HISTORY_SQL = HISTORY_EDGE_TYPES.map((t) => `'${t}'`).join(",");
15683
+ var NOT_HISTORY = `type NOT IN (${HISTORY_SQL})`;
15684
+ var NOT_HISTORICAL_ROW = `json_type(attrs_json, '$.historical') IS NOT 'true'`;
15685
+ var RETAINS_EDGES = (n) => `(${n}.label = 'Problem'
15686
+ AND json_type(${n}.attrs_json, '$.resolvedAt') IS NOT NULL
15687
+ AND COALESCE(json_extract(${n}.attrs_json, '$.resolvedAs'), '') NOT IN ('duplicate', 'false_positive'))`;
15688
+ var PRESENT = (n) => `(${n}.valid_to IS NULL OR ${RETAINS_EDGES(n)})`;
15689
+ var STRANDED_EDGE_PREDICATE = `e.valid_to IS NULL AND e.${NOT_HISTORY}
15690
+ AND (NOT EXISTS (SELECT 1 FROM nodes f WHERE f.id = e.from_id AND ${PRESENT("f")})
15691
+ OR NOT EXISTS (SELECT 1 FROM nodes t WHERE t.id = e.to_id AND ${PRESENT("t")}))`;
15692
+ function countStrandedEdges(db) {
15693
+ const rows = db.prepare(`SELECT e.type AS type, count(*) AS n FROM edges e WHERE ${STRANDED_EDGE_PREDICATE} GROUP BY e.type ORDER BY n DESC`).all();
15694
+ const byType = {};
15695
+ let total = 0;
15696
+ for (const r of rows) {
15697
+ byType[r.type] = Number(r.n);
15698
+ total += Number(r.n);
15699
+ }
15700
+ return { total, byType };
15701
+ }
15702
+ function countLiveHeadConflicts(db) {
15703
+ const row = db.prepare(
15704
+ `SELECT count(*) AS n FROM (
15705
+ SELECT COALESCE(logical_id, id) AS lid FROM nodes
15706
+ WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
15707
+ GROUP BY lid HAVING count(*) > 1)`
15708
+ ).get();
15709
+ return Number(row?.n ?? 0);
15710
+ }
15711
+ function closeIncidentEdges(db, id, t) {
15712
+ let closed = 0;
15713
+ for (const side of ["from_id", "to_id"]) {
15714
+ const r = stmt(db, `UPDATE edges SET valid_to = MAX(valid_from, ?) WHERE ${side} = ? AND valid_to IS NULL AND ${NOT_HISTORY}`).run(t, id);
15715
+ closed += Number(r.changes);
15716
+ }
15717
+ return closed;
15718
+ }
15719
+ function reopenCascadeClosedEdges(db, id, closedAt) {
15720
+ let reopened = 0;
15721
+ for (const [side, other] of [["from_id", "to_id"], ["to_id", "from_id"]]) {
15722
+ const r = stmt(
15723
+ db,
15724
+ `UPDATE edges SET valid_to = NULL
15725
+ WHERE ${side} = ? AND valid_to = ? AND ${NOT_HISTORY}
15726
+ AND EXISTS (SELECT 1 FROM nodes o WHERE o.id = edges.${other} AND o.valid_to IS NULL)`
15727
+ ).run(id, closedAt);
15728
+ reopened += Number(r.changes);
15729
+ }
15730
+ return reopened;
15731
+ }
15732
+ function collapseLiveHeads(db, logicalIds, t) {
15733
+ const headsOf = db.prepare(
15734
+ `SELECT id, version, valid_from FROM (
15735
+ SELECT id, version, valid_from, attrs_json FROM nodes WHERE logical_id = ?1 AND valid_to IS NULL
15736
+ UNION
15737
+ SELECT id, version, valid_from, attrs_json FROM nodes WHERE id = ?1 AND logical_id IS NULL AND valid_to IS NULL
15738
+ ) WHERE ${NOT_HISTORICAL_ROW}`
15739
+ );
15740
+ const continuesInto = db.prepare(
15741
+ `SELECT to_id FROM edges WHERE from_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
15742
+ );
15743
+ const rekeyHead = db.prepare(`UPDATE nodes SET logical_id = ?1 WHERE id = ?1`);
15744
+ const rekeyCopies = db.prepare(
15745
+ `UPDATE nodes SET logical_id = ?1 WHERE logical_id = ?2 AND substr(id, 1, length(?1) + 2) = ?1 || '@v'`
15746
+ );
15747
+ const lineageMembers = db.prepare(
15748
+ `SELECT id FROM nodes WHERE logical_id = ?1 UNION SELECT id FROM nodes WHERE id = ?1`
15749
+ );
15750
+ const closeContinues = db.prepare(
15751
+ `UPDATE edges SET valid_to = MAX(valid_from, ?) WHERE from_id = ? AND to_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
15752
+ );
15753
+ let split = 0;
15754
+ const queue = [...new Set(logicalIds)];
15755
+ for (let i = 0; i < queue.length; i++) {
15756
+ const lid = queue[i];
15757
+ const heads = headsOf.all(lid);
15758
+ if (heads.length < 2) continue;
15759
+ const members = new Set(lineageMembers.all(lid).map((r) => r.id));
15760
+ const splitOff = (h) => {
15761
+ for (const { to_id } of continuesInto.all(h.id)) {
15762
+ if (members.has(to_id)) closeContinues.run(t, h.id, to_id);
15763
+ }
15764
+ rekeyHead.run(h.id);
15765
+ rekeyCopies.run(h.id, lid);
15766
+ queue.push(h.id);
15767
+ split++;
15768
+ };
15769
+ const kept = new Set(heads.map((h) => h.id));
15770
+ for (const h of heads) {
15771
+ if (h.id === lid) continue;
15772
+ const refuted = continuesInto.all(h.id).some(
15773
+ (e) => e.to_id !== h.id && kept.has(e.to_id)
15774
+ );
15775
+ if (refuted && kept.size > 1) {
15776
+ splitOff(h);
15777
+ kept.delete(h.id);
15778
+ }
15779
+ }
15780
+ let remaining = heads.filter((h) => kept.has(h.id));
15781
+ if (remaining.length < 2) continue;
15782
+ remaining = [...remaining].sort(
15783
+ (a, b) => Number(b.id === lid) - Number(a.id === lid) || Number(b.version) - Number(a.version) || Number(b.valid_from) - Number(a.valid_from) || (a.id < b.id ? -1 : 1)
15784
+ );
15785
+ for (const h of remaining.slice(1)) splitOff(h);
15786
+ }
15787
+ return split;
15788
+ }
15789
+ function repairLineageIntegrity(db, now) {
15790
+ const historicalRowsClosed = Number(
15791
+ db.prepare(
15792
+ `UPDATE nodes SET valid_to = MAX(valid_from, COALESCE(
15793
+ (SELECT s.valid_from FROM edges s WHERE s.id = 'edge_superseded_' || nodes.id), ?))
15794
+ WHERE valid_to IS NULL AND json_type(attrs_json, '$.historical') = 'true'`
15795
+ ).run(now).changes
15796
+ );
15797
+ const conflicted = db.prepare(
15798
+ `SELECT COALESCE(logical_id, id) AS lid FROM nodes
15799
+ WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
15800
+ GROUP BY lid HAVING count(*) > 1`
15801
+ ).all().map((r) => r.lid);
15802
+ const headsSplit = collapseLiveHeads(db, conflicted, now);
15803
+ const strandedEdgesClosed = Number(
15804
+ db.prepare(
15805
+ `UPDATE edges SET valid_to = MAX(valid_from, COALESCE(
15806
+ (SELECT min(v) FROM (
15807
+ SELECT f.valid_to AS v FROM nodes f WHERE f.id = edges.from_id AND NOT ${PRESENT("f")}
15808
+ UNION ALL
15809
+ SELECT t.valid_to FROM nodes t WHERE t.id = edges.to_id AND NOT ${PRESENT("t")})), ?))
15810
+ WHERE id IN (SELECT e.id FROM edges e WHERE ${STRANDED_EDGE_PREDICATE})`
15811
+ ).run(now).changes
15812
+ );
15813
+ return { historicalRowsClosed, headsSplit, strandedEdgesClosed };
15814
+ }
15815
+ function installLineageIntegrityRepair(db, now = Date.now()) {
15816
+ if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(LINEAGE_INTEGRITY_KEY)) return null;
15817
+ return db.transaction(() => {
15818
+ const report = repairLineageIntegrity(db, now);
15819
+ db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, ?)").run(
15820
+ LINEAGE_INTEGRITY_KEY,
15821
+ JSON.stringify({ at: now, ...report })
15822
+ );
15823
+ return report;
15824
+ });
15825
+ }
15826
+
15664
15827
  // ../../packages/math/src/relevance.ts
15665
15828
  function pageRankScale(ranks) {
15666
15829
  const positive = [...ranks].filter((r) => Number.isFinite(r) && r > 0).sort((a, b) => a - b);
@@ -16107,6 +16270,7 @@ var SqliteGraphStore = class {
16107
16270
  this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, 'done')").run(SESSION_REPAIR_KEY);
16108
16271
  });
16109
16272
  }
16273
+ installLineageIntegrityRepair(this.db);
16110
16274
  this.stmts = {
16111
16275
  insertNode: this.db.prepare(`
16112
16276
  INSERT INTO nodes (
@@ -16317,6 +16481,12 @@ var SqliteGraphStore = class {
16317
16481
  AND (t.id IS NULL OR t.valid_to IS NOT NULL)
16318
16482
  `).all();
16319
16483
  }
16484
+ strandedEdges() {
16485
+ return countStrandedEdges(this.db);
16486
+ }
16487
+ liveHeadConflicts() {
16488
+ return countLiveHeadConflicts(this.db);
16489
+ }
16320
16490
  fileIndexStates(ids) {
16321
16491
  if (ids.length === 0) return [];
16322
16492
  if (ids.length > 500) throw new RangeError("fileIndexStates accepts at most 500 ids per read");
@@ -16485,15 +16655,15 @@ var SqliteGraphStore = class {
16485
16655
  findNodeFieldsByLabel(label, attrKeys) {
16486
16656
  for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeFieldsByLabel: bad attr key ${k}`);
16487
16657
  const cacheKey = attrKeys.join(",");
16488
- let stmt = this.fieldStmts.get(cacheKey);
16489
- if (!stmt) {
16658
+ let stmt2 = this.fieldStmts.get(cacheKey);
16659
+ if (!stmt2) {
16490
16660
  const cols = attrKeys.map((k, i) => `, ${attrValueSql(`'$.${k}'`)} AS a${i}, json_type(attrs_json, '$.${k}') AS t${i}`).join("");
16491
- stmt = this.db.prepare(
16661
+ stmt2 = this.db.prepare(
16492
16662
  `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL`
16493
16663
  );
16494
- this.fieldStmts.set(cacheKey, stmt);
16664
+ this.fieldStmts.set(cacheKey, stmt2);
16495
16665
  }
16496
- return stmt.all(label).map((r) => {
16666
+ return stmt2.all(label).map((r) => {
16497
16667
  const attrs = {};
16498
16668
  attrKeys.forEach((k, i) => {
16499
16669
  const v = decodeAttr(r[`a${i}`], r[`t${i}`]);
@@ -16613,14 +16783,14 @@ var SqliteGraphStore = class {
16613
16783
  }
16614
16784
  nodeLabels(ids) {
16615
16785
  const width = NODE_LABELS_CHUNK;
16616
- const stmt = this.typedEdgeStmt(
16786
+ const stmt2 = this.typedEdgeStmt(
16617
16787
  `SELECT id, label FROM nodes WHERE id IN (${Array.from({ length: width }, () => "?").join(",")})`
16618
16788
  );
16619
16789
  const labels = /* @__PURE__ */ new Map();
16620
16790
  for (let i = 0; i < ids.length; i += width) {
16621
16791
  const chunk = ids.slice(i, i + width);
16622
16792
  while (chunk.length < width) chunk.push(null);
16623
- for (const row of stmt.all(...chunk)) labels.set(row.id, row.label);
16793
+ for (const row of stmt2.all(...chunk)) labels.set(row.id, row.label);
16624
16794
  }
16625
16795
  return labels;
16626
16796
  }
@@ -16819,12 +16989,12 @@ var SqliteGraphStore = class {
16819
16989
  return scale;
16820
16990
  }
16821
16991
  typedEdgeStmt(sql) {
16822
- let stmt = this.typedEdgeStmts.get(sql);
16823
- if (!stmt) {
16824
- stmt = this.db.prepare(sql);
16825
- this.typedEdgeStmts.set(sql, stmt);
16992
+ let stmt2 = this.typedEdgeStmts.get(sql);
16993
+ if (!stmt2) {
16994
+ stmt2 = this.db.prepare(sql);
16995
+ this.typedEdgeStmts.set(sql, stmt2);
16826
16996
  }
16827
- return stmt;
16997
+ return stmt2;
16828
16998
  }
16829
16999
  outEdges(nodeId, types) {
16830
17000
  if (!types || types.length === 0) {
@@ -17025,22 +17195,28 @@ var SqliteGraphStore = class {
17025
17195
  }
17026
17196
  }
17027
17197
  reviveReobserved(relPaths, workspaceId, since) {
17028
- let revived = 0;
17198
+ const revived = [];
17029
17199
  const CHUNK = 400;
17030
17200
  for (let i = 0; i < relPaths.length; i += CHUNK) {
17031
17201
  const slice = relPaths.slice(i, i + CHUNK);
17032
17202
  if (slice.length === 0) continue;
17033
- const r = this.db.prepare(
17034
- `UPDATE nodes SET valid_to = NULL
17203
+ const rows = this.db.prepare(
17204
+ `SELECT id, valid_to AS closedAt, COALESCE(logical_id, id) AS lid FROM nodes
17035
17205
  WHERE valid_to IS NOT NULL
17036
17206
  AND last_updated_at >= ?
17037
17207
  AND json_extract(attrs_json, '$.workspaceId') = ?
17038
- AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})`
17039
- ).run(since, workspaceId, ...slice);
17040
- revived += Number(r.changes);
17041
- }
17042
- if (revived > 0) this.mutations++;
17043
- return revived;
17208
+ AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})
17209
+ AND json_type(attrs_json, '$.historical') IS NOT 'true'`
17210
+ ).all(since, workspaceId, ...slice);
17211
+ revived.push(...rows);
17212
+ }
17213
+ if (revived.length === 0) return 0;
17214
+ const reopen = this.db.prepare("UPDATE nodes SET valid_to = NULL WHERE id = ? AND valid_to IS NOT NULL");
17215
+ for (const r of revived) reopen.run(r.id);
17216
+ for (const r of revived) reopenCascadeClosedEdges(this.db, r.id, Number(r.closedAt));
17217
+ collapseLiveHeads(this.db, revived.map((r) => r.lid), since);
17218
+ this.mutations++;
17219
+ return revived.length;
17044
17220
  }
17045
17221
  incomingEdgeCoverage(label, edgeTypes, cloudOnly) {
17046
17222
  if (edgeTypes.length === 0) return { count: 0, total: 0 };
@@ -17186,8 +17362,9 @@ var SqliteGraphStore = class {
17186
17362
  this.stmts.advanceLive.run({ live_id: liveId, t });
17187
17363
  return frozenId;
17188
17364
  }
17189
- closeNode(id, t) {
17190
- this.stmts.closeNode.run({ id, t });
17365
+ closeNode(id, t, opts = {}) {
17366
+ const r = this.stmts.closeNode.run({ id, t });
17367
+ if (Number(r.changes) > 0 && !opts.retainEdges) closeIncidentEdges(this.db, id, t);
17191
17368
  }
17192
17369
  closeEdge(id, t) {
17193
17370
  this.stmts.closeEdge.run({ id, t });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.22-dev.4173",
3
+ "version": "2.0.22-dev.4186",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {