@inerrata-corporation/errata 2.0.2-dev.646 → 2.0.2-dev.682

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.
@@ -15579,6 +15579,7 @@ function openDatabase(path) {
15579
15579
  db.exec("PRAGMA journal_mode = WAL");
15580
15580
  db.exec("PRAGMA synchronous = NORMAL");
15581
15581
  db.exec("PRAGMA busy_timeout = 5000");
15582
+ db.exec("PRAGMA journal_size_limit = 67108864");
15582
15583
  } catch {
15583
15584
  }
15584
15585
  }
@@ -16562,6 +16563,96 @@ var SqliteGraphStore = class {
16562
16563
  const rows = this.db.prepare("SELECT from_id, to_id, type FROM edges WHERE valid_to IS NULL").all();
16563
16564
  return rows.map((r) => ({ from: r.from_id, to: r.to_id, type: r.type }));
16564
16565
  }
16566
+ getMeta(key) {
16567
+ const r = this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key);
16568
+ return r?.value ?? null;
16569
+ }
16570
+ setMeta(key, value) {
16571
+ this.db.prepare(
16572
+ "INSERT INTO store_meta (key, value) VALUES (:key, :value) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
16573
+ ).run({ key, value });
16574
+ }
16575
+ dirtyNodeIdsSince(ts) {
16576
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE valid_to IS NULL AND last_updated_at > ?").all(ts);
16577
+ return rows.map((r) => r.id);
16578
+ }
16579
+ /** Endpoints of edges TOUCHED since `ts` — created, re-seen, or CLOSED.
16580
+ * Closures matter as much as additions: a node that lost inflow is rank-dirty
16581
+ * while its own row never updated, so removal endpoints must seed the
16582
+ * incremental region or the stale inflow persists until the full backstop. */
16583
+ edgeEndpointsTouchedSince(ts) {
16584
+ const rows = this.db.prepare(
16585
+ `SELECT from_id, to_id FROM edges
16586
+ WHERE (valid_to IS NULL AND (created_at > :ts OR last_seen_at > :ts))
16587
+ OR (valid_to IS NOT NULL AND valid_to > :ts)`
16588
+ ).all({ ts });
16589
+ return rows.map((r) => ({ from: r.from_id, to: r.to_id }));
16590
+ }
16591
+ /** Lightweight out-edges for a SET of sources, chunked IN-lists — the region
16592
+ * assembly path for incremental PageRank (per-node outEdges() at region
16593
+ * scale re-creates the 106k-prepared-calls problem the batch scan solved). */
16594
+ outEdgesForMany(ids) {
16595
+ return this.edgesForMany(ids, "from_id");
16596
+ }
16597
+ inEdgesForMany(ids) {
16598
+ return this.edgesForMany(ids, "to_id");
16599
+ }
16600
+ /** Stored pageRank for a SET of ids (live rows only — a closed id is simply
16601
+ * absent, which is how incremental region assembly drops dead endpoints). */
16602
+ ranksForMany(ids) {
16603
+ const out = /* @__PURE__ */ new Map();
16604
+ const CHUNK = 400;
16605
+ for (let i = 0; i < ids.length; i += CHUNK) {
16606
+ const chunk = ids.slice(i, i + CHUNK);
16607
+ const placeholders = chunk.map(() => "?").join(", ");
16608
+ const rows = this.db.prepare(
16609
+ `SELECT id, page_rank FROM nodes WHERE id IN (${placeholders}) AND valid_to IS NULL`
16610
+ ).all(...chunk);
16611
+ for (const r of rows) out.set(r.id, r.page_rank);
16612
+ }
16613
+ return out;
16614
+ }
16615
+ /** The minimal row set the landmark sweep needs — landmark CANDIDATES
16616
+ * (Pattern/RootCause), everything currently flagged, and every
16617
+ * persistent-tier node (force-landmarks). A few thousand rows, so the
16618
+ * incremental path can refresh the GLOBAL landmark set without the 179k-row
16619
+ * scanLiveNodes materialization. */
16620
+ landmarkSweepRows() {
16621
+ const rows = this.db.prepare(
16622
+ `SELECT id, label, page_rank, is_landmark, memory_tier, extraction_source FROM nodes
16623
+ WHERE valid_to IS NULL
16624
+ AND (memory_tier = 'persistent' OR is_landmark = 1 OR label IN ('Pattern', 'RootCause'))`
16625
+ ).all();
16626
+ return rows.map((r) => ({
16627
+ id: r.id,
16628
+ label: r.label,
16629
+ pageRank: r.page_rank,
16630
+ isLandmark: r.is_landmark === 1,
16631
+ memoryTier: r.memory_tier,
16632
+ extractionSource: r.extraction_source
16633
+ }));
16634
+ }
16635
+ edgesForMany(ids, col) {
16636
+ const out = [];
16637
+ const CHUNK = 400;
16638
+ for (let i = 0; i < ids.length; i += CHUNK) {
16639
+ const chunk = ids.slice(i, i + CHUNK);
16640
+ const placeholders = chunk.map(() => "?").join(", ");
16641
+ const rows = this.db.prepare(
16642
+ `SELECT from_id, to_id, type FROM edges WHERE ${col} IN (${placeholders}) AND valid_to IS NULL`
16643
+ ).all(...chunk);
16644
+ for (const r of rows) out.push({ from: r.from_id, to: r.to_id, type: r.type });
16645
+ }
16646
+ return out;
16647
+ }
16648
+ checkpointWal() {
16649
+ try {
16650
+ const r = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
16651
+ return r ?? null;
16652
+ } catch {
16653
+ return null;
16654
+ }
16655
+ }
16565
16656
  reviveReobserved(relPaths, workspaceId, since) {
16566
16657
  let revived = 0;
16567
16658
  const CHUNK = 400;
package/errata.mjs CHANGED
@@ -305,6 +305,10 @@ var init_castalia = __esm({
305
305
  // git topology, not signal-flow
306
306
  "AUTHORED_BY",
307
307
  // git authorship, not signal-flow
308
+ "PRODUCED",
309
+ // edit-episode provenance (Episode→symbol), not signal-flow — the family
310
+ // above was excluded together but this member slipped the net (LC-produced-pagerank);
311
+ // at 43% of all live edges it was the largest single edge population flowing rank
308
312
  "CONTRIBUTED",
309
313
  // agent attribution (Agent → knowledge), not signal-flow
310
314
  "SUPERSEDES",
@@ -16399,6 +16403,7 @@ function openDatabase(path2) {
16399
16403
  db.exec("PRAGMA journal_mode = WAL");
16400
16404
  db.exec("PRAGMA synchronous = NORMAL");
16401
16405
  db.exec("PRAGMA busy_timeout = 5000");
16406
+ db.exec("PRAGMA journal_size_limit = 67108864");
16402
16407
  } catch {
16403
16408
  }
16404
16409
  }
@@ -17237,6 +17242,96 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
17237
17242
  const rows = this.db.prepare("SELECT from_id, to_id, type FROM edges WHERE valid_to IS NULL").all();
17238
17243
  return rows.map((r) => ({ from: r.from_id, to: r.to_id, type: r.type }));
17239
17244
  }
17245
+ getMeta(key) {
17246
+ const r = this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key);
17247
+ return r?.value ?? null;
17248
+ }
17249
+ setMeta(key, value) {
17250
+ this.db.prepare(
17251
+ "INSERT INTO store_meta (key, value) VALUES (:key, :value) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
17252
+ ).run({ key, value });
17253
+ }
17254
+ dirtyNodeIdsSince(ts) {
17255
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE valid_to IS NULL AND last_updated_at > ?").all(ts);
17256
+ return rows.map((r) => r.id);
17257
+ }
17258
+ /** Endpoints of edges TOUCHED since `ts` — created, re-seen, or CLOSED.
17259
+ * Closures matter as much as additions: a node that lost inflow is rank-dirty
17260
+ * while its own row never updated, so removal endpoints must seed the
17261
+ * incremental region or the stale inflow persists until the full backstop. */
17262
+ edgeEndpointsTouchedSince(ts) {
17263
+ const rows = this.db.prepare(
17264
+ `SELECT from_id, to_id FROM edges
17265
+ WHERE (valid_to IS NULL AND (created_at > :ts OR last_seen_at > :ts))
17266
+ OR (valid_to IS NOT NULL AND valid_to > :ts)`
17267
+ ).all({ ts });
17268
+ return rows.map((r) => ({ from: r.from_id, to: r.to_id }));
17269
+ }
17270
+ /** Lightweight out-edges for a SET of sources, chunked IN-lists — the region
17271
+ * assembly path for incremental PageRank (per-node outEdges() at region
17272
+ * scale re-creates the 106k-prepared-calls problem the batch scan solved). */
17273
+ outEdgesForMany(ids) {
17274
+ return this.edgesForMany(ids, "from_id");
17275
+ }
17276
+ inEdgesForMany(ids) {
17277
+ return this.edgesForMany(ids, "to_id");
17278
+ }
17279
+ /** Stored pageRank for a SET of ids (live rows only — a closed id is simply
17280
+ * absent, which is how incremental region assembly drops dead endpoints). */
17281
+ ranksForMany(ids) {
17282
+ const out2 = /* @__PURE__ */ new Map();
17283
+ const CHUNK = 400;
17284
+ for (let i2 = 0; i2 < ids.length; i2 += CHUNK) {
17285
+ const chunk = ids.slice(i2, i2 + CHUNK);
17286
+ const placeholders = chunk.map(() => "?").join(", ");
17287
+ const rows = this.db.prepare(
17288
+ `SELECT id, page_rank FROM nodes WHERE id IN (${placeholders}) AND valid_to IS NULL`
17289
+ ).all(...chunk);
17290
+ for (const r of rows) out2.set(r.id, r.page_rank);
17291
+ }
17292
+ return out2;
17293
+ }
17294
+ /** The minimal row set the landmark sweep needs — landmark CANDIDATES
17295
+ * (Pattern/RootCause), everything currently flagged, and every
17296
+ * persistent-tier node (force-landmarks). A few thousand rows, so the
17297
+ * incremental path can refresh the GLOBAL landmark set without the 179k-row
17298
+ * scanLiveNodes materialization. */
17299
+ landmarkSweepRows() {
17300
+ const rows = this.db.prepare(
17301
+ `SELECT id, label, page_rank, is_landmark, memory_tier, extraction_source FROM nodes
17302
+ WHERE valid_to IS NULL
17303
+ AND (memory_tier = 'persistent' OR is_landmark = 1 OR label IN ('Pattern', 'RootCause'))`
17304
+ ).all();
17305
+ return rows.map((r) => ({
17306
+ id: r.id,
17307
+ label: r.label,
17308
+ pageRank: r.page_rank,
17309
+ isLandmark: r.is_landmark === 1,
17310
+ memoryTier: r.memory_tier,
17311
+ extractionSource: r.extraction_source
17312
+ }));
17313
+ }
17314
+ edgesForMany(ids, col) {
17315
+ const out2 = [];
17316
+ const CHUNK = 400;
17317
+ for (let i2 = 0; i2 < ids.length; i2 += CHUNK) {
17318
+ const chunk = ids.slice(i2, i2 + CHUNK);
17319
+ const placeholders = chunk.map(() => "?").join(", ");
17320
+ const rows = this.db.prepare(
17321
+ `SELECT from_id, to_id, type FROM edges WHERE ${col} IN (${placeholders}) AND valid_to IS NULL`
17322
+ ).all(...chunk);
17323
+ for (const r of rows) out2.push({ from: r.from_id, to: r.to_id, type: r.type });
17324
+ }
17325
+ return out2;
17326
+ }
17327
+ checkpointWal() {
17328
+ try {
17329
+ const r = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
17330
+ return r ?? null;
17331
+ } catch {
17332
+ return null;
17333
+ }
17334
+ }
17240
17335
  reviveReobserved(relPaths, workspaceId2, since) {
17241
17336
  let revived = 0;
17242
17337
  const CHUNK = 400;
@@ -20199,7 +20294,7 @@ function pagerank(input) {
20199
20294
  const ids = input.nodeIds;
20200
20295
  const n = ids.length;
20201
20296
  if (n === 0) {
20202
- return { scores: /* @__PURE__ */ new Map(), iterations: 0, converged: true };
20297
+ return { scores: /* @__PURE__ */ new Map(), iterations: 0, converged: true, danglingRank: 0 };
20203
20298
  }
20204
20299
  const index = /* @__PURE__ */ new Map();
20205
20300
  for (let i2 = 0; i2 < n; i2++) index.set(ids[i2], i2);
@@ -20271,8 +20366,12 @@ function pagerank(input) {
20271
20366
  }
20272
20367
  }
20273
20368
  const scores = /* @__PURE__ */ new Map();
20274
- for (let i2 = 0; i2 < n; i2++) scores.set(ids[i2], score2[i2]);
20275
- return { scores, iterations: iter, converged };
20369
+ let danglingRank = 0;
20370
+ for (let i2 = 0; i2 < n; i2++) {
20371
+ scores.set(ids[i2], score2[i2]);
20372
+ if (dangling[i2]) danglingRank += score2[i2];
20373
+ }
20374
+ return { scores, iterations: iter, converged, danglingRank };
20276
20375
  }
20277
20376
  function markLandmarks(scores, percentile = 0.1, filter) {
20278
20377
  const entries = [...scores.entries()];
@@ -20282,7 +20381,7 @@ function markLandmarks(scores, percentile = 0.1, filter) {
20282
20381
  }
20283
20382
  }
20284
20383
  if (entries.length === 0) return /* @__PURE__ */ new Set();
20285
- entries.sort((a, b) => b[1] - a[1]);
20384
+ entries.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
20286
20385
  const cutoff = Math.max(1, Math.floor(entries.length * percentile));
20287
20386
  const out2 = /* @__PURE__ */ new Set();
20288
20387
  for (let i2 = 0; i2 < cutoff; i2++) {
@@ -20739,6 +20838,92 @@ var init_bko = __esm({
20739
20838
  }
20740
20839
  });
20741
20840
 
20841
+ // ../../packages/math/src/pagerank-local.ts
20842
+ function pagerankLocal(input) {
20843
+ const damping = input.damping ?? 0.85;
20844
+ const tol = input.tolerance ?? 1e-6;
20845
+ const maxIter = input.maxIterations ?? 100;
20846
+ const ids = input.regionIds;
20847
+ const n = ids.length;
20848
+ if (n === 0 || input.globalN === 0) {
20849
+ return { scores: /* @__PURE__ */ new Map(), iterations: 0, converged: true };
20850
+ }
20851
+ const index = /* @__PURE__ */ new Map();
20852
+ for (let i2 = 0; i2 < n; i2++) index.set(ids[i2], i2);
20853
+ const base = (1 - damping) / input.globalN;
20854
+ const outSum = new Float64Array(n);
20855
+ const inflow = new Float64Array(n);
20856
+ let score2 = new Float64Array(n);
20857
+ for (let i2 = 0; i2 < n; i2++) {
20858
+ const id = ids[i2];
20859
+ outSum[i2] = input.outSum.get(id) ?? 0;
20860
+ inflow[i2] = input.boundaryInflow.get(id) ?? 0;
20861
+ score2[i2] = input.rank0.get(id) ?? base;
20862
+ }
20863
+ const rowLen = new Int32Array(n);
20864
+ let edgeCount = 0;
20865
+ for (const [from, edges] of input.out) {
20866
+ const fi = index.get(from);
20867
+ if (fi === void 0) continue;
20868
+ let inSet = 0;
20869
+ for (const e of edges) if (index.has(e.to)) inSet++;
20870
+ rowLen[fi] = inSet;
20871
+ edgeCount += inSet;
20872
+ }
20873
+ const rowStart = new Int32Array(n + 1);
20874
+ for (let i2 = 0; i2 < n; i2++) rowStart[i2 + 1] = rowStart[i2] + rowLen[i2];
20875
+ const colIdx = new Int32Array(edgeCount);
20876
+ const colW = new Float64Array(edgeCount);
20877
+ const cursor = rowStart.slice(0, n);
20878
+ for (const [from, edges] of input.out) {
20879
+ const fi = index.get(from);
20880
+ if (fi === void 0) continue;
20881
+ for (const e of edges) {
20882
+ const ti = index.get(e.to);
20883
+ if (ti === void 0) continue;
20884
+ const c = cursor[fi];
20885
+ cursor[fi] = c + 1;
20886
+ colIdx[c] = ti;
20887
+ colW[c] = e.weight;
20888
+ }
20889
+ }
20890
+ const externalDangling = input.externalDanglingRank ?? 0;
20891
+ let next = new Float64Array(n);
20892
+ let iter = 0;
20893
+ let converged = false;
20894
+ for (; iter < maxIter; iter++) {
20895
+ let internalDangling = 0;
20896
+ for (let i2 = 0; i2 < n; i2++) if (outSum[i2] <= 0) internalDangling += score2[i2];
20897
+ const danglingShare = damping * (internalDangling + externalDangling) / input.globalN;
20898
+ for (let i2 = 0; i2 < n; i2++) next[i2] = base + danglingShare + damping * inflow[i2];
20899
+ for (let i2 = 0; i2 < n; i2++) {
20900
+ const os2 = outSum[i2];
20901
+ if (os2 <= 0) continue;
20902
+ const f = damping * score2[i2] / os2;
20903
+ const end = rowStart[i2 + 1];
20904
+ for (let c = rowStart[i2]; c < end; c++) next[colIdx[c]] += f * colW[c];
20905
+ }
20906
+ let diff = 0;
20907
+ for (let i2 = 0; i2 < n; i2++) diff += Math.abs(next[i2] - score2[i2]);
20908
+ const tmp = score2;
20909
+ score2 = next;
20910
+ next = tmp;
20911
+ if (diff < tol) {
20912
+ iter++;
20913
+ converged = true;
20914
+ break;
20915
+ }
20916
+ }
20917
+ const scores = /* @__PURE__ */ new Map();
20918
+ for (let i2 = 0; i2 < n; i2++) scores.set(ids[i2], score2[i2]);
20919
+ return { scores, iterations: iter, converged };
20920
+ }
20921
+ var init_pagerank_local = __esm({
20922
+ "../../packages/math/src/pagerank-local.ts"() {
20923
+ "use strict";
20924
+ }
20925
+ });
20926
+
20742
20927
  // ../../packages/math/src/index.ts
20743
20928
  var init_src4 = __esm({
20744
20929
  "../../packages/math/src/index.ts"() {
@@ -20762,6 +20947,7 @@ var init_src4 = __esm({
20762
20947
  init_spectral();
20763
20948
  init_motif_gate();
20764
20949
  init_bko();
20950
+ init_pagerank_local();
20765
20951
  }
20766
20952
  });
20767
20953
 
@@ -29194,7 +29380,161 @@ var init_motifs = __esm({
29194
29380
  });
29195
29381
 
29196
29382
  // ../../packages/generalizer/src/nightly.ts
29197
- function runGraphRescore(store) {
29383
+ function runGraphRescore(store, opts = {}) {
29384
+ const lastRescoreAt = Number(store.getMeta?.("lastRescoreAt") ?? 0);
29385
+ const sinceFull = Number(store.getMeta?.("rescoresSinceFull") ?? 0);
29386
+ const incrementalCapable = !opts.forceFull && lastRescoreAt > 0 && sinceFull < FULL_RESCORE_EVERY - 1 && !!store.getMeta && !!store.setMeta && !!store.dirtyNodeIdsSince && !!store.edgeEndpointsTouchedSince && !!store.outEdgesForMany && !!store.inEdgesForMany && !!store.ranksForMany && !!store.landmarkSweepRows;
29387
+ if (incrementalCapable) {
29388
+ const started2 = Date.now();
29389
+ const inc = tryIncrementalRescore(store, lastRescoreAt, started2);
29390
+ if (inc) {
29391
+ store.setMeta("lastRescoreAt", String(started2));
29392
+ store.setMeta("rescoresSinceFull", String(sinceFull + 1));
29393
+ return inc;
29394
+ }
29395
+ }
29396
+ const report = runFullRescore(store);
29397
+ store.setMeta?.("lastRescoreAt", String(report.startedAt));
29398
+ store.setMeta?.("rescoresSinceFull", "0");
29399
+ store.setMeta?.("danglingRankShare", String(report.danglingRank));
29400
+ const { startedAt: _drop, danglingRank: _drop2, ...rest2 } = report;
29401
+ return rest2;
29402
+ }
29403
+ function tryIncrementalRescore(store, lastRescoreAt, started2) {
29404
+ const allowedTypes = new Set(pagerankEdgeTypes());
29405
+ const liveN = store.nodeCount();
29406
+ const cap = Math.max(1e3, Math.floor(liveN * INCREMENTAL_REGION_MAX_FRACTION));
29407
+ const region = new Set(store.dirtyNodeIdsSince(lastRescoreAt));
29408
+ for (const e of store.edgeEndpointsTouchedSince(lastRescoreAt)) {
29409
+ region.add(e.from);
29410
+ region.add(e.to);
29411
+ }
29412
+ if (region.size > cap) return null;
29413
+ let frontier = [...region];
29414
+ for (let hop = 0; hop < INCREMENTAL_HOPS && frontier.length > 0; hop++) {
29415
+ const next = [];
29416
+ for (const e of store.outEdgesForMany(frontier)) {
29417
+ if (!allowedTypes.has(e.type)) continue;
29418
+ if (!region.has(e.to)) {
29419
+ region.add(e.to);
29420
+ next.push(e.to);
29421
+ }
29422
+ }
29423
+ if (region.size > cap) return null;
29424
+ frontier = next;
29425
+ }
29426
+ const rank0 = store.ranksForMany([...region]);
29427
+ const regionIds = [...rank0.keys()];
29428
+ if (regionIds.length === 0) {
29429
+ return {
29430
+ scoredNodes: 0,
29431
+ iterations: 0,
29432
+ converged: true,
29433
+ landmarks: 0,
29434
+ communities: 0,
29435
+ motifsPromoted: 0,
29436
+ techniques: 0,
29437
+ antipatterns: 0,
29438
+ pageRankWritten: 0,
29439
+ landmarkFlips: 0,
29440
+ mode: "incremental",
29441
+ regionSize: 0,
29442
+ durationMs: Date.now() - started2
29443
+ };
29444
+ }
29445
+ const inRegion = new Set(regionIds);
29446
+ const out2 = /* @__PURE__ */ new Map();
29447
+ const outSum = /* @__PURE__ */ new Map();
29448
+ for (const e of store.outEdgesForMany(regionIds)) {
29449
+ if (!allowedTypes.has(e.type)) continue;
29450
+ const w = EDGE_WEIGHT[e.type] ?? 1;
29451
+ if (w <= 0) continue;
29452
+ outSum.set(e.from, (outSum.get(e.from) ?? 0) + w);
29453
+ if (!inRegion.has(e.to)) continue;
29454
+ const l = out2.get(e.from);
29455
+ if (l) l.push({ to: e.to, weight: w });
29456
+ else out2.set(e.from, [{ to: e.to, weight: w }]);
29457
+ }
29458
+ const boundaryEdges = store.inEdgesForMany(regionIds).filter((e) => allowedTypes.has(e.type) && !inRegion.has(e.from) && (EDGE_WEIGHT[e.type] ?? 1) > 0);
29459
+ const boundarySources = [...new Set(boundaryEdges.map((e) => e.from))];
29460
+ if (boundarySources.length > cap) return null;
29461
+ const boundaryRanks = store.ranksForMany(boundarySources);
29462
+ const boundaryOutSum = /* @__PURE__ */ new Map();
29463
+ for (const e of store.outEdgesForMany(boundarySources)) {
29464
+ if (!allowedTypes.has(e.type)) continue;
29465
+ const w = EDGE_WEIGHT[e.type] ?? 1;
29466
+ if (w > 0) boundaryOutSum.set(e.from, (boundaryOutSum.get(e.from) ?? 0) + w);
29467
+ }
29468
+ const boundaryInflow = /* @__PURE__ */ new Map();
29469
+ for (const e of boundaryEdges) {
29470
+ const r = boundaryRanks.get(e.from);
29471
+ const os2 = boundaryOutSum.get(e.from);
29472
+ if (r === void 0 || !os2) continue;
29473
+ const w = EDGE_WEIGHT[e.type] ?? 1;
29474
+ boundaryInflow.set(e.to, (boundaryInflow.get(e.to) ?? 0) + r * w / os2);
29475
+ }
29476
+ const globalDangling = Number(store.getMeta("danglingRankShare") ?? 0);
29477
+ let regionDangling = 0;
29478
+ for (const id of regionIds) {
29479
+ if ((outSum.get(id) ?? 0) <= 0) regionDangling += rank0.get(id) ?? 0;
29480
+ }
29481
+ const result = pagerankLocal({
29482
+ regionIds,
29483
+ rank0,
29484
+ out: out2,
29485
+ outSum,
29486
+ boundaryInflow,
29487
+ globalN: liveN,
29488
+ externalDanglingRank: Math.max(0, globalDangling - regionDangling),
29489
+ damping: 0.85,
29490
+ tolerance: 1e-6,
29491
+ maxIterations: 100
29492
+ });
29493
+ let pageRankWritten = 0;
29494
+ store.transaction(() => {
29495
+ for (const [id, score2] of result.scores) {
29496
+ if (Math.abs(score2 - (rank0.get(id) ?? 0)) < 1e-9) continue;
29497
+ store.setPageRank(id, score2);
29498
+ pageRankWritten++;
29499
+ }
29500
+ });
29501
+ const sweep = store.landmarkSweepRows();
29502
+ const candidates = /* @__PURE__ */ new Map();
29503
+ for (const row of sweep) {
29504
+ if (row.label === "Pattern" || row.label === "RootCause") {
29505
+ candidates.set(row.id, result.scores.get(row.id) ?? row.pageRank);
29506
+ }
29507
+ }
29508
+ const want = markLandmarks(candidates, 0.1);
29509
+ for (const row of sweep) if (row.memoryTier === "persistent") want.add(row.id);
29510
+ let landmarkFlips = 0;
29511
+ store.transaction(() => {
29512
+ for (const row of sweep) {
29513
+ if (row.extractionSource === "bko-inferred") continue;
29514
+ const should = want.has(row.id);
29515
+ if (row.isLandmark === should) continue;
29516
+ store.setLandmark(row.id, should);
29517
+ landmarkFlips++;
29518
+ }
29519
+ });
29520
+ return {
29521
+ scoredNodes: regionIds.length,
29522
+ iterations: result.iterations,
29523
+ converged: result.converged,
29524
+ landmarks: want.size,
29525
+ communities: 0,
29526
+ // deferred to the full backstop — see mode docs
29527
+ motifsPromoted: 0,
29528
+ techniques: 0,
29529
+ antipatterns: 0,
29530
+ pageRankWritten,
29531
+ landmarkFlips,
29532
+ mode: "incremental",
29533
+ regionSize: regionIds.length,
29534
+ durationMs: Date.now() - started2
29535
+ };
29536
+ }
29537
+ function runFullRescore(store) {
29198
29538
  const started2 = Date.now();
29199
29539
  const allowedTypes = new Set(pagerankEdgeTypes());
29200
29540
  const nodeIds = [];
@@ -29220,10 +29560,12 @@ function runGraphRescore(store) {
29220
29560
  maxIterations: 100
29221
29561
  });
29222
29562
  const scored = result.scores.size;
29563
+ let pageRankWritten = 0;
29223
29564
  store.transaction(() => {
29224
29565
  for (const [id, score2] of result.scores) {
29225
29566
  if (Math.abs(score2 - (meta3.get(id)?.pageRank ?? 0)) < 1e-9) continue;
29226
29567
  store.setPageRank(id, score2);
29568
+ pageRankWritten++;
29227
29569
  }
29228
29570
  });
29229
29571
  const landmarkCandidates = /* @__PURE__ */ new Map();
@@ -29238,11 +29580,13 @@ function runGraphRescore(store) {
29238
29580
  for (const id of nodeIds) {
29239
29581
  if (meta3.get(id)?.memoryTier === "persistent") allLandmarks.add(id);
29240
29582
  }
29583
+ let landmarkFlips = 0;
29241
29584
  store.transaction(() => {
29242
29585
  for (const id of nodeIds) {
29243
29586
  const want = allLandmarks.has(id);
29244
29587
  if ((meta3.get(id)?.isLandmark ?? false) === want) continue;
29245
29588
  store.setLandmark(id, want);
29589
+ landmarkFlips++;
29246
29590
  }
29247
29591
  });
29248
29592
  const MOTIF_LABELS = /* @__PURE__ */ new Set(["Pattern", "Technique", "AntiPattern"]);
@@ -29286,6 +29630,12 @@ function runGraphRescore(store) {
29286
29630
  motifsPromoted: motifs.promoted,
29287
29631
  techniques: motifs.techniques,
29288
29632
  antipatterns: motifs.antipatterns,
29633
+ pageRankWritten,
29634
+ landmarkFlips,
29635
+ mode: "full",
29636
+ regionSize: 0,
29637
+ startedAt: started2,
29638
+ danglingRank: result.danglingRank,
29289
29639
  durationMs: Date.now() - started2
29290
29640
  };
29291
29641
  }
@@ -29323,6 +29673,10 @@ function runNightlyPipeline(store) {
29323
29673
  skillsInduced: 0,
29324
29674
  // skills are cloud-induced now (see above)
29325
29675
  skillsRefreshed: 0,
29676
+ pageRankWritten: rescore.pageRankWritten,
29677
+ landmarkFlips: rescore.landmarkFlips,
29678
+ mode: rescore.mode,
29679
+ regionSize: rescore.regionSize,
29326
29680
  designResolved: semantic.designResolved,
29327
29681
  revisitsCleared: semantic.revisitsCleared,
29328
29682
  fixCandidatesSettled: semantic.fixCandidatesSettled,
@@ -29353,6 +29707,7 @@ function codeAnchorProjection(store, semanticIds, opts = {}) {
29353
29707
  }
29354
29708
  return out2;
29355
29709
  }
29710
+ var FULL_RESCORE_EVERY, INCREMENTAL_REGION_MAX_FRACTION, INCREMENTAL_HOPS;
29356
29711
  var init_nightly = __esm({
29357
29712
  "../../packages/generalizer/src/nightly.ts"() {
29358
29713
  "use strict";
@@ -29360,6 +29715,9 @@ var init_nightly = __esm({
29360
29715
  init_src4();
29361
29716
  init_src5();
29362
29717
  init_motifs();
29718
+ FULL_RESCORE_EVERY = 12;
29719
+ INCREMENTAL_REGION_MAX_FRACTION = 0.2;
29720
+ INCREMENTAL_HOPS = 3;
29363
29721
  }
29364
29722
  });
29365
29723
 
@@ -29595,6 +29953,8 @@ var init_relevance_rank = __esm({
29595
29953
  // ../../packages/generalizer/src/index.ts
29596
29954
  var src_exports4 = {};
29597
29955
  __export(src_exports4, {
29956
+ FULL_RESCORE_EVERY: () => FULL_RESCORE_EVERY,
29957
+ INCREMENTAL_REGION_MAX_FRACTION: () => INCREMENTAL_REGION_MAX_FRACTION,
29598
29958
  anchorByLine: () => anchorByLine,
29599
29959
  anchorCandidates: () => anchorCandidates,
29600
29960
  anchorProblemToCode: () => anchorProblemToCode,
@@ -53284,6 +53644,8 @@ function editSurprise(d) {
53284
53644
  function episodeId(workspaceId2, t) {
53285
53645
  return `episode_${digest({ kind: "episode", workspaceId: workspaceId2, t }).slice(0, 16)}`;
53286
53646
  }
53647
+ var EPISODE_DELTA_CAP = 200;
53648
+ var EPISODE_PRODUCED_CAP = 500;
53287
53649
  function producedEdge(from, to, t) {
53288
53650
  return {
53289
53651
  id: `edge_${digest({ from, type: "PRODUCED", to }).slice(0, 16)}`,
@@ -53305,6 +53667,10 @@ function recordEpisode(store, workspaceId2, t, delta, causal) {
53305
53667
  if (deltaIsEmpty(delta)) return null;
53306
53668
  const id = episodeId(workspaceId2, t);
53307
53669
  const surprise = editSurprise(delta);
53670
+ const overCap = delta.symbols.length > EPISODE_DELTA_CAP || delta.symbols.length > EPISODE_PRODUCED_CAP;
53671
+ const changeRank = { changed: 0, added: 1, removed: 2 };
53672
+ const prioritized = overCap ? [...delta.symbols].sort((a, b) => changeRank[a.change] - changeRank[b.change]) : delta.symbols;
53673
+ const storedDelta = prioritized.slice(0, EPISODE_DELTA_CAP);
53308
53674
  const node2 = {
53309
53675
  id,
53310
53676
  label: "Episode",
@@ -53336,8 +53702,11 @@ function recordEpisode(store, workspaceId2, t, delta, causal) {
53336
53702
  edgesAdded: delta.edgesAdded,
53337
53703
  edgesRemoved: delta.edgesRemoved,
53338
53704
  editSurprise: surprise,
53339
- // The per-symbol structural delta (signatures before→after, no raw text).
53340
- delta: delta.symbols,
53705
+ // The per-symbol structural delta (signatures before→after, no raw text),
53706
+ // capped changed-first; the aggregate counts above always cover the whole
53707
+ // edit, so nothing about the edit's MAGNITUDE is lost to the cap.
53708
+ delta: storedDelta,
53709
+ ...delta.symbols.length > EPISODE_DELTA_CAP ? { deltaTotal: delta.symbols.length } : {},
53341
53710
  // Causal context from the agent's tool stream, when correlated. Project
53342
53711
  // ONLY the scrub-safe fields — opaque session id, enum tool, closed-vocab
53343
53712
  // hints. declaredRenames carry identifier names (consumed upstream as
@@ -53352,11 +53721,14 @@ function recordEpisode(store, workspaceId2, t, delta, causal) {
53352
53721
  }
53353
53722
  };
53354
53723
  store.transaction(() => {
53724
+ let minted = 0;
53355
53725
  store.mergeNode(node2);
53356
- for (const s of delta.symbols) {
53726
+ for (const s of prioritized) {
53357
53727
  if (s.change === "removed") continue;
53728
+ if (minted >= EPISODE_PRODUCED_CAP) break;
53358
53729
  if (store.getNode(s.nodeId)) {
53359
53730
  store.mergeEdge(producedEdge(id, s.nodeId, t));
53731
+ minted++;
53360
53732
  }
53361
53733
  if (s.change === "changed") {
53362
53734
  const live = store.getNode(s.nodeId);
@@ -53364,6 +53736,7 @@ function recordEpisode(store, workspaceId2, t, delta, causal) {
53364
53736
  const frozenId = `${s.nodeId}@v${prev}`;
53365
53737
  if (store.getNode(frozenId)) {
53366
53738
  store.mergeEdge(producedEdge(id, frozenId, t));
53739
+ minted++;
53367
53740
  }
53368
53741
  }
53369
53742
  }
@@ -53371,6 +53744,49 @@ function recordEpisode(store, workspaceId2, t, delta, causal) {
53371
53744
  return id;
53372
53745
  }
53373
53746
 
53747
+ // src/episode-retention.ts
53748
+ var EPISODE_RETAIN_DAYS_DEFAULT = 90;
53749
+ var EPISODE_RETIRE_BATCH_DEFAULT = 300;
53750
+ var DAY_MS = 864e5;
53751
+ function numEnv(key, fallback) {
53752
+ const raw2 = process.env[key];
53753
+ if (raw2 == null || raw2.trim() === "") return fallback;
53754
+ const n = Number(raw2);
53755
+ return Number.isFinite(n) && n > 0 ? n : fallback;
53756
+ }
53757
+ function retireEpisodeProvenance(store, now, opts = {}) {
53758
+ const retainDays = opts.retainDays ?? numEnv("ERRATA_EPISODE_RETAIN_DAYS", EPISODE_RETAIN_DAYS_DEFAULT);
53759
+ const batch = opts.batch ?? EPISODE_RETIRE_BATCH_DEFAULT;
53760
+ const cutoff = now - retainDays * DAY_MS;
53761
+ const due = store.findNodesByLabel("Episode").filter((ep) => {
53762
+ if (ep.attrs["provenanceRetired"] != null) return false;
53763
+ const ts = ep.attrs["ts"] ?? ep.createdAt;
53764
+ return ts < cutoff;
53765
+ }).sort((a, b) => (a.attrs["ts"] ?? a.createdAt) - (b.attrs["ts"] ?? b.createdAt)).slice(0, batch);
53766
+ let edgesClosed = 0;
53767
+ let deltasDropped = 0;
53768
+ for (const ep of due) {
53769
+ store.transaction(() => {
53770
+ for (const e of store.outEdges(ep.id, ["PRODUCED"])) {
53771
+ store.closeEdge(e.id, now);
53772
+ edgesClosed++;
53773
+ }
53774
+ const { delta, ...rest2 } = ep.attrs;
53775
+ if (Array.isArray(delta)) deltasDropped++;
53776
+ store.updateNode(ep.id, {
53777
+ attrs: {
53778
+ ...rest2,
53779
+ provenanceRetired: now,
53780
+ // How much detail the compaction elided — the aggregate counts
53781
+ // (symbolsAdded/Changed/Removed) still describe the edit itself.
53782
+ ...Array.isArray(delta) ? { deltaDropped: delta.length } : {}
53783
+ }
53784
+ });
53785
+ });
53786
+ }
53787
+ return { retired: due.length, edgesClosed, deltasDropped };
53788
+ }
53789
+
53374
53790
  // src/pass-worker-client.ts
53375
53791
  import { Worker } from "node:worker_threads";
53376
53792
  var PassWorker = class {
@@ -54991,7 +55407,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
54991
55407
  }
54992
55408
 
54993
55409
  // src/engine.ts
54994
- var DAEMON_VERSION = true ? "2.0.2-dev.646" : "2.0.0-alpha.0";
55410
+ var DAEMON_VERSION = true ? "2.0.2-dev.682" : "2.0.0-alpha.0";
54995
55411
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
54996
55412
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
54997
55413
  var GIT_OP_MUTE_MS = 4e3;
@@ -56593,7 +57009,14 @@ function createWorkspaceEngine(opts) {
56593
57009
  scoredNodes: report.scoredNodes,
56594
57010
  landmarks: report.landmarks,
56595
57011
  communities: report.communities,
56596
- motifsPromoted: report.motifsPromoted
57012
+ motifsPromoted: report.motifsPromoted,
57013
+ // Consumer-visible effect (momentum-gate calibration reads these):
57014
+ // a run of zero-flip rows is the gate learning to fire less.
57015
+ pageRankWritten: report.pageRankWritten,
57016
+ landmarkFlips: report.landmarkFlips,
57017
+ // HZ-incremental-pagerank: 1 = full backstop, 0 = dirty-region solve.
57018
+ fullPass: report.mode === "full" ? 1 : 0,
57019
+ regionSize: report.regionSize
56597
57020
  });
56598
57021
  appendPassLedger(paths.configDir, "semantic-maintenance", report.durationMs, {
56599
57022
  revisitsCleared: report.revisitsCleared,
@@ -56601,6 +57024,26 @@ function createWorkspaceEngine(opts) {
56601
57024
  designResolved: report.designResolved,
56602
57025
  claimsEvaluated: report.claimsEvaluated
56603
57026
  });
57027
+ try {
57028
+ const t0 = Date.now();
57029
+ const ret = retireEpisodeProvenance(store, t0);
57030
+ if (ret.retired > 0) {
57031
+ appendPassLedger(paths.configDir, "provenance-retire", Date.now() - t0, {
57032
+ episodes: ret.retired,
57033
+ edgesClosed: ret.edgesClosed,
57034
+ deltasDropped: ret.deltasDropped
57035
+ });
57036
+ }
57037
+ } catch (err2) {
57038
+ console.warn("[errata] provenance retention failed:", err2.message?.slice(0, 120));
57039
+ }
57040
+ try {
57041
+ const cp = store.checkpointWal?.();
57042
+ if (cp && cp.busy === 0 && cp.log > 25e3) {
57043
+ console.log(`[errata] wal checkpoint: truncated ${cp.log} frame(s)`);
57044
+ }
57045
+ } catch {
57046
+ }
56604
57047
  return report;
56605
57048
  },
56606
57049
  async embedSettled() {
@@ -58623,9 +59066,11 @@ async function startMultiDaemon(opts = {}) {
58623
59066
  }
58624
59067
  return out2;
58625
59068
  },
58626
- async nightlyAll() {
59069
+ async nightlyFor(ids) {
59070
+ const want = new Set(ids);
58627
59071
  const out2 = /* @__PURE__ */ new Map();
58628
59072
  for (const r of records) {
59073
+ if (!want.has(r.id)) continue;
58629
59074
  const done = markPass(`nightly:${r.entry.name}`);
58630
59075
  try {
58631
59076
  out2.set(r.id, await r.engine.nightly());
@@ -58637,6 +59082,9 @@ async function startMultiDaemon(opts = {}) {
58637
59082
  }
58638
59083
  return out2;
58639
59084
  },
59085
+ async nightlyAll() {
59086
+ return daemon.nightlyFor(records.map((r) => r.id));
59087
+ },
58640
59088
  async percolateAll() {
58641
59089
  const ts = Date.now();
58642
59090
  const out2 = /* @__PURE__ */ new Map();
@@ -59779,16 +60227,28 @@ function formatWhoami(response, localProfile) {
59779
60227
  }
59780
60228
 
59781
60229
  // src/consolidation-trigger.ts
60230
+ function effectiveMomentumThreshold(policy, liveNodes) {
60231
+ return Math.max(policy.momentumThreshold, Math.ceil(liveNodes * policy.momentumRatio));
60232
+ }
60233
+ function nextMomentumThreshold(args2) {
60234
+ const { current, consumed, landmarkFlips, floor, cap } = args2;
60235
+ const next = landmarkFlips > 0 ? Math.min(current, Math.round(consumed / 2)) : Math.max(current, Math.round(consumed * 1.5));
60236
+ return Math.max(floor, Math.min(cap, next));
60237
+ }
59782
60238
  var DEFAULT_CONSOLIDATION_POLICY = {
59783
60239
  baseFloorMs: 6e4,
59784
60240
  dutyFactor: 20,
59785
60241
  // ≤5% worst-case duty at any graph size (crater guard)
59786
60242
  momentumThreshold: 500,
59787
- // significance: ~mutations needed to meaningfully move PageRank
60243
+ // POLICY freshness bound the gate never demands less
60244
+ momentumRatio: 0.01,
60245
+ // cold-start PRIOR only; calibration replaces it after the first pass
60246
+ momentumRatioCap: 0.05,
60247
+ // POLICY staleness bound — refresh by 5% churn no matter what
59788
60248
  quiescenceMs: 6e4
59789
60249
  // sim preferred 60s over 90s (fresher, esp. heavy-code)
59790
60250
  };
59791
- function numEnv(env2, key, fallback) {
60251
+ function numEnv2(env2, key, fallback) {
59792
60252
  const raw2 = env2[key];
59793
60253
  if (raw2 == null || raw2.trim() === "") return fallback;
59794
60254
  const n = Number(raw2);
@@ -59796,21 +60256,17 @@ function numEnv(env2, key, fallback) {
59796
60256
  }
59797
60257
  function consolidationPolicyFromEnv(env2 = process.env) {
59798
60258
  return {
59799
- baseFloorMs: numEnv(env2, "ERRATA_CONSOLIDATE_BASE_FLOOR_MS", DEFAULT_CONSOLIDATION_POLICY.baseFloorMs),
59800
- dutyFactor: numEnv(env2, "ERRATA_CONSOLIDATE_DUTY_FACTOR", DEFAULT_CONSOLIDATION_POLICY.dutyFactor),
59801
- momentumThreshold: numEnv(env2, "ERRATA_CONSOLIDATE_MOMENTUM", DEFAULT_CONSOLIDATION_POLICY.momentumThreshold),
59802
- quiescenceMs: numEnv(env2, "ERRATA_CONSOLIDATE_QUIESCENCE_MS", DEFAULT_CONSOLIDATION_POLICY.quiescenceMs)
60259
+ baseFloorMs: numEnv2(env2, "ERRATA_CONSOLIDATE_BASE_FLOOR_MS", DEFAULT_CONSOLIDATION_POLICY.baseFloorMs),
60260
+ dutyFactor: numEnv2(env2, "ERRATA_CONSOLIDATE_DUTY_FACTOR", DEFAULT_CONSOLIDATION_POLICY.dutyFactor),
60261
+ momentumThreshold: numEnv2(env2, "ERRATA_CONSOLIDATE_MOMENTUM", DEFAULT_CONSOLIDATION_POLICY.momentumThreshold),
60262
+ momentumRatio: numEnv2(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO", DEFAULT_CONSOLIDATION_POLICY.momentumRatio),
60263
+ momentumRatioCap: numEnv2(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO_CAP", DEFAULT_CONSOLIDATION_POLICY.momentumRatioCap),
60264
+ quiescenceMs: numEnv2(env2, "ERRATA_CONSOLIDATE_QUIESCENCE_MS", DEFAULT_CONSOLIDATION_POLICY.quiescenceMs)
59803
60265
  };
59804
60266
  }
59805
60267
  function consolidationGapMs(lastPassMs, policy) {
59806
60268
  return Math.max(policy.baseFloorMs, lastPassMs * policy.dutyFactor);
59807
60269
  }
59808
- function shouldConsolidate(args2) {
59809
- const { now, state, policy, force = false } = args2;
59810
- if (state.momentum < policy.momentumThreshold) return false;
59811
- if (force) return true;
59812
- return now - state.lastConsolidationEnd >= consolidationGapMs(state.lastPassMs, policy);
59813
- }
59814
60270
 
59815
60271
  // src/cli.ts
59816
60272
  var exitCleanOnEpipe = (err2) => {
@@ -62170,10 +62626,10 @@ async function cmdDash(args2) {
62170
62626
  let nightlyCount = 0;
62171
62627
  const hotspotScanMark = /* @__PURE__ */ new Map();
62172
62628
  const HOTSPOT_FANIN = 20;
62173
- const runNightlyPass = async () => {
62629
+ const runNightlyPass = async (dueIds) => {
62174
62630
  const yieldToLoop2 = () => new Promise((resolve6) => setImmediate(resolve6));
62175
62631
  const fullReclusterPass = nightlyCount++ % FULL_RECLUSTER_EVERY === 0;
62176
- const m = await handle2.nightlyAll();
62632
+ const m = dueIds ? await handle2.nightlyFor(dueIds) : await handle2.nightlyAll();
62177
62633
  let skillsLearned = 0;
62178
62634
  for (const [id, n] of m) {
62179
62635
  skillsLearned += n.skillsInduced;
@@ -62317,36 +62773,57 @@ async function cmdDash(args2) {
62317
62773
  }
62318
62774
  }
62319
62775
  maybeFlushDigests();
62776
+ return m;
62320
62777
  };
62321
62778
  const CONSOLIDATE_BOOT_GRACE_S = 45;
62322
62779
  const consolidationPolicy = consolidationPolicyFromEnv();
62323
62780
  const consolidationState = { momentum: 0, lastConsolidationEnd: 0, lastPassMs: 0 };
62324
- const totalMutations = () => handle2.records.reduce((n, r) => n + r.engine.store.mutationCount(), 0);
62325
- let consolidatedAtMutations = 0;
62781
+ const consolidatedAtByRecord = /* @__PURE__ */ new Map();
62782
+ const calibratedThreshold = /* @__PURE__ */ new Map();
62783
+ const thresholdFor = (r) => calibratedThreshold.get(r.id) ?? effectiveMomentumThreshold(consolidationPolicy, r.engine.store.nodeCount());
62784
+ const dueRecords = () => handle2.records.map((r) => ({
62785
+ id: r.id,
62786
+ momentum: r.engine.store.mutationCount() - (consolidatedAtByRecord.get(r.id) ?? 0),
62787
+ threshold: thresholdFor(r)
62788
+ })).filter((x) => x.momentum >= x.threshold).map(({ id, momentum }) => ({ id, momentum }));
62326
62789
  let consolidating = false;
62327
62790
  const maybeConsolidate = async (force) => {
62328
62791
  if (consolidating) return;
62329
62792
  if (process.uptime() < CONSOLIDATE_BOOT_GRACE_S) return;
62330
- consolidationState.momentum = totalMutations() - consolidatedAtMutations;
62331
- if (!shouldConsolidate({
62332
- now: Date.now(),
62333
- state: consolidationState,
62334
- policy: consolidationPolicy,
62335
- force
62336
- })) {
62793
+ const due = dueRecords();
62794
+ consolidationState.momentum = due.length;
62795
+ if (due.length === 0) return;
62796
+ if (!force && Date.now() - consolidationState.lastConsolidationEnd < consolidationGapMs(consolidationState.lastPassMs, consolidationPolicy)) {
62337
62797
  return;
62338
62798
  }
62339
62799
  consolidating = true;
62340
62800
  const startedAt = Date.now();
62341
62801
  try {
62342
- await runNightlyPass();
62802
+ const reports = await runNightlyPass(due.map((d) => d.id));
62803
+ for (const { id, momentum } of due) {
62804
+ const r = handle2.records.find((x) => x.id === id);
62805
+ const report = reports?.get(id);
62806
+ if (!r || !report) continue;
62807
+ calibratedThreshold.set(
62808
+ id,
62809
+ nextMomentumThreshold({
62810
+ current: thresholdFor(r),
62811
+ consumed: momentum,
62812
+ landmarkFlips: report.landmarkFlips,
62813
+ floor: consolidationPolicy.momentumThreshold,
62814
+ cap: Math.ceil(r.engine.store.nodeCount() * consolidationPolicy.momentumRatioCap)
62815
+ })
62816
+ );
62817
+ }
62343
62818
  } catch (err2) {
62344
62819
  console.warn(`[consolidate] pass failed: ${err2 instanceof Error ? err2.message : err2}`);
62345
62820
  } finally {
62346
62821
  consolidationState.lastPassMs = Date.now() - startedAt;
62347
62822
  consolidationState.lastConsolidationEnd = Date.now();
62348
- consolidatedAtMutations = totalMutations();
62349
- consolidationState.momentum = 0;
62823
+ for (const { id } of due) {
62824
+ const r = handle2.records.find((x) => x.id === id);
62825
+ if (r) consolidatedAtByRecord.set(id, r.engine.store.mutationCount());
62826
+ }
62350
62827
  consolidating = false;
62351
62828
  }
62352
62829
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.646",
3
+ "version": "2.0.2-dev.682",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -266,6 +266,10 @@ var init_castalia = __esm({
266
266
  // git topology, not signal-flow
267
267
  "AUTHORED_BY",
268
268
  // git authorship, not signal-flow
269
+ "PRODUCED",
270
+ // edit-episode provenance (Episode→symbol), not signal-flow — the family
271
+ // above was excluded together but this member slipped the net (LC-produced-pagerank);
272
+ // at 43% of all live edges it was the largest single edge population flowing rank
269
273
  "CONTRIBUTED",
270
274
  // agent attribution (Agent → knowledge), not signal-flow
271
275
  "SUPERSEDES",
@@ -15436,6 +15440,7 @@ function openDatabase(path) {
15436
15440
  db.exec("PRAGMA journal_mode = WAL");
15437
15441
  db.exec("PRAGMA synchronous = NORMAL");
15438
15442
  db.exec("PRAGMA busy_timeout = 5000");
15443
+ db.exec("PRAGMA journal_size_limit = 67108864");
15439
15444
  } catch {
15440
15445
  }
15441
15446
  }
@@ -24903,6 +24908,96 @@ var SqliteGraphStore = class {
24903
24908
  const rows = this.db.prepare("SELECT from_id, to_id, type FROM edges WHERE valid_to IS NULL").all();
24904
24909
  return rows.map((r) => ({ from: r.from_id, to: r.to_id, type: r.type }));
24905
24910
  }
24911
+ getMeta(key) {
24912
+ const r = this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key);
24913
+ return r?.value ?? null;
24914
+ }
24915
+ setMeta(key, value) {
24916
+ this.db.prepare(
24917
+ "INSERT INTO store_meta (key, value) VALUES (:key, :value) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
24918
+ ).run({ key, value });
24919
+ }
24920
+ dirtyNodeIdsSince(ts) {
24921
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE valid_to IS NULL AND last_updated_at > ?").all(ts);
24922
+ return rows.map((r) => r.id);
24923
+ }
24924
+ /** Endpoints of edges TOUCHED since `ts` — created, re-seen, or CLOSED.
24925
+ * Closures matter as much as additions: a node that lost inflow is rank-dirty
24926
+ * while its own row never updated, so removal endpoints must seed the
24927
+ * incremental region or the stale inflow persists until the full backstop. */
24928
+ edgeEndpointsTouchedSince(ts) {
24929
+ const rows = this.db.prepare(
24930
+ `SELECT from_id, to_id FROM edges
24931
+ WHERE (valid_to IS NULL AND (created_at > :ts OR last_seen_at > :ts))
24932
+ OR (valid_to IS NOT NULL AND valid_to > :ts)`
24933
+ ).all({ ts });
24934
+ return rows.map((r) => ({ from: r.from_id, to: r.to_id }));
24935
+ }
24936
+ /** Lightweight out-edges for a SET of sources, chunked IN-lists — the region
24937
+ * assembly path for incremental PageRank (per-node outEdges() at region
24938
+ * scale re-creates the 106k-prepared-calls problem the batch scan solved). */
24939
+ outEdgesForMany(ids) {
24940
+ return this.edgesForMany(ids, "from_id");
24941
+ }
24942
+ inEdgesForMany(ids) {
24943
+ return this.edgesForMany(ids, "to_id");
24944
+ }
24945
+ /** Stored pageRank for a SET of ids (live rows only — a closed id is simply
24946
+ * absent, which is how incremental region assembly drops dead endpoints). */
24947
+ ranksForMany(ids) {
24948
+ const out2 = /* @__PURE__ */ new Map();
24949
+ const CHUNK = 400;
24950
+ for (let i2 = 0; i2 < ids.length; i2 += CHUNK) {
24951
+ const chunk = ids.slice(i2, i2 + CHUNK);
24952
+ const placeholders = chunk.map(() => "?").join(", ");
24953
+ const rows = this.db.prepare(
24954
+ `SELECT id, page_rank FROM nodes WHERE id IN (${placeholders}) AND valid_to IS NULL`
24955
+ ).all(...chunk);
24956
+ for (const r of rows) out2.set(r.id, r.page_rank);
24957
+ }
24958
+ return out2;
24959
+ }
24960
+ /** The minimal row set the landmark sweep needs — landmark CANDIDATES
24961
+ * (Pattern/RootCause), everything currently flagged, and every
24962
+ * persistent-tier node (force-landmarks). A few thousand rows, so the
24963
+ * incremental path can refresh the GLOBAL landmark set without the 179k-row
24964
+ * scanLiveNodes materialization. */
24965
+ landmarkSweepRows() {
24966
+ const rows = this.db.prepare(
24967
+ `SELECT id, label, page_rank, is_landmark, memory_tier, extraction_source FROM nodes
24968
+ WHERE valid_to IS NULL
24969
+ AND (memory_tier = 'persistent' OR is_landmark = 1 OR label IN ('Pattern', 'RootCause'))`
24970
+ ).all();
24971
+ return rows.map((r) => ({
24972
+ id: r.id,
24973
+ label: r.label,
24974
+ pageRank: r.page_rank,
24975
+ isLandmark: r.is_landmark === 1,
24976
+ memoryTier: r.memory_tier,
24977
+ extractionSource: r.extraction_source
24978
+ }));
24979
+ }
24980
+ edgesForMany(ids, col) {
24981
+ const out2 = [];
24982
+ const CHUNK = 400;
24983
+ for (let i2 = 0; i2 < ids.length; i2 += CHUNK) {
24984
+ const chunk = ids.slice(i2, i2 + CHUNK);
24985
+ const placeholders = chunk.map(() => "?").join(", ");
24986
+ const rows = this.db.prepare(
24987
+ `SELECT from_id, to_id, type FROM edges WHERE ${col} IN (${placeholders}) AND valid_to IS NULL`
24988
+ ).all(...chunk);
24989
+ for (const r of rows) out2.push({ from: r.from_id, to: r.to_id, type: r.type });
24990
+ }
24991
+ return out2;
24992
+ }
24993
+ checkpointWal() {
24994
+ try {
24995
+ const r = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
24996
+ return r ?? null;
24997
+ } catch {
24998
+ return null;
24999
+ }
25000
+ }
24906
25001
  reviveReobserved(relPaths, workspaceId, since) {
24907
25002
  let revived = 0;
24908
25003
  const CHUNK = 400;
@@ -25926,7 +26021,7 @@ function pagerank(input) {
25926
26021
  const ids = input.nodeIds;
25927
26022
  const n = ids.length;
25928
26023
  if (n === 0) {
25929
- return { scores: /* @__PURE__ */ new Map(), iterations: 0, converged: true };
26024
+ return { scores: /* @__PURE__ */ new Map(), iterations: 0, converged: true, danglingRank: 0 };
25930
26025
  }
25931
26026
  const index = /* @__PURE__ */ new Map();
25932
26027
  for (let i2 = 0; i2 < n; i2++) index.set(ids[i2], i2);
@@ -25998,8 +26093,12 @@ function pagerank(input) {
25998
26093
  }
25999
26094
  }
26000
26095
  const scores = /* @__PURE__ */ new Map();
26001
- for (let i2 = 0; i2 < n; i2++) scores.set(ids[i2], score2[i2]);
26002
- return { scores, iterations: iter, converged };
26096
+ let danglingRank = 0;
26097
+ for (let i2 = 0; i2 < n; i2++) {
26098
+ scores.set(ids[i2], score2[i2]);
26099
+ if (dangling[i2]) danglingRank += score2[i2];
26100
+ }
26101
+ return { scores, iterations: iter, converged, danglingRank };
26003
26102
  }
26004
26103
  function markLandmarks(scores, percentile = 0.1, filter) {
26005
26104
  const entries = [...scores.entries()];
@@ -26009,7 +26108,7 @@ function markLandmarks(scores, percentile = 0.1, filter) {
26009
26108
  }
26010
26109
  }
26011
26110
  if (entries.length === 0) return /* @__PURE__ */ new Set();
26012
- entries.sort((a, b) => b[1] - a[1]);
26111
+ entries.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
26013
26112
  const cutoff = Math.max(1, Math.floor(entries.length * percentile));
26014
26113
  const out2 = /* @__PURE__ */ new Set();
26015
26114
  for (let i2 = 0; i2 < cutoff; i2++) {
@@ -26247,6 +26346,87 @@ function motifDecision(input) {
26247
26346
  };
26248
26347
  }
26249
26348
 
26349
+ // ../../packages/math/src/pagerank-local.ts
26350
+ function pagerankLocal(input) {
26351
+ const damping = input.damping ?? 0.85;
26352
+ const tol = input.tolerance ?? 1e-6;
26353
+ const maxIter = input.maxIterations ?? 100;
26354
+ const ids = input.regionIds;
26355
+ const n = ids.length;
26356
+ if (n === 0 || input.globalN === 0) {
26357
+ return { scores: /* @__PURE__ */ new Map(), iterations: 0, converged: true };
26358
+ }
26359
+ const index = /* @__PURE__ */ new Map();
26360
+ for (let i2 = 0; i2 < n; i2++) index.set(ids[i2], i2);
26361
+ const base = (1 - damping) / input.globalN;
26362
+ const outSum = new Float64Array(n);
26363
+ const inflow = new Float64Array(n);
26364
+ let score2 = new Float64Array(n);
26365
+ for (let i2 = 0; i2 < n; i2++) {
26366
+ const id = ids[i2];
26367
+ outSum[i2] = input.outSum.get(id) ?? 0;
26368
+ inflow[i2] = input.boundaryInflow.get(id) ?? 0;
26369
+ score2[i2] = input.rank0.get(id) ?? base;
26370
+ }
26371
+ const rowLen = new Int32Array(n);
26372
+ let edgeCount = 0;
26373
+ for (const [from, edges] of input.out) {
26374
+ const fi = index.get(from);
26375
+ if (fi === void 0) continue;
26376
+ let inSet = 0;
26377
+ for (const e of edges) if (index.has(e.to)) inSet++;
26378
+ rowLen[fi] = inSet;
26379
+ edgeCount += inSet;
26380
+ }
26381
+ const rowStart = new Int32Array(n + 1);
26382
+ for (let i2 = 0; i2 < n; i2++) rowStart[i2 + 1] = rowStart[i2] + rowLen[i2];
26383
+ const colIdx = new Int32Array(edgeCount);
26384
+ const colW = new Float64Array(edgeCount);
26385
+ const cursor = rowStart.slice(0, n);
26386
+ for (const [from, edges] of input.out) {
26387
+ const fi = index.get(from);
26388
+ if (fi === void 0) continue;
26389
+ for (const e of edges) {
26390
+ const ti = index.get(e.to);
26391
+ if (ti === void 0) continue;
26392
+ const c = cursor[fi];
26393
+ cursor[fi] = c + 1;
26394
+ colIdx[c] = ti;
26395
+ colW[c] = e.weight;
26396
+ }
26397
+ }
26398
+ const externalDangling = input.externalDanglingRank ?? 0;
26399
+ let next = new Float64Array(n);
26400
+ let iter = 0;
26401
+ let converged = false;
26402
+ for (; iter < maxIter; iter++) {
26403
+ let internalDangling = 0;
26404
+ for (let i2 = 0; i2 < n; i2++) if (outSum[i2] <= 0) internalDangling += score2[i2];
26405
+ const danglingShare = damping * (internalDangling + externalDangling) / input.globalN;
26406
+ for (let i2 = 0; i2 < n; i2++) next[i2] = base + danglingShare + damping * inflow[i2];
26407
+ for (let i2 = 0; i2 < n; i2++) {
26408
+ const os2 = outSum[i2];
26409
+ if (os2 <= 0) continue;
26410
+ const f = damping * score2[i2] / os2;
26411
+ const end = rowStart[i2 + 1];
26412
+ for (let c = rowStart[i2]; c < end; c++) next[colIdx[c]] += f * colW[c];
26413
+ }
26414
+ let diff = 0;
26415
+ for (let i2 = 0; i2 < n; i2++) diff += Math.abs(next[i2] - score2[i2]);
26416
+ const tmp = score2;
26417
+ score2 = next;
26418
+ next = tmp;
26419
+ if (diff < tol) {
26420
+ iter++;
26421
+ converged = true;
26422
+ break;
26423
+ }
26424
+ }
26425
+ const scores = /* @__PURE__ */ new Map();
26426
+ for (let i2 = 0; i2 < n; i2++) scores.set(ids[i2], score2[i2]);
26427
+ return { scores, iterations: iter, converged };
26428
+ }
26429
+
26250
26430
  // ../../packages/local-graph/src/triage.ts
26251
26431
  init_src();
26252
26432
  init_src();
@@ -26394,7 +26574,164 @@ function promoteMotifs(store2, t) {
26394
26574
  }
26395
26575
 
26396
26576
  // ../../packages/generalizer/src/nightly.ts
26397
- function runGraphRescore(store2) {
26577
+ var FULL_RESCORE_EVERY = 12;
26578
+ var INCREMENTAL_REGION_MAX_FRACTION = 0.2;
26579
+ var INCREMENTAL_HOPS = 3;
26580
+ function runGraphRescore(store2, opts = {}) {
26581
+ const lastRescoreAt = Number(store2.getMeta?.("lastRescoreAt") ?? 0);
26582
+ const sinceFull = Number(store2.getMeta?.("rescoresSinceFull") ?? 0);
26583
+ const incrementalCapable = !opts.forceFull && lastRescoreAt > 0 && sinceFull < FULL_RESCORE_EVERY - 1 && !!store2.getMeta && !!store2.setMeta && !!store2.dirtyNodeIdsSince && !!store2.edgeEndpointsTouchedSince && !!store2.outEdgesForMany && !!store2.inEdgesForMany && !!store2.ranksForMany && !!store2.landmarkSweepRows;
26584
+ if (incrementalCapable) {
26585
+ const started = Date.now();
26586
+ const inc = tryIncrementalRescore(store2, lastRescoreAt, started);
26587
+ if (inc) {
26588
+ store2.setMeta("lastRescoreAt", String(started));
26589
+ store2.setMeta("rescoresSinceFull", String(sinceFull + 1));
26590
+ return inc;
26591
+ }
26592
+ }
26593
+ const report = runFullRescore(store2);
26594
+ store2.setMeta?.("lastRescoreAt", String(report.startedAt));
26595
+ store2.setMeta?.("rescoresSinceFull", "0");
26596
+ store2.setMeta?.("danglingRankShare", String(report.danglingRank));
26597
+ const { startedAt: _drop, danglingRank: _drop2, ...rest } = report;
26598
+ return rest;
26599
+ }
26600
+ function tryIncrementalRescore(store2, lastRescoreAt, started) {
26601
+ const allowedTypes = new Set(pagerankEdgeTypes());
26602
+ const liveN = store2.nodeCount();
26603
+ const cap = Math.max(1e3, Math.floor(liveN * INCREMENTAL_REGION_MAX_FRACTION));
26604
+ const region = new Set(store2.dirtyNodeIdsSince(lastRescoreAt));
26605
+ for (const e of store2.edgeEndpointsTouchedSince(lastRescoreAt)) {
26606
+ region.add(e.from);
26607
+ region.add(e.to);
26608
+ }
26609
+ if (region.size > cap) return null;
26610
+ let frontier = [...region];
26611
+ for (let hop = 0; hop < INCREMENTAL_HOPS && frontier.length > 0; hop++) {
26612
+ const next = [];
26613
+ for (const e of store2.outEdgesForMany(frontier)) {
26614
+ if (!allowedTypes.has(e.type)) continue;
26615
+ if (!region.has(e.to)) {
26616
+ region.add(e.to);
26617
+ next.push(e.to);
26618
+ }
26619
+ }
26620
+ if (region.size > cap) return null;
26621
+ frontier = next;
26622
+ }
26623
+ const rank0 = store2.ranksForMany([...region]);
26624
+ const regionIds = [...rank0.keys()];
26625
+ if (regionIds.length === 0) {
26626
+ return {
26627
+ scoredNodes: 0,
26628
+ iterations: 0,
26629
+ converged: true,
26630
+ landmarks: 0,
26631
+ communities: 0,
26632
+ motifsPromoted: 0,
26633
+ techniques: 0,
26634
+ antipatterns: 0,
26635
+ pageRankWritten: 0,
26636
+ landmarkFlips: 0,
26637
+ mode: "incremental",
26638
+ regionSize: 0,
26639
+ durationMs: Date.now() - started
26640
+ };
26641
+ }
26642
+ const inRegion = new Set(regionIds);
26643
+ const out2 = /* @__PURE__ */ new Map();
26644
+ const outSum = /* @__PURE__ */ new Map();
26645
+ for (const e of store2.outEdgesForMany(regionIds)) {
26646
+ if (!allowedTypes.has(e.type)) continue;
26647
+ const w = EDGE_WEIGHT[e.type] ?? 1;
26648
+ if (w <= 0) continue;
26649
+ outSum.set(e.from, (outSum.get(e.from) ?? 0) + w);
26650
+ if (!inRegion.has(e.to)) continue;
26651
+ const l = out2.get(e.from);
26652
+ if (l) l.push({ to: e.to, weight: w });
26653
+ else out2.set(e.from, [{ to: e.to, weight: w }]);
26654
+ }
26655
+ const boundaryEdges = store2.inEdgesForMany(regionIds).filter((e) => allowedTypes.has(e.type) && !inRegion.has(e.from) && (EDGE_WEIGHT[e.type] ?? 1) > 0);
26656
+ const boundarySources = [...new Set(boundaryEdges.map((e) => e.from))];
26657
+ if (boundarySources.length > cap) return null;
26658
+ const boundaryRanks = store2.ranksForMany(boundarySources);
26659
+ const boundaryOutSum = /* @__PURE__ */ new Map();
26660
+ for (const e of store2.outEdgesForMany(boundarySources)) {
26661
+ if (!allowedTypes.has(e.type)) continue;
26662
+ const w = EDGE_WEIGHT[e.type] ?? 1;
26663
+ if (w > 0) boundaryOutSum.set(e.from, (boundaryOutSum.get(e.from) ?? 0) + w);
26664
+ }
26665
+ const boundaryInflow = /* @__PURE__ */ new Map();
26666
+ for (const e of boundaryEdges) {
26667
+ const r = boundaryRanks.get(e.from);
26668
+ const os2 = boundaryOutSum.get(e.from);
26669
+ if (r === void 0 || !os2) continue;
26670
+ const w = EDGE_WEIGHT[e.type] ?? 1;
26671
+ boundaryInflow.set(e.to, (boundaryInflow.get(e.to) ?? 0) + r * w / os2);
26672
+ }
26673
+ const globalDangling = Number(store2.getMeta("danglingRankShare") ?? 0);
26674
+ let regionDangling = 0;
26675
+ for (const id of regionIds) {
26676
+ if ((outSum.get(id) ?? 0) <= 0) regionDangling += rank0.get(id) ?? 0;
26677
+ }
26678
+ const result = pagerankLocal({
26679
+ regionIds,
26680
+ rank0,
26681
+ out: out2,
26682
+ outSum,
26683
+ boundaryInflow,
26684
+ globalN: liveN,
26685
+ externalDanglingRank: Math.max(0, globalDangling - regionDangling),
26686
+ damping: 0.85,
26687
+ tolerance: 1e-6,
26688
+ maxIterations: 100
26689
+ });
26690
+ let pageRankWritten = 0;
26691
+ store2.transaction(() => {
26692
+ for (const [id, score2] of result.scores) {
26693
+ if (Math.abs(score2 - (rank0.get(id) ?? 0)) < 1e-9) continue;
26694
+ store2.setPageRank(id, score2);
26695
+ pageRankWritten++;
26696
+ }
26697
+ });
26698
+ const sweep = store2.landmarkSweepRows();
26699
+ const candidates = /* @__PURE__ */ new Map();
26700
+ for (const row of sweep) {
26701
+ if (row.label === "Pattern" || row.label === "RootCause") {
26702
+ candidates.set(row.id, result.scores.get(row.id) ?? row.pageRank);
26703
+ }
26704
+ }
26705
+ const want = markLandmarks(candidates, 0.1);
26706
+ for (const row of sweep) if (row.memoryTier === "persistent") want.add(row.id);
26707
+ let landmarkFlips = 0;
26708
+ store2.transaction(() => {
26709
+ for (const row of sweep) {
26710
+ if (row.extractionSource === "bko-inferred") continue;
26711
+ const should = want.has(row.id);
26712
+ if (row.isLandmark === should) continue;
26713
+ store2.setLandmark(row.id, should);
26714
+ landmarkFlips++;
26715
+ }
26716
+ });
26717
+ return {
26718
+ scoredNodes: regionIds.length,
26719
+ iterations: result.iterations,
26720
+ converged: result.converged,
26721
+ landmarks: want.size,
26722
+ communities: 0,
26723
+ // deferred to the full backstop — see mode docs
26724
+ motifsPromoted: 0,
26725
+ techniques: 0,
26726
+ antipatterns: 0,
26727
+ pageRankWritten,
26728
+ landmarkFlips,
26729
+ mode: "incremental",
26730
+ regionSize: regionIds.length,
26731
+ durationMs: Date.now() - started
26732
+ };
26733
+ }
26734
+ function runFullRescore(store2) {
26398
26735
  const started = Date.now();
26399
26736
  const allowedTypes = new Set(pagerankEdgeTypes());
26400
26737
  const nodeIds = [];
@@ -26420,10 +26757,12 @@ function runGraphRescore(store2) {
26420
26757
  maxIterations: 100
26421
26758
  });
26422
26759
  const scored = result.scores.size;
26760
+ let pageRankWritten = 0;
26423
26761
  store2.transaction(() => {
26424
26762
  for (const [id, score2] of result.scores) {
26425
26763
  if (Math.abs(score2 - (meta3.get(id)?.pageRank ?? 0)) < 1e-9) continue;
26426
26764
  store2.setPageRank(id, score2);
26765
+ pageRankWritten++;
26427
26766
  }
26428
26767
  });
26429
26768
  const landmarkCandidates = /* @__PURE__ */ new Map();
@@ -26438,11 +26777,13 @@ function runGraphRescore(store2) {
26438
26777
  for (const id of nodeIds) {
26439
26778
  if (meta3.get(id)?.memoryTier === "persistent") allLandmarks.add(id);
26440
26779
  }
26780
+ let landmarkFlips = 0;
26441
26781
  store2.transaction(() => {
26442
26782
  for (const id of nodeIds) {
26443
26783
  const want = allLandmarks.has(id);
26444
26784
  if ((meta3.get(id)?.isLandmark ?? false) === want) continue;
26445
26785
  store2.setLandmark(id, want);
26786
+ landmarkFlips++;
26446
26787
  }
26447
26788
  });
26448
26789
  const MOTIF_LABELS = /* @__PURE__ */ new Set(["Pattern", "Technique", "AntiPattern"]);
@@ -26486,6 +26827,12 @@ function runGraphRescore(store2) {
26486
26827
  motifsPromoted: motifs.promoted,
26487
26828
  techniques: motifs.techniques,
26488
26829
  antipatterns: motifs.antipatterns,
26830
+ pageRankWritten,
26831
+ landmarkFlips,
26832
+ mode: "full",
26833
+ regionSize: 0,
26834
+ startedAt: started,
26835
+ danglingRank: result.danglingRank,
26489
26836
  durationMs: Date.now() - started
26490
26837
  };
26491
26838
  }
@@ -26523,6 +26870,10 @@ function runNightlyPipeline(store2) {
26523
26870
  skillsInduced: 0,
26524
26871
  // skills are cloud-induced now (see above)
26525
26872
  skillsRefreshed: 0,
26873
+ pageRankWritten: rescore.pageRankWritten,
26874
+ landmarkFlips: rescore.landmarkFlips,
26875
+ mode: rescore.mode,
26876
+ regionSize: rescore.regionSize,
26526
26877
  designResolved: semantic.designResolved,
26527
26878
  revisitsCleared: semantic.revisitsCleared,
26528
26879
  fixCandidatesSettled: semantic.fixCandidatesSettled,