@inerrata-corporation/errata 2.0.8-dev.2914 → 2.0.8-dev.2929

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.
@@ -16137,6 +16137,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
16137
16137
  `;
16138
16138
  var TEMPORAL_INDEX_SQL = `
16139
16139
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
16140
+ -- Covering + partial index for labelFingerprint over the instance labels: the
16141
+ -- live-push probe runs it per workspace on a timer, and each hit used to read
16142
+ -- its table row. Partial, so it maintains a few thousand rows instead of adding
16143
+ -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
16144
+ DROP INDEX IF EXISTS nodes_live_fingerprint;
16145
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
16140
16146
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
16141
16147
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
16142
16148
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -16207,6 +16213,7 @@ function rowToEdge(r) {
16207
16213
  attrs: JSON.parse(r.attrs_json)
16208
16214
  };
16209
16215
  }
16216
+ var FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
16210
16217
  var SqliteGraphStore = class {
16211
16218
  db;
16212
16219
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -16683,6 +16690,32 @@ var SqliteGraphStore = class {
16683
16690
  attrs: JSON.parse(r.attrs_json)
16684
16691
  }));
16685
16692
  }
16693
+ *iterateNodeIdentitiesByLabel(label, pageSize = 2e3) {
16694
+ const page = this.db.prepare(
16695
+ "SELECT rowid AS rid, id, json_extract(attrs_json, '$.relPath') AS relPath, json_extract(attrs_json, '$.qname') AS qname FROM nodes WHERE rowid > ? AND label = ? AND valid_to IS NULL ORDER BY rowid LIMIT ?"
16696
+ );
16697
+ let after = 0;
16698
+ for (; ; ) {
16699
+ const rows = page.all(after, label, pageSize);
16700
+ for (const r of rows) {
16701
+ yield {
16702
+ id: r.id,
16703
+ relPath: typeof r.relPath === "string" ? r.relPath : null,
16704
+ qname: typeof r.qname === "string" ? r.qname : null
16705
+ };
16706
+ }
16707
+ if (rows.length < pageSize) return;
16708
+ after = rows[rows.length - 1].rid;
16709
+ }
16710
+ }
16711
+ recentNodeIds(label, limit) {
16712
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(label, Math.max(0, limit));
16713
+ return rows.map((r) => r.id);
16714
+ }
16715
+ hasLiveNodes() {
16716
+ const r = this.db.prepare("SELECT EXISTS(SELECT 1 FROM nodes WHERE valid_to IS NULL) AS e").get();
16717
+ return r.e === 1;
16718
+ }
16686
16719
  findNodeIdentitiesByLabel(label) {
16687
16720
  const rows = this.stmts.identitiesByLabel.all(label);
16688
16721
  return rows.map((r) => ({
@@ -16830,6 +16863,15 @@ var SqliteGraphStore = class {
16830
16863
  }
16831
16864
  labelFingerprint(labels) {
16832
16865
  if (labels.length === 0) return "0";
16866
+ if (labels.length === FINGERPRINT_INDEXED_LABELS.length && FINGERPRINT_INDEXED_LABELS.every((l) => labels.includes(l))) {
16867
+ const r2 = this.db.prepare(
16868
+ `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
16869
+ MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
16870
+ FROM nodes INDEXED BY nodes_instance_fingerprint
16871
+ WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
16872
+ ).get();
16873
+ return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
16874
+ }
16833
16875
  const r = this.db.prepare(
16834
16876
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
16835
16877
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
@@ -18174,6 +18216,15 @@ function generalize(text, opts = {}) {
18174
18216
  // src/instance-ingest.ts
18175
18217
  init_src();
18176
18218
 
18219
+ // src/instance-receipts.ts
18220
+ function receiptMap(attrs) {
18221
+ const value = attrs["instanceReceipts"];
18222
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
18223
+ }
18224
+ function instanceReceipt(n, destination) {
18225
+ return destination ? receiptMap(n.attrs)[destination] ?? {} : n.attrs;
18226
+ }
18227
+
18177
18228
  // src/contribution-ready.ts
18178
18229
  function cosine2(a, b) {
18179
18230
  if (a.length === 0 || a.length !== b.length) return 0;
@@ -19043,8 +19094,8 @@ function stripCodebaseScope(scope) {
19043
19094
  delete s["codebase"];
19044
19095
  return s;
19045
19096
  }
19046
- function contributedWireId(n) {
19047
- const cloudId = n.attrs["cloudNodeId"];
19097
+ function contributedWireId(n, destinationKey) {
19098
+ const cloudId = instanceReceipt(n, destinationKey)["cloudNodeId"];
19048
19099
  return isMembraneSaltedId(cloudId) ? cloudId : n.id;
19049
19100
  }
19050
19101
  var WIRE_CANONICAL_ID_MIN = 6;
@@ -19188,10 +19239,26 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19188
19239
  fileRefsSubstituted: 0,
19189
19240
  fileRefLexiconMisses: 0
19190
19241
  };
19242
+ const sharedCauses = /* @__PURE__ */ new Map();
19243
+ const causalQueue = store.findNodesByLabel("Problem").filter(
19244
+ (n) => n.attrs["source"] !== "cloud" && n.attrs["resolvedAs"] !== PROBLEM_RESOLUTION.FALSE_POSITIVE && !ignored(n.description)
19245
+ ).map((n) => n.id);
19246
+ const causalSeen = new Set(causalQueue);
19247
+ for (let i = 0; i < causalQueue.length; i++) {
19248
+ const id = causalQueue[i];
19249
+ for (const e of [...store.outEdges(id, ["CAUSED_BY"]), ...opts.sharedStore?.outEdges(id, ["CAUSED_BY"]) ?? []]) {
19250
+ if (causalSeen.has(e.to)) continue;
19251
+ causalSeen.add(e.to);
19252
+ const cause = nodeOf(e.to);
19253
+ if (!cause || cause.label !== "RootCause" || cause.attrs["source"] === "cloud" || ignored(cause.description)) continue;
19254
+ sharedCauses.set(cause.id, cause);
19255
+ causalQueue.push(cause.id);
19256
+ }
19257
+ }
19191
19258
  for (const label of INSTANCE_LABELS) {
19192
19259
  const ref = [];
19193
19260
  includedByLabel.set(label, ref);
19194
- const population = label === "RootCause" && opts.sharedStore ? [...store.findNodesByLabel(label), ...opts.sharedStore.findNodesByLabel(label)] : store.findNodesByLabel(label);
19261
+ const population = label === "RootCause" && opts.sharedStore ? [...store.findNodesByLabel(label), ...sharedCauses.values()] : store.findNodesByLabel(label);
19195
19262
  for (const n of population) {
19196
19263
  if (seen.has(n.id)) continue;
19197
19264
  if (walked.has(n.id)) continue;
@@ -19209,18 +19276,18 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19209
19276
  composition.ignored++;
19210
19277
  continue;
19211
19278
  }
19212
- const contributedAtSeq = n.attrs["contributedAtSeq"];
19279
+ const contributedAtSeq = instanceReceipt(n, opts.destinationKey)["contributedAtSeq"];
19213
19280
  if (typeof contributedAtSeq === "number" && (n.lastReinforcedAtSeq ?? 0) <= contributedAtSeq) {
19214
19281
  composition.watermarked++;
19215
19282
  anchorSources.add(n.id);
19216
- semanticEndpoints.set(n.id, contributedWireId(n));
19283
+ semanticEndpoints.set(n.id, contributedWireId(n, opts.destinationKey));
19217
19284
  continue;
19218
19285
  }
19219
19286
  if (label === "Solution" && n.description.startsWith(AUTO_MINT_PREFIX)) {
19220
19287
  composition.autoMint++;
19221
19288
  continue;
19222
19289
  }
19223
- if (isTerminallyRefused(n)) {
19290
+ if (isTerminallyRefused({ ...n, attrs: instanceReceipt(n, opts.destinationKey) })) {
19224
19291
  composition.terminalRefused++;
19225
19292
  continue;
19226
19293
  }
@@ -19279,9 +19346,9 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19279
19346
  }
19280
19347
  const dg = digest(targets.map(({ e, wireId }) => `${e.type}>${wireId}`).sort());
19281
19348
  const sourceNode = nodeOf(sourceId);
19282
- if (sourceNode?.attrs["anchorsContributedDigest"] === dg) continue;
19349
+ if ((sourceNode && instanceReceipt(sourceNode, opts.destinationKey)["anchorsContributedDigest"]) === dg) continue;
19283
19350
  anchorDigests[sourceId] = dg;
19284
- const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode);
19351
+ const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode, opts.destinationKey);
19285
19352
  for (const { e, t, wireId } of targets) {
19286
19353
  const reject = unshippableReason(wireId, t.label);
19287
19354
  if (reject) {
@@ -19310,7 +19377,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19310
19377
  for (const sourceId of anchorSources) {
19311
19378
  const source = nodeOf(sourceId);
19312
19379
  if (!source) continue;
19313
- const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source);
19380
+ const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source, opts.destinationKey);
19314
19381
  const planned = [];
19315
19382
  for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
19316
19383
  const target = nodeOf(e.to);
@@ -19345,7 +19412,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19345
19412
  const dg = digest(
19346
19413
  planned.map(({ symId, wireAttrs }) => `ANCHORED_AT>${symId}>${JSON.stringify(wireAttrs)}`).sort()
19347
19414
  );
19348
- if (source.attrs["projectAnchorsContributedDigest"] === dg) continue;
19415
+ if (instanceReceipt(source, opts.destinationKey)["projectAnchorsContributedDigest"] === dg) continue;
19349
19416
  projectAnchorDigests[sourceId] = dg;
19350
19417
  for (const { e, target, symId, wireAttrs } of planned) {
19351
19418
  if (!contextSeen.has(symId)) {
@@ -19391,7 +19458,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19391
19458
  if (outs.length === 0) continue;
19392
19459
  const dg = digest(outs.map(({ e, wireTo }) => `${e.type}>${wireTo}`).sort());
19393
19460
  const owner = nodeOf(sourceId);
19394
- const digestSkip = owner?.attrs["semanticEdgesContributedDigest"] === dg;
19461
+ const digestSkip = (owner && instanceReceipt(owner, opts.destinationKey)["semanticEdgesContributedDigest"]) === dg;
19395
19462
  if (!digestSkip) semanticEdgeDigests[sourceId] = dg;
19396
19463
  for (const { e, wireTo } of outs) {
19397
19464
  if (digestSkip && !shippedById.has(e.to)) continue;
@@ -19448,7 +19515,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19448
19515
  for (const wn of nodes) {
19449
19516
  if (wn.label !== "RootCause" && wn.label !== "Solution") continue;
19450
19517
  const src = nodeOf(wn.id);
19451
- if (typeof src?.attrs["contributedAtSeq"] === "number") continue;
19518
+ if (src && typeof instanceReceipt(src, opts.destinationKey)["contributedAtSeq"] === "number") continue;
19452
19519
  if (bindingEndpoints.has(wn.id)) continue;
19453
19520
  heldOrphans.add(wn.id);
19454
19521
  }
@@ -19526,6 +19593,7 @@ function buildDrainInstances(store, sharedStore, input) {
19526
19593
  ...input.includePackages !== void 0 ? { includePackages: input.includePackages } : {},
19527
19594
  workspaceRoot: input.workspaceRoot,
19528
19595
  sharedStore,
19596
+ destinationKey: input.destinationKey,
19529
19597
  onComposition: (c) => {
19530
19598
  composition = c;
19531
19599
  },
package/errata.mjs CHANGED
@@ -17438,7 +17438,7 @@ function rowToEdge(r) {
17438
17438
  function openGraphStore(opts) {
17439
17439
  return new SqliteGraphStore(opts);
17440
17440
  }
17441
- var LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SCHEMA_SQL, TEMPORAL_INDEX_SQL, SqliteGraphStore;
17441
+ var LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, SqliteGraphStore;
17442
17442
  var init_store = __esm({
17443
17443
  "../../packages/local-graph/src/store.ts"() {
17444
17444
  "use strict";
@@ -17559,6 +17559,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
17559
17559
  `;
17560
17560
  TEMPORAL_INDEX_SQL = `
17561
17561
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
17562
+ -- Covering + partial index for labelFingerprint over the instance labels: the
17563
+ -- live-push probe runs it per workspace on a timer, and each hit used to read
17564
+ -- its table row. Partial, so it maintains a few thousand rows instead of adding
17565
+ -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
17566
+ DROP INDEX IF EXISTS nodes_live_fingerprint;
17567
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
17562
17568
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
17563
17569
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
17564
17570
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -17572,6 +17578,7 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
17572
17578
  -- every edited file of every turn, and a scan parses every Problem's attrs.
17573
17579
  CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
17574
17580
  `;
17581
+ FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
17575
17582
  SqliteGraphStore = class {
17576
17583
  db;
17577
17584
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -18048,6 +18055,32 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18048
18055
  attrs: JSON.parse(r.attrs_json)
18049
18056
  }));
18050
18057
  }
18058
+ *iterateNodeIdentitiesByLabel(label, pageSize = 2e3) {
18059
+ const page = this.db.prepare(
18060
+ "SELECT rowid AS rid, id, json_extract(attrs_json, '$.relPath') AS relPath, json_extract(attrs_json, '$.qname') AS qname FROM nodes WHERE rowid > ? AND label = ? AND valid_to IS NULL ORDER BY rowid LIMIT ?"
18061
+ );
18062
+ let after = 0;
18063
+ for (; ; ) {
18064
+ const rows = page.all(after, label, pageSize);
18065
+ for (const r of rows) {
18066
+ yield {
18067
+ id: r.id,
18068
+ relPath: typeof r.relPath === "string" ? r.relPath : null,
18069
+ qname: typeof r.qname === "string" ? r.qname : null
18070
+ };
18071
+ }
18072
+ if (rows.length < pageSize) return;
18073
+ after = rows[rows.length - 1].rid;
18074
+ }
18075
+ }
18076
+ recentNodeIds(label, limit) {
18077
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(label, Math.max(0, limit));
18078
+ return rows.map((r) => r.id);
18079
+ }
18080
+ hasLiveNodes() {
18081
+ const r = this.db.prepare("SELECT EXISTS(SELECT 1 FROM nodes WHERE valid_to IS NULL) AS e").get();
18082
+ return r.e === 1;
18083
+ }
18051
18084
  findNodeIdentitiesByLabel(label) {
18052
18085
  const rows = this.stmts.identitiesByLabel.all(label);
18053
18086
  return rows.map((r) => ({
@@ -18195,6 +18228,15 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18195
18228
  }
18196
18229
  labelFingerprint(labels) {
18197
18230
  if (labels.length === 0) return "0";
18231
+ if (labels.length === FINGERPRINT_INDEXED_LABELS.length && FINGERPRINT_INDEXED_LABELS.every((l) => labels.includes(l))) {
18232
+ const r2 = this.db.prepare(
18233
+ `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
18234
+ MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
18235
+ FROM nodes INDEXED BY nodes_instance_fingerprint
18236
+ WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
18237
+ ).get();
18238
+ return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
18239
+ }
18198
18240
  const r = this.db.prepare(
18199
18241
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
18200
18242
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
@@ -23978,6 +24020,7 @@ __export(src_exports2, {
23978
24020
  CODE_REACH_EDGES: () => CODE_REACH_EDGES,
23979
24021
  CREDIT_FAMILY: () => CREDIT_FAMILY,
23980
24022
  DEFAULT_MIN_COVERAGE: () => DEFAULT_MIN_COVERAGE,
24023
+ FINGERPRINT_INDEXED_LABELS: () => FINGERPRINT_INDEXED_LABELS,
23981
24024
  FIX_CANDIDATE_ASK_LIMIT: () => FIX_CANDIDATE_ASK_LIMIT,
23982
24025
  FOLD_EMBED_AUTO_COSINE: () => FOLD_EMBED_AUTO_COSINE,
23983
24026
  FOLD_JUDGE_MIN_COSINE: () => FOLD_JUDGE_MIN_COSINE,
@@ -80224,7 +80267,7 @@ function createWatchBreaker(deps) {
80224
80267
  }
80225
80268
 
80226
80269
  // src/engine.ts
80227
- var DAEMON_VERSION = true ? "2.0.8-dev.2914" : "2.0.0-alpha.0";
80270
+ var DAEMON_VERSION = true ? "2.0.8-dev.2929" : "2.0.0-alpha.0";
80228
80271
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
80229
80272
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
80230
80273
  var GIT_OP_MUTE_MS = 4e3;
@@ -80791,7 +80834,7 @@ function createWorkspaceEngine(opts) {
80791
80834
  lastRemoteAt = now;
80792
80835
  try {
80793
80836
  const norm = normStackTokens;
80794
- const seed = store.findNodesByLabel("Problem").sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, 6).map((p) => p.id);
80837
+ const seed = store.recentNodeIds("Problem", 6);
80795
80838
  const q = {
80796
80839
  stack: outboundStack(),
80797
80840
  domains: norm(profile.domains),
@@ -82670,6 +82713,40 @@ async function listenServer(fetchFn, port) {
82670
82713
  init_registry();
82671
82714
  init_identity2();
82672
82715
 
82716
+ // src/instance-receipts.ts
82717
+ import { createHash as createHash16 } from "node:crypto";
82718
+ var FIELDS = ["contributedAtSeq", "cloudNodeId", "anchorsContributedDigest", "projectAnchorsContributedDigest", "semanticEdgesContributedDigest", "shipRefusedSinceSeq", "shipRefusedCount", "shipRefusedReason", "shipRefusedFingerprint"];
82719
+ function instanceDestinationKey(input) {
82720
+ return createHash16("sha256").update(JSON.stringify([
82721
+ input.workspace,
82722
+ input.cloudUrl.replace(/\/+$/, ""),
82723
+ input.installationId ?? input.profileName ?? "default",
82724
+ input.projectId ?? null,
82725
+ input.writeTarget?.visibility ?? null,
82726
+ input.writeTarget?.org_id ?? null,
82727
+ input.writeTarget?.team_id ?? null
82728
+ ])).digest("hex");
82729
+ }
82730
+ function receiptMap(attrs) {
82731
+ const value = attrs["instanceReceipts"];
82732
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
82733
+ }
82734
+ function instanceReceipt(n, destination) {
82735
+ return destination ? receiptMap(n.attrs)[destination] ?? {} : n.attrs;
82736
+ }
82737
+ function updateInstanceReceipt(n, destination, patch, mirrorLegacy) {
82738
+ const receipt = { ...instanceReceipt(n, destination), ...patch };
82739
+ const attrs = { ...n.attrs, instanceReceipts: { ...receiptMap(n.attrs), [destination]: receipt } };
82740
+ if (mirrorLegacy) for (const field of FIELDS) {
82741
+ if (field in receipt) attrs[field] = receipt[field];
82742
+ else delete attrs[field];
82743
+ }
82744
+ return attrs;
82745
+ }
82746
+ function instanceReceiptSequence(n) {
82747
+ return Math.max(n.lastReinforcedAtSeq ?? 0, n.createdAtSeq ?? 0);
82748
+ }
82749
+
82673
82750
  // src/multi.ts
82674
82751
  init_dist();
82675
82752
  init_src5();
@@ -83074,8 +83151,8 @@ function compositionCounters(c) {
83074
83151
  ...Object.fromEntries(buckets.map((b) => [`sidecarDrop_${b}`, sidecarDropReasons[b] ?? 0]))
83075
83152
  };
83076
83153
  }
83077
- function contributedWireId(n) {
83078
- const cloudId = n.attrs["cloudNodeId"];
83154
+ function contributedWireId(n, destinationKey) {
83155
+ const cloudId = instanceReceipt(n, destinationKey)["cloudNodeId"];
83079
83156
  return isMembraneSaltedId(cloudId) ? cloudId : n.id;
83080
83157
  }
83081
83158
  var WIRE_CANONICAL_ID_MIN = 6;
@@ -83219,10 +83296,26 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83219
83296
  fileRefsSubstituted: 0,
83220
83297
  fileRefLexiconMisses: 0
83221
83298
  };
83299
+ const sharedCauses = /* @__PURE__ */ new Map();
83300
+ const causalQueue = store.findNodesByLabel("Problem").filter(
83301
+ (n) => n.attrs["source"] !== "cloud" && n.attrs["resolvedAs"] !== PROBLEM_RESOLUTION.FALSE_POSITIVE && !ignored(n.description)
83302
+ ).map((n) => n.id);
83303
+ const causalSeen = new Set(causalQueue);
83304
+ for (let i2 = 0; i2 < causalQueue.length; i2++) {
83305
+ const id = causalQueue[i2];
83306
+ for (const e of [...store.outEdges(id, ["CAUSED_BY"]), ...opts.sharedStore?.outEdges(id, ["CAUSED_BY"]) ?? []]) {
83307
+ if (causalSeen.has(e.to)) continue;
83308
+ causalSeen.add(e.to);
83309
+ const cause = nodeOf(e.to);
83310
+ if (!cause || cause.label !== "RootCause" || cause.attrs["source"] === "cloud" || ignored(cause.description)) continue;
83311
+ sharedCauses.set(cause.id, cause);
83312
+ causalQueue.push(cause.id);
83313
+ }
83314
+ }
83222
83315
  for (const label of INSTANCE_LABELS) {
83223
83316
  const ref = [];
83224
83317
  includedByLabel.set(label, ref);
83225
- const population = label === "RootCause" && opts.sharedStore ? [...store.findNodesByLabel(label), ...opts.sharedStore.findNodesByLabel(label)] : store.findNodesByLabel(label);
83318
+ const population = label === "RootCause" && opts.sharedStore ? [...store.findNodesByLabel(label), ...sharedCauses.values()] : store.findNodesByLabel(label);
83226
83319
  for (const n of population) {
83227
83320
  if (seen.has(n.id)) continue;
83228
83321
  if (walked.has(n.id)) continue;
@@ -83240,18 +83333,18 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83240
83333
  composition.ignored++;
83241
83334
  continue;
83242
83335
  }
83243
- const contributedAtSeq = n.attrs["contributedAtSeq"];
83336
+ const contributedAtSeq = instanceReceipt(n, opts.destinationKey)["contributedAtSeq"];
83244
83337
  if (typeof contributedAtSeq === "number" && (n.lastReinforcedAtSeq ?? 0) <= contributedAtSeq) {
83245
83338
  composition.watermarked++;
83246
83339
  anchorSources.add(n.id);
83247
- semanticEndpoints.set(n.id, contributedWireId(n));
83340
+ semanticEndpoints.set(n.id, contributedWireId(n, opts.destinationKey));
83248
83341
  continue;
83249
83342
  }
83250
83343
  if (label === "Solution" && n.description.startsWith(AUTO_MINT_PREFIX)) {
83251
83344
  composition.autoMint++;
83252
83345
  continue;
83253
83346
  }
83254
- if (isTerminallyRefused(n)) {
83347
+ if (isTerminallyRefused({ ...n, attrs: instanceReceipt(n, opts.destinationKey) })) {
83255
83348
  composition.terminalRefused++;
83256
83349
  continue;
83257
83350
  }
@@ -83310,9 +83403,9 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83310
83403
  }
83311
83404
  const dg = digest(targets.map(({ e, wireId }) => `${e.type}>${wireId}`).sort());
83312
83405
  const sourceNode = nodeOf(sourceId);
83313
- if (sourceNode?.attrs["anchorsContributedDigest"] === dg) continue;
83406
+ if ((sourceNode && instanceReceipt(sourceNode, opts.destinationKey)["anchorsContributedDigest"]) === dg) continue;
83314
83407
  anchorDigests[sourceId] = dg;
83315
- const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode);
83408
+ const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode, opts.destinationKey);
83316
83409
  for (const { e, t, wireId } of targets) {
83317
83410
  const reject = unshippableReason(wireId, t.label);
83318
83411
  if (reject) {
@@ -83341,7 +83434,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83341
83434
  for (const sourceId of anchorSources) {
83342
83435
  const source = nodeOf(sourceId);
83343
83436
  if (!source) continue;
83344
- const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source);
83437
+ const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source, opts.destinationKey);
83345
83438
  const planned = [];
83346
83439
  for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
83347
83440
  const target = nodeOf(e.to);
@@ -83376,7 +83469,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83376
83469
  const dg = digest(
83377
83470
  planned.map(({ symId, wireAttrs }) => `ANCHORED_AT>${symId}>${JSON.stringify(wireAttrs)}`).sort()
83378
83471
  );
83379
- if (source.attrs["projectAnchorsContributedDigest"] === dg) continue;
83472
+ if (instanceReceipt(source, opts.destinationKey)["projectAnchorsContributedDigest"] === dg) continue;
83380
83473
  projectAnchorDigests[sourceId] = dg;
83381
83474
  for (const { e, target, symId, wireAttrs } of planned) {
83382
83475
  if (!contextSeen.has(symId)) {
@@ -83422,7 +83515,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83422
83515
  if (outs.length === 0) continue;
83423
83516
  const dg = digest(outs.map(({ e, wireTo }) => `${e.type}>${wireTo}`).sort());
83424
83517
  const owner = nodeOf(sourceId);
83425
- const digestSkip = owner?.attrs["semanticEdgesContributedDigest"] === dg;
83518
+ const digestSkip = (owner && instanceReceipt(owner, opts.destinationKey)["semanticEdgesContributedDigest"]) === dg;
83426
83519
  if (!digestSkip) semanticEdgeDigests[sourceId] = dg;
83427
83520
  for (const { e, wireTo } of outs) {
83428
83521
  if (digestSkip && !shippedById.has(e.to)) continue;
@@ -83479,7 +83572,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83479
83572
  for (const wn of nodes) {
83480
83573
  if (wn.label !== "RootCause" && wn.label !== "Solution") continue;
83481
83574
  const src = nodeOf(wn.id);
83482
- if (typeof src?.attrs["contributedAtSeq"] === "number") continue;
83575
+ if (src && typeof instanceReceipt(src, opts.destinationKey)["contributedAtSeq"] === "number") continue;
83483
83576
  if (bindingEndpoints.has(wn.id)) continue;
83484
83577
  heldOrphans.add(wn.id);
83485
83578
  }
@@ -83633,6 +83726,7 @@ function buildDrainInstances(store, sharedStore, input) {
83633
83726
  ...input.includePackages !== void 0 ? { includePackages: input.includePackages } : {},
83634
83727
  workspaceRoot: input.workspaceRoot,
83635
83728
  sharedStore,
83729
+ destinationKey: input.destinationKey,
83636
83730
  onComposition: (c) => {
83637
83731
  composition = c;
83638
83732
  },
@@ -84130,7 +84224,7 @@ function buildReverseSymbolMap(opts) {
84130
84224
  function* reverseSymbolEntries(opts) {
84131
84225
  const { store, salt, projectId } = opts;
84132
84226
  for (const kind of ANCHOR_SYMBOL_KINDS2) {
84133
- for (const n of store.findNodeIdentitiesByLabel(kind)) {
84227
+ for (const n of store.iterateNodeIdentitiesByLabel(kind)) {
84134
84228
  const { relPath, qname } = n;
84135
84229
  if (relPath === null || qname === null) continue;
84136
84230
  const sym = projectSymbolId(salt, projectId, relPath, qname, kind);
@@ -84138,7 +84232,7 @@ function* reverseSymbolEntries(opts) {
84138
84232
  yield [projectSaltedNodeId(sym, salt), n.id];
84139
84233
  }
84140
84234
  }
84141
- for (const n of store.findNodeIdentitiesByLabel("File")) {
84235
+ for (const n of store.iterateNodeIdentitiesByLabel("File")) {
84142
84236
  if (n.relPath === null) continue;
84143
84237
  const sym = projectSymbolId(salt, projectId, n.relPath, FILE_ANCHOR_QNAME, "File");
84144
84238
  yield [sym, n.id];
@@ -84178,7 +84272,7 @@ function buildOwnKnowledgeMap(opts) {
84178
84272
  function* ownKnowledgeEntries(opts) {
84179
84273
  const { store, salt } = opts;
84180
84274
  for (const kind of ANCHOR_SOURCE_KINDS) {
84181
- for (const n of store.findNodeIdentitiesByLabel(kind)) {
84275
+ for (const n of store.iterateNodeIdentitiesByLabel(kind)) {
84182
84276
  if (n.id.startsWith("projn_")) continue;
84183
84277
  yield [projectSaltedNodeId(n.id, salt), n.id];
84184
84278
  }
@@ -85300,7 +85394,7 @@ async function startHistoryStage(root, input, client, walk11 = {}) {
85300
85394
 
85301
85395
  // src/link-flow.ts
85302
85396
  init_cloud_auth();
85303
- import { createHash as createHash16 } from "node:crypto";
85397
+ import { createHash as createHash17 } from "node:crypto";
85304
85398
  import { execFileSync as execFileSync5 } from "node:child_process";
85305
85399
  import { existsSync as existsSync30, writeFileSync as writeFileSync23 } from "node:fs";
85306
85400
  import { join as join38 } from "node:path";
@@ -85594,7 +85688,7 @@ async function loginOAuthLoopback(opts) {
85594
85688
 
85595
85689
  // src/link-flow.ts
85596
85690
  function remoteFingerprint(locator) {
85597
- return createHash16("sha256").update(locator).digest("hex");
85691
+ return createHash17("sha256").update(locator).digest("hex");
85598
85692
  }
85599
85693
  function isDecision(value) {
85600
85694
  if (!value || typeof value !== "object") return false;
@@ -86489,7 +86583,7 @@ async function startMultiDaemon(opts = {}) {
86489
86583
  registerWorkspace(rec.engine.profile, rec.root);
86490
86584
  } catch {
86491
86585
  }
86492
- if (wakeIndexPlan(rec.engine.store.nodeCount(), opts.reindexOnStart !== false) === "full-index") {
86586
+ if (wakeIndexPlan(rec.engine.store.hasLiveNodes() ? 1 : 0, opts.reindexOnStart !== false) === "full-index") {
86493
86587
  void enqueueWakeIndex(async () => {
86494
86588
  const done = markPass(`wake-index:${rec.entry.name}`);
86495
86589
  try {
@@ -87311,6 +87405,7 @@ async function startMultiDaemon(opts = {}) {
87311
87405
  }
87312
87406
  const ignore2 = loadClaimIgnorePatterns(globalDir());
87313
87407
  const clientPool = /* @__PURE__ */ new Map();
87408
+ const receiptContexts = /* @__PURE__ */ new Map();
87314
87409
  const poolFor = (identity) => {
87315
87410
  const k = identityKey(identity);
87316
87411
  let c = clientPool.get(k);
@@ -87453,7 +87548,26 @@ async function startMultiDaemon(opts = {}) {
87453
87548
  return res.accepted;
87454
87549
  });
87455
87550
  }
87551
+ const receiptProfile = recordIdentity.profile ?? (cfg2.activeInstallationProfile ? cfg2.installationProfiles[cfg2.activeInstallationProfile] : void 0);
87552
+ let receiptContext = receiptContexts.get(identityKey(recordIdentity));
87553
+ if (!receiptContext) {
87554
+ try {
87555
+ receiptContext = await client.context();
87556
+ receiptContexts.set(identityKey(recordIdentity), receiptContext);
87557
+ } catch (err2) {
87558
+ errors.push(`[${projectName}] instance destination unresolved: ${err2 instanceof Error ? err2.message : String(err2)}`);
87559
+ continue;
87560
+ }
87561
+ }
87562
+ const destinationKey = instanceDestinationKey({
87563
+ workspace: r.id,
87564
+ cloudUrl: opts.cloudUrl ?? receiptProfile?.cloudUrl ?? cfg2.cloudUrl,
87565
+ installationId: receiptContext.context.installation_id ?? receiptContext.identity.credential_id,
87566
+ projectId: project?.projectId,
87567
+ writeTarget: project ? null : receiptContext.context.default_write_target
87568
+ });
87456
87569
  const built = await buildInstanceBatch(r, {
87570
+ destinationKey,
87457
87571
  profile: r.engine.profile,
87458
87572
  daemonVersion: DAEMON_VERSION,
87459
87573
  ignorePatterns: [...ignore2],
@@ -87498,7 +87612,6 @@ async function startMultiDaemon(opts = {}) {
87498
87612
  );
87499
87613
  }
87500
87614
  uploaded += res.accepted;
87501
- const seq = store.currentIngestSeq();
87502
87615
  const cloudIdByLocal = new Map(
87503
87616
  res.result.nodes.filter((rn) => rn.nodeId).map((rn) => [rn.canonicalId, rn.nodeId])
87504
87617
  );
@@ -87507,6 +87620,7 @@ async function startMultiDaemon(opts = {}) {
87507
87620
  if (rn.decision === "rejected") rejected.set(rn.canonicalId, rn.reason ?? "no reason given");
87508
87621
  }
87509
87622
  const rejectedEdges = res.result.edges.filter((re) => re.decision === "rejected");
87623
+ const edgeReceiptsComplete = rejectedEdges.length === 0 && res.result.edges.length === instances.edges.length;
87510
87624
  if (rejectedEdges.length > 0) {
87511
87625
  const byReason = /* @__PURE__ */ new Map();
87512
87626
  for (const re of rejectedEdges) {
@@ -87537,17 +87651,17 @@ async function startMultiDaemon(opts = {}) {
87537
87651
  const owner = owningStore(id);
87538
87652
  if (!owner) return;
87539
87653
  const fp = refusalFingerprint(reason, owner.n.description);
87540
- const same = owner.n.attrs["shipRefusedFingerprint"] === fp;
87541
- const count = same ? Number(owner.n.attrs["shipRefusedCount"] ?? 0) + 1 : 1;
87542
- if (owner.n.attrs["shipRefusedSinceSeq"] !== void 0) repeat();
87654
+ const same = instanceReceipt(owner.n, destinationKey)["shipRefusedFingerprint"] === fp;
87655
+ const count = same ? Number(instanceReceipt(owner.n, destinationKey)["shipRefusedCount"] ?? 0) + 1 : 1;
87656
+ if (instanceReceipt(owner.n, destinationKey)["shipRefusedSinceSeq"] !== void 0) repeat();
87543
87657
  owner.s.updateNode(id, {
87544
- attrs: {
87545
- ...owner.n.attrs,
87546
- ...same && owner.n.attrs["shipRefusedSinceSeq"] !== void 0 ? {} : { shipRefusedSinceSeq: seq },
87658
+ attrs: updateInstanceReceipt(owner.n, destinationKey, {
87659
+ ...instanceReceipt(owner.n, destinationKey),
87660
+ ...same && instanceReceipt(owner.n, destinationKey)["shipRefusedSinceSeq"] !== void 0 ? {} : { shipRefusedSinceSeq: owner.s.currentIngestSeq() },
87547
87661
  shipRefusedCount: count,
87548
87662
  shipRefusedReason: reason,
87549
87663
  shipRefusedFingerprint: fp
87550
- }
87664
+ }, owner.s === store)
87551
87665
  });
87552
87666
  if (same && count === SHIP_TERMINAL_REFUSALS && !isRetryableRefusal(reason)) {
87553
87667
  nowHeld.push({ id, reason });
@@ -87564,6 +87678,7 @@ async function startMultiDaemon(opts = {}) {
87564
87678
  const owner = owningStore(n.id);
87565
87679
  if (!owner || !["Problem", "Solution", "RootCause"].includes(owner.n.label)) return;
87566
87680
  if (blocked.has(n.id)) return;
87681
+ if (!cloudIdByLocal.has(n.id)) return;
87567
87682
  if (rejected.has(n.id)) return;
87568
87683
  const cloudNodeId = cloudIdByLocal.get(n.id);
87569
87684
  const {
@@ -87572,37 +87687,41 @@ async function startMultiDaemon(opts = {}) {
87572
87687
  shipRefusedReason: _clearedReason,
87573
87688
  shipRefusedFingerprint: _clearedFp,
87574
87689
  ...carried
87575
- } = owner.n.attrs;
87690
+ } = instanceReceipt(owner.n, destinationKey);
87576
87691
  owner.s.updateNode(n.id, {
87577
- attrs: {
87692
+ attrs: updateInstanceReceipt(owner.n, destinationKey, {
87578
87693
  ...carried,
87579
- contributedAtSeq: seq,
87580
- ...cloudNodeId && cloudNodeId !== n.id ? { cloudNodeId } : {}
87581
- }
87694
+ shipRefusedSinceSeq: void 0,
87695
+ shipRefusedCount: void 0,
87696
+ shipRefusedReason: void 0,
87697
+ shipRefusedFingerprint: void 0,
87698
+ contributedAtSeq: instanceReceiptSequence(n),
87699
+ cloudNodeId
87700
+ }, owner.s === store)
87582
87701
  });
87583
87702
  });
87584
87703
  await stampBatched(Object.entries(instances.anchorDigests), ([localId, dg]) => {
87585
87704
  const owner = owningStore(localId);
87586
87705
  if (!owner) return;
87587
- if (rejected.has(localId) || blocked.has(localId)) return;
87706
+ if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
87588
87707
  owner.s.updateNode(localId, {
87589
- attrs: { ...owner.n.attrs, anchorsContributedDigest: dg }
87708
+ attrs: updateInstanceReceipt(owner.n, destinationKey, { anchorsContributedDigest: dg }, owner.s === store)
87590
87709
  });
87591
87710
  });
87592
87711
  await stampBatched(Object.entries(instances.projectAnchorDigests), ([localId, dg]) => {
87593
87712
  const owner = owningStore(localId);
87594
87713
  if (!owner) return;
87595
- if (rejected.has(localId) || blocked.has(localId)) return;
87714
+ if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
87596
87715
  owner.s.updateNode(localId, {
87597
- attrs: { ...owner.n.attrs, projectAnchorsContributedDigest: dg }
87716
+ attrs: updateInstanceReceipt(owner.n, destinationKey, { projectAnchorsContributedDigest: dg }, owner.s === store)
87598
87717
  });
87599
87718
  });
87600
87719
  await stampBatched(Object.entries(instances.semanticEdgeDigests), ([localId, dg]) => {
87601
87720
  const owner = owningStore(localId);
87602
87721
  if (!owner) return;
87603
- if (rejected.has(localId) || blocked.has(localId)) return;
87722
+ if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
87604
87723
  owner.s.updateNode(localId, {
87605
- attrs: { ...owner.n.attrs, semanticEdgesContributedDigest: dg }
87724
+ attrs: updateInstanceReceipt(owner.n, destinationKey, { semanticEdgesContributedDigest: dg }, owner.s === store)
87606
87725
  });
87607
87726
  });
87608
87727
  appendPassLedger(r.engine.paths.configDir, "ship", Date.now() - shipStartedAt, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.8-dev.2914",
3
+ "version": "2.0.8-dev.2929",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -24519,6 +24519,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
24519
24519
  `;
24520
24520
  var TEMPORAL_INDEX_SQL = `
24521
24521
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
24522
+ -- Covering + partial index for labelFingerprint over the instance labels: the
24523
+ -- live-push probe runs it per workspace on a timer, and each hit used to read
24524
+ -- its table row. Partial, so it maintains a few thousand rows instead of adding
24525
+ -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
24526
+ DROP INDEX IF EXISTS nodes_live_fingerprint;
24527
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
24522
24528
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
24523
24529
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
24524
24530
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -24589,6 +24595,7 @@ function rowToEdge(r) {
24589
24595
  attrs: JSON.parse(r.attrs_json)
24590
24596
  };
24591
24597
  }
24598
+ var FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
24592
24599
  var SqliteGraphStore = class {
24593
24600
  db;
24594
24601
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -25065,6 +25072,32 @@ var SqliteGraphStore = class {
25065
25072
  attrs: JSON.parse(r.attrs_json)
25066
25073
  }));
25067
25074
  }
25075
+ *iterateNodeIdentitiesByLabel(label, pageSize = 2e3) {
25076
+ const page = this.db.prepare(
25077
+ "SELECT rowid AS rid, id, json_extract(attrs_json, '$.relPath') AS relPath, json_extract(attrs_json, '$.qname') AS qname FROM nodes WHERE rowid > ? AND label = ? AND valid_to IS NULL ORDER BY rowid LIMIT ?"
25078
+ );
25079
+ let after = 0;
25080
+ for (; ; ) {
25081
+ const rows = page.all(after, label, pageSize);
25082
+ for (const r of rows) {
25083
+ yield {
25084
+ id: r.id,
25085
+ relPath: typeof r.relPath === "string" ? r.relPath : null,
25086
+ qname: typeof r.qname === "string" ? r.qname : null
25087
+ };
25088
+ }
25089
+ if (rows.length < pageSize) return;
25090
+ after = rows[rows.length - 1].rid;
25091
+ }
25092
+ }
25093
+ recentNodeIds(label, limit) {
25094
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(label, Math.max(0, limit));
25095
+ return rows.map((r) => r.id);
25096
+ }
25097
+ hasLiveNodes() {
25098
+ const r = this.db.prepare("SELECT EXISTS(SELECT 1 FROM nodes WHERE valid_to IS NULL) AS e").get();
25099
+ return r.e === 1;
25100
+ }
25068
25101
  findNodeIdentitiesByLabel(label) {
25069
25102
  const rows = this.stmts.identitiesByLabel.all(label);
25070
25103
  return rows.map((r) => ({
@@ -25212,6 +25245,15 @@ var SqliteGraphStore = class {
25212
25245
  }
25213
25246
  labelFingerprint(labels) {
25214
25247
  if (labels.length === 0) return "0";
25248
+ if (labels.length === FINGERPRINT_INDEXED_LABELS.length && FINGERPRINT_INDEXED_LABELS.every((l) => labels.includes(l))) {
25249
+ const r2 = this.db.prepare(
25250
+ `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
25251
+ MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
25252
+ FROM nodes INDEXED BY nodes_instance_fingerprint
25253
+ WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
25254
+ ).get();
25255
+ return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
25256
+ }
25215
25257
  const r = this.db.prepare(
25216
25258
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
25217
25259
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s