@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.
package/errata.mjs CHANGED
@@ -18137,20 +18137,20 @@ function openDatabase(path3, opts) {
18137
18137
  } catch {
18138
18138
  }
18139
18139
  }
18140
- const wrapStmt = (stmt) => ({
18140
+ const wrapStmt = (stmt2) => ({
18141
18141
  run: (...params) => {
18142
- const r = stmt.run(...coerceParams(params));
18142
+ const r = stmt2.run(...coerceParams(params));
18143
18143
  return {
18144
18144
  changes: Number(r.changes),
18145
18145
  lastInsertRowid: Number(r.lastInsertRowid)
18146
18146
  };
18147
18147
  },
18148
18148
  get: (...params) => {
18149
- const r = stmt.get(...coerceParams(params));
18149
+ const r = stmt2.get(...coerceParams(params));
18150
18150
  return r;
18151
18151
  },
18152
18152
  all: (...params) => {
18153
- const r = stmt.all(...coerceParams(params));
18153
+ const r = stmt2.all(...coerceParams(params));
18154
18154
  return r;
18155
18155
  }
18156
18156
  });
@@ -19134,6 +19134,175 @@ var init_text_index = __esm({
19134
19134
  }
19135
19135
  });
19136
19136
 
19137
+ // ../../packages/local-graph/src/lineage-integrity.ts
19138
+ function stmt(db, sql) {
19139
+ let byDb = statements.get(db);
19140
+ if (!byDb) statements.set(db, byDb = /* @__PURE__ */ new Map());
19141
+ let s = byDb.get(sql);
19142
+ if (!s) byDb.set(sql, s = db.prepare(sql));
19143
+ return s;
19144
+ }
19145
+ function countStrandedEdges(db) {
19146
+ 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();
19147
+ const byType = {};
19148
+ let total = 0;
19149
+ for (const r of rows) {
19150
+ byType[r.type] = Number(r.n);
19151
+ total += Number(r.n);
19152
+ }
19153
+ return { total, byType };
19154
+ }
19155
+ function countLiveHeadConflicts(db) {
19156
+ const row = db.prepare(
19157
+ `SELECT count(*) AS n FROM (
19158
+ SELECT COALESCE(logical_id, id) AS lid FROM nodes
19159
+ WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
19160
+ GROUP BY lid HAVING count(*) > 1)`
19161
+ ).get();
19162
+ return Number(row?.n ?? 0);
19163
+ }
19164
+ function closeIncidentEdges(db, id, t) {
19165
+ let closed = 0;
19166
+ for (const side of ["from_id", "to_id"]) {
19167
+ 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);
19168
+ closed += Number(r.changes);
19169
+ }
19170
+ return closed;
19171
+ }
19172
+ function reopenCascadeClosedEdges(db, id, closedAt) {
19173
+ let reopened = 0;
19174
+ for (const [side, other] of [["from_id", "to_id"], ["to_id", "from_id"]]) {
19175
+ const r = stmt(
19176
+ db,
19177
+ `UPDATE edges SET valid_to = NULL
19178
+ WHERE ${side} = ? AND valid_to = ? AND ${NOT_HISTORY}
19179
+ AND EXISTS (SELECT 1 FROM nodes o WHERE o.id = edges.${other} AND o.valid_to IS NULL)`
19180
+ ).run(id, closedAt);
19181
+ reopened += Number(r.changes);
19182
+ }
19183
+ return reopened;
19184
+ }
19185
+ function collapseLiveHeads(db, logicalIds, t) {
19186
+ const headsOf = db.prepare(
19187
+ `SELECT id, version, valid_from FROM (
19188
+ SELECT id, version, valid_from, attrs_json FROM nodes WHERE logical_id = ?1 AND valid_to IS NULL
19189
+ UNION
19190
+ SELECT id, version, valid_from, attrs_json FROM nodes WHERE id = ?1 AND logical_id IS NULL AND valid_to IS NULL
19191
+ ) WHERE ${NOT_HISTORICAL_ROW}`
19192
+ );
19193
+ const continuesInto = db.prepare(
19194
+ `SELECT to_id FROM edges WHERE from_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
19195
+ );
19196
+ const rekeyHead = db.prepare(`UPDATE nodes SET logical_id = ?1 WHERE id = ?1`);
19197
+ const rekeyCopies = db.prepare(
19198
+ `UPDATE nodes SET logical_id = ?1 WHERE logical_id = ?2 AND substr(id, 1, length(?1) + 2) = ?1 || '@v'`
19199
+ );
19200
+ const lineageMembers = db.prepare(
19201
+ `SELECT id FROM nodes WHERE logical_id = ?1 UNION SELECT id FROM nodes WHERE id = ?1`
19202
+ );
19203
+ const closeContinues = db.prepare(
19204
+ `UPDATE edges SET valid_to = MAX(valid_from, ?) WHERE from_id = ? AND to_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
19205
+ );
19206
+ let split = 0;
19207
+ const queue = [...new Set(logicalIds)];
19208
+ for (let i2 = 0; i2 < queue.length; i2++) {
19209
+ const lid = queue[i2];
19210
+ const heads = headsOf.all(lid);
19211
+ if (heads.length < 2) continue;
19212
+ const members = new Set(lineageMembers.all(lid).map((r) => r.id));
19213
+ const splitOff = (h) => {
19214
+ for (const { to_id } of continuesInto.all(h.id)) {
19215
+ if (members.has(to_id)) closeContinues.run(t, h.id, to_id);
19216
+ }
19217
+ rekeyHead.run(h.id);
19218
+ rekeyCopies.run(h.id, lid);
19219
+ queue.push(h.id);
19220
+ split++;
19221
+ };
19222
+ const kept = new Set(heads.map((h) => h.id));
19223
+ for (const h of heads) {
19224
+ if (h.id === lid) continue;
19225
+ const refuted = continuesInto.all(h.id).some(
19226
+ (e) => e.to_id !== h.id && kept.has(e.to_id)
19227
+ );
19228
+ if (refuted && kept.size > 1) {
19229
+ splitOff(h);
19230
+ kept.delete(h.id);
19231
+ }
19232
+ }
19233
+ let remaining = heads.filter((h) => kept.has(h.id));
19234
+ if (remaining.length < 2) continue;
19235
+ remaining = [...remaining].sort(
19236
+ (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)
19237
+ );
19238
+ for (const h of remaining.slice(1)) splitOff(h);
19239
+ }
19240
+ return split;
19241
+ }
19242
+ function repairLineageIntegrity(db, now) {
19243
+ const historicalRowsClosed = Number(
19244
+ db.prepare(
19245
+ `UPDATE nodes SET valid_to = MAX(valid_from, COALESCE(
19246
+ (SELECT s.valid_from FROM edges s WHERE s.id = 'edge_superseded_' || nodes.id), ?))
19247
+ WHERE valid_to IS NULL AND json_type(attrs_json, '$.historical') = 'true'`
19248
+ ).run(now).changes
19249
+ );
19250
+ const conflicted = db.prepare(
19251
+ `SELECT COALESCE(logical_id, id) AS lid FROM nodes
19252
+ WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
19253
+ GROUP BY lid HAVING count(*) > 1`
19254
+ ).all().map((r) => r.lid);
19255
+ const headsSplit = collapseLiveHeads(db, conflicted, now);
19256
+ const strandedEdgesClosed = Number(
19257
+ db.prepare(
19258
+ `UPDATE edges SET valid_to = MAX(valid_from, COALESCE(
19259
+ (SELECT min(v) FROM (
19260
+ SELECT f.valid_to AS v FROM nodes f WHERE f.id = edges.from_id AND NOT ${PRESENT("f")}
19261
+ UNION ALL
19262
+ SELECT t.valid_to FROM nodes t WHERE t.id = edges.to_id AND NOT ${PRESENT("t")})), ?))
19263
+ WHERE id IN (SELECT e.id FROM edges e WHERE ${STRANDED_EDGE_PREDICATE})`
19264
+ ).run(now).changes
19265
+ );
19266
+ return { historicalRowsClosed, headsSplit, strandedEdgesClosed };
19267
+ }
19268
+ function installLineageIntegrityRepair(db, now = Date.now()) {
19269
+ if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(LINEAGE_INTEGRITY_KEY)) return null;
19270
+ return db.transaction(() => {
19271
+ const report = repairLineageIntegrity(db, now);
19272
+ db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, ?)").run(
19273
+ LINEAGE_INTEGRITY_KEY,
19274
+ JSON.stringify({ at: now, ...report })
19275
+ );
19276
+ return report;
19277
+ });
19278
+ }
19279
+ var statements, HISTORY_EDGE_TYPES, LINEAGE_INTEGRITY_KEY, HISTORY_SQL, NOT_HISTORY, NOT_HISTORICAL_ROW, RETAINS_EDGES, PRESENT, STRANDED_EDGE_PREDICATE;
19280
+ var init_lineage_integrity = __esm({
19281
+ "../../packages/local-graph/src/lineage-integrity.ts"() {
19282
+ "use strict";
19283
+ statements = /* @__PURE__ */ new WeakMap();
19284
+ HISTORY_EDGE_TYPES = [
19285
+ "SUPERSEDED_BY",
19286
+ "CONTINUES",
19287
+ "FORKED_FROM",
19288
+ "REGRESSION_OF",
19289
+ "SPLIT_INTO",
19290
+ "REVEALED_BY"
19291
+ ];
19292
+ LINEAGE_INTEGRITY_KEY = "lineage-integrity-v1";
19293
+ HISTORY_SQL = HISTORY_EDGE_TYPES.map((t) => `'${t}'`).join(",");
19294
+ NOT_HISTORY = `type NOT IN (${HISTORY_SQL})`;
19295
+ NOT_HISTORICAL_ROW = `json_type(attrs_json, '$.historical') IS NOT 'true'`;
19296
+ RETAINS_EDGES = (n) => `(${n}.label = 'Problem'
19297
+ AND json_type(${n}.attrs_json, '$.resolvedAt') IS NOT NULL
19298
+ AND COALESCE(json_extract(${n}.attrs_json, '$.resolvedAs'), '') NOT IN ('duplicate', 'false_positive'))`;
19299
+ PRESENT = (n) => `(${n}.valid_to IS NULL OR ${RETAINS_EDGES(n)})`;
19300
+ STRANDED_EDGE_PREDICATE = `e.valid_to IS NULL AND e.${NOT_HISTORY}
19301
+ AND (NOT EXISTS (SELECT 1 FROM nodes f WHERE f.id = e.from_id AND ${PRESENT("f")})
19302
+ OR NOT EXISTS (SELECT 1 FROM nodes t WHERE t.id = e.to_id AND ${PRESENT("t")}))`;
19303
+ }
19304
+ });
19305
+
19137
19306
  // ../../packages/math/src/constants.ts
19138
19307
  var SURPRISE_MOMENTUM, SURPRISE_LEARNING_RATE, SURPRISE_LEARNING_RATE_SCALING, TIER_HALFLIFE_MULTIPLIER, HIT_RATE_SCALING_ALPHA, BASE_HALF_LIFE_SEQ, MOMENTUM_GATE_FLOOR, SURPRISE_RECENCY_LAMBDA_SEQ, NEMORI_SOFT_WRITE_MIN_COSINE, NEMORI_SOFT_WRITE_MAX_COSINE, NEMORI_DEDUP_COSINE, NEMORI_SOFT_ALPHA_MIN, NEMORI_SOFT_ALPHA_MAX, NEMORI_HIGH_SURPRISE_THRESHOLD, NEMORI_PREDICTION_FLOOR, REVIEW_SURPRISE_THRESHOLD, REVIEW_HITS_THRESHOLD;
19139
19308
  var init_constants = __esm({
@@ -20219,7 +20388,7 @@ function graphStoreNeedsPreparation(path3) {
20219
20388
  for (const match2 of (SCHEMA_SQL + TEMPORAL_INDEX_SQL).matchAll(/CREATE INDEX IF NOT EXISTS (\w+)/g)) {
20220
20389
  if (!indexes.has(match2[1])) return true;
20221
20390
  }
20222
- return [SESSION_REPAIR_KEY, HISTORY_CACHE_FLOOR_KEY, EDIT_PLACEHOLDER_FLOOR_KEY, CLOUD_ATTR_COMPACTION_KEY, HISTORY_ATTR_SLIM_KEY, TEXT_INDEX_KEY].some((key) => !db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
20391
+ return [SESSION_REPAIR_KEY, HISTORY_CACHE_FLOOR_KEY, EDIT_PLACEHOLDER_FLOOR_KEY, CLOUD_ATTR_COMPACTION_KEY, HISTORY_ATTR_SLIM_KEY, TEXT_INDEX_KEY, LINEAGE_INTEGRITY_KEY].some((key) => !db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
20223
20392
  } catch {
20224
20393
  return true;
20225
20394
  } finally {
@@ -20298,6 +20467,7 @@ var init_store = __esm({
20298
20467
  init_cloud_attr_compaction();
20299
20468
  init_history_attr_slim();
20300
20469
  init_text_index();
20470
+ init_lineage_integrity();
20301
20471
  init_src3();
20302
20472
  init_node_select();
20303
20473
  PAGE_RANK_SCALE_KEY = "pageRankScale.p95.v1";
@@ -20523,6 +20693,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
20523
20693
  this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, 'done')").run(SESSION_REPAIR_KEY);
20524
20694
  });
20525
20695
  }
20696
+ installLineageIntegrityRepair(this.db);
20526
20697
  this.stmts = {
20527
20698
  insertNode: this.db.prepare(`
20528
20699
  INSERT INTO nodes (
@@ -20733,6 +20904,12 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
20733
20904
  AND (t.id IS NULL OR t.valid_to IS NOT NULL)
20734
20905
  `).all();
20735
20906
  }
20907
+ strandedEdges() {
20908
+ return countStrandedEdges(this.db);
20909
+ }
20910
+ liveHeadConflicts() {
20911
+ return countLiveHeadConflicts(this.db);
20912
+ }
20736
20913
  fileIndexStates(ids) {
20737
20914
  if (ids.length === 0) return [];
20738
20915
  if (ids.length > 500) throw new RangeError("fileIndexStates accepts at most 500 ids per read");
@@ -20901,15 +21078,15 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
20901
21078
  findNodeFieldsByLabel(label, attrKeys) {
20902
21079
  for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeFieldsByLabel: bad attr key ${k}`);
20903
21080
  const cacheKey2 = attrKeys.join(",");
20904
- let stmt = this.fieldStmts.get(cacheKey2);
20905
- if (!stmt) {
21081
+ let stmt2 = this.fieldStmts.get(cacheKey2);
21082
+ if (!stmt2) {
20906
21083
  const cols = attrKeys.map((k, i2) => `, ${attrValueSql(`'$.${k}'`)} AS a${i2}, json_type(attrs_json, '$.${k}') AS t${i2}`).join("");
20907
- stmt = this.db.prepare(
21084
+ stmt2 = this.db.prepare(
20908
21085
  `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL`
20909
21086
  );
20910
- this.fieldStmts.set(cacheKey2, stmt);
21087
+ this.fieldStmts.set(cacheKey2, stmt2);
20911
21088
  }
20912
- return stmt.all(label).map((r) => {
21089
+ return stmt2.all(label).map((r) => {
20913
21090
  const attrs = {};
20914
21091
  attrKeys.forEach((k, i2) => {
20915
21092
  const v = decodeAttr(r[`a${i2}`], r[`t${i2}`]);
@@ -21029,14 +21206,14 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21029
21206
  }
21030
21207
  nodeLabels(ids) {
21031
21208
  const width = NODE_LABELS_CHUNK;
21032
- const stmt = this.typedEdgeStmt(
21209
+ const stmt2 = this.typedEdgeStmt(
21033
21210
  `SELECT id, label FROM nodes WHERE id IN (${Array.from({ length: width }, () => "?").join(",")})`
21034
21211
  );
21035
21212
  const labels = /* @__PURE__ */ new Map();
21036
21213
  for (let i2 = 0; i2 < ids.length; i2 += width) {
21037
21214
  const chunk = ids.slice(i2, i2 + width);
21038
21215
  while (chunk.length < width) chunk.push(null);
21039
- for (const row of stmt.all(...chunk)) labels.set(row.id, row.label);
21216
+ for (const row of stmt2.all(...chunk)) labels.set(row.id, row.label);
21040
21217
  }
21041
21218
  return labels;
21042
21219
  }
@@ -21235,12 +21412,12 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21235
21412
  return scale;
21236
21413
  }
21237
21414
  typedEdgeStmt(sql) {
21238
- let stmt = this.typedEdgeStmts.get(sql);
21239
- if (!stmt) {
21240
- stmt = this.db.prepare(sql);
21241
- this.typedEdgeStmts.set(sql, stmt);
21415
+ let stmt2 = this.typedEdgeStmts.get(sql);
21416
+ if (!stmt2) {
21417
+ stmt2 = this.db.prepare(sql);
21418
+ this.typedEdgeStmts.set(sql, stmt2);
21242
21419
  }
21243
- return stmt;
21420
+ return stmt2;
21244
21421
  }
21245
21422
  outEdges(nodeId, types) {
21246
21423
  if (!types || types.length === 0) {
@@ -21441,22 +21618,28 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21441
21618
  }
21442
21619
  }
21443
21620
  reviveReobserved(relPaths, workspaceId2, since) {
21444
- let revived = 0;
21621
+ const revived = [];
21445
21622
  const CHUNK2 = 400;
21446
21623
  for (let i2 = 0; i2 < relPaths.length; i2 += CHUNK2) {
21447
21624
  const slice = relPaths.slice(i2, i2 + CHUNK2);
21448
21625
  if (slice.length === 0) continue;
21449
- const r = this.db.prepare(
21450
- `UPDATE nodes SET valid_to = NULL
21626
+ const rows = this.db.prepare(
21627
+ `SELECT id, valid_to AS closedAt, COALESCE(logical_id, id) AS lid FROM nodes
21451
21628
  WHERE valid_to IS NOT NULL
21452
21629
  AND last_updated_at >= ?
21453
21630
  AND json_extract(attrs_json, '$.workspaceId') = ?
21454
- AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})`
21455
- ).run(since, workspaceId2, ...slice);
21456
- revived += Number(r.changes);
21457
- }
21458
- if (revived > 0) this.mutations++;
21459
- return revived;
21631
+ AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})
21632
+ AND json_type(attrs_json, '$.historical') IS NOT 'true'`
21633
+ ).all(since, workspaceId2, ...slice);
21634
+ revived.push(...rows);
21635
+ }
21636
+ if (revived.length === 0) return 0;
21637
+ const reopen = this.db.prepare("UPDATE nodes SET valid_to = NULL WHERE id = ? AND valid_to IS NOT NULL");
21638
+ for (const r of revived) reopen.run(r.id);
21639
+ for (const r of revived) reopenCascadeClosedEdges(this.db, r.id, Number(r.closedAt));
21640
+ collapseLiveHeads(this.db, revived.map((r) => r.lid), since);
21641
+ this.mutations++;
21642
+ return revived.length;
21460
21643
  }
21461
21644
  incomingEdgeCoverage(label, edgeTypes, cloudOnly) {
21462
21645
  if (edgeTypes.length === 0) return { count: 0, total: 0 };
@@ -21602,8 +21785,9 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21602
21785
  this.stmts.advanceLive.run({ live_id: liveId, t });
21603
21786
  return frozenId;
21604
21787
  }
21605
- closeNode(id, t) {
21606
- this.stmts.closeNode.run({ id, t });
21788
+ closeNode(id, t, opts = {}) {
21789
+ const r = this.stmts.closeNode.run({ id, t });
21790
+ if (Number(r.changes) > 0 && !opts.retainEdges) closeIncidentEdges(this.db, id, t);
21607
21791
  }
21608
21792
  closeEdge(id, t) {
21609
21793
  this.stmts.closeEdge.run({ id, t });
@@ -23579,7 +23763,7 @@ function reinforceSameAnchorProblem(store, relPath, workspaceId2, statement, sou
23579
23763
  }
23580
23764
  function foldSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind = "problem") {
23581
23765
  const report = { ...EMPTY_FOLD_REPORT };
23582
- const stmt = statement.trim();
23766
+ const stmt2 = statement.trim();
23583
23767
  if (!isAnchorableCodePath(relPath)) {
23584
23768
  report.noFile = true;
23585
23769
  return report;
@@ -23589,7 +23773,7 @@ function foldSameAnchorProblem(store, relPath, workspaceId2, statement, source,
23589
23773
  report.noFile = true;
23590
23774
  return report;
23591
23775
  }
23592
- const selfId = identityId({ kind: "DesignProblem", statement: stmt });
23776
+ const selfId = identityId({ kind: "DesignProblem", statement: stmt2 });
23593
23777
  let stmtVec = null;
23594
23778
  let best = null;
23595
23779
  let bestScout = null;
@@ -23603,11 +23787,11 @@ function foldSameAnchorProblem(store, relPath, workspaceId2, statement, source,
23603
23787
  continue;
23604
23788
  }
23605
23789
  report.candidates++;
23606
- const score2 = tokenJaccard(stmt, cand.description);
23790
+ const score2 = tokenJaccard(stmt2, cand.description);
23607
23791
  if (score2 > report.bestJaccard) report.bestJaccard = score2;
23608
23792
  if (score2 >= SAME_ANCHOR_DEDUP_JACCARD && (!best || score2 > best.score)) best = { node: cand, score: score2 };
23609
23793
  if (!best) {
23610
- stmtVec ??= embed(stmt);
23794
+ stmtVec ??= embed(stmt2);
23611
23795
  const hc = cosine(stmtVec, embed(cand.description));
23612
23796
  if (hc > report.bestHashCos) report.bestHashCos = hc;
23613
23797
  if ((hc >= SAME_ANCHOR_SCOUT_MIN_COSINE || score2 >= SAME_ANCHOR_NOMINATE_JACCARD) && (!bestScout || hc > bestScout.hashCos)) {
@@ -23654,11 +23838,11 @@ function foldSameAnchorProblem(store, relPath, workspaceId2, statement, source,
23654
23838
  return report;
23655
23839
  }
23656
23840
  function findResolvedAnchorMatch(store, relPath, workspaceId2, statement, kind = "problem") {
23657
- const stmt = statement.trim();
23841
+ const stmt2 = statement.trim();
23658
23842
  if (!isAnchorableCodePath(relPath)) return null;
23659
23843
  const file2 = resolveFileNode(store, relPath, workspaceId2);
23660
23844
  if (!file2) return null;
23661
- const selfId = identityId({ kind: "DesignProblem", statement: stmt });
23845
+ const selfId = identityId({ kind: "DesignProblem", statement: stmt2 });
23662
23846
  let best = null;
23663
23847
  for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
23664
23848
  const cand = store.getNode(e.from);
@@ -23668,7 +23852,7 @@ function findResolvedAnchorMatch(store, relPath, workspaceId2, statement, kind =
23668
23852
  if (cand.id === selfId) continue;
23669
23853
  const candKind = cand.attrs["kind"] === "constraint" ? "constraint" : "problem";
23670
23854
  if (candKind !== (kind === "constraint" ? "constraint" : "problem")) continue;
23671
- const score2 = tokenJaccard(stmt, cand.description);
23855
+ const score2 = tokenJaccard(stmt2, cand.description);
23672
23856
  if (score2 >= SAME_ANCHOR_DEDUP_JACCARD && (!best || score2 > best.score)) {
23673
23857
  best = { id: cand.id, score: score2 };
23674
23858
  }
@@ -26850,8 +27034,10 @@ __export(src_exports2, {
26850
27034
  FOLD_SCOUT_AUTO_COSINE: () => FOLD_SCOUT_AUTO_COSINE,
26851
27035
  FOLD_SCOUT_MIN_COSINE: () => FOLD_SCOUT_MIN_COSINE,
26852
27036
  FOLD_SCOUT_MIN_JACCARD: () => FOLD_SCOUT_MIN_JACCARD,
27037
+ HISTORY_EDGE_TYPES: () => HISTORY_EDGE_TYPES,
26853
27038
  JOINT_COMMUNITY_EDGES: () => JOINT_COMMUNITY_EDGES,
26854
27039
  JOINT_COMMUNITY_LABELS: () => JOINT_COMMUNITY_LABELS,
27040
+ LINEAGE_INTEGRITY_KEY: () => LINEAGE_INTEGRITY_KEY,
26855
27041
  MAX_ANCHOR_FILES: () => MAX_ANCHOR_FILES,
26856
27042
  MECHANISMS: () => MECHANISMS,
26857
27043
  MIN_INTENT_TOKENS: () => MIN_INTENT_TOKENS,
@@ -26893,11 +27079,15 @@ __export(src_exports2, {
26893
27079
  clearRevisit: () => clearRevisit,
26894
27080
  clearSettledFixCandidates: () => clearSettledFixCandidates,
26895
27081
  closableByCaller: () => closableByCaller,
27082
+ closeIncidentEdges: () => closeIncidentEdges,
27083
+ collapseLiveHeads: () => collapseLiveHeads,
26896
27084
  communityInductionRequests: () => communityInductionRequests,
26897
27085
  communitySeeds: () => communitySeeds,
26898
27086
  compareSemver: () => compareSemver,
26899
27087
  confirmFixHeld: () => confirmFixHeld,
26900
27088
  corroborateClaim: () => corroborateClaim,
27089
+ countLiveHeadConflicts: () => countLiveHeadConflicts,
27090
+ countStrandedEdges: () => countStrandedEdges,
26901
27091
  creditAssignment: () => creditAssignment,
26902
27092
  crystallize: () => crystallize,
26903
27093
  deriveContextFromAnchors: () => deriveContextFromAnchors,
@@ -26928,6 +27118,7 @@ __export(src_exports2, {
26928
27118
  induceTriage: () => induceTriage,
26929
27119
  ingestDesignProblem: () => ingestDesignProblem,
26930
27120
  ingestWitnessedVulnerability: () => ingestWitnessedVulnerability,
27121
+ installLineageIntegrityRepair: () => installLineageIntegrityRepair,
26931
27122
  isConstraintProblem: () => isConstraintProblem,
26932
27123
  isFragmentFixNote: () => isFragmentFixNote,
26933
27124
  isPlaceholderStatement: () => isPlaceholderStatement,
@@ -26983,7 +27174,9 @@ __export(src_exports2, {
26983
27174
  reinforceSameAnchorProblem: () => reinforceSameAnchorProblem,
26984
27175
  relateClaims: () => relateClaims,
26985
27176
  reopenAutoClosedProblems: () => reopenAutoClosedProblems,
27177
+ reopenCascadeClosedEdges: () => reopenCascadeClosedEdges,
26986
27178
  repairLegacyAnchorsFromStatement: () => repairLegacyAnchorsFromStatement,
27179
+ repairLineageIntegrity: () => repairLineageIntegrity,
26987
27180
  resolveDesignProblemById: () => resolveDesignProblemById,
26988
27181
  resolveDesignProblemByStatement: () => resolveDesignProblemByStatement,
26989
27182
  resolveEnclosingSymbol: () => resolveEnclosingSymbol,
@@ -27033,6 +27226,7 @@ var init_src5 = __esm({
27033
27226
  init_tools();
27034
27227
  init_principle_sync();
27035
27228
  init_mechanism_liveness();
27229
+ init_lineage_integrity();
27036
27230
  init_wm_calibration();
27037
27231
  }
27038
27232
  });
@@ -33296,7 +33490,7 @@ function detectResolution(opts, ev) {
33296
33490
  function closeProblem(store, problemId, t) {
33297
33491
  const n = store.getNode(problemId);
33298
33492
  if (!n || (n.validTo ?? null) !== null) return false;
33299
- store.closeNode(problemId, t);
33493
+ store.closeNode(problemId, t, { retainEdges: true });
33300
33494
  store.updateNode(problemId, { attrs: { ...n.attrs, resolvedAt: t } });
33301
33495
  return true;
33302
33496
  }
@@ -37988,9 +38182,11 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
37988
38182
  signatureAfter: a.signature
37989
38183
  });
37990
38184
  }
38185
+ const repointable = /* @__PURE__ */ new Map();
37991
38186
  store.transaction(() => {
37992
38187
  for (const nid of changedIds) store.freezeNode(nid, t);
37993
38188
  for (const nid of removedIds) {
38189
+ repointable.set(nid, store.inEdges(nid, ["CALLS", "ACCESSES", "ANCHORED_AT"]));
37994
38190
  for (const e of store.outEdges(nid)) store.closeEdge(e.id, t);
37995
38191
  store.closeNode(nid, t);
37996
38192
  }
@@ -38079,8 +38275,8 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
38079
38275
  version: b.version + 1
38080
38276
  });
38081
38277
  store.mergeEdge(continuesEdge(m, t));
38082
- for (const e of store.inEdges(m.fromNid, ["CALLS", "ACCESSES", "ANCHORED_AT"])) {
38083
- if (e.validTo != null) continue;
38278
+ for (const e of repointable.get(m.fromNid) ?? []) {
38279
+ if (e.from === m.fromNid || !store.liveNodeIds([e.from]).has(e.from)) continue;
38084
38280
  const newId = edgeId(e.from, e.type, m.toNid);
38085
38281
  if (!store.getEdge(newId)) {
38086
38282
  store.mergeEdge({ ...e, id: newId, to: m.toNid, lastSeenAt: t });
@@ -44172,10 +44368,10 @@ function collectDocstrings(root, records, out2) {
44172
44368
  }
44173
44369
  }
44174
44370
  }
44175
- function docstringIn(stmt) {
44176
- if (!stmt) return null;
44177
- if (stmt.type !== "expression_statement") return null;
44178
- const expr = stmt.namedChild(0);
44371
+ function docstringIn(stmt2) {
44372
+ if (!stmt2) return null;
44373
+ if (stmt2.type !== "expression_statement") return null;
44374
+ const expr = stmt2.namedChild(0);
44179
44375
  if (!expr) return null;
44180
44376
  if (expr.type !== "string") return null;
44181
44377
  const text = expr.text;
@@ -47773,7 +47969,7 @@ var init_mcp = __esm({
47773
47969
  },
47774
47970
  {
47775
47971
  name: "errata.health",
47776
- description: "Graph-quality + backlog snapshot \u2014 the dashboard's health view, for an agent. Returns counts of: stranded symbols (a live edge points at a non-live node \u2014 invisible to name resolution), open Problems, open Problems with NO proposed fix, retracted false-positives + dissolutions (the agentic-misread rate \u2014 PLAN_RETRACTION \xA7D), ungrounded-but-'derived' Claims (belief-layer noise), and facts needing revisit. Use to triage the workspace's state or sanity-check the graph before trusting a transitive answer.",
47972
+ description: "Graph-quality + backlog snapshot \u2014 the dashboard's health view, for an agent. Returns counts of: stranded symbols (a live File DEFINES/CONTAINS edge points at a non-live node \u2014 invisible to name resolution), stranded edges (ANY live non-history edge with a closed endpoint, by type), lineages with more than one live head, open Problems, open Problems with NO proposed fix, retracted false-positives + dissolutions (the agentic-misread rate \u2014 PLAN_RETRACTION \xA7D), ungrounded-but-'derived' Claims (belief-layer noise), and facts needing revisit. Use to triage the workspace's state or sanity-check the graph before trusting a transitive answer.",
47777
47973
  inputSchema: { type: "object", properties: {} },
47778
47974
  handler: (_args, store) => {
47779
47975
  const unresolved2 = store.selectNodes({
@@ -47786,10 +47982,16 @@ var init_mcp = __esm({
47786
47982
  const noFix = open2.filter((p) => store.outEdges(p.id, ["SOLVED_BY"]).length === 0);
47787
47983
  const retracted = (resolvedAs) => store.countNodes({ labels: ["Problem"], includeClosed: true, where: [{ attr: "resolvedAs", eq: resolvedAs }] });
47788
47984
  const ungroundedDerived = store.selectNodes({ labels: ["Claim"], attrs: ["groundedSupport"], where: [{ attr: "crystallized", eq: "derived" }] }).filter((c) => Number(c.attrs["groundedSupport"] ?? 0) === 0);
47985
+ const stranded = store.strandedEdges();
47789
47986
  return {
47790
47987
  nodes: store.nodeCount(),
47791
47988
  edges: store.edgeCount(),
47792
47989
  strandedSymbols: findStrandedSymbols(store).length,
47990
+ // strandedSymbols only sees File-rooted lineage; it read 0 while ~105k
47991
+ // live edges hung off closed nodes (2026-10-12). This counts all of them.
47992
+ strandedEdges: stranded.total,
47993
+ strandedEdgesByType: stranded.byType,
47994
+ liveHeadConflicts: store.liveHeadConflicts(),
47793
47995
  openProblems: open2.length,
47794
47996
  openProblemsWithoutFix: noFix.length,
47795
47997
  designConstraints: constraints.length,
@@ -48443,10 +48645,15 @@ function formatHotspotsMd(r) {
48443
48645
  }
48444
48646
  return lines.join("\n") + "\n";
48445
48647
  }
48648
+ function strandedTypes(byType) {
48649
+ const top = Object.entries(byType ?? {}).sort((a, b) => b[1] - a[1]).slice(0, 3);
48650
+ return top.length ? ` (${top.map(([t, n]) => `${t} ${n}`).join(", ")})` : "";
48651
+ }
48446
48652
  function formatHealthMd(r) {
48447
48653
  return `${head("health")}
48448
48654
  nodes ${r.nodes} \xB7 edges ${r.edges}
48449
48655
  stranded symbols: ${r.strandedSymbols}
48656
+ stranded edges: ${r.strandedEdges ?? 0}${strandedTypes(r.strandedEdgesByType)} \xB7 lineages with >1 live head: ${r.liveHeadConflicts ?? 0}
48450
48657
  open problems: ${r.openProblems} (${r.openProblemsWithoutFix} without a proposed fix)
48451
48658
  retracted: ${r.retractedFalsePositive} false-positive \xB7 ${r.retractedDissolution} dissolution
48452
48659
  ungrounded "derived" claims: ${r.ungroundedDerivedClaims}
@@ -81472,15 +81679,15 @@ function alreadyDone(configDir) {
81472
81679
  }
81473
81680
  }
81474
81681
  function replay(root) {
81475
- const statements = /* @__PURE__ */ new Set();
81682
+ const statements2 = /* @__PURE__ */ new Set();
81476
81683
  const citedByFix = /* @__PURE__ */ new Set();
81477
81684
  const dir = claudeProjectDir(root, homedir5());
81478
- if (!existsSync20(dir)) return { statements, citedByFix };
81685
+ if (!existsSync20(dir)) return { statements: statements2, citedByFix };
81479
81686
  let names;
81480
81687
  try {
81481
81688
  names = readdirSync8(dir).filter((n) => n.endsWith(".jsonl"));
81482
81689
  } catch {
81483
- return { statements, citedByFix };
81690
+ return { statements: statements2, citedByFix };
81484
81691
  }
81485
81692
  for (const f of names) {
81486
81693
  let lines;
@@ -81501,7 +81708,7 @@ function replay(root) {
81501
81708
  for (const b of rec.message.content) {
81502
81709
  if (b?.type !== "text" || typeof b.text !== "string") continue;
81503
81710
  for (const tag of parseInlineTags(b.text)) {
81504
- if (tag.kind === "constraint" && tag.statement) statements.add(tag.statement);
81711
+ if (tag.kind === "constraint" && tag.statement) statements2.add(tag.statement);
81505
81712
  if (tag.kind === "fix") {
81506
81713
  const t = tag;
81507
81714
  if (t.handle) citedByFix.add(t.handle);
@@ -81511,11 +81718,11 @@ function replay(root) {
81511
81718
  }
81512
81719
  }
81513
81720
  }
81514
- return { statements, citedByFix };
81721
+ return { statements: statements2, citedByFix };
81515
81722
  }
81516
81723
  function backfillConstraintKind(store, opts) {
81517
81724
  if (!opts.force && !opts.dryRun && alreadyDone(opts.configDir)) return EMPTY;
81518
- const { statements, citedByFix } = replay(opts.root);
81725
+ const { statements: statements2, citedByFix } = replay(opts.root);
81519
81726
  const report = {
81520
81727
  skipped: false,
81521
81728
  stamped: 0,
@@ -81529,7 +81736,7 @@ function backfillConstraintKind(store, opts) {
81529
81736
  const missing = [];
81530
81737
  const seen = /* @__PURE__ */ new Set();
81531
81738
  const work = [];
81532
- for (const statement of statements) {
81739
+ for (const statement of statements2) {
81533
81740
  const id = designProblemId(statement);
81534
81741
  if (seen.has(id)) continue;
81535
81742
  seen.add(id);
@@ -85836,7 +86043,7 @@ function createWatchBreaker(deps) {
85836
86043
  }
85837
86044
 
85838
86045
  // src/engine.ts
85839
- var DAEMON_VERSION = true ? "2.0.22-dev.4173" : "2.0.0-alpha.0";
86046
+ var DAEMON_VERSION = true ? "2.0.22-dev.4180" : "2.0.0-alpha.0";
85840
86047
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
85841
86048
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
85842
86049
  var GIT_OP_MUTE_MS = 4e3;