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

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.
@@ -16010,20 +16010,20 @@ function openDatabase(path2, opts) {
16010
16010
  } catch {
16011
16011
  }
16012
16012
  }
16013
- const wrapStmt = (stmt) => ({
16013
+ const wrapStmt = (stmt2) => ({
16014
16014
  run: (...params) => {
16015
- const r = stmt.run(...coerceParams(params));
16015
+ const r = stmt2.run(...coerceParams(params));
16016
16016
  return {
16017
16017
  changes: Number(r.changes),
16018
16018
  lastInsertRowid: Number(r.lastInsertRowid)
16019
16019
  };
16020
16020
  },
16021
16021
  get: (...params) => {
16022
- const r = stmt.get(...coerceParams(params));
16022
+ const r = stmt2.get(...coerceParams(params));
16023
16023
  return r;
16024
16024
  },
16025
16025
  all: (...params) => {
16026
- const r = stmt.all(...coerceParams(params));
16026
+ const r = stmt2.all(...coerceParams(params));
16027
16027
  return r;
16028
16028
  }
16029
16029
  });
@@ -17083,6 +17083,169 @@ function textIndexReady(db) {
17083
17083
  return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
17084
17084
  }
17085
17085
 
17086
+ // ../../packages/local-graph/src/lineage-integrity.ts
17087
+ var statements = /* @__PURE__ */ new WeakMap();
17088
+ function stmt(db, sql) {
17089
+ let byDb = statements.get(db);
17090
+ if (!byDb) statements.set(db, byDb = /* @__PURE__ */ new Map());
17091
+ let s = byDb.get(sql);
17092
+ if (!s) byDb.set(sql, s = db.prepare(sql));
17093
+ return s;
17094
+ }
17095
+ var HISTORY_EDGE_TYPES = [
17096
+ "SUPERSEDED_BY",
17097
+ "CONTINUES",
17098
+ "FORKED_FROM",
17099
+ "REGRESSION_OF",
17100
+ "SPLIT_INTO",
17101
+ "REVEALED_BY"
17102
+ ];
17103
+ var LINEAGE_INTEGRITY_KEY = "lineage-integrity-v1";
17104
+ var HISTORY_SQL = HISTORY_EDGE_TYPES.map((t) => `'${t}'`).join(",");
17105
+ var NOT_HISTORY = `type NOT IN (${HISTORY_SQL})`;
17106
+ var NOT_HISTORICAL_ROW = `json_type(attrs_json, '$.historical') IS NOT 'true'`;
17107
+ var RETAINS_EDGES = (n) => `(${n}.label = 'Problem'
17108
+ AND json_type(${n}.attrs_json, '$.resolvedAt') IS NOT NULL
17109
+ AND COALESCE(json_extract(${n}.attrs_json, '$.resolvedAs'), '') NOT IN ('duplicate', 'false_positive'))`;
17110
+ var PRESENT = (n) => `(${n}.valid_to IS NULL OR ${RETAINS_EDGES(n)})`;
17111
+ var STRANDED_EDGE_PREDICATE = `e.valid_to IS NULL AND e.${NOT_HISTORY}
17112
+ AND (NOT EXISTS (SELECT 1 FROM nodes f WHERE f.id = e.from_id AND ${PRESENT("f")})
17113
+ OR NOT EXISTS (SELECT 1 FROM nodes t WHERE t.id = e.to_id AND ${PRESENT("t")}))`;
17114
+ function countStrandedEdges(db) {
17115
+ 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();
17116
+ const byType = {};
17117
+ let total = 0;
17118
+ for (const r of rows) {
17119
+ byType[r.type] = Number(r.n);
17120
+ total += Number(r.n);
17121
+ }
17122
+ return { total, byType };
17123
+ }
17124
+ function countLiveHeadConflicts(db) {
17125
+ const row = db.prepare(
17126
+ `SELECT count(*) AS n FROM (
17127
+ SELECT COALESCE(logical_id, id) AS lid FROM nodes
17128
+ WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
17129
+ GROUP BY lid HAVING count(*) > 1)`
17130
+ ).get();
17131
+ return Number(row?.n ?? 0);
17132
+ }
17133
+ function closeIncidentEdges(db, id, t) {
17134
+ let closed = 0;
17135
+ for (const side of ["from_id", "to_id"]) {
17136
+ 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);
17137
+ closed += Number(r.changes);
17138
+ }
17139
+ return closed;
17140
+ }
17141
+ function reopenCascadeClosedEdges(db, id, closedAt) {
17142
+ let reopened = 0;
17143
+ for (const [side, other] of [["from_id", "to_id"], ["to_id", "from_id"]]) {
17144
+ const r = stmt(
17145
+ db,
17146
+ `UPDATE edges SET valid_to = NULL
17147
+ WHERE ${side} = ? AND valid_to = ? AND ${NOT_HISTORY}
17148
+ AND EXISTS (SELECT 1 FROM nodes o WHERE o.id = edges.${other} AND o.valid_to IS NULL)`
17149
+ ).run(id, closedAt);
17150
+ reopened += Number(r.changes);
17151
+ }
17152
+ return reopened;
17153
+ }
17154
+ function collapseLiveHeads(db, logicalIds, t) {
17155
+ const headsOf = db.prepare(
17156
+ `SELECT id, version, valid_from FROM (
17157
+ SELECT id, version, valid_from, attrs_json FROM nodes WHERE logical_id = ?1 AND valid_to IS NULL
17158
+ UNION
17159
+ SELECT id, version, valid_from, attrs_json FROM nodes WHERE id = ?1 AND logical_id IS NULL AND valid_to IS NULL
17160
+ ) WHERE ${NOT_HISTORICAL_ROW}`
17161
+ );
17162
+ const continuesInto = db.prepare(
17163
+ `SELECT to_id FROM edges WHERE from_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
17164
+ );
17165
+ const rekeyHead = db.prepare(`UPDATE nodes SET logical_id = ?1 WHERE id = ?1`);
17166
+ const rekeyCopies = db.prepare(
17167
+ `UPDATE nodes SET logical_id = ?1 WHERE logical_id = ?2 AND substr(id, 1, length(?1) + 2) = ?1 || '@v'`
17168
+ );
17169
+ const lineageMembers = db.prepare(
17170
+ `SELECT id FROM nodes WHERE logical_id = ?1 UNION SELECT id FROM nodes WHERE id = ?1`
17171
+ );
17172
+ const closeContinues = db.prepare(
17173
+ `UPDATE edges SET valid_to = MAX(valid_from, ?) WHERE from_id = ? AND to_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
17174
+ );
17175
+ let split = 0;
17176
+ const queue = [...new Set(logicalIds)];
17177
+ for (let i = 0; i < queue.length; i++) {
17178
+ const lid = queue[i];
17179
+ const heads = headsOf.all(lid);
17180
+ if (heads.length < 2) continue;
17181
+ const members = new Set(lineageMembers.all(lid).map((r) => r.id));
17182
+ const splitOff = (h) => {
17183
+ for (const { to_id } of continuesInto.all(h.id)) {
17184
+ if (members.has(to_id)) closeContinues.run(t, h.id, to_id);
17185
+ }
17186
+ rekeyHead.run(h.id);
17187
+ rekeyCopies.run(h.id, lid);
17188
+ queue.push(h.id);
17189
+ split++;
17190
+ };
17191
+ const kept = new Set(heads.map((h) => h.id));
17192
+ for (const h of heads) {
17193
+ if (h.id === lid) continue;
17194
+ const refuted = continuesInto.all(h.id).some(
17195
+ (e) => e.to_id !== h.id && kept.has(e.to_id)
17196
+ );
17197
+ if (refuted && kept.size > 1) {
17198
+ splitOff(h);
17199
+ kept.delete(h.id);
17200
+ }
17201
+ }
17202
+ let remaining = heads.filter((h) => kept.has(h.id));
17203
+ if (remaining.length < 2) continue;
17204
+ remaining = [...remaining].sort(
17205
+ (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)
17206
+ );
17207
+ for (const h of remaining.slice(1)) splitOff(h);
17208
+ }
17209
+ return split;
17210
+ }
17211
+ function repairLineageIntegrity(db, now) {
17212
+ const historicalRowsClosed = Number(
17213
+ db.prepare(
17214
+ `UPDATE nodes SET valid_to = MAX(valid_from, COALESCE(
17215
+ (SELECT s.valid_from FROM edges s WHERE s.id = 'edge_superseded_' || nodes.id), ?))
17216
+ WHERE valid_to IS NULL AND json_type(attrs_json, '$.historical') = 'true'`
17217
+ ).run(now).changes
17218
+ );
17219
+ const conflicted = db.prepare(
17220
+ `SELECT COALESCE(logical_id, id) AS lid FROM nodes
17221
+ WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
17222
+ GROUP BY lid HAVING count(*) > 1`
17223
+ ).all().map((r) => r.lid);
17224
+ const headsSplit = collapseLiveHeads(db, conflicted, now);
17225
+ const strandedEdgesClosed = Number(
17226
+ db.prepare(
17227
+ `UPDATE edges SET valid_to = MAX(valid_from, COALESCE(
17228
+ (SELECT min(v) FROM (
17229
+ SELECT f.valid_to AS v FROM nodes f WHERE f.id = edges.from_id AND NOT ${PRESENT("f")}
17230
+ UNION ALL
17231
+ SELECT t.valid_to FROM nodes t WHERE t.id = edges.to_id AND NOT ${PRESENT("t")})), ?))
17232
+ WHERE id IN (SELECT e.id FROM edges e WHERE ${STRANDED_EDGE_PREDICATE})`
17233
+ ).run(now).changes
17234
+ );
17235
+ return { historicalRowsClosed, headsSplit, strandedEdgesClosed };
17236
+ }
17237
+ function installLineageIntegrityRepair(db, now = Date.now()) {
17238
+ if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(LINEAGE_INTEGRITY_KEY)) return null;
17239
+ return db.transaction(() => {
17240
+ const report = repairLineageIntegrity(db, now);
17241
+ db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, ?)").run(
17242
+ LINEAGE_INTEGRITY_KEY,
17243
+ JSON.stringify({ at: now, ...report })
17244
+ );
17245
+ return report;
17246
+ });
17247
+ }
17248
+
17086
17249
  // ../../packages/math/src/constants.ts
17087
17250
  var NEMORI_DEDUP_COSINE = 0.88;
17088
17251
 
@@ -17706,6 +17869,7 @@ var SqliteGraphStore = class {
17706
17869
  this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, 'done')").run(SESSION_REPAIR_KEY);
17707
17870
  });
17708
17871
  }
17872
+ installLineageIntegrityRepair(this.db);
17709
17873
  this.stmts = {
17710
17874
  insertNode: this.db.prepare(`
17711
17875
  INSERT INTO nodes (
@@ -17916,6 +18080,12 @@ var SqliteGraphStore = class {
17916
18080
  AND (t.id IS NULL OR t.valid_to IS NOT NULL)
17917
18081
  `).all();
17918
18082
  }
18083
+ strandedEdges() {
18084
+ return countStrandedEdges(this.db);
18085
+ }
18086
+ liveHeadConflicts() {
18087
+ return countLiveHeadConflicts(this.db);
18088
+ }
17919
18089
  fileIndexStates(ids) {
17920
18090
  if (ids.length === 0) return [];
17921
18091
  if (ids.length > 500) throw new RangeError("fileIndexStates accepts at most 500 ids per read");
@@ -18084,15 +18254,15 @@ var SqliteGraphStore = class {
18084
18254
  findNodeFieldsByLabel(label, attrKeys) {
18085
18255
  for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeFieldsByLabel: bad attr key ${k}`);
18086
18256
  const cacheKey = attrKeys.join(",");
18087
- let stmt = this.fieldStmts.get(cacheKey);
18088
- if (!stmt) {
18257
+ let stmt2 = this.fieldStmts.get(cacheKey);
18258
+ if (!stmt2) {
18089
18259
  const cols = attrKeys.map((k, i) => `, ${attrValueSql(`'$.${k}'`)} AS a${i}, json_type(attrs_json, '$.${k}') AS t${i}`).join("");
18090
- stmt = this.db.prepare(
18260
+ stmt2 = this.db.prepare(
18091
18261
  `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL`
18092
18262
  );
18093
- this.fieldStmts.set(cacheKey, stmt);
18263
+ this.fieldStmts.set(cacheKey, stmt2);
18094
18264
  }
18095
- return stmt.all(label).map((r) => {
18265
+ return stmt2.all(label).map((r) => {
18096
18266
  const attrs = {};
18097
18267
  attrKeys.forEach((k, i) => {
18098
18268
  const v = decodeAttr(r[`a${i}`], r[`t${i}`]);
@@ -18212,14 +18382,14 @@ var SqliteGraphStore = class {
18212
18382
  }
18213
18383
  nodeLabels(ids) {
18214
18384
  const width = NODE_LABELS_CHUNK;
18215
- const stmt = this.typedEdgeStmt(
18385
+ const stmt2 = this.typedEdgeStmt(
18216
18386
  `SELECT id, label FROM nodes WHERE id IN (${Array.from({ length: width }, () => "?").join(",")})`
18217
18387
  );
18218
18388
  const labels = /* @__PURE__ */ new Map();
18219
18389
  for (let i = 0; i < ids.length; i += width) {
18220
18390
  const chunk = ids.slice(i, i + width);
18221
18391
  while (chunk.length < width) chunk.push(null);
18222
- for (const row of stmt.all(...chunk)) labels.set(row.id, row.label);
18392
+ for (const row of stmt2.all(...chunk)) labels.set(row.id, row.label);
18223
18393
  }
18224
18394
  return labels;
18225
18395
  }
@@ -18418,12 +18588,12 @@ var SqliteGraphStore = class {
18418
18588
  return scale;
18419
18589
  }
18420
18590
  typedEdgeStmt(sql) {
18421
- let stmt = this.typedEdgeStmts.get(sql);
18422
- if (!stmt) {
18423
- stmt = this.db.prepare(sql);
18424
- this.typedEdgeStmts.set(sql, stmt);
18591
+ let stmt2 = this.typedEdgeStmts.get(sql);
18592
+ if (!stmt2) {
18593
+ stmt2 = this.db.prepare(sql);
18594
+ this.typedEdgeStmts.set(sql, stmt2);
18425
18595
  }
18426
- return stmt;
18596
+ return stmt2;
18427
18597
  }
18428
18598
  outEdges(nodeId, types) {
18429
18599
  if (!types || types.length === 0) {
@@ -18624,22 +18794,28 @@ var SqliteGraphStore = class {
18624
18794
  }
18625
18795
  }
18626
18796
  reviveReobserved(relPaths, workspaceId, since) {
18627
- let revived = 0;
18797
+ const revived = [];
18628
18798
  const CHUNK = 400;
18629
18799
  for (let i = 0; i < relPaths.length; i += CHUNK) {
18630
18800
  const slice = relPaths.slice(i, i + CHUNK);
18631
18801
  if (slice.length === 0) continue;
18632
- const r = this.db.prepare(
18633
- `UPDATE nodes SET valid_to = NULL
18802
+ const rows = this.db.prepare(
18803
+ `SELECT id, valid_to AS closedAt, COALESCE(logical_id, id) AS lid FROM nodes
18634
18804
  WHERE valid_to IS NOT NULL
18635
18805
  AND last_updated_at >= ?
18636
18806
  AND json_extract(attrs_json, '$.workspaceId') = ?
18637
- AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})`
18638
- ).run(since, workspaceId, ...slice);
18639
- revived += Number(r.changes);
18807
+ AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})
18808
+ AND json_type(attrs_json, '$.historical') IS NOT 'true'`
18809
+ ).all(since, workspaceId, ...slice);
18810
+ revived.push(...rows);
18640
18811
  }
18641
- if (revived > 0) this.mutations++;
18642
- return revived;
18812
+ if (revived.length === 0) return 0;
18813
+ const reopen = this.db.prepare("UPDATE nodes SET valid_to = NULL WHERE id = ? AND valid_to IS NOT NULL");
18814
+ for (const r of revived) reopen.run(r.id);
18815
+ for (const r of revived) reopenCascadeClosedEdges(this.db, r.id, Number(r.closedAt));
18816
+ collapseLiveHeads(this.db, revived.map((r) => r.lid), since);
18817
+ this.mutations++;
18818
+ return revived.length;
18643
18819
  }
18644
18820
  incomingEdgeCoverage(label, edgeTypes, cloudOnly) {
18645
18821
  if (edgeTypes.length === 0) return { count: 0, total: 0 };
@@ -18785,8 +18961,9 @@ var SqliteGraphStore = class {
18785
18961
  this.stmts.advanceLive.run({ live_id: liveId, t });
18786
18962
  return frozenId;
18787
18963
  }
18788
- closeNode(id, t) {
18789
- this.stmts.closeNode.run({ id, t });
18964
+ closeNode(id, t, opts = {}) {
18965
+ const r = this.stmts.closeNode.run({ id, t });
18966
+ if (Number(r.changes) > 0 && !opts.retainEdges) closeIncidentEdges(this.db, id, t);
18790
18967
  }
18791
18968
  closeEdge(id, t) {
18792
18969
  this.stmts.closeEdge.run({ id, t });