@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.
package/pass-worker.mjs CHANGED
@@ -16402,20 +16402,20 @@ function openDatabase(path2, opts) {
16402
16402
  } catch {
16403
16403
  }
16404
16404
  }
16405
- const wrapStmt = (stmt) => ({
16405
+ const wrapStmt = (stmt2) => ({
16406
16406
  run: (...params) => {
16407
- const r = stmt.run(...coerceParams(params));
16407
+ const r = stmt2.run(...coerceParams(params));
16408
16408
  return {
16409
16409
  changes: Number(r.changes),
16410
16410
  lastInsertRowid: Number(r.lastInsertRowid)
16411
16411
  };
16412
16412
  },
16413
16413
  get: (...params) => {
16414
- const r = stmt.get(...coerceParams(params));
16414
+ const r = stmt2.get(...coerceParams(params));
16415
16415
  return r;
16416
16416
  },
16417
16417
  all: (...params) => {
16418
- const r = stmt.all(...coerceParams(params));
16418
+ const r = stmt2.all(...coerceParams(params));
16419
16419
  return r;
16420
16420
  }
16421
16421
  });
@@ -17609,9 +17609,11 @@ async function incrementalReindex(store2, rootPath, workspaceId, changedAbsPaths
17609
17609
  signatureAfter: a.signature
17610
17610
  });
17611
17611
  }
17612
+ const repointable = /* @__PURE__ */ new Map();
17612
17613
  store2.transaction(() => {
17613
17614
  for (const nid of changedIds) store2.freezeNode(nid, t);
17614
17615
  for (const nid of removedIds) {
17616
+ repointable.set(nid, store2.inEdges(nid, ["CALLS", "ACCESSES", "ANCHORED_AT"]));
17615
17617
  for (const e of store2.outEdges(nid)) store2.closeEdge(e.id, t);
17616
17618
  store2.closeNode(nid, t);
17617
17619
  }
@@ -17700,8 +17702,8 @@ async function incrementalReindex(store2, rootPath, workspaceId, changedAbsPaths
17700
17702
  version: b.version + 1
17701
17703
  });
17702
17704
  store2.mergeEdge(continuesEdge(m, t));
17703
- for (const e of store2.inEdges(m.fromNid, ["CALLS", "ACCESSES", "ANCHORED_AT"])) {
17704
- if (e.validTo != null) continue;
17705
+ for (const e of repointable.get(m.fromNid) ?? []) {
17706
+ if (e.from === m.fromNid || !store2.liveNodeIds([e.from]).has(e.from)) continue;
17705
17707
  const newId = edgeId(e.from, e.type, m.toNid);
17706
17708
  if (!store2.getEdge(newId)) {
17707
17709
  store2.mergeEdge({ ...e, id: newId, to: m.toNid, lastSeenAt: t });
@@ -23793,10 +23795,10 @@ function collectDocstrings(root, records, out2) {
23793
23795
  }
23794
23796
  }
23795
23797
  }
23796
- function docstringIn(stmt) {
23797
- if (!stmt) return null;
23798
- if (stmt.type !== "expression_statement") return null;
23799
- const expr = stmt.namedChild(0);
23798
+ function docstringIn(stmt2) {
23799
+ if (!stmt2) return null;
23800
+ if (stmt2.type !== "expression_statement") return null;
23801
+ const expr = stmt2.namedChild(0);
23800
23802
  if (!expr) return null;
23801
23803
  if (expr.type !== "string") return null;
23802
23804
  const text = expr.text;
@@ -26091,6 +26093,169 @@ function textIndexReady(db) {
26091
26093
  return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
26092
26094
  }
26093
26095
 
26096
+ // ../../packages/local-graph/src/lineage-integrity.ts
26097
+ var statements = /* @__PURE__ */ new WeakMap();
26098
+ function stmt(db, sql) {
26099
+ let byDb = statements.get(db);
26100
+ if (!byDb) statements.set(db, byDb = /* @__PURE__ */ new Map());
26101
+ let s = byDb.get(sql);
26102
+ if (!s) byDb.set(sql, s = db.prepare(sql));
26103
+ return s;
26104
+ }
26105
+ var HISTORY_EDGE_TYPES = [
26106
+ "SUPERSEDED_BY",
26107
+ "CONTINUES",
26108
+ "FORKED_FROM",
26109
+ "REGRESSION_OF",
26110
+ "SPLIT_INTO",
26111
+ "REVEALED_BY"
26112
+ ];
26113
+ var LINEAGE_INTEGRITY_KEY = "lineage-integrity-v1";
26114
+ var HISTORY_SQL = HISTORY_EDGE_TYPES.map((t) => `'${t}'`).join(",");
26115
+ var NOT_HISTORY = `type NOT IN (${HISTORY_SQL})`;
26116
+ var NOT_HISTORICAL_ROW = `json_type(attrs_json, '$.historical') IS NOT 'true'`;
26117
+ var RETAINS_EDGES = (n) => `(${n}.label = 'Problem'
26118
+ AND json_type(${n}.attrs_json, '$.resolvedAt') IS NOT NULL
26119
+ AND COALESCE(json_extract(${n}.attrs_json, '$.resolvedAs'), '') NOT IN ('duplicate', 'false_positive'))`;
26120
+ var PRESENT = (n) => `(${n}.valid_to IS NULL OR ${RETAINS_EDGES(n)})`;
26121
+ var STRANDED_EDGE_PREDICATE = `e.valid_to IS NULL AND e.${NOT_HISTORY}
26122
+ AND (NOT EXISTS (SELECT 1 FROM nodes f WHERE f.id = e.from_id AND ${PRESENT("f")})
26123
+ OR NOT EXISTS (SELECT 1 FROM nodes t WHERE t.id = e.to_id AND ${PRESENT("t")}))`;
26124
+ function countStrandedEdges(db) {
26125
+ 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();
26126
+ const byType = {};
26127
+ let total = 0;
26128
+ for (const r of rows) {
26129
+ byType[r.type] = Number(r.n);
26130
+ total += Number(r.n);
26131
+ }
26132
+ return { total, byType };
26133
+ }
26134
+ function countLiveHeadConflicts(db) {
26135
+ const row = db.prepare(
26136
+ `SELECT count(*) AS n FROM (
26137
+ SELECT COALESCE(logical_id, id) AS lid FROM nodes
26138
+ WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
26139
+ GROUP BY lid HAVING count(*) > 1)`
26140
+ ).get();
26141
+ return Number(row?.n ?? 0);
26142
+ }
26143
+ function closeIncidentEdges(db, id, t) {
26144
+ let closed = 0;
26145
+ for (const side of ["from_id", "to_id"]) {
26146
+ 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);
26147
+ closed += Number(r.changes);
26148
+ }
26149
+ return closed;
26150
+ }
26151
+ function reopenCascadeClosedEdges(db, id, closedAt) {
26152
+ let reopened = 0;
26153
+ for (const [side, other] of [["from_id", "to_id"], ["to_id", "from_id"]]) {
26154
+ const r = stmt(
26155
+ db,
26156
+ `UPDATE edges SET valid_to = NULL
26157
+ WHERE ${side} = ? AND valid_to = ? AND ${NOT_HISTORY}
26158
+ AND EXISTS (SELECT 1 FROM nodes o WHERE o.id = edges.${other} AND o.valid_to IS NULL)`
26159
+ ).run(id, closedAt);
26160
+ reopened += Number(r.changes);
26161
+ }
26162
+ return reopened;
26163
+ }
26164
+ function collapseLiveHeads(db, logicalIds, t) {
26165
+ const headsOf = db.prepare(
26166
+ `SELECT id, version, valid_from FROM (
26167
+ SELECT id, version, valid_from, attrs_json FROM nodes WHERE logical_id = ?1 AND valid_to IS NULL
26168
+ UNION
26169
+ SELECT id, version, valid_from, attrs_json FROM nodes WHERE id = ?1 AND logical_id IS NULL AND valid_to IS NULL
26170
+ ) WHERE ${NOT_HISTORICAL_ROW}`
26171
+ );
26172
+ const continuesInto = db.prepare(
26173
+ `SELECT to_id FROM edges WHERE from_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
26174
+ );
26175
+ const rekeyHead = db.prepare(`UPDATE nodes SET logical_id = ?1 WHERE id = ?1`);
26176
+ const rekeyCopies = db.prepare(
26177
+ `UPDATE nodes SET logical_id = ?1 WHERE logical_id = ?2 AND substr(id, 1, length(?1) + 2) = ?1 || '@v'`
26178
+ );
26179
+ const lineageMembers = db.prepare(
26180
+ `SELECT id FROM nodes WHERE logical_id = ?1 UNION SELECT id FROM nodes WHERE id = ?1`
26181
+ );
26182
+ const closeContinues = db.prepare(
26183
+ `UPDATE edges SET valid_to = MAX(valid_from, ?) WHERE from_id = ? AND to_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
26184
+ );
26185
+ let split = 0;
26186
+ const queue = [...new Set(logicalIds)];
26187
+ for (let i2 = 0; i2 < queue.length; i2++) {
26188
+ const lid = queue[i2];
26189
+ const heads = headsOf.all(lid);
26190
+ if (heads.length < 2) continue;
26191
+ const members = new Set(lineageMembers.all(lid).map((r) => r.id));
26192
+ const splitOff = (h) => {
26193
+ for (const { to_id } of continuesInto.all(h.id)) {
26194
+ if (members.has(to_id)) closeContinues.run(t, h.id, to_id);
26195
+ }
26196
+ rekeyHead.run(h.id);
26197
+ rekeyCopies.run(h.id, lid);
26198
+ queue.push(h.id);
26199
+ split++;
26200
+ };
26201
+ const kept = new Set(heads.map((h) => h.id));
26202
+ for (const h of heads) {
26203
+ if (h.id === lid) continue;
26204
+ const refuted = continuesInto.all(h.id).some(
26205
+ (e) => e.to_id !== h.id && kept.has(e.to_id)
26206
+ );
26207
+ if (refuted && kept.size > 1) {
26208
+ splitOff(h);
26209
+ kept.delete(h.id);
26210
+ }
26211
+ }
26212
+ let remaining = heads.filter((h) => kept.has(h.id));
26213
+ if (remaining.length < 2) continue;
26214
+ remaining = [...remaining].sort(
26215
+ (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)
26216
+ );
26217
+ for (const h of remaining.slice(1)) splitOff(h);
26218
+ }
26219
+ return split;
26220
+ }
26221
+ function repairLineageIntegrity(db, now) {
26222
+ const historicalRowsClosed = Number(
26223
+ db.prepare(
26224
+ `UPDATE nodes SET valid_to = MAX(valid_from, COALESCE(
26225
+ (SELECT s.valid_from FROM edges s WHERE s.id = 'edge_superseded_' || nodes.id), ?))
26226
+ WHERE valid_to IS NULL AND json_type(attrs_json, '$.historical') = 'true'`
26227
+ ).run(now).changes
26228
+ );
26229
+ const conflicted = db.prepare(
26230
+ `SELECT COALESCE(logical_id, id) AS lid FROM nodes
26231
+ WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
26232
+ GROUP BY lid HAVING count(*) > 1`
26233
+ ).all().map((r) => r.lid);
26234
+ const headsSplit = collapseLiveHeads(db, conflicted, now);
26235
+ const strandedEdgesClosed = Number(
26236
+ db.prepare(
26237
+ `UPDATE edges SET valid_to = MAX(valid_from, COALESCE(
26238
+ (SELECT min(v) FROM (
26239
+ SELECT f.valid_to AS v FROM nodes f WHERE f.id = edges.from_id AND NOT ${PRESENT("f")}
26240
+ UNION ALL
26241
+ SELECT t.valid_to FROM nodes t WHERE t.id = edges.to_id AND NOT ${PRESENT("t")})), ?))
26242
+ WHERE id IN (SELECT e.id FROM edges e WHERE ${STRANDED_EDGE_PREDICATE})`
26243
+ ).run(now).changes
26244
+ );
26245
+ return { historicalRowsClosed, headsSplit, strandedEdgesClosed };
26246
+ }
26247
+ function installLineageIntegrityRepair(db, now = Date.now()) {
26248
+ if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(LINEAGE_INTEGRITY_KEY)) return null;
26249
+ return db.transaction(() => {
26250
+ const report = repairLineageIntegrity(db, now);
26251
+ db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, ?)").run(
26252
+ LINEAGE_INTEGRITY_KEY,
26253
+ JSON.stringify({ at: now, ...report })
26254
+ );
26255
+ return report;
26256
+ });
26257
+ }
26258
+
26094
26259
  // ../../packages/math/src/pagerank.ts
26095
26260
  function pagerank(input) {
26096
26261
  const damping = input.damping ?? 0.85;
@@ -26973,6 +27138,7 @@ var SqliteGraphStore = class {
26973
27138
  this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, 'done')").run(SESSION_REPAIR_KEY);
26974
27139
  });
26975
27140
  }
27141
+ installLineageIntegrityRepair(this.db);
26976
27142
  this.stmts = {
26977
27143
  insertNode: this.db.prepare(`
26978
27144
  INSERT INTO nodes (
@@ -27183,6 +27349,12 @@ var SqliteGraphStore = class {
27183
27349
  AND (t.id IS NULL OR t.valid_to IS NOT NULL)
27184
27350
  `).all();
27185
27351
  }
27352
+ strandedEdges() {
27353
+ return countStrandedEdges(this.db);
27354
+ }
27355
+ liveHeadConflicts() {
27356
+ return countLiveHeadConflicts(this.db);
27357
+ }
27186
27358
  fileIndexStates(ids) {
27187
27359
  if (ids.length === 0) return [];
27188
27360
  if (ids.length > 500) throw new RangeError("fileIndexStates accepts at most 500 ids per read");
@@ -27351,15 +27523,15 @@ var SqliteGraphStore = class {
27351
27523
  findNodeFieldsByLabel(label, attrKeys) {
27352
27524
  for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeFieldsByLabel: bad attr key ${k}`);
27353
27525
  const cacheKey = attrKeys.join(",");
27354
- let stmt = this.fieldStmts.get(cacheKey);
27355
- if (!stmt) {
27526
+ let stmt2 = this.fieldStmts.get(cacheKey);
27527
+ if (!stmt2) {
27356
27528
  const cols = attrKeys.map((k, i2) => `, ${attrValueSql(`'$.${k}'`)} AS a${i2}, json_type(attrs_json, '$.${k}') AS t${i2}`).join("");
27357
- stmt = this.db.prepare(
27529
+ stmt2 = this.db.prepare(
27358
27530
  `SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL`
27359
27531
  );
27360
- this.fieldStmts.set(cacheKey, stmt);
27532
+ this.fieldStmts.set(cacheKey, stmt2);
27361
27533
  }
27362
- return stmt.all(label).map((r) => {
27534
+ return stmt2.all(label).map((r) => {
27363
27535
  const attrs = {};
27364
27536
  attrKeys.forEach((k, i2) => {
27365
27537
  const v = decodeAttr(r[`a${i2}`], r[`t${i2}`]);
@@ -27479,14 +27651,14 @@ var SqliteGraphStore = class {
27479
27651
  }
27480
27652
  nodeLabels(ids) {
27481
27653
  const width = NODE_LABELS_CHUNK;
27482
- const stmt = this.typedEdgeStmt(
27654
+ const stmt2 = this.typedEdgeStmt(
27483
27655
  `SELECT id, label FROM nodes WHERE id IN (${Array.from({ length: width }, () => "?").join(",")})`
27484
27656
  );
27485
27657
  const labels = /* @__PURE__ */ new Map();
27486
27658
  for (let i2 = 0; i2 < ids.length; i2 += width) {
27487
27659
  const chunk = ids.slice(i2, i2 + width);
27488
27660
  while (chunk.length < width) chunk.push(null);
27489
- for (const row of stmt.all(...chunk)) labels.set(row.id, row.label);
27661
+ for (const row of stmt2.all(...chunk)) labels.set(row.id, row.label);
27490
27662
  }
27491
27663
  return labels;
27492
27664
  }
@@ -27685,12 +27857,12 @@ var SqliteGraphStore = class {
27685
27857
  return scale;
27686
27858
  }
27687
27859
  typedEdgeStmt(sql) {
27688
- let stmt = this.typedEdgeStmts.get(sql);
27689
- if (!stmt) {
27690
- stmt = this.db.prepare(sql);
27691
- this.typedEdgeStmts.set(sql, stmt);
27860
+ let stmt2 = this.typedEdgeStmts.get(sql);
27861
+ if (!stmt2) {
27862
+ stmt2 = this.db.prepare(sql);
27863
+ this.typedEdgeStmts.set(sql, stmt2);
27692
27864
  }
27693
- return stmt;
27865
+ return stmt2;
27694
27866
  }
27695
27867
  outEdges(nodeId, types) {
27696
27868
  if (!types || types.length === 0) {
@@ -27891,22 +28063,28 @@ var SqliteGraphStore = class {
27891
28063
  }
27892
28064
  }
27893
28065
  reviveReobserved(relPaths, workspaceId, since) {
27894
- let revived = 0;
28066
+ const revived = [];
27895
28067
  const CHUNK = 400;
27896
28068
  for (let i2 = 0; i2 < relPaths.length; i2 += CHUNK) {
27897
28069
  const slice = relPaths.slice(i2, i2 + CHUNK);
27898
28070
  if (slice.length === 0) continue;
27899
- const r = this.db.prepare(
27900
- `UPDATE nodes SET valid_to = NULL
28071
+ const rows = this.db.prepare(
28072
+ `SELECT id, valid_to AS closedAt, COALESCE(logical_id, id) AS lid FROM nodes
27901
28073
  WHERE valid_to IS NOT NULL
27902
28074
  AND last_updated_at >= ?
27903
28075
  AND json_extract(attrs_json, '$.workspaceId') = ?
27904
- AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})`
27905
- ).run(since, workspaceId, ...slice);
27906
- revived += Number(r.changes);
27907
- }
27908
- if (revived > 0) this.mutations++;
27909
- return revived;
28076
+ AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})
28077
+ AND json_type(attrs_json, '$.historical') IS NOT 'true'`
28078
+ ).all(since, workspaceId, ...slice);
28079
+ revived.push(...rows);
28080
+ }
28081
+ if (revived.length === 0) return 0;
28082
+ const reopen = this.db.prepare("UPDATE nodes SET valid_to = NULL WHERE id = ? AND valid_to IS NOT NULL");
28083
+ for (const r of revived) reopen.run(r.id);
28084
+ for (const r of revived) reopenCascadeClosedEdges(this.db, r.id, Number(r.closedAt));
28085
+ collapseLiveHeads(this.db, revived.map((r) => r.lid), since);
28086
+ this.mutations++;
28087
+ return revived.length;
27910
28088
  }
27911
28089
  incomingEdgeCoverage(label, edgeTypes, cloudOnly) {
27912
28090
  if (edgeTypes.length === 0) return { count: 0, total: 0 };
@@ -28052,8 +28230,9 @@ var SqliteGraphStore = class {
28052
28230
  this.stmts.advanceLive.run({ live_id: liveId, t });
28053
28231
  return frozenId;
28054
28232
  }
28055
- closeNode(id, t) {
28056
- this.stmts.closeNode.run({ id, t });
28233
+ closeNode(id, t, opts = {}) {
28234
+ const r = this.stmts.closeNode.run({ id, t });
28235
+ if (Number(r.changes) > 0 && !opts.retainEdges) closeIncidentEdges(this.db, id, t);
28057
28236
  }
28058
28237
  closeEdge(id, t) {
28059
28238
  this.stmts.closeEdge.run({ id, t });
@@ -29588,6 +29767,28 @@ var MECHANISMS = [
29588
29767
  // climbs while `accepted` stays flat is the shape of a silently-closed door.
29589
29768
  backlogCounter: "candidates"
29590
29769
  },
29770
+ {
29771
+ id: "edge-park-replay",
29772
+ what: "parks causal/anchor edges the door refused as unresolvable, replays them when due, drops them with a disposition",
29773
+ pass: "ship",
29774
+ // FED = a source whose whole binding-edge family LANDED: the digest is
29775
+ // stamped only when every edge in it came back created/reinforced, so it is
29776
+ // the store-side proof that edges (not just nodes) cross. Live dev
29777
+ // 2026-10-12 sat with the same 241 refusals re-sent every drain and this
29778
+ // stamp frozen — nodes crossed, their causal wiring did not.
29779
+ fed: {
29780
+ labels: ["Problem", "RootCause"],
29781
+ attr: "semanticEdgesContributedDigest",
29782
+ minFraction: 0.01
29783
+ },
29784
+ // A parked edge that later LANDED — the replay doing its job.
29785
+ effectCounter: "edgesReplayLanded",
29786
+ // Outstanding parked edges (a level). Flat-and-nonzero while replays keep
29787
+ // re-parking is an endpoint that never resolves; it drains to tombstones
29788
+ // (`edgesDroppedExhausted`) instead of being resent forever.
29789
+ backlogCounter: "edgeParkSize",
29790
+ note: "edge-park.ts; quiet (no-effect) is healthy when nothing is refused \u2014 read edgesParked/edgesDropped* on the ship row"
29791
+ },
29591
29792
  {
29592
29793
  id: "orphan-reap",
29593
29794
  what: "closes File nodes whose path the indexer no longer covers",
@@ -29642,12 +29843,31 @@ var MECHANISMS = [
29642
29843
  // the bar is "the field is written at all", the revisit-sweep precedent.
29643
29844
  fed: { labels: ["Problem"], attr: "foldCandidate", minFraction: 0 },
29644
29845
  effectCounter: "folded",
29645
- // Standing nominations ARE the judge-less backlog — without the gauge a
29646
- // deployment with no judge configured reads as "nothing to fold".
29647
- backlogCounter: "standing",
29846
+ // The open-nomination POOL is the backlog. It used to be `standing`, which
29847
+ // only counts what the pass examined — capped at the per-run limit — so it
29848
+ // read a flat 8 for weeks while 824 nominations sat open (2026-10-12): the
29849
+ // gauge could not see past the slice it was meant to be measuring.
29850
+ backlogCounter: "pending",
29648
29851
  configCounter: "judgeConfigured",
29649
29852
  note: "the ambiguous band needs the opt-in judge: set ERRATA_FOLD_JUDGE=1 + a configured chat provider (azure env), or leave it off and the nominations stand"
29650
29853
  },
29854
+ {
29855
+ id: "fold-candidate-coverage",
29856
+ what: "rotates the fold-judge pass through every open nomination (never-examined first, standing ones re-examined on new evidence)",
29857
+ // Judge-INDEPENDENT on purpose — no configCounter. The embedding-certain
29858
+ // bands fold/reject with no judge at all, and the defect this watches is
29859
+ // the pass re-reading the same oldest 8 forever (101 runs, 8 distinct
29860
+ // nodes, 816 of 824 nominations never looked at). That starves those
29861
+ // bands on a judge-less daemon exactly as much as on a judged one, so an
29862
+ // opt-in being off must not excuse it.
29863
+ pass: "fold-judge",
29864
+ fed: { labels: ["Problem"], attr: "foldCandidate", minFraction: 0 },
29865
+ effectCounter: "firstExamined",
29866
+ // A LEVEL: open nominations never examined. Flat-and-positive with no
29867
+ // first examinations across the stall window = the rotation is stuck.
29868
+ backlogCounter: "neverExamined",
29869
+ note: "neverExamined must drain while the pass runs; flat means the selection is re-reading the same slice"
29870
+ },
29651
29871
  {
29652
29872
  id: "cite-anchor-accrual",
29653
29873
  what: "accrues cite-context anchor evidence on anchorless cited priors; two distinct sessions promote a cite-confirmed ANCHORED_AT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.22-dev.4173",
3
+ "version": "2.0.22-dev.4186",
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
- 43b3c654071e6c4abeaa0d49cc279d846fec01d0d38972ac2df4a1617076f727 .codex-plugin/plugin.json
1
+ 0c3dd7adb669771f96dc8641fcdb302bb1d5441252d072f8b4cd9e3c947ab6c8 .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js