@inerrata-corporation/errata 2.0.2-dev.77 → 2.0.2-dev.789

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.
@@ -133,7 +133,14 @@ var init_castalia = __esm({
133
133
  "SOLVED_BY",
134
134
  "MITIGATES",
135
135
  "REPORTED_FAILURE",
136
- "CONTRADICTS"
136
+ "CONTRADICTS",
137
+ // Motif twin-faces (KN-twin-faces): failure-face motif → remedy-face motif
138
+ // across layers (AntiPattern/Weakness ↔ Technique/Pattern). Same
139
+ // problem→fix direction as FIXED_BY/SOLVED_BY. Minted by the nightly LLM
140
+ // twin-face pass — these are the near-identical cross-layer pairs the
141
+ // polarity gate (finding 5) correctly refuses to FUSE; the link carries
142
+ // what fusion can't.
143
+ "REMEDIED_BY"
137
144
  ];
138
145
  CONCEPTUAL_EDGES = [
139
146
  "INSTANCE_OF",
@@ -223,6 +230,10 @@ var init_castalia = __esm({
223
230
  CAUSED_BY: 3,
224
231
  FIXED_BY: 3,
225
232
  SOLVED_BY: 3,
233
+ // Twin-face link (failure motif → remedy motif, KN-twin-faces) — causal-grade
234
+ // but a notch below witnessed FIXED_BY/SOLVED_BY: the pairing is an LLM
235
+ // judgment over descriptions, not an agent-witnessed resolution.
236
+ REMEDIED_BY: 2.5,
226
237
  MANIFESTS_AS: 2,
227
238
  ESCALATES_TO: 1.5,
228
239
  AFFECTS: 1.2,
@@ -662,7 +673,7 @@ function lemma(token) {
662
673
  }
663
674
  return token;
664
675
  }
665
- function conceptTokens(text) {
676
+ function tokenize(text, resolve) {
666
677
  const matches = text.normalize("NFC").toLowerCase().match(TOKEN_RE) ?? [];
667
678
  const out = /* @__PURE__ */ new Set();
668
679
  for (const raw of matches) {
@@ -672,11 +683,14 @@ function conceptTokens(text) {
672
683
  out.add(token);
673
684
  continue;
674
685
  }
675
- const canonical = resolveCanonicalId(token);
686
+ const canonical = resolve(token);
676
687
  out.add(canonical ?? lemma(token));
677
688
  }
678
689
  return [...out].sort();
679
690
  }
691
+ function conceptTokens(text) {
692
+ return tokenize(text, resolveCanonicalId);
693
+ }
680
694
  function conceptBag(text) {
681
695
  return conceptTokens(text).join(" ");
682
696
  }
@@ -15194,13 +15208,21 @@ var init_edge_rules = __esm({
15194
15208
  // NOT ruled here — the type pre-exists with broader extractor senses, and a
15195
15209
  // new rule on an old type would reject legitimate live flows (reject-never-flip
15196
15210
  // cuts both ways: only rule types you introduce or senses that are documented).
15197
- CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool"] },
15211
+ // `Component` joined the target set with OM-agent-anchors: an agent-named
15212
+ // component ("React Router") is the same knowledge→named-unit anchor shape as
15213
+ // a Tool — the knowledge is ABOUT it, not dependent on it.
15214
+ CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool", "Component"] },
15198
15215
  OPERATES_ON: { from: ["Algorithm"], to: ["DataStructure"] },
15199
15216
  INVOLVES: { from: ["Problem", "Solution"], to: ["DataStructure"] },
15200
15217
  // ── Taxonomy (Track 4 Stream C: MITRE CWE `ChildOf` → `Weakness —IS_A→ Weakness`) ──
15201
15218
  IS_A: { from: ["Weakness"], to: ["Weakness"] },
15202
15219
  // ── Conceptual (v1 taxonomy.ts: instance → motif/pattern reference) ──
15203
- INSTANCE_OF: { to: ["Pattern", "Weakness", "Technique"] },
15220
+ // `AntiPattern` joined the target set 2026-08-02: it is one of the three motif
15221
+ // kinds (generalizer motifs.ts `layerOf`: pattern | technique | antipattern) and
15222
+ // the SUPPRESSED-close path mints `Problem ─INSTANCE_OF→ AntiPattern` as the
15223
+ // negative-knowledge binding ("silenced, not solved") — the original three-label
15224
+ // rule predates AntiPattern joining the motif layer and silently ate that edge.
15225
+ INSTANCE_OF: { to: ["Pattern", "AntiPattern", "Weakness", "Technique"] },
15204
15226
  IMPLEMENTS: { from: ["Solution", "Language", "Component"], to: ["Pattern", "Technique"] },
15205
15227
  MATCHES: { to: ["Pattern"] },
15206
15228
  // ── Artifact evidence (v1 taxonomy.ts artifact rows) ──
@@ -15301,6 +15323,23 @@ var init_wire = __esm({
15301
15323
  /** Canonical human-readable description (no raw paths — daemon scrubs; server rechecks). */
15302
15324
  description: external_exports.string().min(1).max(4e3),
15303
15325
  attrs: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
15326
+ /** One-way origin key of the SESSION that minted this node (`ws_…`, a
15327
+ * truncated digest — never the raw session id). Stamped onto the created
15328
+ * node as `authoringSession`, which is the independence unit for the
15329
+ * evidence channels: the session that authored a claim may not corroborate
15330
+ * or refute it, while a DIFFERENT session on the same checkout may (alyssa,
15331
+ * 2026-07-31 — the workspace key made every witness on a single-checkout
15332
+ * deployment a self-corroboration). Optional + additive: absent leaves the
15333
+ * gate fail-open for that node, exactly today's behaviour. */
15334
+ originSession: external_exports.string().min(3).max(64).optional(),
15335
+ /** Epoch ms of the ORIGINATING local node's creation — when the knowledge was
15336
+ * actually captured, as opposed to when its public twin reached the cloud.
15337
+ * Stored as `observedAt`; the network board's chronological view orders on
15338
+ * it. Without this the wire carried NO timestamp at all, so a node captured
15339
+ * days ago surfaced as "new" the moment it was generalized and published —
15340
+ * the board showed ingest order wearing a chronology's clothes. Optional +
15341
+ * additive: absent keeps ingest-time ordering for that node. */
15342
+ originCreatedAtMs: external_exports.number().int().positive().optional(),
15304
15343
  extractionSource: external_exports.enum(INGEST_EXTRACTION_SOURCES),
15305
15344
  validationSource: external_exports.enum(VALIDATION_SOURCES).optional(),
15306
15345
  /** Org-membrane (M2): the daemon's anchor tag — it owns the lockfile, so it
@@ -15401,7 +15440,7 @@ var init_canonicalize = __esm({
15401
15440
 
15402
15441
  // ../../packages/shared/src/nlp/triage-canon.ts
15403
15442
  function canonicalizeToken(t) {
15404
- const base = t.trim().toLowerCase().replace(/[@\s].*$/, "");
15443
+ const base = t.trim().toLowerCase().replace(/(?!^)@.*$|\s.*$/, "");
15405
15444
  if (!base) return "";
15406
15445
  return resolveCanonicalId(base) ?? base;
15407
15446
  }
@@ -15483,6 +15522,51 @@ var init_review = __esm({
15483
15522
  }
15484
15523
  });
15485
15524
 
15525
+ // ../../packages/local-shared/src/anchorable.ts
15526
+ function anchorableExtensionAlternation() {
15527
+ return BY_LENGTH.map((e) => e.slice(1).replace(/[+.]/g, (c) => `\\${c}`)).join("|");
15528
+ }
15529
+ var ANCHORABLE_EXTENSIONS, BY_LENGTH;
15530
+ var init_anchorable = __esm({
15531
+ "../../packages/local-shared/src/anchorable.ts"() {
15532
+ "use strict";
15533
+ ANCHORABLE_EXTENSIONS = /* @__PURE__ */ new Set([
15534
+ // typescript provider
15535
+ ".ts",
15536
+ ".tsx",
15537
+ ".js",
15538
+ ".jsx",
15539
+ ".mjs",
15540
+ ".cjs",
15541
+ // python / go / rust / ruby / csharp providers
15542
+ ".py",
15543
+ ".go",
15544
+ ".rs",
15545
+ ".rb",
15546
+ ".cs",
15547
+ // cpp provider — every variant it parses, not just the three that were listed
15548
+ ".c",
15549
+ ".h",
15550
+ ".cpp",
15551
+ ".cc",
15552
+ ".cxx",
15553
+ ".c++",
15554
+ ".hpp",
15555
+ ".hh",
15556
+ ".hxx",
15557
+ ".h++",
15558
+ // CUDA — the cpp provider parses these too. Missed when this list was first
15559
+ // transcribed by hand; the parity test caught them on its very first run,
15560
+ // which is the argument for the test existing.
15561
+ ".cu",
15562
+ ".cuh",
15563
+ // no provider yet; the grammar ships. Inert, not wrong — see above.
15564
+ ".java"
15565
+ ]);
15566
+ BY_LENGTH = [...ANCHORABLE_EXTENSIONS].sort((a, b) => b.length - a.length);
15567
+ }
15568
+ });
15569
+
15486
15570
  // ../../packages/local-shared/src/sqlite-adapter.ts
15487
15571
  function openDatabase(path) {
15488
15572
  const db = new DatabaseSync(path);
@@ -15495,6 +15579,7 @@ function openDatabase(path) {
15495
15579
  db.exec("PRAGMA journal_mode = WAL");
15496
15580
  db.exec("PRAGMA synchronous = NORMAL");
15497
15581
  db.exec("PRAGMA busy_timeout = 5000");
15582
+ db.exec("PRAGMA journal_size_limit = 67108864");
15498
15583
  } catch {
15499
15584
  }
15500
15585
  }
@@ -15530,7 +15615,7 @@ function openDatabase(path) {
15530
15615
  return db.prepare(`PRAGMA ${key}`).get();
15531
15616
  },
15532
15617
  transaction(fn) {
15533
- db.exec("BEGIN");
15618
+ db.exec("BEGIN IMMEDIATE");
15534
15619
  try {
15535
15620
  const r = fn();
15536
15621
  db.exec("COMMIT");
@@ -15584,6 +15669,7 @@ var init_src2 = __esm({
15584
15669
  init_profile();
15585
15670
  init_daemon_wire();
15586
15671
  init_review();
15672
+ init_anchorable();
15587
15673
  init_sqlite_adapter();
15588
15674
  }
15589
15675
  });
@@ -15749,9 +15835,32 @@ var LOCAL_RULE_OVERRIDES = {
15749
15835
  to: [...CODE_NODE_LABELS, "Symbol"]
15750
15836
  },
15751
15837
  REVEALED_BY: null,
15752
- PRODUCED: null
15838
+ PRODUCED: null,
15839
+ // FIXED_BY locally ALSO carries the fix-provenance sense: `resolveProblem`
15840
+ // attributes a closed Problem to the fixing `Episode` (PLAN_PLASTICITY §2.1)
15841
+ // alongside the cloud's Problem→Solution knowledge claim. Same shape as
15842
+ // PRODUCED/REVEALED_BY above — an Episode is code-layer and never drains, so
15843
+ // the cloud door's `to: [Solution]` rule is untouched. The `from` constraint
15844
+ // stays: the reversed (Solution)-FIXED_BY->(...) splash bug is the reason
15845
+ // this rule exists at all.
15846
+ FIXED_BY: { from: EDGE_RULES["FIXED_BY"]?.from, to: ["Solution", "Episode"] }
15753
15847
  };
15754
- var SCHEMA_VERSION = 5;
15848
+ function localEdgeViolation(fromLabel, type, toLabel) {
15849
+ if (type in LOCAL_RULE_OVERRIDES) {
15850
+ const rule = LOCAL_RULE_OVERRIDES[type];
15851
+ if (!rule) return null;
15852
+ if (fromLabel && rule.from && !rule.from.includes(fromLabel)) {
15853
+ return `${type} cannot originate from ${fromLabel} locally (allowed: ${rule.from.join(", ")})`;
15854
+ }
15855
+ if (toLabel && rule.to && !rule.to.includes(toLabel)) {
15856
+ return `${type} cannot target ${toLabel} locally (allowed: ${rule.to.join(", ")})`;
15857
+ }
15858
+ return null;
15859
+ }
15860
+ const verdict = isValidEdge(fromLabel, type, toLabel);
15861
+ return verdict.ok ? null : verdict.reason ?? "edge rule violation";
15862
+ }
15863
+ var SCHEMA_VERSION = 6;
15755
15864
  var SCHEMA_SQL = `
15756
15865
  CREATE TABLE IF NOT EXISTS schema_version (
15757
15866
  version INTEGER PRIMARY KEY
@@ -15766,6 +15875,21 @@ CREATE TABLE IF NOT EXISTS store_meta (
15766
15875
  value TEXT NOT NULL
15767
15876
  );
15768
15877
 
15878
+ -- Durable ledger of edges the ontology gate REFUSED, keyed by edge type.
15879
+ -- Durable rather than in-memory for one specific reason: the status command runs
15880
+ -- in a SEPARATE process and opens its own store handle, so a counter living on
15881
+ -- the instance reads 0 there forever. That is exactly how a producer rejecting
15882
+ -- 100% of its output stayed invisible for 17 days. Persisting it also survives
15883
+ -- the daemon restart that would otherwise erase the evidence.
15884
+ -- Keyed by type because a systematic producer bug shows up as ONE type
15885
+ -- dominating; sample keeps the latest reason so the count is actionable.
15886
+ CREATE TABLE IF NOT EXISTS edge_rejections (
15887
+ type TEXT PRIMARY KEY,
15888
+ count INTEGER NOT NULL DEFAULT 0,
15889
+ last_at INTEGER NOT NULL,
15890
+ sample TEXT
15891
+ );
15892
+
15769
15893
  CREATE TABLE IF NOT EXISTS nodes (
15770
15894
  id TEXT PRIMARY KEY,
15771
15895
  label TEXT NOT NULL,
@@ -16028,6 +16152,13 @@ var SqliteGraphStore = class {
16028
16152
  findByLabel: this.db.prepare(
16029
16153
  "SELECT * FROM nodes WHERE label = ? AND valid_to IS NULL"
16030
16154
  ),
16155
+ // Scalar count twin of findByLabel — same live-row semantics, no row
16156
+ // hydration. Exists because status surfaces (daemon `/` + `/health`) used
16157
+ // findNodesByLabel(...).length, materializing every row's attrs JSON and
16158
+ // embedding blob per request — seconds of synchronous loop-hold per poll.
16159
+ countByLabel: this.db.prepare(
16160
+ "SELECT COUNT(*) AS n FROM nodes WHERE label = ? AND valid_to IS NULL"
16161
+ ),
16031
16162
  // ALL versions of a label — incl frozen/closed (valid_to set). Used by the
16032
16163
  // clean-reindex purge so a true wipe removes history too, not just live rows.
16033
16164
  findByLabelAll: this.db.prepare("SELECT * FROM nodes WHERE label = ?"),
@@ -16135,6 +16266,16 @@ var SqliteGraphStore = class {
16135
16266
  if (!cols.has(name)) this.db.exec(ddl);
16136
16267
  }
16137
16268
  }
16269
+ if (from < 6) {
16270
+ this.db.exec(`
16271
+ CREATE TABLE IF NOT EXISTS edge_rejections (
16272
+ type TEXT PRIMARY KEY,
16273
+ count INTEGER NOT NULL DEFAULT 0,
16274
+ last_at INTEGER NOT NULL,
16275
+ sample TEXT
16276
+ )
16277
+ `);
16278
+ }
16138
16279
  this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
16139
16280
  });
16140
16281
  }
@@ -16216,6 +16357,7 @@ var SqliteGraphStore = class {
16216
16357
  const violation = this.edgeRuleViolation(edge);
16217
16358
  if (violation) {
16218
16359
  this.rejectedEdgeCount++;
16360
+ this.recordEdgeRejection(edge.type, violation, edge.lastSeenAt || edge.createdAt || 0);
16219
16361
  console.warn(`[local-graph] rejected edge ${edge.from}-[:${edge.type}]->${edge.to}: ${violation}`);
16220
16362
  return;
16221
16363
  }
@@ -16240,22 +16382,51 @@ var SqliteGraphStore = class {
16240
16382
  * overlay consulted first. Returns the reason string on a documented-
16241
16383
  * forbidden combination, else null. Point lookups on the id PK — negligible
16242
16384
  * next to the insert itself. */
16243
- edgeRuleViolation(edge) {
16244
- const fromLabel = this.getNode(edge.from)?.label;
16245
- const toLabel = this.getNode(edge.to)?.label;
16246
- if (edge.type in LOCAL_RULE_OVERRIDES) {
16247
- const rule = LOCAL_RULE_OVERRIDES[edge.type];
16248
- if (!rule) return null;
16249
- if (fromLabel && rule.from && !rule.from.includes(fromLabel)) {
16250
- return `${edge.type} cannot originate from ${fromLabel} locally (allowed: ${rule.from.join(", ")})`;
16251
- }
16252
- if (toLabel && rule.to && !rule.to.includes(toLabel)) {
16253
- return `${edge.type} cannot target ${toLabel} locally (allowed: ${rule.to.join(", ")})`;
16254
- }
16255
- return null;
16385
+ /** Upsert one refusal into the durable ledger. Best-effort: a bookkeeping
16386
+ * failure must never turn a refused edge into a thrown write. */
16387
+ recordEdgeRejection(type, reason, at) {
16388
+ try {
16389
+ this.db.prepare(
16390
+ `INSERT INTO edge_rejections (type, count, last_at, sample) VALUES (?, 1, ?, ?)
16391
+ ON CONFLICT(type) DO UPDATE SET
16392
+ count = count + 1, last_at = excluded.last_at, sample = excluded.sample`
16393
+ ).run(type, at, reason.slice(0, 200));
16394
+ } catch {
16395
+ }
16396
+ }
16397
+ /** Refusals recorded by the ontology gate, per edge type, newest activity first.
16398
+ * Durable across restarts and readable from any process (see the table note). */
16399
+ edgeRejections() {
16400
+ try {
16401
+ return this.db.prepare(
16402
+ "SELECT type, count, last_at AS lastAt, sample FROM edge_rejections ORDER BY count DESC, last_at DESC"
16403
+ ).all();
16404
+ } catch {
16405
+ return [];
16406
+ }
16407
+ }
16408
+ /** Drop ledger entries whose last refusal predates `cutoff`. A still-misbehaving
16409
+ * producer keeps refreshing `last_at` and survives; a fixed one fades out. */
16410
+ pruneEdgeRejections(cutoff) {
16411
+ try {
16412
+ this.db.prepare("DELETE FROM edge_rejections WHERE last_at < ?").run(cutoff);
16413
+ } catch {
16414
+ }
16415
+ }
16416
+ /** Clear the ledger outright, whole or per type — operator escape hatch. */
16417
+ clearEdgeRejections(type) {
16418
+ try {
16419
+ if (type) this.db.prepare("DELETE FROM edge_rejections WHERE type = ?").run(type);
16420
+ else this.db.exec("DELETE FROM edge_rejections");
16421
+ } catch {
16256
16422
  }
16257
- const verdict = isValidEdge(fromLabel, edge.type, toLabel);
16258
- return verdict.ok ? null : verdict.reason ?? "edge rule violation";
16423
+ }
16424
+ edgeRuleViolation(edge) {
16425
+ return localEdgeViolation(
16426
+ this.getNode(edge.from)?.label,
16427
+ edge.type,
16428
+ this.getNode(edge.to)?.label
16429
+ );
16259
16430
  }
16260
16431
  updateEdge(id, patch) {
16261
16432
  this.stmts.updateEdge.run({
@@ -16281,6 +16452,13 @@ var SqliteGraphStore = class {
16281
16452
  const rows = this.stmts.findByLabel.all(label);
16282
16453
  return rows.map(rowToNode);
16283
16454
  }
16455
+ /** Live-row count for a label — `findNodesByLabel(label).length` without the
16456
+ * per-row hydration (attrs JSON + embedding blob). Status surfaces poll this
16457
+ * per project per request; the materializing form held the daemon's event
16458
+ * loop for seconds at scale. */
16459
+ countNodesByLabel(label) {
16460
+ return Number(this.stmts.countByLabel.get(label).n);
16461
+ }
16284
16462
  findAllVersionsByLabel(label) {
16285
16463
  const rows = this.stmts.findByLabelAll.all(label);
16286
16464
  return rows.map(rowToNode);
@@ -16385,6 +16563,163 @@ var SqliteGraphStore = class {
16385
16563
  const rows = this.db.prepare("SELECT from_id, to_id, type FROM edges WHERE valid_to IS NULL").all();
16386
16564
  return rows.map((r) => ({ from: r.from_id, to: r.to_id, type: r.type }));
16387
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
+ }
16656
+ reviveReobserved(relPaths, workspaceId, since) {
16657
+ let revived = 0;
16658
+ const CHUNK = 400;
16659
+ for (let i = 0; i < relPaths.length; i += CHUNK) {
16660
+ const slice = relPaths.slice(i, i + CHUNK);
16661
+ if (slice.length === 0) continue;
16662
+ const r = this.db.prepare(
16663
+ `UPDATE nodes SET valid_to = NULL
16664
+ WHERE valid_to IS NOT NULL
16665
+ AND last_updated_at >= ?
16666
+ AND json_extract(attrs_json, '$.workspaceId') = ?
16667
+ AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})`
16668
+ ).run(since, workspaceId, ...slice);
16669
+ revived += Number(r.changes);
16670
+ }
16671
+ if (revived > 0) this.mutations++;
16672
+ return revived;
16673
+ }
16674
+ recentEdgeAttrCoverage(marker, attr, limit) {
16675
+ const r = this.db.prepare(
16676
+ `SELECT COUNT(*) AS total,
16677
+ COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
16678
+ FROM (SELECT attrs_json FROM edges
16679
+ WHERE valid_to IS NULL AND attrs_json LIKE ?
16680
+ ORDER BY created_at DESC LIMIT ?)`
16681
+ ).get(attr, `%${marker}%`, limit);
16682
+ return { count: Number(r.count), total: Number(r.total) };
16683
+ }
16684
+ liveNodeIds(ids) {
16685
+ const live = /* @__PURE__ */ new Set();
16686
+ const CHUNK = 900;
16687
+ for (let i = 0; i < ids.length; i += CHUNK) {
16688
+ const slice = ids.slice(i, i + CHUNK);
16689
+ if (slice.length === 0) continue;
16690
+ const rows = this.db.prepare(
16691
+ `SELECT id FROM nodes WHERE valid_to IS NULL AND id IN (${slice.map(() => "?").join(",")})`
16692
+ ).all(...slice);
16693
+ for (const r of rows) live.add(r.id);
16694
+ }
16695
+ return live;
16696
+ }
16697
+ /**
16698
+ * Live edges WITH their endpoint labels and ids, resolved in ONE join.
16699
+ *
16700
+ * The ontology sweep needs (id, type, fromLabel, toLabel) for every live edge.
16701
+ * Doing that as `scanLiveEdges()` + two `getNode()` calls is 2N node reads —
16702
+ * 550,000 on this store — and each one deserializes the node's embedding blob.
16703
+ * Measured: the sweep did not finish in 10 minutes. As a single join it is one
16704
+ * query over an index-covered scan. Labels only; nothing here touches embeddings.
16705
+ */
16706
+ scanLiveEdgeRows() {
16707
+ const rows = this.db.prepare(
16708
+ `SELECT e.id, e.from_id, e.to_id, e.type, a.label AS from_label, b.label AS to_label
16709
+ FROM edges e
16710
+ LEFT JOIN nodes a ON a.id = e.from_id AND a.valid_to IS NULL
16711
+ LEFT JOIN nodes b ON b.id = e.to_id AND b.valid_to IS NULL
16712
+ WHERE e.valid_to IS NULL`
16713
+ ).all();
16714
+ return rows.map((r) => ({
16715
+ id: r.id,
16716
+ from: r.from_id,
16717
+ to: r.to_id,
16718
+ type: r.type,
16719
+ fromLabel: r.from_label ?? void 0,
16720
+ toLabel: r.to_label ?? void 0
16721
+ }));
16722
+ }
16388
16723
  /** Live nodes whose attrs.relPath is one of `relPaths`. Uses the nodes_relpath
16389
16724
  * expression index so the incremental reindex fetches only the changed files'
16390
16725
  * symbols instead of scanning every versioned node. */
@@ -16405,6 +16740,10 @@ var SqliteGraphStore = class {
16405
16740
  if (!live) return null;
16406
16741
  const version2 = live.version ?? 1;
16407
16742
  const frozenId = `${liveId}@v${version2}`;
16743
+ if (this.getNode(frozenId)) {
16744
+ this.stmts.advanceLive.run({ live_id: liveId, t });
16745
+ return frozenId;
16746
+ }
16408
16747
  this.stmts.freezeCopy.run({ frozen_id: frozenId, live_id: liveId, t });
16409
16748
  this.mergeEdge({
16410
16749
  id: `edge_superseded_${frozenId}`,
@@ -16463,6 +16802,7 @@ var CAUSAL_FAMILY = [
16463
16802
 
16464
16803
  // ../../packages/local-graph/src/justification.ts
16465
16804
  init_src();
16805
+ init_src2();
16466
16806
 
16467
16807
  // ../../packages/local-graph/src/credit.ts
16468
16808
  init_src2();
@@ -16479,11 +16819,28 @@ init_src();
16479
16819
  // ../../packages/local-graph/src/design-problem.ts
16480
16820
  init_src();
16481
16821
  init_src();
16482
- init_src2();
16483
16822
 
16484
16823
  // ../../packages/local-graph/src/problem-package-link.ts
16485
16824
  init_src();
16486
16825
 
16826
+ // ../../packages/local-graph/src/design-problem.ts
16827
+ init_src2();
16828
+
16829
+ // ../../packages/local-graph/src/problem-dedup.ts
16830
+ init_src();
16831
+ init_src();
16832
+
16833
+ // ../../packages/local-graph/src/intent.ts
16834
+ init_src();
16835
+
16836
+ // ../../packages/local-graph/src/anchor-backfill.ts
16837
+ init_src();
16838
+ init_src2();
16839
+ var STATEMENT_PATH_RE = new RegExp(
16840
+ String.raw`(?:[\w.+-]+\/)+[\w.+-]+\.(?:${anchorableExtensionAlternation()})`,
16841
+ "g"
16842
+ );
16843
+
16487
16844
  // ../../packages/local-graph/src/percolate.ts
16488
16845
  init_src2();
16489
16846
  var PERCOLATING_LABELS = ["Claim", "Problem", "Solution"];
@@ -16754,219 +17111,13 @@ function detectCommunitiesLeiden(input, opts = {}) {
16754
17111
  return normalize(assignment);
16755
17112
  }
16756
17113
 
16757
- // ../../packages/local-graph/src/abstraction.ts
16758
- var DEFAULT_MIN_CLUSTER_SIZE = 3;
16759
- var DEFAULT_MIN_DISTINCT_CONTEXTS = 2;
16760
- function sharedScope(l2, memberIds) {
16761
- const shared = (field) => {
16762
- const vals = /* @__PURE__ */ new Set();
16763
- for (const id of memberIds) {
16764
- const s = l2.getNode(id)?.attrs["scope"] ?? {};
16765
- if (typeof s[field] === "string") vals.add(s[field]);
16766
- }
16767
- return vals.size === 1 ? [...vals][0] : void 0;
16768
- };
16769
- const lang = shared("lang");
16770
- const versionRange = shared("versionRange");
16771
- return { ...lang ? { lang } : {}, ...versionRange ? { versionRange } : {} };
16772
- }
16773
- function buildCandidate(id, ts, members, contexts, originMachine, scope) {
16774
- return {
16775
- id,
16776
- label: "Claim",
16777
- description: "",
16778
- // empty — the harness distills the prose in P3
16779
- extractionConfidence: 0.4,
16780
- extractionSource: "agent-observed",
16781
- embedding: [],
16782
- cumulativeSurprise: 0,
16783
- peakSurprise: 0,
16784
- cumulativeHits: 1,
16785
- lastUpdatedAt: ts,
16786
- createdAt: ts,
16787
- memoryTier: "short-term",
16788
- pageRank: 0,
16789
- isLandmark: false,
16790
- community: null,
16791
- stability: "unstable",
16792
- attrs: {
16793
- abstractionLevel: ABSTRACTION_LEVEL.PRINCIPLE,
16794
- kind: "abstraction-candidate",
16795
- provisional: true,
16796
- pendingDistillation: true,
16797
- // membership is the unit of truth — the harness validates its fence against
16798
- // this set (P3 / C3) and may only phrase, never alter, it.
16799
- members,
16800
- memberContexts: contexts,
16801
- distinctContexts: contexts.length,
16802
- // standard Claim envelope so it crystallizes/syncs unchanged once distilled
16803
- truthKind: "contextual",
16804
- // Derived from members (P1): a single-language cluster becomes a lang-scoped
16805
- // principle; a cross-language one stays universal (`{}`).
16806
- scope,
16807
- groundedSupport: 0,
16808
- sources: [],
16809
- crystallized: "hypothesis",
16810
- confidence: 0.4,
16811
- ...originMachine ? { originMachine } : {}
16812
- }
16813
- };
16814
- }
16815
- function mergeGeneralizes(store, principleId, memberId, ts) {
16816
- store.mergeEdge({
16817
- id: `edge_${digest({ from: principleId, type: "GENERALIZES", to: memberId })}`.slice(0, 24),
16818
- from: principleId,
16819
- to: memberId,
16820
- type: "GENERALIZES",
16821
- confidence: 0.4,
16822
- extractionSource: "agent-observed",
16823
- createdAt: ts,
16824
- lastSeenAt: ts,
16825
- navSuccesses: 0,
16826
- navFailures: 0,
16827
- attrs: { provisional: true }
16828
- });
16829
- }
16830
- function induceAbstractions(l2, opts) {
16831
- const K = opts.minClusterSize ?? DEFAULT_MIN_CLUSTER_SIZE;
16832
- const minCtx = opts.minDistinctContexts ?? DEFAULT_MIN_DISTINCT_CONTEXTS;
16833
- const report = {
16834
- communities: 0,
16835
- candidatesMinted: 0,
16836
- candidatesExisting: 0,
16837
- skippedSmall: 0,
16838
- skippedSingleContext: 0,
16839
- generalizesEdges: 0
16840
- };
16841
- const ids = /* @__PURE__ */ new Set();
16842
- for (const label of PERCOLATING_LABELS) {
16843
- for (const n of l2.findNodesByLabel(label)) {
16844
- if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) >= ABSTRACTION_LEVEL.PRINCIPLE) {
16845
- continue;
16846
- }
16847
- if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
16848
- ids.add(n.id);
16849
- }
16850
- }
16851
- if (ids.size === 0) return report;
16852
- const adj = /* @__PURE__ */ new Map();
16853
- const protect = [];
16854
- const add = (a, b, w) => {
16855
- const l = adj.get(a) ?? [];
16856
- l.push({ to: b, weight: w });
16857
- adj.set(a, l);
16858
- };
16859
- for (const id of ids) {
16860
- for (const e of l2.outEdges(id, [...PERCOLATING_EDGES])) {
16861
- if (!ids.has(e.to)) continue;
16862
- const conf = e.confidence > 0 ? e.confidence : 0.5;
16863
- const w = Math.max(EDGE_WEIGHT[e.type] ?? 1, 0.1) * conf;
16864
- add(id, e.to, w);
16865
- add(e.to, id, w);
16866
- if (isCausalProtected(e.type)) protect.push([id, e.to]);
16867
- }
16868
- }
16869
- const comm = detectCommunitiesLeiden({ nodeIds: [...ids], adj }, { protect });
16870
- report.communities = comm.count;
16871
- const members = /* @__PURE__ */ new Map();
16872
- for (const [id, c] of comm.community) {
16873
- const l = members.get(c) ?? [];
16874
- l.push(id);
16875
- members.set(c, l);
16876
- }
16877
- l2.transaction(() => {
16878
- for (const mem of members.values()) {
16879
- if (opts.touched && !mem.some((id) => opts.touched.has(id))) continue;
16880
- if (mem.length < K) {
16881
- report.skippedSmall++;
16882
- continue;
16883
- }
16884
- if (mem.some((id) => l2.inEdges(id, ["GENERALIZES"]).length > 0)) {
16885
- report.candidatesExisting++;
16886
- continue;
16887
- }
16888
- const contexts = /* @__PURE__ */ new Set();
16889
- for (const id of mem) {
16890
- const n = l2.getNode(id);
16891
- const obs = n?.attrs["observedInProjects"];
16892
- if (Array.isArray(obs)) {
16893
- for (const ws of obs) contexts.add(opts.contextOf(ws) ?? `project:${ws}`);
16894
- }
16895
- }
16896
- if (contexts.size < minCtx) {
16897
- report.skippedSingleContext++;
16898
- continue;
16899
- }
16900
- const sorted = [...mem].sort();
16901
- const principleId = `princ_${digest({ members: sorted })}`.slice(0, 56);
16902
- l2.mergeNode(
16903
- buildCandidate(principleId, opts.ts, sorted, [...contexts].sort(), opts.originMachine, sharedScope(l2, sorted))
16904
- );
16905
- for (const id of sorted) {
16906
- mergeGeneralizes(l2, principleId, id, opts.ts);
16907
- report.generalizesEdges++;
16908
- }
16909
- report.candidatesMinted++;
16910
- }
16911
- });
16912
- return report;
16913
- }
16914
- function revisitContradictedPrinciples(l2, ts) {
16915
- const report = { flagged: 0 };
16916
- l2.transaction(() => {
16917
- for (const n of l2.findNodesByLabel("Claim")) {
16918
- if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) continue;
16919
- if (n.attrs["revisit"] === true) continue;
16920
- const members = Array.isArray(n.attrs["members"]) ? n.attrs["members"] : [];
16921
- const memberSet = new Set(members);
16922
- let reason = "";
16923
- for (const id of members) {
16924
- const m = l2.getNode(id);
16925
- if (!m) {
16926
- reason = `member ${id} was removed`;
16927
- break;
16928
- }
16929
- if (m.attrs["revisit"] === true) {
16930
- reason = `member "${m.description}" needs revisit`;
16931
- break;
16932
- }
16933
- const contradictors = [
16934
- ...l2.outEdges(id, ["CONTRADICTS"]).map((e) => e.to),
16935
- ...l2.inEdges(id, ["CONTRADICTS"]).map((e) => e.from)
16936
- ];
16937
- if (contradictors.some((other) => !memberSet.has(other))) {
16938
- reason = `member "${m.description}" is now contradicted by external evidence`;
16939
- break;
16940
- }
16941
- }
16942
- if (!reason) continue;
16943
- l2.updateNode(n.id, {
16944
- attrs: {
16945
- ...n.attrs,
16946
- revisit: true,
16947
- revisitReason: reason,
16948
- revisitSinceTs: ts,
16949
- provisional: true,
16950
- // re-queue for re-distillation (P3 re-name)
16951
- pendingDistillation: true
16952
- },
16953
- lastUpdatedAt: ts
16954
- });
16955
- report.flagged++;
16956
- }
16957
- });
16958
- return report;
16959
- }
16960
-
16961
- // ../../packages/local-graph/src/community.ts
16962
- init_src2();
16963
-
16964
17114
  // ../../packages/local-graph/src/triage.ts
16965
17115
  init_src();
16966
17116
  init_src();
16967
17117
  init_src();
16968
17118
  init_src2();
16969
- function semNode(id, label, description, ts, attrs) {
17119
+ function semNode(store, id, label, description, ts, attrs) {
17120
+ const seq = SEMANTIC_NODE_LABELS.includes(label) ? store.nextIngestSeq() : void 0;
16970
17121
  return {
16971
17122
  id,
16972
17123
  label,
@@ -16984,6 +17135,7 @@ function semNode(id, label, description, ts, attrs) {
16984
17135
  isLandmark: false,
16985
17136
  community: null,
16986
17137
  stability: "unstable",
17138
+ ...seq !== void 0 ? { createdAtSeq: seq, lastReinforcedAtSeq: seq } : {},
16987
17139
  attrs
16988
17140
  };
16989
17141
  }
@@ -17014,7 +17166,7 @@ function recordTriageObservation(l2, obs, ts) {
17014
17166
  const buckets = [...triageContextBuckets(canonicalizeContext(obs.context)), TRIAGE_GLOBAL_BUCKET];
17015
17167
  l2.transaction(() => {
17016
17168
  if (!l2.getNode(presentingId)) {
17017
- l2.mergeNode(semNode(presentingId, "Problem", statement, ts, { scope: {} }));
17169
+ l2.mergeNode(semNode(l2, presentingId, "Problem", statement, ts, { scope: {} }));
17018
17170
  }
17019
17171
  const tri = l2.getNode(triageId);
17020
17172
  const seen = {
@@ -17028,7 +17180,7 @@ function recordTriageObservation(l2, obs, ts) {
17028
17180
  });
17029
17181
  } else {
17030
17182
  l2.mergeNode(
17031
- semNode(triageId, "Triage", `differential for: ${statement}`, ts, {
17183
+ semNode(l2, triageId, "Triage", `differential for: ${statement}`, ts, {
17032
17184
  statement,
17033
17185
  perContextSeen: seen
17034
17186
  })
@@ -17040,7 +17192,7 @@ function recordTriageObservation(l2, obs, ts) {
17040
17192
  }
17041
17193
  if (!l2.getNode(obs.causeId)) {
17042
17194
  l2.mergeNode(
17043
- semNode(obs.causeId, obs.causeLabel ?? "RootCause", obs.causeDescription ?? "(cause)", ts, {
17195
+ semNode(l2, obs.causeId, obs.causeLabel ?? "RootCause", obs.causeDescription ?? "(cause)", ts, {
17044
17196
  scope: {}
17045
17197
  })
17046
17198
  );
@@ -17213,9 +17365,212 @@ function revisitStaleRoutes(l2, ts, opts = {}) {
17213
17365
  }
17214
17366
  var DRIFT_VALUES = new Set(Object.values(DRIFT_KIND));
17215
17367
 
17216
- // ../../packages/local-graph/src/problem-dedup.ts
17217
- init_src();
17218
- init_src();
17368
+ // ../../packages/local-graph/src/abstraction.ts
17369
+ var DEFAULT_MIN_CLUSTER_SIZE = 3;
17370
+ var DEFAULT_MIN_DISTINCT_CONTEXTS = 2;
17371
+ function sharedScope(l2, memberIds) {
17372
+ const shared = (field) => {
17373
+ const vals = /* @__PURE__ */ new Set();
17374
+ for (const id of memberIds) {
17375
+ const s = l2.getNode(id)?.attrs["scope"] ?? {};
17376
+ if (typeof s[field] === "string") vals.add(s[field]);
17377
+ }
17378
+ return vals.size === 1 ? [...vals][0] : void 0;
17379
+ };
17380
+ const lang = shared("lang");
17381
+ const versionRange = shared("versionRange");
17382
+ return { ...lang ? { lang } : {}, ...versionRange ? { versionRange } : {} };
17383
+ }
17384
+ function buildCandidate(id, ts, members, contexts, originMachine, scope) {
17385
+ return {
17386
+ id,
17387
+ label: "Claim",
17388
+ description: "",
17389
+ // empty — the harness distills the prose in P3
17390
+ extractionConfidence: 0.4,
17391
+ extractionSource: "agent-observed",
17392
+ embedding: [],
17393
+ cumulativeSurprise: 0,
17394
+ peakSurprise: 0,
17395
+ cumulativeHits: 1,
17396
+ lastUpdatedAt: ts,
17397
+ createdAt: ts,
17398
+ memoryTier: "short-term",
17399
+ pageRank: 0,
17400
+ isLandmark: false,
17401
+ community: null,
17402
+ stability: "unstable",
17403
+ attrs: {
17404
+ abstractionLevel: ABSTRACTION_LEVEL.PRINCIPLE,
17405
+ kind: "abstraction-candidate",
17406
+ provisional: true,
17407
+ pendingDistillation: true,
17408
+ // membership is the unit of truth — the harness validates its fence against
17409
+ // this set (P3 / C3) and may only phrase, never alter, it.
17410
+ members,
17411
+ memberContexts: contexts,
17412
+ distinctContexts: contexts.length,
17413
+ // standard Claim envelope so it crystallizes/syncs unchanged once distilled
17414
+ truthKind: "contextual",
17415
+ // Derived from members (P1): a single-language cluster becomes a lang-scoped
17416
+ // principle; a cross-language one stays universal (`{}`).
17417
+ scope,
17418
+ groundedSupport: 0,
17419
+ sources: [],
17420
+ crystallized: "hypothesis",
17421
+ confidence: 0.4,
17422
+ ...originMachine ? { originMachine } : {}
17423
+ }
17424
+ };
17425
+ }
17426
+ function mergeGeneralizes(store, principleId, memberId, ts) {
17427
+ store.mergeEdge({
17428
+ id: `edge_${digest({ from: principleId, type: "GENERALIZES", to: memberId })}`.slice(0, 24),
17429
+ from: principleId,
17430
+ to: memberId,
17431
+ type: "GENERALIZES",
17432
+ confidence: 0.4,
17433
+ extractionSource: "agent-observed",
17434
+ createdAt: ts,
17435
+ lastSeenAt: ts,
17436
+ navSuccesses: 0,
17437
+ navFailures: 0,
17438
+ attrs: { provisional: true }
17439
+ });
17440
+ }
17441
+ function induceAbstractions(l2, opts) {
17442
+ const K = opts.minClusterSize ?? DEFAULT_MIN_CLUSTER_SIZE;
17443
+ const minCtx = opts.minDistinctContexts ?? DEFAULT_MIN_DISTINCT_CONTEXTS;
17444
+ const report = {
17445
+ communities: 0,
17446
+ candidatesMinted: 0,
17447
+ candidatesExisting: 0,
17448
+ skippedSmall: 0,
17449
+ skippedSingleContext: 0,
17450
+ generalizesEdges: 0
17451
+ };
17452
+ const ids = /* @__PURE__ */ new Set();
17453
+ for (const label of PERCOLATING_LABELS) {
17454
+ for (const n of l2.findNodesByLabel(label)) {
17455
+ if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) >= ABSTRACTION_LEVEL.PRINCIPLE) {
17456
+ continue;
17457
+ }
17458
+ if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
17459
+ ids.add(n.id);
17460
+ }
17461
+ }
17462
+ if (ids.size === 0) return report;
17463
+ const adj = /* @__PURE__ */ new Map();
17464
+ const protect = [];
17465
+ const add = (a, b, w) => {
17466
+ const l = adj.get(a) ?? [];
17467
+ l.push({ to: b, weight: w });
17468
+ adj.set(a, l);
17469
+ };
17470
+ for (const id of ids) {
17471
+ for (const e of l2.outEdges(id, [...PERCOLATING_EDGES])) {
17472
+ if (!ids.has(e.to)) continue;
17473
+ const conf = e.confidence > 0 ? e.confidence : 0.5;
17474
+ const w = Math.max(EDGE_WEIGHT[e.type] ?? 1, 0.1) * conf;
17475
+ add(id, e.to, w);
17476
+ add(e.to, id, w);
17477
+ if (isCausalProtected(e.type)) protect.push([id, e.to]);
17478
+ }
17479
+ }
17480
+ const comm = detectCommunitiesLeiden({ nodeIds: [...ids], adj }, { protect });
17481
+ report.communities = comm.count;
17482
+ const members = /* @__PURE__ */ new Map();
17483
+ for (const [id, c] of comm.community) {
17484
+ const l = members.get(c) ?? [];
17485
+ l.push(id);
17486
+ members.set(c, l);
17487
+ }
17488
+ l2.transaction(() => {
17489
+ for (const mem of members.values()) {
17490
+ if (opts.touched && !mem.some((id) => opts.touched.has(id))) continue;
17491
+ if (mem.length < K) {
17492
+ report.skippedSmall++;
17493
+ continue;
17494
+ }
17495
+ if (mem.some((id) => l2.inEdges(id, ["GENERALIZES"]).length > 0)) {
17496
+ report.candidatesExisting++;
17497
+ continue;
17498
+ }
17499
+ const contexts = /* @__PURE__ */ new Set();
17500
+ for (const id of mem) {
17501
+ const n = l2.getNode(id);
17502
+ const obs = n?.attrs["observedInProjects"];
17503
+ if (Array.isArray(obs)) {
17504
+ for (const ws of obs) contexts.add(opts.contextOf(ws) ?? `project:${ws}`);
17505
+ }
17506
+ }
17507
+ if (contexts.size < minCtx) {
17508
+ report.skippedSingleContext++;
17509
+ continue;
17510
+ }
17511
+ const sorted = [...mem].sort();
17512
+ const principleId = `princ_${digest({ members: sorted })}`.slice(0, 56);
17513
+ l2.mergeNode(
17514
+ buildCandidate(principleId, opts.ts, sorted, [...contexts].sort(), opts.originMachine, sharedScope(l2, sorted))
17515
+ );
17516
+ for (const id of sorted) {
17517
+ mergeGeneralizes(l2, principleId, id, opts.ts);
17518
+ report.generalizesEdges++;
17519
+ }
17520
+ report.candidatesMinted++;
17521
+ }
17522
+ });
17523
+ return report;
17524
+ }
17525
+ function revisitContradictedPrinciples(l2, ts) {
17526
+ const report = { flagged: 0 };
17527
+ l2.transaction(() => {
17528
+ for (const n of l2.findNodesByLabel("Claim")) {
17529
+ if (Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) < ABSTRACTION_LEVEL.PRINCIPLE) continue;
17530
+ if (n.attrs["revisit"] === true) continue;
17531
+ const members = Array.isArray(n.attrs["members"]) ? n.attrs["members"] : [];
17532
+ const memberSet = new Set(members);
17533
+ let reason = "";
17534
+ for (const id of members) {
17535
+ const m = l2.getNode(id);
17536
+ if (!m) {
17537
+ reason = `member ${id} was removed`;
17538
+ break;
17539
+ }
17540
+ if (m.attrs["revisit"] === true) {
17541
+ reason = `member "${m.description}" needs revisit`;
17542
+ break;
17543
+ }
17544
+ const contradictors = [
17545
+ ...l2.outEdges(id, ["CONTRADICTS"]).map((e) => e.to),
17546
+ ...l2.inEdges(id, ["CONTRADICTS"]).map((e) => e.from)
17547
+ ];
17548
+ if (contradictors.some((other) => !memberSet.has(other))) {
17549
+ reason = `member "${m.description}" is now contradicted by external evidence`;
17550
+ break;
17551
+ }
17552
+ }
17553
+ if (!reason) continue;
17554
+ l2.updateNode(n.id, {
17555
+ attrs: {
17556
+ ...n.attrs,
17557
+ revisit: true,
17558
+ revisitReason: reason,
17559
+ revisitSinceTs: ts,
17560
+ provisional: true,
17561
+ // re-queue for re-distillation (P3 re-name)
17562
+ pendingDistillation: true
17563
+ },
17564
+ lastUpdatedAt: ts
17565
+ });
17566
+ report.flagged++;
17567
+ }
17568
+ });
17569
+ return report;
17570
+ }
17571
+
17572
+ // ../../packages/local-graph/src/community.ts
17573
+ init_src2();
17219
17574
 
17220
17575
  // ../../packages/local-graph/src/tools.ts
17221
17576
  init_src();
@@ -17223,6 +17578,9 @@ init_src();
17223
17578
  // ../../packages/local-graph/src/principle-sync.ts
17224
17579
  init_src2();
17225
17580
 
17581
+ // ../../packages/local-graph/src/mechanism-liveness.ts
17582
+ var STALL_MIN_SPAN_MS = 60 * 60 * 1e3;
17583
+
17226
17584
  // src/reconcile.ts
17227
17585
  init_src3();
17228
17586
  function isLive(n) {