@inerrata-corporation/errata 2.0.1-dev.99 → 2.0.2-dev.70

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.
Files changed (2) hide show
  1. package/errata.mjs +146 -39
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -21851,8 +21851,14 @@ var init_client = __esm({
21851
21851
  * the per-decision response into flush accounting.
21852
21852
  *
21853
21853
  * The v1 door caps a payload at `MAX_NODES_PER_PAYLOAD` / `MAX_EDGES_PER_PAYLOAD`
21854
- * (413 over). A batch above the cap is split and drained across calls under ONE
21855
- * runId: every NODE chunk first (edges empty), then EDGE chunks (nodes empty).
21854
+ * (413 over). A batch above the cap is split and drained across calls, every
21855
+ * NODE chunk first (edges empty), then EDGE chunks (nodes empty). Each chunk
21856
+ * gets its OWN runId: the door's durable idempotency claim is keyed on
21857
+ * (agent, org, runId) with the payload digest, so reusing one runId across
21858
+ * different chunk payloads 409s "runId was already used with a different
21859
+ * payload" on the second chunk — exactly how the first real >25-node drain
21860
+ * died (2026-07-22). The runId never grouped anything server-side; it exists
21861
+ * for duplicate-POST protection, which is per-request by nature.
21856
21862
  * Recognition resolves an edge's endpoints against nodes already ingested this
21857
21863
  * drain (endpoint-label validation is deferred to the service when an endpoint
21858
21864
  * isn't in-payload), so the split never orphans an edge. Sub-results concat into
@@ -21865,6 +21871,7 @@ var init_client = __esm({
21865
21871
  }
21866
21872
  const merged = { runId, nodes: [], edges: [] };
21867
21873
  let pendingEdges = [...batch.edges];
21874
+ const echoedCloudId = /* @__PURE__ */ new Map();
21868
21875
  for (const nodeChunk of chunkArray(batch.nodes, INGEST_NODE_CHUNK)) {
21869
21876
  const ids = new Set(nodeChunk.map((n) => n.id));
21870
21877
  const inChunk = pendingEdges.filter((e) => ids.has(e.from) && ids.has(e.to)).slice(0, MAX_EDGES_PER_PAYLOAD);
@@ -21872,15 +21879,23 @@ var init_client = __esm({
21872
21879
  const shipped = new Set(inChunk);
21873
21880
  pendingEdges = pendingEdges.filter((e) => !shipped.has(e));
21874
21881
  }
21875
- const r = await this.ingestWire(toWirePayload({ ...batch, nodes: nodeChunk, edges: inChunk }, runId));
21882
+ const r = await this.ingestWire(toWirePayload({ ...batch, nodes: nodeChunk, edges: inChunk }, randomUUID()));
21876
21883
  merged.nodes.push(...r.nodes);
21877
21884
  merged.edges.push(...r.edges);
21885
+ for (const rn of r.nodes) {
21886
+ if (rn.nodeId && rn.nodeId !== rn.canonicalId) echoedCloudId.set(rn.canonicalId, rn.nodeId);
21887
+ }
21878
21888
  if (r.patternReconciliation) {
21879
21889
  merged.patternReconciliation = { ...merged.patternReconciliation, ...r.patternReconciliation };
21880
21890
  }
21881
21891
  }
21882
21892
  for (const edgeChunk of chunkArray(pendingEdges, MAX_EDGES_PER_PAYLOAD)) {
21883
- const r = await this.ingestWire(toWirePayload({ ...batch, nodes: [], edges: edgeChunk }, runId));
21893
+ const rewritten = edgeChunk.map((e) => ({
21894
+ ...e,
21895
+ from: echoedCloudId.get(e.from) ?? e.from,
21896
+ to: echoedCloudId.get(e.to) ?? e.to
21897
+ }));
21898
+ const r = await this.ingestWire(toWirePayload({ ...batch, nodes: [], edges: rewritten }, randomUUID()));
21884
21899
  merged.edges.push(...r.edges);
21885
21900
  }
21886
21901
  return { ...summarizeIngestResult(merged), result: merged };
@@ -51649,7 +51664,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
51649
51664
  }
51650
51665
 
51651
51666
  // src/engine.ts
51652
- var DAEMON_VERSION = true ? "2.0.1-dev.99" : "2.0.0-alpha.0";
51667
+ var DAEMON_VERSION = true ? "2.0.2-dev.70" : "2.0.0-alpha.0";
51653
51668
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
51654
51669
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
51655
51670
  var GIT_OP_MUTE_MS = 4e3;
@@ -53238,10 +53253,13 @@ function buildContextIngest(store, profile, daemonVersion, ignorePatterns = [],
53238
53253
  // Package ids are already the purl (= the cross-stratum canonicalId).
53239
53254
  id: label === "Language" ? languageCanonicalId(String(n.attrs["name"] ?? "").trim() || n.description) : n.id,
53240
53255
  embedding: [],
53256
+ // No `version` attr on the wire: the purl already encodes the resolved
53257
+ // version, and the door's temporal guard rejects ANY `attrs.version` as
53258
+ // bi-temporal bookkeeping (ingest-temporal-guard.ts) — shipping it 422s
53259
+ // the whole batch. `resolved` still travels (range-vs-lockfile signal).
53241
53260
  attrs: label === "Package" ? {
53242
53261
  purl: n.attrs["purl"],
53243
53262
  name: n.attrs["name"],
53244
- version: n.attrs["version"],
53245
53263
  ecosystem: n.attrs["ecosystem"],
53246
53264
  resolved: n.attrs["resolved"]
53247
53265
  } : { name: n.attrs["name"] }
@@ -53356,9 +53374,10 @@ function wireContextId(n) {
53356
53374
  }
53357
53375
  function shareableContext(n, wireId) {
53358
53376
  const attrs = n.label === "Package" ? {
53377
+ // No `version` attr: the purl encodes it, and the door's temporal
53378
+ // guard 422s any `attrs.version` (mirrors buildContextIngest).
53359
53379
  purl: n.attrs["purl"],
53360
53380
  name: n.attrs["name"],
53361
- version: n.attrs["version"],
53362
53381
  ecosystem: n.attrs["ecosystem"],
53363
53382
  resolved: n.attrs["resolved"]
53364
53383
  } : n.label === "Domain" ? { name: n.description, canonicalId: n.attrs["canonicalId"] } : { name: n.attrs["name"] };
@@ -53425,10 +53444,13 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53425
53444
  }
53426
53445
  if (targets.length === 0) continue;
53427
53446
  const dg = digest(targets.map(({ e, wireId }) => `${e.type}>${wireId}`).sort());
53428
- if (store.getNode(sourceId)?.attrs["anchorsContributedDigest"] === dg) continue;
53447
+ const sourceNode = store.getNode(sourceId);
53448
+ if (sourceNode?.attrs["anchorsContributedDigest"] === dg) continue;
53429
53449
  anchorDigests[sourceId] = dg;
53450
+ const skippedCloudId = !seen.has(sourceId) ? sourceNode?.attrs["cloudNodeId"] : void 0;
53451
+ const wireFrom = typeof skippedCloudId === "string" && skippedCloudId.length > 0 ? skippedCloudId : sourceId;
53430
53452
  for (const { e, t, wireId } of targets) {
53431
- anchorEdges.push({ ...e, to: wireId, attrs: {} });
53453
+ anchorEdges.push({ ...e, from: wireFrom, to: wireId, attrs: {} });
53432
53454
  if (t.attrs["source"] === "cloud" || contextSeen.has(wireId)) continue;
53433
53455
  contextSeen.add(wireId);
53434
53456
  contextNodes.push(shareableContext(t, wireId));
@@ -53440,6 +53462,8 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53440
53462
  for (const sourceId of anchorSources) {
53441
53463
  const source = store.getNode(sourceId);
53442
53464
  if (!source) continue;
53465
+ const skippedCloudId = !seen.has(sourceId) ? source.attrs["cloudNodeId"] : void 0;
53466
+ const wireFrom = typeof skippedCloudId === "string" && skippedCloudId.length > 0 ? skippedCloudId : sourceId;
53443
53467
  for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
53444
53468
  const target = store.getNode(e.to);
53445
53469
  if (!target) continue;
@@ -53474,7 +53498,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53474
53498
  stability: "unstable"
53475
53499
  });
53476
53500
  }
53477
- anchorEdges.push({ ...e, to: symId, attrs: {} });
53501
+ anchorEdges.push({ ...e, from: wireFrom, to: symId, attrs: {} });
53478
53502
  }
53479
53503
  }
53480
53504
  }
@@ -53522,6 +53546,46 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53522
53546
  return { ...base, payloadDigest: digest(base), anchorDigests };
53523
53547
  }
53524
53548
 
53549
+ // src/backfill-edges.ts
53550
+ init_src();
53551
+ init_src2();
53552
+ var CAUSAL_LABELS = ["Problem", "Solution", "RootCause"];
53553
+ var CAUSAL_EDGES2 = ["SOLVED_BY", "CAUSED_BY", "FIXED_BY"];
53554
+ function cloudWireId(store, id) {
53555
+ const n = store.getNode(id);
53556
+ if (!n || !CAUSAL_LABELS.includes(n.label)) return null;
53557
+ if (n.label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) return null;
53558
+ const cloudNodeId = n.attrs["cloudNodeId"];
53559
+ if (typeof cloudNodeId === "string" && cloudNodeId.length > 0) return cloudNodeId;
53560
+ if (n.attrs["source"] === "cloud") return n.id;
53561
+ return typeof n.attrs["contributedAtSeq"] === "number" ? n.id : null;
53562
+ }
53563
+ function buildCausalEdgeBackfill(store, profile, daemonVersion) {
53564
+ const edges = [];
53565
+ const seenEdge = /* @__PURE__ */ new Set();
53566
+ for (const label of CAUSAL_LABELS) {
53567
+ for (const n of store.findNodesByLabel(label)) {
53568
+ for (const e of store.outEdges(n.id, [...CAUSAL_EDGES2])) {
53569
+ if (seenEdge.has(e.id)) continue;
53570
+ seenEdge.add(e.id);
53571
+ const from = cloudWireId(store, e.from);
53572
+ const to = cloudWireId(store, e.to);
53573
+ if (!from || !to) continue;
53574
+ edges.push({ ...e, from, to, attrs: {} });
53575
+ }
53576
+ }
53577
+ }
53578
+ if (edges.length === 0) return null;
53579
+ const base = {
53580
+ daemonVersion,
53581
+ profile: { stack: profile.stack, languages: profile.languages, domains: profile.domains },
53582
+ originProject: profile.id,
53583
+ nodes: [],
53584
+ edges
53585
+ };
53586
+ return { ...base, payloadDigest: digest(base) };
53587
+ }
53588
+
53525
53589
  // src/multi.ts
53526
53590
  init_generalize_graph();
53527
53591
  init_symbol_summaries();
@@ -54533,14 +54597,27 @@ async function startMultiDaemon(opts = {}) {
54533
54597
  const ignore = loadClaimIgnorePatterns(globalDir());
54534
54598
  const client = cloudNow();
54535
54599
  let uploaded = 0;
54600
+ const errors = [];
54601
+ const lane = async (name2, projectName, run3) => {
54602
+ try {
54603
+ await run3();
54604
+ } catch (err2) {
54605
+ const msg = `[${projectName}] ${name2}: ${err2 instanceof Error ? err2.message : String(err2)}`;
54606
+ errors.push(msg);
54607
+ console.error(`[sync\u2192cloud] ${msg}`);
54608
+ }
54609
+ };
54536
54610
  for (const r of records) {
54537
54611
  const store = r.engine.store;
54612
+ const projectName = r.engine.profile.name ?? r.engine.profile.id;
54538
54613
  const context = buildContextIngest(store, r.engine.profile, DAEMON_VERSION, ignore, {
54539
54614
  includePackages: cfg2.consent.contributePackages
54540
54615
  });
54541
54616
  if (context) {
54542
- const res = await client.ingest(context);
54543
- uploaded += res.accepted;
54617
+ await lane("context", projectName, async () => {
54618
+ const res = await client.ingest(context);
54619
+ uploaded += res.accepted;
54620
+ });
54544
54621
  }
54545
54622
  let lexicon;
54546
54623
  try {
@@ -54563,34 +54640,58 @@ async function startMultiDaemon(opts = {}) {
54563
54640
  });
54564
54641
  if (instances) {
54565
54642
  if (project) instances.projectId = project.projectId;
54566
- const res = await client.ingest(instances);
54567
- uploaded += res.accepted;
54568
- const seq = store.currentIngestSeq();
54569
- const cloudIdByLocal = new Map(
54570
- res.result.nodes.filter((rn) => rn.nodeId).map((rn) => [rn.canonicalId, rn.nodeId])
54643
+ await lane("instances", projectName, async () => {
54644
+ const res = await client.ingest(instances);
54645
+ uploaded += res.accepted;
54646
+ const seq = store.currentIngestSeq();
54647
+ const cloudIdByLocal = new Map(
54648
+ res.result.nodes.filter((rn) => rn.nodeId).map((rn) => [rn.canonicalId, rn.nodeId])
54649
+ );
54650
+ for (const n of instances.nodes) {
54651
+ const local = store.getNode(n.id);
54652
+ if (!local || !["Problem", "Solution", "RootCause"].includes(local.label)) continue;
54653
+ const cloudNodeId = cloudIdByLocal.get(n.id);
54654
+ store.updateNode(n.id, {
54655
+ attrs: {
54656
+ ...local.attrs,
54657
+ contributedAtSeq: seq,
54658
+ ...cloudNodeId && cloudNodeId !== n.id ? { cloudNodeId } : {}
54659
+ }
54660
+ });
54661
+ }
54662
+ for (const [localId, dg] of Object.entries(instances.anchorDigests)) {
54663
+ const local = store.getNode(localId);
54664
+ if (!local) continue;
54665
+ store.updateNode(localId, {
54666
+ attrs: { ...local.attrs, anchorsContributedDigest: dg }
54667
+ });
54668
+ }
54669
+ });
54670
+ }
54671
+ }
54672
+ return { uploaded, ...errors.length > 0 ? { errors } : {} };
54673
+ },
54674
+ async backfillCausalEdges() {
54675
+ if (!loadConfig().consent.sync) return { accepted: 0, rejected: 0, skipped: "consent-off" };
54676
+ const client = cloudNow();
54677
+ let accepted = 0;
54678
+ let rejected = 0;
54679
+ const errors = [];
54680
+ for (const r of records) {
54681
+ const payload = buildCausalEdgeBackfill(r.engine.store, r.engine.profile, DAEMON_VERSION);
54682
+ if (!payload) continue;
54683
+ try {
54684
+ const res = await client.ingest(payload);
54685
+ accepted += res.accepted;
54686
+ rejected += res.rejected;
54687
+ if (res.rejected > 0) errors.push(...res.violations.slice(0, 5));
54688
+ } catch (err2) {
54689
+ errors.push(
54690
+ `[${r.engine.profile.name ?? r.engine.profile.id}] backfill: ${err2 instanceof Error ? err2.message : String(err2)}`
54571
54691
  );
54572
- for (const n of instances.nodes) {
54573
- const local = store.getNode(n.id);
54574
- if (!local || !["Problem", "Solution", "RootCause"].includes(local.label)) continue;
54575
- const cloudNodeId = cloudIdByLocal.get(n.id);
54576
- store.updateNode(n.id, {
54577
- attrs: {
54578
- ...local.attrs,
54579
- contributedAtSeq: seq,
54580
- ...cloudNodeId && cloudNodeId !== n.id ? { cloudNodeId } : {}
54581
- }
54582
- });
54583
- }
54584
- for (const [localId, dg] of Object.entries(instances.anchorDigests)) {
54585
- const local = store.getNode(localId);
54586
- if (!local) continue;
54587
- store.updateNode(localId, {
54588
- attrs: { ...local.attrs, anchorsContributedDigest: dg }
54589
- });
54590
- }
54591
54692
  }
54592
54693
  }
54593
- return { uploaded };
54694
+ return { accepted, rejected, ...errors.length > 0 ? { errors } : {} };
54594
54695
  },
54595
54696
  async pullTriagePublic() {
54596
54697
  if (!loadConfig().consent.sync) return { merged: 0, skipped: "consent-off" };
@@ -54640,7 +54741,12 @@ async function startMultiDaemon(opts = {}) {
54640
54741
  }
54641
54742
  const inst = await daemon.syncInstancesPublic();
54642
54743
  const skills = await daemon.syncSkillsAll();
54643
- return { uploaded: inst.uploaded, written: skills.written, pruned: skills.pruned };
54744
+ return {
54745
+ uploaded: inst.uploaded,
54746
+ written: skills.written,
54747
+ pruned: skills.pruned,
54748
+ ...inst.errors ? { errors: inst.errors } : {}
54749
+ };
54644
54750
  } finally {
54645
54751
  boundaryFlushing = false;
54646
54752
  }
@@ -54671,7 +54777,8 @@ async function startMultiDaemon(opts = {}) {
54671
54777
  const principles = await daemon.syncPrinciplesPublic();
54672
54778
  const triage = await daemon.syncTriagePublic();
54673
54779
  const instances = await daemon.syncInstancesPublic();
54674
- return c.json({ principles, triage, instances });
54780
+ const edgeBackfill = await daemon.backfillCausalEdges();
54781
+ return c.json({ principles, triage, instances, edgeBackfill });
54675
54782
  } catch (err2) {
54676
54783
  return c.json({ error: err2 instanceof Error ? err2.message : String(err2) }, 500);
54677
54784
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.1-dev.99",
3
+ "version": "2.0.2-dev.70",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {