@inerrata-corporation/errata 2.0.2-dev.133 → 2.0.2-dev.1411

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.
@@ -201,7 +201,7 @@ var init_castalia = __esm({
201
201
  DECOMPOSITION_EDGES = ["SPLIT_INTO"];
202
202
  TRANSFER_EDGES = ["MAY_RESOLVE"];
203
203
  SOLUTION_STRUCTURE_EDGES = ["BUILDS_ON", "SUPERSEDES", "ALTERNATIVE_TO"];
204
- SUSPICION_EDGES = ["SUSPECTED_LINK"];
204
+ SUSPICION_EDGES = ["SUSPECTED_LINK", "REGRESSION_OF"];
205
205
  TAXONOMY_EDGES = ["IS_A"];
206
206
  TRIAGE_EDGES = ["TRIAGED_BY", "CONFIRMS", "INDICATES", "ROUTES_TO"];
207
207
  GIT_EDGES = ["POINTS_AT", "PARENT", "AUTHORED_BY"];
@@ -336,6 +336,7 @@ var init_castalia = __esm({
336
336
  ALTERNATIVE_TO: 0,
337
337
  // Suspicion — a hypothesis, weightless + PageRank-excluded (never flows rank / EIG).
338
338
  SUSPECTED_LINK: 0,
339
+ REGRESSION_OF: 0,
339
340
  // Git — topology/authorship, weightless (not domain signal-flow)
340
341
  POINTS_AT: 0,
341
342
  PARENT: 0,
@@ -365,6 +366,13 @@ var init_castalia = __esm({
365
366
  }
366
367
  });
367
368
 
369
+ // ../../packages/shared/src/graph-events.ts
370
+ var init_graph_events = __esm({
371
+ "../../packages/shared/src/graph-events.ts"() {
372
+ "use strict";
373
+ }
374
+ });
375
+
368
376
  // ../../packages/shared/src/canonical/hash.ts
369
377
  import { createHash } from "node:crypto";
370
378
  function sha256(input) {
@@ -673,7 +681,7 @@ function lemma(token) {
673
681
  }
674
682
  return token;
675
683
  }
676
- function conceptTokens(text) {
684
+ function tokenize(text, resolve) {
677
685
  const matches = text.normalize("NFC").toLowerCase().match(TOKEN_RE) ?? [];
678
686
  const out = /* @__PURE__ */ new Set();
679
687
  for (const raw of matches) {
@@ -683,11 +691,14 @@ function conceptTokens(text) {
683
691
  out.add(token);
684
692
  continue;
685
693
  }
686
- const canonical = resolveCanonicalId(token);
694
+ const canonical = resolve(token);
687
695
  out.add(canonical ?? lemma(token));
688
696
  }
689
697
  return [...out].sort();
690
698
  }
699
+ function conceptTokens(text) {
700
+ return tokenize(text, resolveCanonicalId);
701
+ }
691
702
  function conceptBag(text) {
692
703
  return conceptTokens(text).join(" ");
693
704
  }
@@ -15214,7 +15225,12 @@ var init_edge_rules = __esm({
15214
15225
  // ── Taxonomy (Track 4 Stream C: MITRE CWE `ChildOf` → `Weakness —IS_A→ Weakness`) ──
15215
15226
  IS_A: { from: ["Weakness"], to: ["Weakness"] },
15216
15227
  // ── Conceptual (v1 taxonomy.ts: instance → motif/pattern reference) ──
15217
- INSTANCE_OF: { to: ["Pattern", "Weakness", "Technique"] },
15228
+ // `AntiPattern` joined the target set 2026-08-02: it is one of the three motif
15229
+ // kinds (generalizer motifs.ts `layerOf`: pattern | technique | antipattern) and
15230
+ // the SUPPRESSED-close path mints `Problem ─INSTANCE_OF→ AntiPattern` as the
15231
+ // negative-knowledge binding ("silenced, not solved") — the original three-label
15232
+ // rule predates AntiPattern joining the motif layer and silently ate that edge.
15233
+ INSTANCE_OF: { to: ["Pattern", "AntiPattern", "Weakness", "Technique"] },
15218
15234
  IMPLEMENTS: { from: ["Solution", "Language", "Component"], to: ["Pattern", "Technique"] },
15219
15235
  MATCHES: { to: ["Pattern"] },
15220
15236
  // ── Artifact evidence (v1 taxonomy.ts artifact rows) ──
@@ -15324,6 +15340,14 @@ var init_wire = __esm({
15324
15340
  * deployment a self-corroboration). Optional + additive: absent leaves the
15325
15341
  * gate fail-open for that node, exactly today's behaviour. */
15326
15342
  originSession: external_exports.string().min(3).max(64).optional(),
15343
+ /** Epoch ms of the ORIGINATING local node's creation — when the knowledge was
15344
+ * actually captured, as opposed to when its public twin reached the cloud.
15345
+ * Stored as `observedAt`; the network board's chronological view orders on
15346
+ * it. Without this the wire carried NO timestamp at all, so a node captured
15347
+ * days ago surfaced as "new" the moment it was generalized and published —
15348
+ * the board showed ingest order wearing a chronology's clothes. Optional +
15349
+ * additive: absent keeps ingest-time ordering for that node. */
15350
+ originCreatedAtMs: external_exports.number().int().positive().optional(),
15327
15351
  extractionSource: external_exports.enum(INGEST_EXTRACTION_SOURCES),
15328
15352
  validationSource: external_exports.enum(VALIDATION_SOURCES).optional(),
15329
15353
  /** Org-membrane (M2): the daemon's anchor tag — it owns the lockfile, so it
@@ -15445,6 +15469,7 @@ var init_src = __esm({
15445
15469
  "../../packages/shared/src/index.ts"() {
15446
15470
  "use strict";
15447
15471
  init_castalia();
15472
+ init_graph_events();
15448
15473
  init_identity();
15449
15474
  init_wire();
15450
15475
  init_edge_rules();
@@ -15506,6 +15531,51 @@ var init_review = __esm({
15506
15531
  }
15507
15532
  });
15508
15533
 
15534
+ // ../../packages/local-shared/src/anchorable.ts
15535
+ function anchorableExtensionAlternation() {
15536
+ return BY_LENGTH.map((e) => e.slice(1).replace(/[+.]/g, (c) => `\\${c}`)).join("|");
15537
+ }
15538
+ var ANCHORABLE_EXTENSIONS, BY_LENGTH;
15539
+ var init_anchorable = __esm({
15540
+ "../../packages/local-shared/src/anchorable.ts"() {
15541
+ "use strict";
15542
+ ANCHORABLE_EXTENSIONS = /* @__PURE__ */ new Set([
15543
+ // typescript provider
15544
+ ".ts",
15545
+ ".tsx",
15546
+ ".js",
15547
+ ".jsx",
15548
+ ".mjs",
15549
+ ".cjs",
15550
+ // python / go / rust / ruby / csharp providers
15551
+ ".py",
15552
+ ".go",
15553
+ ".rs",
15554
+ ".rb",
15555
+ ".cs",
15556
+ // cpp provider — every variant it parses, not just the three that were listed
15557
+ ".c",
15558
+ ".h",
15559
+ ".cpp",
15560
+ ".cc",
15561
+ ".cxx",
15562
+ ".c++",
15563
+ ".hpp",
15564
+ ".hh",
15565
+ ".hxx",
15566
+ ".h++",
15567
+ // CUDA — the cpp provider parses these too. Missed when this list was first
15568
+ // transcribed by hand; the parity test caught them on its very first run,
15569
+ // which is the argument for the test existing.
15570
+ ".cu",
15571
+ ".cuh",
15572
+ // no provider yet; the grammar ships. Inert, not wrong — see above.
15573
+ ".java"
15574
+ ]);
15575
+ BY_LENGTH = [...ANCHORABLE_EXTENSIONS].sort((a, b) => b.length - a.length);
15576
+ }
15577
+ });
15578
+
15509
15579
  // ../../packages/local-shared/src/sqlite-adapter.ts
15510
15580
  function openDatabase(path) {
15511
15581
  const db = new DatabaseSync(path);
@@ -15518,6 +15588,7 @@ function openDatabase(path) {
15518
15588
  db.exec("PRAGMA journal_mode = WAL");
15519
15589
  db.exec("PRAGMA synchronous = NORMAL");
15520
15590
  db.exec("PRAGMA busy_timeout = 5000");
15591
+ db.exec("PRAGMA journal_size_limit = 67108864");
15521
15592
  } catch {
15522
15593
  }
15523
15594
  }
@@ -15553,7 +15624,7 @@ function openDatabase(path) {
15553
15624
  return db.prepare(`PRAGMA ${key}`).get();
15554
15625
  },
15555
15626
  transaction(fn) {
15556
- db.exec("BEGIN");
15627
+ db.exec("BEGIN IMMEDIATE");
15557
15628
  try {
15558
15629
  const r = fn();
15559
15630
  db.exec("COMMIT");
@@ -15607,10 +15678,21 @@ var init_src2 = __esm({
15607
15678
  init_profile();
15608
15679
  init_daemon_wire();
15609
15680
  init_review();
15681
+ init_anchorable();
15610
15682
  init_sqlite_adapter();
15611
15683
  }
15612
15684
  });
15613
15685
 
15686
+ // ../../packages/indexer/src/parse-cache.ts
15687
+ var ENTRY_TTL_MS, MAX_ENTRY_BYTES;
15688
+ var init_parse_cache = __esm({
15689
+ "../../packages/indexer/src/parse-cache.ts"() {
15690
+ "use strict";
15691
+ ENTRY_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
15692
+ MAX_ENTRY_BYTES = 2 * 1024 * 1024;
15693
+ }
15694
+ });
15695
+
15614
15696
  // ../../packages/indexer/src/simhash.ts
15615
15697
  var MASK64;
15616
15698
  var init_simhash = __esm({
@@ -15633,6 +15715,7 @@ var init_pipeline = __esm({
15633
15715
  "../../packages/indexer/src/pipeline.ts"() {
15634
15716
  "use strict";
15635
15717
  init_src2();
15718
+ init_parse_cache();
15636
15719
  init_identity2();
15637
15720
  }
15638
15721
  });
@@ -15772,9 +15855,38 @@ var LOCAL_RULE_OVERRIDES = {
15772
15855
  to: [...CODE_NODE_LABELS, "Symbol"]
15773
15856
  },
15774
15857
  REVEALED_BY: null,
15775
- PRODUCED: null
15858
+ PRODUCED: null,
15859
+ // FIXED_BY locally ALSO carries the fix-provenance sense: `resolveProblem`
15860
+ // attributes a closed Problem to the fixing `Episode` (PLAN_PLASTICITY §2.1)
15861
+ // alongside the cloud's Problem→Solution knowledge claim. Same shape as
15862
+ // PRODUCED/REVEALED_BY above — an Episode is code-layer and never drains, so
15863
+ // the cloud door's `to: [Solution]` rule is untouched. The `from` constraint
15864
+ // stays: the reversed (Solution)-FIXED_BY->(...) splash bug is the reason
15865
+ // this rule exists at all.
15866
+ FIXED_BY: { from: EDGE_RULES["FIXED_BY"]?.from, to: ["Solution", "Episode"] },
15867
+ // RG-recognize: a NEW open Problem re-observing a RESOLVED one ("this is
15868
+ // back — the fix didn't hold"). Local-only for now: the instance drain's
15869
+ // INSTANCE_EDGES allowlist doesn't ship it, and the cloud door's matrix
15870
+ // doesn't know it — crossing the membrane is a deliberate later step, never
15871
+ // a side effect (the half-shipped-edge-type lesson).
15872
+ REGRESSION_OF: { from: ["Problem"], to: ["Problem"] }
15776
15873
  };
15777
- var SCHEMA_VERSION = 5;
15874
+ function localEdgeViolation(fromLabel, type, toLabel) {
15875
+ if (type in LOCAL_RULE_OVERRIDES) {
15876
+ const rule = LOCAL_RULE_OVERRIDES[type];
15877
+ if (!rule) return null;
15878
+ if (fromLabel && rule.from && !rule.from.includes(fromLabel)) {
15879
+ return `${type} cannot originate from ${fromLabel} locally (allowed: ${rule.from.join(", ")})`;
15880
+ }
15881
+ if (toLabel && rule.to && !rule.to.includes(toLabel)) {
15882
+ return `${type} cannot target ${toLabel} locally (allowed: ${rule.to.join(", ")})`;
15883
+ }
15884
+ return null;
15885
+ }
15886
+ const verdict = isValidEdge(fromLabel, type, toLabel);
15887
+ return verdict.ok ? null : verdict.reason ?? "edge rule violation";
15888
+ }
15889
+ var SCHEMA_VERSION = 6;
15778
15890
  var SCHEMA_SQL = `
15779
15891
  CREATE TABLE IF NOT EXISTS schema_version (
15780
15892
  version INTEGER PRIMARY KEY
@@ -15789,6 +15901,21 @@ CREATE TABLE IF NOT EXISTS store_meta (
15789
15901
  value TEXT NOT NULL
15790
15902
  );
15791
15903
 
15904
+ -- Durable ledger of edges the ontology gate REFUSED, keyed by edge type.
15905
+ -- Durable rather than in-memory for one specific reason: the status command runs
15906
+ -- in a SEPARATE process and opens its own store handle, so a counter living on
15907
+ -- the instance reads 0 there forever. That is exactly how a producer rejecting
15908
+ -- 100% of its output stayed invisible for 17 days. Persisting it also survives
15909
+ -- the daemon restart that would otherwise erase the evidence.
15910
+ -- Keyed by type because a systematic producer bug shows up as ONE type
15911
+ -- dominating; sample keeps the latest reason so the count is actionable.
15912
+ CREATE TABLE IF NOT EXISTS edge_rejections (
15913
+ type TEXT PRIMARY KEY,
15914
+ count INTEGER NOT NULL DEFAULT 0,
15915
+ last_at INTEGER NOT NULL,
15916
+ sample TEXT
15917
+ );
15918
+
15792
15919
  CREATE TABLE IF NOT EXISTS nodes (
15793
15920
  id TEXT PRIMARY KEY,
15794
15921
  label TEXT NOT NULL,
@@ -16165,6 +16292,16 @@ var SqliteGraphStore = class {
16165
16292
  if (!cols.has(name)) this.db.exec(ddl);
16166
16293
  }
16167
16294
  }
16295
+ if (from < 6) {
16296
+ this.db.exec(`
16297
+ CREATE TABLE IF NOT EXISTS edge_rejections (
16298
+ type TEXT PRIMARY KEY,
16299
+ count INTEGER NOT NULL DEFAULT 0,
16300
+ last_at INTEGER NOT NULL,
16301
+ sample TEXT
16302
+ )
16303
+ `);
16304
+ }
16168
16305
  this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
16169
16306
  });
16170
16307
  }
@@ -16246,6 +16383,7 @@ var SqliteGraphStore = class {
16246
16383
  const violation = this.edgeRuleViolation(edge);
16247
16384
  if (violation) {
16248
16385
  this.rejectedEdgeCount++;
16386
+ this.recordEdgeRejection(edge.type, violation, edge.lastSeenAt || edge.createdAt || 0);
16249
16387
  console.warn(`[local-graph] rejected edge ${edge.from}-[:${edge.type}]->${edge.to}: ${violation}`);
16250
16388
  return;
16251
16389
  }
@@ -16270,22 +16408,51 @@ var SqliteGraphStore = class {
16270
16408
  * overlay consulted first. Returns the reason string on a documented-
16271
16409
  * forbidden combination, else null. Point lookups on the id PK — negligible
16272
16410
  * next to the insert itself. */
16273
- edgeRuleViolation(edge) {
16274
- const fromLabel = this.getNode(edge.from)?.label;
16275
- const toLabel = this.getNode(edge.to)?.label;
16276
- if (edge.type in LOCAL_RULE_OVERRIDES) {
16277
- const rule = LOCAL_RULE_OVERRIDES[edge.type];
16278
- if (!rule) return null;
16279
- if (fromLabel && rule.from && !rule.from.includes(fromLabel)) {
16280
- return `${edge.type} cannot originate from ${fromLabel} locally (allowed: ${rule.from.join(", ")})`;
16281
- }
16282
- if (toLabel && rule.to && !rule.to.includes(toLabel)) {
16283
- return `${edge.type} cannot target ${toLabel} locally (allowed: ${rule.to.join(", ")})`;
16284
- }
16285
- return null;
16411
+ /** Upsert one refusal into the durable ledger. Best-effort: a bookkeeping
16412
+ * failure must never turn a refused edge into a thrown write. */
16413
+ recordEdgeRejection(type, reason, at) {
16414
+ try {
16415
+ this.db.prepare(
16416
+ `INSERT INTO edge_rejections (type, count, last_at, sample) VALUES (?, 1, ?, ?)
16417
+ ON CONFLICT(type) DO UPDATE SET
16418
+ count = count + 1, last_at = excluded.last_at, sample = excluded.sample`
16419
+ ).run(type, at, reason.slice(0, 200));
16420
+ } catch {
16421
+ }
16422
+ }
16423
+ /** Refusals recorded by the ontology gate, per edge type, newest activity first.
16424
+ * Durable across restarts and readable from any process (see the table note). */
16425
+ edgeRejections() {
16426
+ try {
16427
+ return this.db.prepare(
16428
+ "SELECT type, count, last_at AS lastAt, sample FROM edge_rejections ORDER BY count DESC, last_at DESC"
16429
+ ).all();
16430
+ } catch {
16431
+ return [];
16432
+ }
16433
+ }
16434
+ /** Drop ledger entries whose last refusal predates `cutoff`. A still-misbehaving
16435
+ * producer keeps refreshing `last_at` and survives; a fixed one fades out. */
16436
+ pruneEdgeRejections(cutoff) {
16437
+ try {
16438
+ this.db.prepare("DELETE FROM edge_rejections WHERE last_at < ?").run(cutoff);
16439
+ } catch {
16440
+ }
16441
+ }
16442
+ /** Clear the ledger outright, whole or per type — operator escape hatch. */
16443
+ clearEdgeRejections(type) {
16444
+ try {
16445
+ if (type) this.db.prepare("DELETE FROM edge_rejections WHERE type = ?").run(type);
16446
+ else this.db.exec("DELETE FROM edge_rejections");
16447
+ } catch {
16286
16448
  }
16287
- const verdict = isValidEdge(fromLabel, edge.type, toLabel);
16288
- return verdict.ok ? null : verdict.reason ?? "edge rule violation";
16449
+ }
16450
+ edgeRuleViolation(edge) {
16451
+ return localEdgeViolation(
16452
+ this.getNode(edge.from)?.label,
16453
+ edge.type,
16454
+ this.getNode(edge.to)?.label
16455
+ );
16289
16456
  }
16290
16457
  updateEdge(id, patch) {
16291
16458
  this.stmts.updateEdge.run({
@@ -16422,6 +16589,163 @@ var SqliteGraphStore = class {
16422
16589
  const rows = this.db.prepare("SELECT from_id, to_id, type FROM edges WHERE valid_to IS NULL").all();
16423
16590
  return rows.map((r) => ({ from: r.from_id, to: r.to_id, type: r.type }));
16424
16591
  }
16592
+ getMeta(key) {
16593
+ const r = this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key);
16594
+ return r?.value ?? null;
16595
+ }
16596
+ setMeta(key, value) {
16597
+ this.db.prepare(
16598
+ "INSERT INTO store_meta (key, value) VALUES (:key, :value) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
16599
+ ).run({ key, value });
16600
+ }
16601
+ dirtyNodeIdsSince(ts) {
16602
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE valid_to IS NULL AND last_updated_at > ?").all(ts);
16603
+ return rows.map((r) => r.id);
16604
+ }
16605
+ /** Endpoints of edges TOUCHED since `ts` — created, re-seen, or CLOSED.
16606
+ * Closures matter as much as additions: a node that lost inflow is rank-dirty
16607
+ * while its own row never updated, so removal endpoints must seed the
16608
+ * incremental region or the stale inflow persists until the full backstop. */
16609
+ edgeEndpointsTouchedSince(ts) {
16610
+ const rows = this.db.prepare(
16611
+ `SELECT from_id, to_id FROM edges
16612
+ WHERE (valid_to IS NULL AND (created_at > :ts OR last_seen_at > :ts))
16613
+ OR (valid_to IS NOT NULL AND valid_to > :ts)`
16614
+ ).all({ ts });
16615
+ return rows.map((r) => ({ from: r.from_id, to: r.to_id }));
16616
+ }
16617
+ /** Lightweight out-edges for a SET of sources, chunked IN-lists — the region
16618
+ * assembly path for incremental PageRank (per-node outEdges() at region
16619
+ * scale re-creates the 106k-prepared-calls problem the batch scan solved). */
16620
+ outEdgesForMany(ids) {
16621
+ return this.edgesForMany(ids, "from_id");
16622
+ }
16623
+ inEdgesForMany(ids) {
16624
+ return this.edgesForMany(ids, "to_id");
16625
+ }
16626
+ /** Stored pageRank for a SET of ids (live rows only — a closed id is simply
16627
+ * absent, which is how incremental region assembly drops dead endpoints). */
16628
+ ranksForMany(ids) {
16629
+ const out = /* @__PURE__ */ new Map();
16630
+ const CHUNK = 400;
16631
+ for (let i = 0; i < ids.length; i += CHUNK) {
16632
+ const chunk = ids.slice(i, i + CHUNK);
16633
+ const placeholders = chunk.map(() => "?").join(", ");
16634
+ const rows = this.db.prepare(
16635
+ `SELECT id, page_rank FROM nodes WHERE id IN (${placeholders}) AND valid_to IS NULL`
16636
+ ).all(...chunk);
16637
+ for (const r of rows) out.set(r.id, r.page_rank);
16638
+ }
16639
+ return out;
16640
+ }
16641
+ /** The minimal row set the landmark sweep needs — landmark CANDIDATES
16642
+ * (Pattern/RootCause), everything currently flagged, and every
16643
+ * persistent-tier node (force-landmarks). A few thousand rows, so the
16644
+ * incremental path can refresh the GLOBAL landmark set without the 179k-row
16645
+ * scanLiveNodes materialization. */
16646
+ landmarkSweepRows() {
16647
+ const rows = this.db.prepare(
16648
+ `SELECT id, label, page_rank, is_landmark, memory_tier, extraction_source FROM nodes
16649
+ WHERE valid_to IS NULL
16650
+ AND (memory_tier = 'persistent' OR is_landmark = 1 OR label IN ('Pattern', 'RootCause'))`
16651
+ ).all();
16652
+ return rows.map((r) => ({
16653
+ id: r.id,
16654
+ label: r.label,
16655
+ pageRank: r.page_rank,
16656
+ isLandmark: r.is_landmark === 1,
16657
+ memoryTier: r.memory_tier,
16658
+ extractionSource: r.extraction_source
16659
+ }));
16660
+ }
16661
+ edgesForMany(ids, col) {
16662
+ const out = [];
16663
+ const CHUNK = 400;
16664
+ for (let i = 0; i < ids.length; i += CHUNK) {
16665
+ const chunk = ids.slice(i, i + CHUNK);
16666
+ const placeholders = chunk.map(() => "?").join(", ");
16667
+ const rows = this.db.prepare(
16668
+ `SELECT from_id, to_id, type FROM edges WHERE ${col} IN (${placeholders}) AND valid_to IS NULL`
16669
+ ).all(...chunk);
16670
+ for (const r of rows) out.push({ from: r.from_id, to: r.to_id, type: r.type });
16671
+ }
16672
+ return out;
16673
+ }
16674
+ checkpointWal() {
16675
+ try {
16676
+ const r = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
16677
+ return r ?? null;
16678
+ } catch {
16679
+ return null;
16680
+ }
16681
+ }
16682
+ reviveReobserved(relPaths, workspaceId, since) {
16683
+ let revived = 0;
16684
+ const CHUNK = 400;
16685
+ for (let i = 0; i < relPaths.length; i += CHUNK) {
16686
+ const slice = relPaths.slice(i, i + CHUNK);
16687
+ if (slice.length === 0) continue;
16688
+ const r = this.db.prepare(
16689
+ `UPDATE nodes SET valid_to = NULL
16690
+ WHERE valid_to IS NOT NULL
16691
+ AND last_updated_at >= ?
16692
+ AND json_extract(attrs_json, '$.workspaceId') = ?
16693
+ AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})`
16694
+ ).run(since, workspaceId, ...slice);
16695
+ revived += Number(r.changes);
16696
+ }
16697
+ if (revived > 0) this.mutations++;
16698
+ return revived;
16699
+ }
16700
+ recentEdgeAttrCoverage(marker, attr, limit) {
16701
+ const r = this.db.prepare(
16702
+ `SELECT COUNT(*) AS total,
16703
+ COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
16704
+ FROM (SELECT attrs_json FROM edges
16705
+ WHERE valid_to IS NULL AND attrs_json LIKE ?
16706
+ ORDER BY created_at DESC LIMIT ?)`
16707
+ ).get(attr, `%${marker}%`, limit);
16708
+ return { count: Number(r.count), total: Number(r.total) };
16709
+ }
16710
+ liveNodeIds(ids) {
16711
+ const live = /* @__PURE__ */ new Set();
16712
+ const CHUNK = 900;
16713
+ for (let i = 0; i < ids.length; i += CHUNK) {
16714
+ const slice = ids.slice(i, i + CHUNK);
16715
+ if (slice.length === 0) continue;
16716
+ const rows = this.db.prepare(
16717
+ `SELECT id FROM nodes WHERE valid_to IS NULL AND id IN (${slice.map(() => "?").join(",")})`
16718
+ ).all(...slice);
16719
+ for (const r of rows) live.add(r.id);
16720
+ }
16721
+ return live;
16722
+ }
16723
+ /**
16724
+ * Live edges WITH their endpoint labels and ids, resolved in ONE join.
16725
+ *
16726
+ * The ontology sweep needs (id, type, fromLabel, toLabel) for every live edge.
16727
+ * Doing that as `scanLiveEdges()` + two `getNode()` calls is 2N node reads —
16728
+ * 550,000 on this store — and each one deserializes the node's embedding blob.
16729
+ * Measured: the sweep did not finish in 10 minutes. As a single join it is one
16730
+ * query over an index-covered scan. Labels only; nothing here touches embeddings.
16731
+ */
16732
+ scanLiveEdgeRows() {
16733
+ const rows = this.db.prepare(
16734
+ `SELECT e.id, e.from_id, e.to_id, e.type, a.label AS from_label, b.label AS to_label
16735
+ FROM edges e
16736
+ LEFT JOIN nodes a ON a.id = e.from_id AND a.valid_to IS NULL
16737
+ LEFT JOIN nodes b ON b.id = e.to_id AND b.valid_to IS NULL
16738
+ WHERE e.valid_to IS NULL`
16739
+ ).all();
16740
+ return rows.map((r) => ({
16741
+ id: r.id,
16742
+ from: r.from_id,
16743
+ to: r.to_id,
16744
+ type: r.type,
16745
+ fromLabel: r.from_label ?? void 0,
16746
+ toLabel: r.to_label ?? void 0
16747
+ }));
16748
+ }
16425
16749
  /** Live nodes whose attrs.relPath is one of `relPaths`. Uses the nodes_relpath
16426
16750
  * expression index so the incremental reindex fetches only the changed files'
16427
16751
  * symbols instead of scanning every versioned node. */
@@ -16442,6 +16766,10 @@ var SqliteGraphStore = class {
16442
16766
  if (!live) return null;
16443
16767
  const version2 = live.version ?? 1;
16444
16768
  const frozenId = `${liveId}@v${version2}`;
16769
+ if (this.getNode(frozenId)) {
16770
+ this.stmts.advanceLive.run({ live_id: liveId, t });
16771
+ return frozenId;
16772
+ }
16445
16773
  this.stmts.freezeCopy.run({ frozen_id: frozenId, live_id: liveId, t });
16446
16774
  this.mergeEdge({
16447
16775
  id: `edge_superseded_${frozenId}`,
@@ -16500,6 +16828,7 @@ var CAUSAL_FAMILY = [
16500
16828
 
16501
16829
  // ../../packages/local-graph/src/justification.ts
16502
16830
  init_src();
16831
+ init_src2();
16503
16832
 
16504
16833
  // ../../packages/local-graph/src/credit.ts
16505
16834
  init_src2();
@@ -16516,11 +16845,31 @@ init_src();
16516
16845
  // ../../packages/local-graph/src/design-problem.ts
16517
16846
  init_src();
16518
16847
  init_src();
16519
- init_src2();
16520
16848
 
16521
16849
  // ../../packages/local-graph/src/problem-package-link.ts
16522
16850
  init_src();
16523
16851
 
16852
+ // ../../packages/local-graph/src/design-problem.ts
16853
+ init_src2();
16854
+
16855
+ // ../../packages/local-graph/src/problem-dedup.ts
16856
+ init_src();
16857
+ init_src();
16858
+
16859
+ // ../../packages/local-graph/src/design-problem.ts
16860
+ var RECURRENCE_DEBOUNCE_MS = 60 * 60 * 1e3;
16861
+
16862
+ // ../../packages/local-graph/src/intent.ts
16863
+ init_src();
16864
+
16865
+ // ../../packages/local-graph/src/anchor-backfill.ts
16866
+ init_src();
16867
+ init_src2();
16868
+ var STATEMENT_PATH_RE = new RegExp(
16869
+ String.raw`(?:[\w.+-]+\/)+[\w.+-]+\.(?:${anchorableExtensionAlternation()})`,
16870
+ "g"
16871
+ );
16872
+
16524
16873
  // ../../packages/local-graph/src/percolate.ts
16525
16874
  init_src2();
16526
16875
  var PERCOLATING_LABELS = ["Claim", "Problem", "Solution"];
@@ -16791,30 +17140,19 @@ function detectCommunitiesLeiden(input, opts = {}) {
16791
17140
  return normalize(assignment);
16792
17141
  }
16793
17142
 
16794
- // ../../packages/local-graph/src/abstraction.ts
16795
- var DEFAULT_MIN_CLUSTER_SIZE = 3;
16796
- var DEFAULT_MIN_DISTINCT_CONTEXTS = 2;
16797
- function sharedScope(l2, memberIds) {
16798
- const shared = (field) => {
16799
- const vals = /* @__PURE__ */ new Set();
16800
- for (const id of memberIds) {
16801
- const s = l2.getNode(id)?.attrs["scope"] ?? {};
16802
- if (typeof s[field] === "string") vals.add(s[field]);
16803
- }
16804
- return vals.size === 1 ? [...vals][0] : void 0;
16805
- };
16806
- const lang = shared("lang");
16807
- const versionRange = shared("versionRange");
16808
- return { ...lang ? { lang } : {}, ...versionRange ? { versionRange } : {} };
16809
- }
16810
- function buildCandidate(id, ts, members, contexts, originMachine, scope) {
17143
+ // ../../packages/local-graph/src/triage.ts
17144
+ init_src();
17145
+ init_src();
17146
+ init_src();
17147
+ init_src2();
17148
+ function semNode(store, id, label, description, ts, attrs) {
17149
+ const seq = SEMANTIC_NODE_LABELS.includes(label) ? store.nextIngestSeq() : void 0;
16811
17150
  return {
16812
17151
  id,
16813
- label: "Claim",
16814
- description: "",
16815
- // empty — the harness distills the prose in P3
16816
- extractionConfidence: 0.4,
16817
- extractionSource: "agent-observed",
17152
+ label,
17153
+ description,
17154
+ extractionConfidence: 0.5,
17155
+ extractionSource: "daemon-extracted",
16818
17156
  embedding: [],
16819
17157
  cumulativeSurprise: 0,
16820
17158
  peakSurprise: 0,
@@ -16826,250 +17164,56 @@ function buildCandidate(id, ts, members, contexts, originMachine, scope) {
16826
17164
  isLandmark: false,
16827
17165
  community: null,
16828
17166
  stability: "unstable",
16829
- attrs: {
16830
- abstractionLevel: ABSTRACTION_LEVEL.PRINCIPLE,
16831
- kind: "abstraction-candidate",
16832
- provisional: true,
16833
- pendingDistillation: true,
16834
- // membership is the unit of truth — the harness validates its fence against
16835
- // this set (P3 / C3) and may only phrase, never alter, it.
16836
- members,
16837
- memberContexts: contexts,
16838
- distinctContexts: contexts.length,
16839
- // standard Claim envelope so it crystallizes/syncs unchanged once distilled
16840
- truthKind: "contextual",
16841
- // Derived from members (P1): a single-language cluster becomes a lang-scoped
16842
- // principle; a cross-language one stays universal (`{}`).
16843
- scope,
16844
- groundedSupport: 0,
16845
- sources: [],
16846
- crystallized: "hypothesis",
16847
- confidence: 0.4,
16848
- ...originMachine ? { originMachine } : {}
16849
- }
17167
+ ...seq !== void 0 ? { createdAtSeq: seq, lastReinforcedAtSeq: seq } : {},
17168
+ attrs
16850
17169
  };
16851
17170
  }
16852
- function mergeGeneralizes(store, principleId, memberId, ts) {
16853
- store.mergeEdge({
16854
- id: `edge_${digest({ from: principleId, type: "GENERALIZES", to: memberId })}`.slice(0, 24),
16855
- from: principleId,
16856
- to: memberId,
16857
- type: "GENERALIZES",
16858
- confidence: 0.4,
16859
- extractionSource: "agent-observed",
17171
+ function semEdge(id, from, to, type, ts, attrs) {
17172
+ return {
17173
+ id,
17174
+ from,
17175
+ to,
17176
+ type,
17177
+ confidence: 0.5,
17178
+ extractionSource: "daemon-extracted",
16860
17179
  createdAt: ts,
16861
17180
  lastSeenAt: ts,
16862
17181
  navSuccesses: 0,
16863
17182
  navFailures: 0,
16864
- attrs: { provisional: true }
16865
- });
16866
- }
16867
- function induceAbstractions(l2, opts) {
16868
- const K = opts.minClusterSize ?? DEFAULT_MIN_CLUSTER_SIZE;
16869
- const minCtx = opts.minDistinctContexts ?? DEFAULT_MIN_DISTINCT_CONTEXTS;
16870
- const report = {
16871
- communities: 0,
16872
- candidatesMinted: 0,
16873
- candidatesExisting: 0,
16874
- skippedSmall: 0,
16875
- skippedSingleContext: 0,
16876
- generalizesEdges: 0
17183
+ attrs
16877
17184
  };
16878
- const ids = /* @__PURE__ */ new Set();
16879
- for (const label of PERCOLATING_LABELS) {
16880
- for (const n of l2.findNodesByLabel(label)) {
16881
- if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) >= ABSTRACTION_LEVEL.PRINCIPLE) {
16882
- continue;
16883
- }
16884
- if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
16885
- ids.add(n.id);
17185
+ }
17186
+ var routeEdgeId = (triageId, causeId) => `edge_rt_${triageId}_${causeId}`;
17187
+ var ROUTE_EDGE_TYPES = ["CONFIRMS", "INDICATES", "ROUTES_TO"];
17188
+ var routeTypeFor = (provisional) => provisional ? "INDICATES" : "CONFIRMS";
17189
+ var isRouterRoute = (e) => e.type === "CONFIRMS" || e.type === "ROUTES_TO" && e.attrs["provisional"] === false;
17190
+ function recordTriageObservation(l2, obs, ts) {
17191
+ const statement = obs.presentingStatement.trim();
17192
+ const presentingId = obs.presentingId?.trim() || identityId({ kind: "DesignProblem", statement });
17193
+ const triageId = identityId({ kind: "Triage", statement });
17194
+ const routeId = routeEdgeId(triageId, obs.causeId);
17195
+ const buckets = [...triageContextBuckets(canonicalizeContext(obs.context)), TRIAGE_GLOBAL_BUCKET];
17196
+ l2.transaction(() => {
17197
+ if (!l2.getNode(presentingId)) {
17198
+ l2.mergeNode(semNode(l2, presentingId, "Problem", statement, ts, { scope: {} }));
16886
17199
  }
16887
- }
16888
- if (ids.size === 0) return report;
16889
- const adj = /* @__PURE__ */ new Map();
16890
- const protect = [];
16891
- const add = (a, b, w) => {
16892
- const l = adj.get(a) ?? [];
16893
- l.push({ to: b, weight: w });
16894
- adj.set(a, l);
16895
- };
16896
- for (const id of ids) {
16897
- for (const e of l2.outEdges(id, [...PERCOLATING_EDGES])) {
16898
- if (!ids.has(e.to)) continue;
16899
- const conf = e.confidence > 0 ? e.confidence : 0.5;
16900
- const w = Math.max(EDGE_WEIGHT[e.type] ?? 1, 0.1) * conf;
16901
- add(id, e.to, w);
16902
- add(e.to, id, w);
16903
- if (isCausalProtected(e.type)) protect.push([id, e.to]);
16904
- }
16905
- }
16906
- const comm = detectCommunitiesLeiden({ nodeIds: [...ids], adj }, { protect });
16907
- report.communities = comm.count;
16908
- const members = /* @__PURE__ */ new Map();
16909
- for (const [id, c] of comm.community) {
16910
- const l = members.get(c) ?? [];
16911
- l.push(id);
16912
- members.set(c, l);
16913
- }
16914
- l2.transaction(() => {
16915
- for (const mem of members.values()) {
16916
- if (opts.touched && !mem.some((id) => opts.touched.has(id))) continue;
16917
- if (mem.length < K) {
16918
- report.skippedSmall++;
16919
- continue;
16920
- }
16921
- if (mem.some((id) => l2.inEdges(id, ["GENERALIZES"]).length > 0)) {
16922
- report.candidatesExisting++;
16923
- continue;
16924
- }
16925
- const contexts = /* @__PURE__ */ new Set();
16926
- for (const id of mem) {
16927
- const n = l2.getNode(id);
16928
- const obs = n?.attrs["observedInProjects"];
16929
- if (Array.isArray(obs)) {
16930
- for (const ws of obs) contexts.add(opts.contextOf(ws) ?? `project:${ws}`);
16931
- }
16932
- }
16933
- if (contexts.size < minCtx) {
16934
- report.skippedSingleContext++;
16935
- continue;
16936
- }
16937
- const sorted = [...mem].sort();
16938
- const principleId = `princ_${digest({ members: sorted })}`.slice(0, 56);
16939
- l2.mergeNode(
16940
- buildCandidate(principleId, opts.ts, sorted, [...contexts].sort(), opts.originMachine, sharedScope(l2, sorted))
16941
- );
16942
- for (const id of sorted) {
16943
- mergeGeneralizes(l2, principleId, id, opts.ts);
16944
- report.generalizesEdges++;
16945
- }
16946
- report.candidatesMinted++;
16947
- }
16948
- });
16949
- return report;
16950
- }
16951
- function revisitContradictedPrinciples(l2, ts) {
16952
- const report = { flagged: 0 };
16953
- l2.transaction(() => {
16954
- for (const n of l2.findNodesByLabel("Claim")) {
16955
- if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) continue;
16956
- if (n.attrs["revisit"] === true) continue;
16957
- const members = Array.isArray(n.attrs["members"]) ? n.attrs["members"] : [];
16958
- const memberSet = new Set(members);
16959
- let reason = "";
16960
- for (const id of members) {
16961
- const m = l2.getNode(id);
16962
- if (!m) {
16963
- reason = `member ${id} was removed`;
16964
- break;
16965
- }
16966
- if (m.attrs["revisit"] === true) {
16967
- reason = `member "${m.description}" needs revisit`;
16968
- break;
16969
- }
16970
- const contradictors = [
16971
- ...l2.outEdges(id, ["CONTRADICTS"]).map((e) => e.to),
16972
- ...l2.inEdges(id, ["CONTRADICTS"]).map((e) => e.from)
16973
- ];
16974
- if (contradictors.some((other) => !memberSet.has(other))) {
16975
- reason = `member "${m.description}" is now contradicted by external evidence`;
16976
- break;
16977
- }
16978
- }
16979
- if (!reason) continue;
16980
- l2.updateNode(n.id, {
16981
- attrs: {
16982
- ...n.attrs,
16983
- revisit: true,
16984
- revisitReason: reason,
16985
- revisitSinceTs: ts,
16986
- provisional: true,
16987
- // re-queue for re-distillation (P3 re-name)
16988
- pendingDistillation: true
16989
- },
16990
- lastUpdatedAt: ts
16991
- });
16992
- report.flagged++;
16993
- }
16994
- });
16995
- return report;
16996
- }
16997
-
16998
- // ../../packages/local-graph/src/community.ts
16999
- init_src2();
17000
-
17001
- // ../../packages/local-graph/src/triage.ts
17002
- init_src();
17003
- init_src();
17004
- init_src();
17005
- init_src2();
17006
- function semNode(id, label, description, ts, attrs) {
17007
- return {
17008
- id,
17009
- label,
17010
- description,
17011
- extractionConfidence: 0.5,
17012
- extractionSource: "daemon-extracted",
17013
- embedding: [],
17014
- cumulativeSurprise: 0,
17015
- peakSurprise: 0,
17016
- cumulativeHits: 1,
17017
- lastUpdatedAt: ts,
17018
- createdAt: ts,
17019
- memoryTier: "short-term",
17020
- pageRank: 0,
17021
- isLandmark: false,
17022
- community: null,
17023
- stability: "unstable",
17024
- attrs
17025
- };
17026
- }
17027
- function semEdge(id, from, to, type, ts, attrs) {
17028
- return {
17029
- id,
17030
- from,
17031
- to,
17032
- type,
17033
- confidence: 0.5,
17034
- extractionSource: "daemon-extracted",
17035
- createdAt: ts,
17036
- lastSeenAt: ts,
17037
- navSuccesses: 0,
17038
- navFailures: 0,
17039
- attrs
17040
- };
17041
- }
17042
- var routeEdgeId = (triageId, causeId) => `edge_rt_${triageId}_${causeId}`;
17043
- var ROUTE_EDGE_TYPES = ["CONFIRMS", "INDICATES", "ROUTES_TO"];
17044
- var routeTypeFor = (provisional) => provisional ? "INDICATES" : "CONFIRMS";
17045
- var isRouterRoute = (e) => e.type === "CONFIRMS" || e.type === "ROUTES_TO" && e.attrs["provisional"] === false;
17046
- function recordTriageObservation(l2, obs, ts) {
17047
- const statement = obs.presentingStatement.trim();
17048
- const presentingId = obs.presentingId?.trim() || identityId({ kind: "DesignProblem", statement });
17049
- const triageId = identityId({ kind: "Triage", statement });
17050
- const routeId = routeEdgeId(triageId, obs.causeId);
17051
- const buckets = [...triageContextBuckets(canonicalizeContext(obs.context)), TRIAGE_GLOBAL_BUCKET];
17052
- l2.transaction(() => {
17053
- if (!l2.getNode(presentingId)) {
17054
- l2.mergeNode(semNode(presentingId, "Problem", statement, ts, { scope: {} }));
17055
- }
17056
- const tri = l2.getNode(triageId);
17057
- const seen = {
17058
- ...tri?.attrs["perContextSeen"] ?? {}
17059
- };
17060
- for (const b of buckets) seen[b] = (seen[b] ?? 0) + 1;
17061
- if (tri) {
17062
- l2.updateNode(triageId, {
17063
- attrs: { ...tri.attrs, statement, perContextSeen: seen },
17064
- lastUpdatedAt: ts
17065
- });
17066
- } else {
17067
- l2.mergeNode(
17068
- semNode(triageId, "Triage", `differential for: ${statement}`, ts, {
17069
- statement,
17070
- perContextSeen: seen
17071
- })
17072
- );
17200
+ const tri = l2.getNode(triageId);
17201
+ const seen = {
17202
+ ...tri?.attrs["perContextSeen"] ?? {}
17203
+ };
17204
+ for (const b of buckets) seen[b] = (seen[b] ?? 0) + 1;
17205
+ if (tri) {
17206
+ l2.updateNode(triageId, {
17207
+ attrs: { ...tri.attrs, statement, perContextSeen: seen },
17208
+ lastUpdatedAt: ts
17209
+ });
17210
+ } else {
17211
+ l2.mergeNode(
17212
+ semNode(l2, triageId, "Triage", `differential for: ${statement}`, ts, {
17213
+ statement,
17214
+ perContextSeen: seen
17215
+ })
17216
+ );
17073
17217
  }
17074
17218
  const tbId = `edge_tb_${presentingId}_${triageId}`;
17075
17219
  if (!l2.getEdge(tbId)) {
@@ -17077,7 +17221,7 @@ function recordTriageObservation(l2, obs, ts) {
17077
17221
  }
17078
17222
  if (!l2.getNode(obs.causeId)) {
17079
17223
  l2.mergeNode(
17080
- semNode(obs.causeId, obs.causeLabel ?? "RootCause", obs.causeDescription ?? "(cause)", ts, {
17224
+ semNode(l2, obs.causeId, obs.causeLabel ?? "RootCause", obs.causeDescription ?? "(cause)", ts, {
17081
17225
  scope: {}
17082
17226
  })
17083
17227
  );
@@ -17250,9 +17394,212 @@ function revisitStaleRoutes(l2, ts, opts = {}) {
17250
17394
  }
17251
17395
  var DRIFT_VALUES = new Set(Object.values(DRIFT_KIND));
17252
17396
 
17253
- // ../../packages/local-graph/src/problem-dedup.ts
17254
- init_src();
17255
- init_src();
17397
+ // ../../packages/local-graph/src/abstraction.ts
17398
+ var DEFAULT_MIN_CLUSTER_SIZE = 3;
17399
+ var DEFAULT_MIN_DISTINCT_CONTEXTS = 2;
17400
+ function sharedScope(l2, memberIds) {
17401
+ const shared = (field) => {
17402
+ const vals = /* @__PURE__ */ new Set();
17403
+ for (const id of memberIds) {
17404
+ const s = l2.getNode(id)?.attrs["scope"] ?? {};
17405
+ if (typeof s[field] === "string") vals.add(s[field]);
17406
+ }
17407
+ return vals.size === 1 ? [...vals][0] : void 0;
17408
+ };
17409
+ const lang = shared("lang");
17410
+ const versionRange = shared("versionRange");
17411
+ return { ...lang ? { lang } : {}, ...versionRange ? { versionRange } : {} };
17412
+ }
17413
+ function buildCandidate(id, ts, members, contexts, originMachine, scope) {
17414
+ return {
17415
+ id,
17416
+ label: "Claim",
17417
+ description: "",
17418
+ // empty — the harness distills the prose in P3
17419
+ extractionConfidence: 0.4,
17420
+ extractionSource: "agent-observed",
17421
+ embedding: [],
17422
+ cumulativeSurprise: 0,
17423
+ peakSurprise: 0,
17424
+ cumulativeHits: 1,
17425
+ lastUpdatedAt: ts,
17426
+ createdAt: ts,
17427
+ memoryTier: "short-term",
17428
+ pageRank: 0,
17429
+ isLandmark: false,
17430
+ community: null,
17431
+ stability: "unstable",
17432
+ attrs: {
17433
+ abstractionLevel: ABSTRACTION_LEVEL.PRINCIPLE,
17434
+ kind: "abstraction-candidate",
17435
+ provisional: true,
17436
+ pendingDistillation: true,
17437
+ // membership is the unit of truth — the harness validates its fence against
17438
+ // this set (P3 / C3) and may only phrase, never alter, it.
17439
+ members,
17440
+ memberContexts: contexts,
17441
+ distinctContexts: contexts.length,
17442
+ // standard Claim envelope so it crystallizes/syncs unchanged once distilled
17443
+ truthKind: "contextual",
17444
+ // Derived from members (P1): a single-language cluster becomes a lang-scoped
17445
+ // principle; a cross-language one stays universal (`{}`).
17446
+ scope,
17447
+ groundedSupport: 0,
17448
+ sources: [],
17449
+ crystallized: "hypothesis",
17450
+ confidence: 0.4,
17451
+ ...originMachine ? { originMachine } : {}
17452
+ }
17453
+ };
17454
+ }
17455
+ function mergeGeneralizes(store, principleId, memberId, ts) {
17456
+ store.mergeEdge({
17457
+ id: `edge_${digest({ from: principleId, type: "GENERALIZES", to: memberId })}`.slice(0, 24),
17458
+ from: principleId,
17459
+ to: memberId,
17460
+ type: "GENERALIZES",
17461
+ confidence: 0.4,
17462
+ extractionSource: "agent-observed",
17463
+ createdAt: ts,
17464
+ lastSeenAt: ts,
17465
+ navSuccesses: 0,
17466
+ navFailures: 0,
17467
+ attrs: { provisional: true }
17468
+ });
17469
+ }
17470
+ function induceAbstractions(l2, opts) {
17471
+ const K = opts.minClusterSize ?? DEFAULT_MIN_CLUSTER_SIZE;
17472
+ const minCtx = opts.minDistinctContexts ?? DEFAULT_MIN_DISTINCT_CONTEXTS;
17473
+ const report = {
17474
+ communities: 0,
17475
+ candidatesMinted: 0,
17476
+ candidatesExisting: 0,
17477
+ skippedSmall: 0,
17478
+ skippedSingleContext: 0,
17479
+ generalizesEdges: 0
17480
+ };
17481
+ const ids = /* @__PURE__ */ new Set();
17482
+ for (const label of PERCOLATING_LABELS) {
17483
+ for (const n of l2.findNodesByLabel(label)) {
17484
+ if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) >= ABSTRACTION_LEVEL.PRINCIPLE) {
17485
+ continue;
17486
+ }
17487
+ if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
17488
+ ids.add(n.id);
17489
+ }
17490
+ }
17491
+ if (ids.size === 0) return report;
17492
+ const adj = /* @__PURE__ */ new Map();
17493
+ const protect = [];
17494
+ const add = (a, b, w) => {
17495
+ const l = adj.get(a) ?? [];
17496
+ l.push({ to: b, weight: w });
17497
+ adj.set(a, l);
17498
+ };
17499
+ for (const id of ids) {
17500
+ for (const e of l2.outEdges(id, [...PERCOLATING_EDGES])) {
17501
+ if (!ids.has(e.to)) continue;
17502
+ const conf = e.confidence > 0 ? e.confidence : 0.5;
17503
+ const w = Math.max(EDGE_WEIGHT[e.type] ?? 1, 0.1) * conf;
17504
+ add(id, e.to, w);
17505
+ add(e.to, id, w);
17506
+ if (isCausalProtected(e.type)) protect.push([id, e.to]);
17507
+ }
17508
+ }
17509
+ const comm = detectCommunitiesLeiden({ nodeIds: [...ids], adj }, { protect });
17510
+ report.communities = comm.count;
17511
+ const members = /* @__PURE__ */ new Map();
17512
+ for (const [id, c] of comm.community) {
17513
+ const l = members.get(c) ?? [];
17514
+ l.push(id);
17515
+ members.set(c, l);
17516
+ }
17517
+ l2.transaction(() => {
17518
+ for (const mem of members.values()) {
17519
+ if (opts.touched && !mem.some((id) => opts.touched.has(id))) continue;
17520
+ if (mem.length < K) {
17521
+ report.skippedSmall++;
17522
+ continue;
17523
+ }
17524
+ if (mem.some((id) => l2.inEdges(id, ["GENERALIZES"]).length > 0)) {
17525
+ report.candidatesExisting++;
17526
+ continue;
17527
+ }
17528
+ const contexts = /* @__PURE__ */ new Set();
17529
+ for (const id of mem) {
17530
+ const n = l2.getNode(id);
17531
+ const obs = n?.attrs["observedInProjects"];
17532
+ if (Array.isArray(obs)) {
17533
+ for (const ws of obs) contexts.add(opts.contextOf(ws) ?? `project:${ws}`);
17534
+ }
17535
+ }
17536
+ if (contexts.size < minCtx) {
17537
+ report.skippedSingleContext++;
17538
+ continue;
17539
+ }
17540
+ const sorted = [...mem].sort();
17541
+ const principleId = `princ_${digest({ members: sorted })}`.slice(0, 56);
17542
+ l2.mergeNode(
17543
+ buildCandidate(principleId, opts.ts, sorted, [...contexts].sort(), opts.originMachine, sharedScope(l2, sorted))
17544
+ );
17545
+ for (const id of sorted) {
17546
+ mergeGeneralizes(l2, principleId, id, opts.ts);
17547
+ report.generalizesEdges++;
17548
+ }
17549
+ report.candidatesMinted++;
17550
+ }
17551
+ });
17552
+ return report;
17553
+ }
17554
+ function revisitContradictedPrinciples(l2, ts) {
17555
+ const report = { flagged: 0 };
17556
+ l2.transaction(() => {
17557
+ for (const n of l2.findNodesByLabel("Claim")) {
17558
+ if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) continue;
17559
+ if (n.attrs["revisit"] === true) continue;
17560
+ const members = Array.isArray(n.attrs["members"]) ? n.attrs["members"] : [];
17561
+ const memberSet = new Set(members);
17562
+ let reason = "";
17563
+ for (const id of members) {
17564
+ const m = l2.getNode(id);
17565
+ if (!m) {
17566
+ reason = `member ${id} was removed`;
17567
+ break;
17568
+ }
17569
+ if (m.attrs["revisit"] === true) {
17570
+ reason = `member "${m.description}" needs revisit`;
17571
+ break;
17572
+ }
17573
+ const contradictors = [
17574
+ ...l2.outEdges(id, ["CONTRADICTS"]).map((e) => e.to),
17575
+ ...l2.inEdges(id, ["CONTRADICTS"]).map((e) => e.from)
17576
+ ];
17577
+ if (contradictors.some((other) => !memberSet.has(other))) {
17578
+ reason = `member "${m.description}" is now contradicted by external evidence`;
17579
+ break;
17580
+ }
17581
+ }
17582
+ if (!reason) continue;
17583
+ l2.updateNode(n.id, {
17584
+ attrs: {
17585
+ ...n.attrs,
17586
+ revisit: true,
17587
+ revisitReason: reason,
17588
+ revisitSinceTs: ts,
17589
+ provisional: true,
17590
+ // re-queue for re-distillation (P3 re-name)
17591
+ pendingDistillation: true
17592
+ },
17593
+ lastUpdatedAt: ts
17594
+ });
17595
+ report.flagged++;
17596
+ }
17597
+ });
17598
+ return report;
17599
+ }
17600
+
17601
+ // ../../packages/local-graph/src/community.ts
17602
+ init_src2();
17256
17603
 
17257
17604
  // ../../packages/local-graph/src/tools.ts
17258
17605
  init_src();
@@ -17260,6 +17607,9 @@ init_src();
17260
17607
  // ../../packages/local-graph/src/principle-sync.ts
17261
17608
  init_src2();
17262
17609
 
17610
+ // ../../packages/local-graph/src/mechanism-liveness.ts
17611
+ var STALL_MIN_SPAN_MS = 60 * 60 * 1e3;
17612
+
17263
17613
  // src/reconcile.ts
17264
17614
  init_src3();
17265
17615
  function isLive(n) {