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

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,
@@ -15194,7 +15205,10 @@ var init_edge_rules = __esm({
15194
15205
  // NOT ruled here — the type pre-exists with broader extractor senses, and a
15195
15206
  // new rule on an old type would reject legitimate live flows (reject-never-flip
15196
15207
  // cuts both ways: only rule types you introduce or senses that are documented).
15197
- CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool"] },
15208
+ // `Component` joined the target set with OM-agent-anchors: an agent-named
15209
+ // component ("React Router") is the same knowledge→named-unit anchor shape as
15210
+ // a Tool — the knowledge is ABOUT it, not dependent on it.
15211
+ CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool", "Component"] },
15198
15212
  OPERATES_ON: { from: ["Algorithm"], to: ["DataStructure"] },
15199
15213
  INVOLVES: { from: ["Problem", "Solution"], to: ["DataStructure"] },
15200
15214
  // ── Taxonomy (Track 4 Stream C: MITRE CWE `ChildOf` → `Weakness —IS_A→ Weakness`) ──
@@ -15307,7 +15321,12 @@ var init_wire = __esm({
15307
15321
  * knows which packages are private. The door does NOT trust a `public` claim
15308
15322
  * blindly (it re-confirms on the spine); absent ⇒ unknown ⇒ fail-closed to
15309
15323
  * org-private. Accepted-but-ignored until ORG_MEMBRANE_ENABLED flips. */
15310
- anchorVisibility: external_exports.enum(["public", "private"]).optional()
15324
+ anchorVisibility: external_exports.enum(["public", "private"]).optional(),
15325
+ /** The spine-resolvable anchor id backing a `public` tag (OM-anchor-tag): a
15326
+ * Package purl or `languageCanonicalId`. The door validates THIS on the
15327
+ * public spine (falling back to `canonicalId` when absent — context stubs
15328
+ * are self-anchored). Never trusted without spine confirmation. */
15329
+ anchor: external_exports.string().min(1).max(300).optional()
15311
15330
  });
15312
15331
  RouteContextCountWireSchema = external_exports.object({
15313
15332
  confirmed: external_exports.number().int().min(0),
@@ -16023,6 +16042,13 @@ var SqliteGraphStore = class {
16023
16042
  findByLabel: this.db.prepare(
16024
16043
  "SELECT * FROM nodes WHERE label = ? AND valid_to IS NULL"
16025
16044
  ),
16045
+ // Scalar count twin of findByLabel — same live-row semantics, no row
16046
+ // hydration. Exists because status surfaces (daemon `/` + `/health`) used
16047
+ // findNodesByLabel(...).length, materializing every row's attrs JSON and
16048
+ // embedding blob per request — seconds of synchronous loop-hold per poll.
16049
+ countByLabel: this.db.prepare(
16050
+ "SELECT COUNT(*) AS n FROM nodes WHERE label = ? AND valid_to IS NULL"
16051
+ ),
16026
16052
  // ALL versions of a label — incl frozen/closed (valid_to set). Used by the
16027
16053
  // clean-reindex purge so a true wipe removes history too, not just live rows.
16028
16054
  findByLabelAll: this.db.prepare("SELECT * FROM nodes WHERE label = ?"),
@@ -16276,6 +16302,13 @@ var SqliteGraphStore = class {
16276
16302
  const rows = this.stmts.findByLabel.all(label);
16277
16303
  return rows.map(rowToNode);
16278
16304
  }
16305
+ /** Live-row count for a label — `findNodesByLabel(label).length` without the
16306
+ * per-row hydration (attrs JSON + embedding blob). Status surfaces poll this
16307
+ * per project per request; the materializing form held the daemon's event
16308
+ * loop for seconds at scale. */
16309
+ countNodesByLabel(label) {
16310
+ return Number(this.stmts.countByLabel.get(label).n);
16311
+ }
16279
16312
  findAllVersionsByLabel(label) {
16280
16313
  const rows = this.stmts.findByLabelAll.all(label);
16281
16314
  return rows.map(rowToNode);
package/errata.mjs CHANGED
@@ -183,7 +183,14 @@ var init_castalia = __esm({
183
183
  "SOLVED_BY",
184
184
  "MITIGATES",
185
185
  "REPORTED_FAILURE",
186
- "CONTRADICTS"
186
+ "CONTRADICTS",
187
+ // Motif twin-faces (KN-twin-faces): failure-face motif → remedy-face motif
188
+ // across layers (AntiPattern/Weakness ↔ Technique/Pattern). Same
189
+ // problem→fix direction as FIXED_BY/SOLVED_BY. Minted by the nightly LLM
190
+ // twin-face pass — these are the near-identical cross-layer pairs the
191
+ // polarity gate (finding 5) correctly refuses to FUSE; the link carries
192
+ // what fusion can't.
193
+ "REMEDIED_BY"
187
194
  ];
188
195
  CONCEPTUAL_EDGES = [
189
196
  "INSTANCE_OF",
@@ -313,6 +320,10 @@ var init_castalia = __esm({
313
320
  CAUSED_BY: 3,
314
321
  FIXED_BY: 3,
315
322
  SOLVED_BY: 3,
323
+ // Twin-face link (failure motif → remedy motif, KN-twin-faces) — causal-grade
324
+ // but a notch below witnessed FIXED_BY/SOLVED_BY: the pairing is an LLM
325
+ // judgment over descriptions, not an agent-witnessed resolution.
326
+ REMEDIED_BY: 2.5,
316
327
  MANIFESTS_AS: 2,
317
328
  ESCALATES_TO: 1.5,
318
329
  AFFECTS: 1.2,
@@ -938,6 +949,12 @@ function packageCanonicalId(p) {
938
949
  const name2 = eco === "npm" ? p.name.toLowerCase() : p.name;
939
950
  return `pkg:${eco}/${name2}${p.version ? `@${p.version}` : ""}`;
940
951
  }
952
+ function versionlessPurl(purl) {
953
+ if (!purl.startsWith("pkg:")) return null;
954
+ const lastSlash = purl.lastIndexOf("/");
955
+ const lastAt = purl.lastIndexOf("@");
956
+ return lastAt > lastSlash ? purl.slice(0, lastAt) : purl;
957
+ }
941
958
  function parsePackageRef(ref) {
942
959
  let rest2 = ref.trim();
943
960
  if (rest2.startsWith("pkg:")) {
@@ -15363,7 +15380,10 @@ var init_edge_rules = __esm({
15363
15380
  // NOT ruled here — the type pre-exists with broader extractor senses, and a
15364
15381
  // new rule on an old type would reject legitimate live flows (reject-never-flip
15365
15382
  // cuts both ways: only rule types you introduce or senses that are documented).
15366
- CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool"] },
15383
+ // `Component` joined the target set with OM-agent-anchors: an agent-named
15384
+ // component ("React Router") is the same knowledge→named-unit anchor shape as
15385
+ // a Tool — the knowledge is ABOUT it, not dependent on it.
15386
+ CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool", "Component"] },
15367
15387
  OPERATES_ON: { from: ["Algorithm"], to: ["DataStructure"] },
15368
15388
  INVOLVES: { from: ["Problem", "Solution"], to: ["DataStructure"] },
15369
15389
  // ── Taxonomy (Track 4 Stream C: MITRE CWE `ChildOf` → `Weakness —IS_A→ Weakness`) ──
@@ -15602,7 +15622,12 @@ var init_wire = __esm({
15602
15622
  * knows which packages are private. The door does NOT trust a `public` claim
15603
15623
  * blindly (it re-confirms on the spine); absent ⇒ unknown ⇒ fail-closed to
15604
15624
  * org-private. Accepted-but-ignored until ORG_MEMBRANE_ENABLED flips. */
15605
- anchorVisibility: external_exports.enum(["public", "private"]).optional()
15625
+ anchorVisibility: external_exports.enum(["public", "private"]).optional(),
15626
+ /** The spine-resolvable anchor id backing a `public` tag (OM-anchor-tag): a
15627
+ * Package purl or `languageCanonicalId`. The door validates THIS on the
15628
+ * public spine (falling back to `canonicalId` when absent — context stubs
15629
+ * are self-anchored). Never trusted without spine confirmation. */
15630
+ anchor: external_exports.string().min(1).max(300).optional()
15606
15631
  });
15607
15632
  RouteContextCountWireSchema = external_exports.object({
15608
15633
  confirmed: external_exports.number().int().min(0),
@@ -15993,6 +16018,7 @@ __export(src_exports, {
15993
16018
  toCloudAttrs: () => toCloudAttrs,
15994
16019
  toolCanonicalId: () => toolCanonicalId,
15995
16020
  validateCastaliaPayload: () => validateCastaliaPayload,
16021
+ versionlessPurl: () => versionlessPurl,
15996
16022
  vetSidecarSummaries: () => vetSidecarSummaries
15997
16023
  });
15998
16024
  var init_src = __esm({
@@ -16662,6 +16688,13 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
16662
16688
  findByLabel: this.db.prepare(
16663
16689
  "SELECT * FROM nodes WHERE label = ? AND valid_to IS NULL"
16664
16690
  ),
16691
+ // Scalar count twin of findByLabel — same live-row semantics, no row
16692
+ // hydration. Exists because status surfaces (daemon `/` + `/health`) used
16693
+ // findNodesByLabel(...).length, materializing every row's attrs JSON and
16694
+ // embedding blob per request — seconds of synchronous loop-hold per poll.
16695
+ countByLabel: this.db.prepare(
16696
+ "SELECT COUNT(*) AS n FROM nodes WHERE label = ? AND valid_to IS NULL"
16697
+ ),
16665
16698
  // ALL versions of a label — incl frozen/closed (valid_to set). Used by the
16666
16699
  // clean-reindex purge so a true wipe removes history too, not just live rows.
16667
16700
  findByLabelAll: this.db.prepare("SELECT * FROM nodes WHERE label = ?"),
@@ -16915,6 +16948,13 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
16915
16948
  const rows = this.stmts.findByLabel.all(label);
16916
16949
  return rows.map(rowToNode);
16917
16950
  }
16951
+ /** Live-row count for a label — `findNodesByLabel(label).length` without the
16952
+ * per-row hydration (attrs JSON + embedding blob). Status surfaces poll this
16953
+ * per project per request; the materializing form held the daemon's event
16954
+ * loop for seconds at scale. */
16955
+ countNodesByLabel(label) {
16956
+ return Number(this.stmts.countByLabel.get(label).n);
16957
+ }
16918
16958
  findAllVersionsByLabel(label) {
16919
16959
  const rows = this.stmts.findByLabelAll.all(label);
16920
16960
  return rows.map(rowToNode);
@@ -18316,6 +18356,55 @@ function mintDomainNode(store, name2, ts) {
18316
18356
  }
18317
18357
  return id;
18318
18358
  }
18359
+ function mintCitedPackageNode(store, ref, ts) {
18360
+ const cleaned = ref.replace(/\s+/g, " ").trim();
18361
+ const slash = /^([a-z0-9-]+)\/(.+)$/i.exec(cleaned);
18362
+ const hasEco = slash != null && !cleaned.startsWith("@");
18363
+ const name2 = (hasEco ? slash[2] : cleaned).trim();
18364
+ const nameLc = name2.toLowerCase();
18365
+ const existing = store.findNodesByLabel("Package").find((n) => String(n.attrs["name"] ?? "").toLowerCase() === nameLc);
18366
+ if (existing) return existing.id;
18367
+ let eco = hasEco ? slash[1].toLowerCase() : "";
18368
+ if (!eco && cleaned.startsWith("@")) eco = "npm";
18369
+ if (!eco) {
18370
+ const counts = /* @__PURE__ */ new Map();
18371
+ for (const p of store.findNodesByLabel("Package")) {
18372
+ const e = String(p.attrs["ecosystem"] ?? "").toLowerCase();
18373
+ if (e) counts.set(e, (counts.get(e) ?? 0) + 1);
18374
+ }
18375
+ eco = [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? "npm";
18376
+ }
18377
+ const purlName = eco === "npm" ? nameLc : name2;
18378
+ const purl = `pkg:${eco}/${purlName}`;
18379
+ if (!store.getNode(purl)) {
18380
+ store.mergeNode(
18381
+ buildNode(purl, "Package", name2, ts, {
18382
+ purl,
18383
+ name: name2,
18384
+ ecosystem: eco,
18385
+ resolved: false,
18386
+ // agent-cited, no lockfile resolution
18387
+ source: "convo",
18388
+ provisional: true
18389
+ })
18390
+ );
18391
+ }
18392
+ return purl;
18393
+ }
18394
+ function mintComponentNode(store, name2, ts) {
18395
+ const display = name2.replace(/\s+/g, " ").trim();
18396
+ const slug2 = display.toLowerCase();
18397
+ if (!store.getNode(slug2)) {
18398
+ store.mergeNode(
18399
+ buildNode(slug2, "Component", display, ts, {
18400
+ name: display,
18401
+ source: "convo",
18402
+ provisional: true
18403
+ })
18404
+ );
18405
+ }
18406
+ return slug2;
18407
+ }
18319
18408
  function resolveFileNode(store, path2, workspaceId2) {
18320
18409
  const want = path2.trim().replace(/^\.?\//, "");
18321
18410
  for (const n of store.findNodesByLabel("File")) {
@@ -20766,6 +20855,8 @@ __export(src_exports2, {
20766
20855
  matchSymbolsInText: () => matchSymbolsInText,
20767
20856
  mergeCloudCounts: () => mergeCloudCounts,
20768
20857
  mergeDuplicateProblems: () => mergeDuplicateProblems,
20858
+ mintCitedPackageNode: () => mintCitedPackageNode,
20859
+ mintComponentNode: () => mintComponentNode,
20769
20860
  mintDomainNode: () => mintDomainNode,
20770
20861
  mintPatternNode: () => mintPatternNode,
20771
20862
  openGraphStore: () => openGraphStore,
@@ -21530,7 +21621,12 @@ function toWirePayload(batch, runId) {
21530
21621
  // attrs travel; node-level bookkeeping (embedding, momentum counters,
21531
21622
  // bi-temporal fields) is local-stratum and never crosses.
21532
21623
  attrs: n.attrs ?? {},
21533
- extractionSource: n.extractionSource
21624
+ extractionSource: n.extractionSource,
21625
+ // Org-membrane anchor tag (OM-anchor-tag): the batch builders set these
21626
+ // on wire-bound projections only; the door re-validates the claim on the
21627
+ // public spine, so lifting them is routing input, not a grant.
21628
+ ...n.anchorVisibility ? { anchorVisibility: n.anchorVisibility } : {},
21629
+ ...n.anchor ? { anchor: n.anchor } : {}
21534
21630
  }));
21535
21631
  const edges = batch.edges.filter((e) => !droppedNodeIds.has(e.from) && !droppedNodeIds.has(e.to)).map((e) => {
21536
21632
  const perContext = wirePerContext(e.attrs?.["perContext"]);
@@ -21851,8 +21947,14 @@ var init_client = __esm({
21851
21947
  * the per-decision response into flush accounting.
21852
21948
  *
21853
21949
  * 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).
21950
+ * (413 over). A batch above the cap is split and drained across calls, every
21951
+ * NODE chunk first (edges empty), then EDGE chunks (nodes empty). Each chunk
21952
+ * gets its OWN runId: the door's durable idempotency claim is keyed on
21953
+ * (agent, org, runId) with the payload digest, so reusing one runId across
21954
+ * different chunk payloads 409s "runId was already used with a different
21955
+ * payload" on the second chunk — exactly how the first real >25-node drain
21956
+ * died (2026-07-22). The runId never grouped anything server-side; it exists
21957
+ * for duplicate-POST protection, which is per-request by nature.
21856
21958
  * Recognition resolves an edge's endpoints against nodes already ingested this
21857
21959
  * drain (endpoint-label validation is deferred to the service when an endpoint
21858
21960
  * isn't in-payload), so the split never orphans an edge. Sub-results concat into
@@ -21865,6 +21967,7 @@ var init_client = __esm({
21865
21967
  }
21866
21968
  const merged = { runId, nodes: [], edges: [] };
21867
21969
  let pendingEdges = [...batch.edges];
21970
+ const echoedCloudId = /* @__PURE__ */ new Map();
21868
21971
  for (const nodeChunk of chunkArray(batch.nodes, INGEST_NODE_CHUNK)) {
21869
21972
  const ids = new Set(nodeChunk.map((n) => n.id));
21870
21973
  const inChunk = pendingEdges.filter((e) => ids.has(e.from) && ids.has(e.to)).slice(0, MAX_EDGES_PER_PAYLOAD);
@@ -21872,15 +21975,23 @@ var init_client = __esm({
21872
21975
  const shipped = new Set(inChunk);
21873
21976
  pendingEdges = pendingEdges.filter((e) => !shipped.has(e));
21874
21977
  }
21875
- const r = await this.ingestWire(toWirePayload({ ...batch, nodes: nodeChunk, edges: inChunk }, runId));
21978
+ const r = await this.ingestWire(toWirePayload({ ...batch, nodes: nodeChunk, edges: inChunk }, randomUUID()));
21876
21979
  merged.nodes.push(...r.nodes);
21877
21980
  merged.edges.push(...r.edges);
21981
+ for (const rn of r.nodes) {
21982
+ if (rn.nodeId && rn.nodeId !== rn.canonicalId) echoedCloudId.set(rn.canonicalId, rn.nodeId);
21983
+ }
21878
21984
  if (r.patternReconciliation) {
21879
21985
  merged.patternReconciliation = { ...merged.patternReconciliation, ...r.patternReconciliation };
21880
21986
  }
21881
21987
  }
21882
21988
  for (const edgeChunk of chunkArray(pendingEdges, MAX_EDGES_PER_PAYLOAD)) {
21883
- const r = await this.ingestWire(toWirePayload({ ...batch, nodes: [], edges: edgeChunk }, runId));
21989
+ const rewritten = edgeChunk.map((e) => ({
21990
+ ...e,
21991
+ from: echoedCloudId.get(e.from) ?? e.from,
21992
+ to: echoedCloudId.get(e.to) ?? e.to
21993
+ }));
21994
+ const r = await this.ingestWire(toWirePayload({ ...batch, nodes: [], edges: rewritten }, randomUUID()));
21884
21995
  merged.edges.push(...r.edges);
21885
21996
  }
21886
21997
  return { ...summarizeIngestResult(merged), result: merged };
@@ -25110,6 +25221,11 @@ function linkBullet(ref) {
25110
25221
  ` \xB7 ${TAG_EXAMPLE.domain()} \u2014 name the ABSTRACT AREA it's about (e.g. (domain: Observability),`,
25111
25222
  " (domain: Community Detection)). An abstract problem with no code anchor NEEDS this or it's",
25112
25223
  " an invisible island: the Domain is the topic other problems in the area cluster on. Title Case.",
25224
+ ` \xB7 ${TAG_EXAMPLE.package()} \u2014 the PUBLIC PACKAGE the problem is about, even when this`,
25225
+ " workspace doesn't depend on it (e.g. (package: chokidar), (package: pypi/requests)) \u2014 a",
25226
+ " public-registry anchor lets the knowledge cross to the collective; internal names stay private.",
25227
+ ` \xB7 ${TAG_EXAMPLE.component()} \u2014 the framework/product-level UNIT it concerns when it's not a`,
25228
+ " package or a language (e.g. (component: React Router), (component: V8 Isolate)).",
25113
25229
  ` \xB7 (aids:[${ref}],\u2026) \u2014 your FIX could also help these other, even unrelated, problems.`,
25114
25230
  " A hypothesis, not a claim \u2014 it's recorded as may-resolve and checked by whoever tries it.",
25115
25231
  ` \xB7 when your fix relates to a PRIOR solution you were primed with (a problem's`,
@@ -25181,6 +25297,12 @@ var init_agent_signals = __esm({
25181
25297
  // DOMAIN — the abstract area a problem is about; the concept layer an
25182
25298
  // anchor-less problem clusters on. Mints/resolves a Domain by name.
25183
25299
  domain: () => `(domain: The Area)`,
25300
+ // PACKAGE/COMPONENT — agent-named public anchors (OM-agent-anchors): the
25301
+ // package a problem is ABOUT (even when not a dependency) and the
25302
+ // framework/product-level unit it concerns. Public spine anchors → the
25303
+ // knowledge can cross to the collective.
25304
+ package: () => `(package: the-package-name)`,
25305
+ component: () => `(component: The Component)`,
25184
25306
  aids: (ref) => `(aids:[${ref}])`
25185
25307
  };
25186
25308
  GLOSS = {
@@ -37324,7 +37446,7 @@ var init_mcp = __esm({
37324
37446
  },
37325
37447
  {
37326
37448
  name: "errata.why",
37327
- description: "Causal/resolution neighborhood along the causal edge families (CAUSED_BY, MANIFESTS_AS, FIXED_BY, SOLVED_BY, \u2026), walked in both directions. For a Problem this surfaces its root causes AND solutions; for a Solution, the problems it resolves. Ranked by accumulated conductance. Pass nodeId or qname. Returns {seedId, nodes:[{id,label,description,hops,edgeType,relevance}]}.",
37449
+ description: "Causal/resolution neighborhood along the causal edge families (CAUSED_BY, MANIFESTS_AS, FIXED_BY, SOLVED_BY, \u2026), walked in both directions. For a Problem this surfaces its root causes AND solutions; for a Solution, the problems it resolves. Merges the machine-wide shared store's triage layer (cause chains from inline `<-` diagnoses land there, keyed by the same content-derived problem id). Ranked by accumulated conductance. Pass nodeId or qname. Returns {seedId, nodes:[{id,label,description,hops,edgeType,relevance}]}.",
37328
37450
  inputSchema: {
37329
37451
  type: "object",
37330
37452
  properties: {
@@ -37339,7 +37461,26 @@ var init_mcp = __esm({
37339
37461
  if (!id) return unresolved(args2);
37340
37462
  const maxHops = Math.max(1, Math.min(8, Number(args2["maxHops"] ?? 4)));
37341
37463
  const limit = Math.max(1, Math.min(100, Number(args2["limit"] ?? 30)));
37342
- return { found: true, ...causalChain(store, { seedId: id, direction: "both", maxHops, limit }) };
37464
+ const local = causalChain(store, { seedId: id, direction: "both", maxHops, limit });
37465
+ try {
37466
+ const path2 = sharedStorePath();
37467
+ if (!existsSync10(path2)) return { found: true, ...local };
37468
+ const shared = openGraphStore({ path: path2 });
37469
+ try {
37470
+ if (!shared.getNode(id)) return { found: true, ...local };
37471
+ const l2 = causalChain(shared, { seedId: id, direction: "both", maxHops, limit });
37472
+ const seen = new Set(local.nodes.map((n) => n.id));
37473
+ const merged = [
37474
+ ...local.nodes,
37475
+ ...l2.nodes.filter((n) => !seen.has(n.id)).map((n) => ({ ...n, store: "shared" }))
37476
+ ].sort((a, b) => (b.relevance ?? 0) - (a.relevance ?? 0)).slice(0, limit);
37477
+ return { found: true, seedId: local.seedId, nodes: merged };
37478
+ } finally {
37479
+ shared.close();
37480
+ }
37481
+ } catch {
37482
+ return { found: true, ...local };
37483
+ }
37343
37484
  }
37344
37485
  },
37345
37486
  {
@@ -49579,6 +49720,8 @@ var ALTERNATIVE_PREFIX = /^\s*alternative:(?:#([a-z][\w-]{0,63}))?\s*/i;
49579
49720
  var INSTANCE_PREFIX = /^\s*instance:\s*/i;
49580
49721
  var PATTERN_RE = /\(\s*pattern:\s*([^()\n]{3,}?)\s*\)/gi;
49581
49722
  var DOMAIN_RE = /\(\s*domain:\s*([^()\n]{3,}?)\s*\)/gi;
49723
+ var PACKAGE_RE = /\(\s*package:\s*([^()\n]{2,}?)\s*\)/gi;
49724
+ var COMPONENT_RE = /\(\s*component:\s*([^()\n]{2,}?)\s*\)/gi;
49582
49725
  var CAUSE_TEXT_MIN = 8;
49583
49726
  var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*([^\]\n]{8,}?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
49584
49727
  var CONSTRAINT_MIN = 8;
@@ -49774,6 +49917,18 @@ function parseInlineTags(text) {
49774
49917
  const raw3 = dm[1].replace(/\s+/g, " ").trim();
49775
49918
  if (raw3.length >= 3) out2.push({ kind: "domain", domainText: raw3, sentence: sfield });
49776
49919
  }
49920
+ PACKAGE_RE.lastIndex = 0;
49921
+ let pk;
49922
+ while ((pk = PACKAGE_RE.exec(sentence)) !== null) {
49923
+ const raw3 = pk[1].replace(/\s+/g, " ").trim();
49924
+ if (raw3.length >= 2) out2.push({ kind: "package", packageText: raw3, sentence: sfield });
49925
+ }
49926
+ COMPONENT_RE.lastIndex = 0;
49927
+ let cm;
49928
+ while ((cm = COMPONENT_RE.exec(sentence)) !== null) {
49929
+ const raw3 = cm[1].replace(/\s+/g, " ").trim();
49930
+ if (raw3.length >= 2) out2.push({ kind: "component", componentText: raw3, sentence: sfield });
49931
+ }
49777
49932
  CONSTRAINT_RE.lastIndex = 0;
49778
49933
  let c;
49779
49934
  while ((c = CONSTRAINT_RE.exec(sentence)) !== null) {
@@ -49808,7 +49963,12 @@ var LABEL_PAIR = {
49808
49963
  // EDGE_RULES-clean by construction: CONCERNS allows exactly these sources.
49809
49964
  "Problem>Tool": "CONCERNS",
49810
49965
  "Solution>Tool": "CONCERNS",
49811
- "RootCause>Tool": "CONCERNS"
49966
+ "RootCause>Tool": "CONCERNS",
49967
+ // Agent-named component anchors (OM-agent-anchors): a Component is a
49968
+ // framework/product-level unit the knowledge is ABOUT — CONCERNS, like Tool.
49969
+ "Problem>Component": "CONCERNS",
49970
+ "Solution>Component": "CONCERNS",
49971
+ "RootCause>Component": "CONCERNS"
49812
49972
  };
49813
49973
  var STACK_GROUNDING_EDGES = ["WRITTEN_IN", "DEPENDS_ON", "OCCURS_IN"];
49814
49974
  var TIEBREAK = [
@@ -49907,7 +50067,7 @@ function harvestInlineTags(store, text, opts) {
49907
50067
  const source = opts.sourceId ? store.getNode(opts.sourceId) : null;
49908
50068
  const mintPriors = opts.mintPriors ?? true;
49909
50069
  const touched = opts.sessionTouchedIds ?? /* @__PURE__ */ new Set();
49910
- const plan = { priorEdges: 0, problems: [], fixes: [], triages: [], attempts: [], unresolvedFixes: [], transfers: [], solutionLinks: [], instances: [], patterns: [], domains: [], refutes: [], corroborations: [] };
50070
+ const plan = { priorEdges: 0, problems: [], fixes: [], triages: [], attempts: [], unresolvedFixes: [], transfers: [], solutionLinks: [], instances: [], patterns: [], domains: [], packages: [], components: [], refutes: [], corroborations: [] };
49911
50071
  const tags = parseInlineTags(text);
49912
50072
  const symptomSeqs = tags.filter((t) => (t.kind === "problem" || t.kind === "todo" || t.kind === "constraint") && t.statement).map((t) => ({ seq: t.seq ?? -1, statement: t.statement, threadId: t.threadId })).sort((a, b) => a.seq - b.seq);
49913
50073
  const bindSymptom = (seq, threadId) => {
@@ -50047,6 +50207,16 @@ function harvestInlineTags(store, text, opts) {
50047
50207
  ...tag.threadId ? { threadId: tag.threadId } : {},
50048
50208
  evidence: b.evidence
50049
50209
  });
50210
+ } else if (tag.kind === "package" || tag.kind === "component") {
50211
+ const b = bindSymptom(tag.seq, tag.threadId);
50212
+ const bound = {
50213
+ ...b.statement ? { boundStatement: b.statement } : {},
50214
+ ...b.problemId ? { problemId: b.problemId } : {},
50215
+ ...tag.threadId ? { threadId: tag.threadId } : {},
50216
+ evidence: b.evidence
50217
+ };
50218
+ if (tag.kind === "package") plan.packages.push({ packageText: tag.packageText, ...bound });
50219
+ else plan.components.push({ componentText: tag.componentText, ...bound });
50050
50220
  } else if (tag.kind === "attempt" || tag.kind === "failure") {
50051
50221
  for (const h of tag.refuteHandles ?? []) {
50052
50222
  const nodeId = resolveHandle(store, h, opts.handleMap);
@@ -50071,6 +50241,17 @@ function harvestInlineTags(store, text, opts) {
50071
50241
  boundStatement: b.statement
50072
50242
  });
50073
50243
  }
50244
+ } else if (tag.kind === "prior") {
50245
+ const targetId = resolveHandle(store, tag.handle, opts.handleMap);
50246
+ const target = targetId ? store.getNode(targetId) : null;
50247
+ const citedLabel = target?.label ?? opts.handleMap[tag.handle]?.label;
50248
+ if (targetId && citedLabel && CORROBORATABLE_LABELS.has(citedLabel)) {
50249
+ const witnessKey = `corrob:${targetId}:lean:${digest({ h: tag.handle })}`.slice(0, 72);
50250
+ if (!plan.corroborations.some((c) => c.witnessKey === witnessKey)) {
50251
+ plan.corroborations.push({ nodeId: targetId, witnessKey });
50252
+ }
50253
+ }
50254
+ if (mintPriors && source && target && mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts)) plan.priorEdges++;
50074
50255
  } else if (mintPriors && source) {
50075
50256
  const targetId = resolveHandle(store, tag.handle, opts.handleMap);
50076
50257
  const target = targetId ? store.getNode(targetId) : null;
@@ -50079,6 +50260,12 @@ function harvestInlineTags(store, text, opts) {
50079
50260
  }
50080
50261
  return plan;
50081
50262
  }
50263
+ var CORROBORATABLE_LABELS = /* @__PURE__ */ new Set(["Problem", "Solution", "RootCause"]);
50264
+ function stampWitnessOrigin(items, origin) {
50265
+ if (!origin) return [...items];
50266
+ const o = digest({ p: origin }).slice(0, 12);
50267
+ return items.map((i2) => ({ ...i2, witnessKey: `${i2.witnessKey}:o:${o}` }));
50268
+ }
50082
50269
 
50083
50270
  // src/rollup.ts
50084
50271
  function readConversation(transcriptPath, includeThinking = true, maxChars = 6e4) {
@@ -50581,7 +50768,9 @@ function generalizeRouteForSync(edge2, level) {
50581
50768
  navFailures: 0
50582
50769
  };
50583
50770
  }
50584
- function buildTriageIngest(shared, daemonVersion, ignorePatterns = [], level = 2) {
50771
+ function buildTriageIngest(shared, daemonVersion, ignorePatterns = [], level = 2, opts = {}) {
50772
+ const lang = opts.primaryLanguage?.trim().toLowerCase();
50773
+ const claim = lang ? { anchorVisibility: "public", anchor: `lang:${lang}` } : {};
50585
50774
  const nodes = [];
50586
50775
  const edges = [];
50587
50776
  const seenIds = /* @__PURE__ */ new Set();
@@ -50594,7 +50783,8 @@ function buildTriageIngest(shared, daemonVersion, ignorePatterns = [], level = 2
50594
50783
  ...n,
50595
50784
  description: generalize(n.description, { level }).text,
50596
50785
  embedding: [],
50597
- attrs: { scope: {} }
50786
+ attrs: { scope: {} },
50787
+ ...claim
50598
50788
  });
50599
50789
  };
50600
50790
  for (const tri of shared.findNodesByLabel("Triage")) {
@@ -50610,7 +50800,8 @@ function buildTriageIngest(shared, daemonVersion, ignorePatterns = [], level = 2
50610
50800
  attrs: {
50611
50801
  ...statement ? { statement: generalize(statement, { level }).text } : {},
50612
50802
  ...tri.attrs["perContextSeen"] ? { perContextSeen: tri.attrs["perContextSeen"] } : {}
50613
- }
50803
+ },
50804
+ ...claim
50614
50805
  });
50615
50806
  }
50616
50807
  for (const tb of shared.inEdges(tri.id, ["TRIAGED_BY"])) {
@@ -51649,7 +51840,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
51649
51840
  }
51650
51841
 
51651
51842
  // src/engine.ts
51652
- var DAEMON_VERSION = true ? "2.0.1-dev.99" : "2.0.0-alpha.0";
51843
+ var DAEMON_VERSION = true ? "2.0.2-dev.135" : "2.0.0-alpha.0";
51653
51844
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
51654
51845
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
51655
51846
  var GIT_OP_MUTE_MS = 4e3;
@@ -52483,6 +52674,20 @@ function createWorkspaceEngine(opts) {
52483
52674
  if (domainId === pid) continue;
52484
52675
  mintCiteEdge(pid, domainId, "PERTAIN_TO", d.evidence === "witnessed" ? 0.4 : 0.3, { domainCite: true, evidence: d.evidence });
52485
52676
  }
52677
+ for (const p of plan.packages) {
52678
+ const pid = bindPid(p);
52679
+ if (!pid) continue;
52680
+ const pkgId = mintCitedPackageNode(store, p.packageText, t);
52681
+ if (pkgId === pid) continue;
52682
+ mintCiteEdge(pid, pkgId, "DEPENDS_ON", p.evidence === "witnessed" ? 0.4 : 0.3, { packageCite: true, evidence: p.evidence });
52683
+ }
52684
+ for (const cpt of plan.components) {
52685
+ const pid = bindPid(cpt);
52686
+ if (!pid) continue;
52687
+ const componentId = mintComponentNode(store, cpt.componentText, t);
52688
+ if (componentId === pid) continue;
52689
+ mintCiteEdge(pid, componentId, "CONCERNS", cpt.evidence === "witnessed" ? 0.4 : 0.3, { componentCite: true, evidence: cpt.evidence });
52690
+ }
52486
52691
  for (const inst of plan.instances) {
52487
52692
  const pid = bindPid(inst);
52488
52693
  if (!pid) continue;
@@ -52524,14 +52729,14 @@ function createWorkspaceEngine(opts) {
52524
52729
  }
52525
52730
  }
52526
52731
  if (plan.refutes.length > 0 && typeof cloud.reportContradictions === "function") {
52527
- void cloud.reportContradictions({ daemonVersion: DAEMON_VERSION, projectId: profile.id, items: plan.refutes }).then((r) => {
52732
+ void cloud.reportContradictions({ daemonVersion: DAEMON_VERSION, projectId: profile.id, items: stampWitnessOrigin(plan.refutes, profile.id) }).then((r) => {
52528
52733
  if (r?.recorded) console.log(`[errata] refute: ${r.recorded} contradiction(s) recorded`);
52529
52734
  }).catch(
52530
52735
  (err2) => console.warn("[errata] refute transport failed (continuing):", err2 instanceof Error ? err2.message : err2)
52531
52736
  );
52532
52737
  }
52533
52738
  if (plan.corroborations.length > 0 && typeof cloud.reportCorroborations === "function") {
52534
- void cloud.reportCorroborations({ daemonVersion: DAEMON_VERSION, projectId: profile.id, items: plan.corroborations }).then((r) => {
52739
+ void cloud.reportCorroborations({ daemonVersion: DAEMON_VERSION, projectId: profile.id, items: stampWitnessOrigin(plan.corroborations, profile.id) }).then((r) => {
52535
52740
  if (r?.recorded) console.log(`[errata] corroborate: ${r.recorded} corroboration(s) recorded`);
52536
52741
  }).catch(
52537
52742
  (err2) => console.warn("[errata] corroboration transport failed (continuing):", err2 instanceof Error ? err2.message : err2)
@@ -53238,13 +53443,21 @@ function buildContextIngest(store, profile, daemonVersion, ignorePatterns = [],
53238
53443
  // Package ids are already the purl (= the cross-stratum canonicalId).
53239
53444
  id: label === "Language" ? languageCanonicalId(String(n.attrs["name"] ?? "").trim() || n.description) : n.id,
53240
53445
  embedding: [],
53446
+ // No `version` attr on the wire: the purl already encodes the resolved
53447
+ // version, and the door's temporal guard rejects ANY `attrs.version` as
53448
+ // bi-temporal bookkeeping (ingest-temporal-guard.ts) — shipping it 422s
53449
+ // the whole batch. `resolved` still travels (range-vs-lockfile signal).
53241
53450
  attrs: label === "Package" ? {
53242
53451
  purl: n.attrs["purl"],
53243
53452
  name: n.attrs["name"],
53244
- version: n.attrs["version"],
53245
53453
  ecosystem: n.attrs["ecosystem"],
53246
53454
  resolved: n.attrs["resolved"]
53247
- } : { name: n.attrs["name"] }
53455
+ } : { name: n.attrs["name"] },
53456
+ // OM-anchor-tag: context stubs are self-anchored — the wire id IS the
53457
+ // spine key (purl / languageCanonicalId). The door re-confirms on the
53458
+ // public spine; a private-registry or workspace package never confirms
53459
+ // and fails closed to org, exactly as an untagged one would.
53460
+ anchorVisibility: "public"
53248
53461
  });
53249
53462
  seen.add(n.id);
53250
53463
  }
@@ -53258,6 +53471,10 @@ function buildContextIngest(store, profile, daemonVersion, ignorePatterns = [],
53258
53471
  }
53259
53472
  const base = {
53260
53473
  daemonVersion,
53474
+ // Origin key (= project, the salted `wp_…`) — the door stamps it onto created
53475
+ // nodes as `authoringProject`, arming the evidence channels' cross-origin gate
53476
+ // (EE-corrob-live: without it every node is fail-open to self-corroboration).
53477
+ ...profile.id ? { originProject: profile.id } : {},
53261
53478
  profile: { stack: profile.stack, languages: profile.languages, domains: profile.domains },
53262
53479
  nodes,
53263
53480
  edges
@@ -53337,8 +53554,8 @@ function noveltyAgainst(node2, neighbors, opts = {}) {
53337
53554
  // src/instance-ingest.ts
53338
53555
  var INSTANCE_LABELS = ["Problem", "Solution", "RootCause"];
53339
53556
  var INSTANCE_EDGES = ["CAUSED_BY", "SOLVED_BY"];
53340
- var ANCHOR_EDGES = ["OCCURS_IN", "DEPENDS_ON", "PERTAIN_TO"];
53341
- var ANCHOR_TARGET_LABELS = ["Language", "Package", "Domain"];
53557
+ var ANCHOR_EDGES = ["OCCURS_IN", "DEPENDS_ON", "PERTAIN_TO", "CONCERNS"];
53558
+ var ANCHOR_TARGET_LABELS = ["Language", "Package", "Domain", "Component"];
53342
53559
  function stripCodebaseScope(scope) {
53343
53560
  const s = { ...scope ?? {} };
53344
53561
  delete s["codebase"];
@@ -53354,15 +53571,31 @@ function wireContextId(n) {
53354
53571
  }
53355
53572
  return n.id;
53356
53573
  }
53574
+ function langAnchor(t) {
53575
+ const name2 = String(t.attrs["name"] ?? "").trim() || t.description.trim() || t.id.replace(/^lang:/, "").trim();
53576
+ return name2 ? `lang:${name2.toLowerCase()}` : void 0;
53577
+ }
53357
53578
  function shareableContext(n, wireId) {
53358
53579
  const attrs = n.label === "Package" ? {
53580
+ // No `version` attr: the purl encodes it, and the door's temporal
53581
+ // guard 422s any `attrs.version` (mirrors buildContextIngest).
53359
53582
  purl: n.attrs["purl"],
53360
53583
  name: n.attrs["name"],
53361
- version: n.attrs["version"],
53362
53584
  ecosystem: n.attrs["ecosystem"],
53363
53585
  resolved: n.attrs["resolved"]
53364
53586
  } : n.label === "Domain" ? { name: n.description, canonicalId: n.attrs["canonicalId"] } : { name: n.attrs["name"] };
53365
- return { ...n, id: wireId, embedding: [], attrs };
53587
+ return {
53588
+ ...n,
53589
+ id: wireId,
53590
+ embedding: [],
53591
+ attrs,
53592
+ ...n.label === "Package" ? { anchorVisibility: "public" } : {},
53593
+ ...n.label === "Language" && langAnchor(n) ? { anchorVisibility: "public", anchor: langAnchor(n) } : {},
53594
+ // A Component stub claims itself by slug (OM-agent-anchors) — the door
53595
+ // confirms only against an EXISTING public Component of that slug, so an
53596
+ // org-internal component name never crosses (fails closed to org).
53597
+ ...n.label === "Component" ? { anchorVisibility: "public", anchor: `component:${wireId}` } : {}
53598
+ };
53366
53599
  }
53367
53600
  function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [], opts = {}) {
53368
53601
  const level = opts.level ?? 1;
@@ -53380,6 +53613,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53380
53613
  });
53381
53614
  const nodes = [];
53382
53615
  const seen = /* @__PURE__ */ new Set();
53616
+ const shippedById = /* @__PURE__ */ new Map();
53383
53617
  const anchorSources = /* @__PURE__ */ new Set();
53384
53618
  const includedByLabel = /* @__PURE__ */ new Map();
53385
53619
  for (const label of INSTANCE_LABELS) {
@@ -53397,7 +53631,9 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53397
53631
  }
53398
53632
  if (!noveltyAgainst(n, ref, noveltyOpts).ready) continue;
53399
53633
  ref.push(n);
53400
- nodes.push(shareable(n));
53634
+ const wireNode = shareable(n);
53635
+ nodes.push(wireNode);
53636
+ shippedById.set(n.id, wireNode);
53401
53637
  seen.add(n.id);
53402
53638
  anchorSources.add(n.id);
53403
53639
  }
@@ -53419,27 +53655,53 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53419
53655
  if (e.type === "OCCURS_IN" && t.label !== "Language") continue;
53420
53656
  if (e.type === "DEPENDS_ON" && t.label !== "Package") continue;
53421
53657
  if (e.type === "PERTAIN_TO" && t.label !== "Domain") continue;
53422
- if (t.label === "Package" && opts.includePackages !== true) continue;
53658
+ if (e.type === "CONCERNS" && t.label !== "Component") continue;
53659
+ if ((t.label === "Package" || t.label === "Component") && opts.includePackages !== true) continue;
53423
53660
  if (ignored(t.description) || ignored(String(t.attrs["name"] ?? ""))) continue;
53424
53661
  targets.push({ e, t, wireId: wireContextId(t) });
53425
53662
  }
53426
53663
  if (targets.length === 0) continue;
53664
+ const shipped = shippedById.get(sourceId);
53665
+ if (shipped) {
53666
+ const anchors = (label) => targets.filter((x) => x.t.label === label).map(
53667
+ (x) => label === "Language" ? langAnchor(x.t) : label === "Component" ? `component:${x.wireId}` : x.wireId
53668
+ ).filter((a) => a != null).sort();
53669
+ const anchor = anchors("Package")[0] ?? anchors("Component")[0] ?? anchors("Language")[0];
53670
+ if (anchor) {
53671
+ shipped.anchorVisibility = "public";
53672
+ shipped.anchor = anchor;
53673
+ }
53674
+ }
53427
53675
  const dg = digest(targets.map(({ e, wireId }) => `${e.type}>${wireId}`).sort());
53428
- if (store.getNode(sourceId)?.attrs["anchorsContributedDigest"] === dg) continue;
53676
+ const sourceNode = store.getNode(sourceId);
53677
+ if (sourceNode?.attrs["anchorsContributedDigest"] === dg) continue;
53429
53678
  anchorDigests[sourceId] = dg;
53679
+ const skippedCloudId = !seen.has(sourceId) ? sourceNode?.attrs["cloudNodeId"] : void 0;
53680
+ const wireFrom = typeof skippedCloudId === "string" && skippedCloudId.length > 0 ? skippedCloudId : sourceId;
53430
53681
  for (const { e, t, wireId } of targets) {
53431
- anchorEdges.push({ ...e, to: wireId, attrs: {} });
53682
+ anchorEdges.push({ ...e, from: wireFrom, to: wireId, attrs: {} });
53432
53683
  if (t.attrs["source"] === "cloud" || contextSeen.has(wireId)) continue;
53433
53684
  contextSeen.add(wireId);
53434
53685
  contextNodes.push(shareableContext(t, wireId));
53435
53686
  }
53436
53687
  }
53688
+ const primaryLang = (profile.languages ?? []).map((l) => String(l).trim().toLowerCase()).filter(Boolean)[0];
53689
+ if (primaryLang) {
53690
+ for (const shipped of shippedById.values()) {
53691
+ if (!shipped.anchorVisibility) {
53692
+ shipped.anchorVisibility = "public";
53693
+ shipped.anchor = `lang:${primaryLang}`;
53694
+ }
53695
+ }
53696
+ }
53437
53697
  const project = opts.project;
53438
53698
  if (project) {
53439
53699
  const now = Date.now();
53440
53700
  for (const sourceId of anchorSources) {
53441
53701
  const source = store.getNode(sourceId);
53442
53702
  if (!source) continue;
53703
+ const skippedCloudId = !seen.has(sourceId) ? source.attrs["cloudNodeId"] : void 0;
53704
+ const wireFrom = typeof skippedCloudId === "string" && skippedCloudId.length > 0 ? skippedCloudId : sourceId;
53443
53705
  for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
53444
53706
  const target = store.getNode(e.to);
53445
53707
  if (!target) continue;
@@ -53474,7 +53736,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53474
53736
  stability: "unstable"
53475
53737
  });
53476
53738
  }
53477
- anchorEdges.push({ ...e, to: symId, attrs: {} });
53739
+ anchorEdges.push({ ...e, from: wireFrom, to: symId, attrs: {} });
53478
53740
  }
53479
53741
  }
53480
53742
  }
@@ -53509,6 +53771,11 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53509
53771
  edges.push(...anchorEdges);
53510
53772
  const base = {
53511
53773
  daemonVersion,
53774
+ // Origin key (= project, the salted `wp_…`) — the door stamps it onto created
53775
+ // nodes as `authoringProject`, arming the evidence channels' cross-origin gate
53776
+ // (EE-corrob-live: this is the MAIN semantic drain; without the stamp every
53777
+ // Problem/Solution lands fail-open to self-corroboration).
53778
+ ...profile.id ? { originProject: profile.id } : {},
53512
53779
  profile: { stack: profile.stack, languages: profile.languages, domains: profile.domains },
53513
53780
  // Context nodes FIRST: a chunked drain (cloud-client, 25-node chunks) then
53514
53781
  // co-locates the few Language/Package stubs with the first instance chunk,
@@ -53522,6 +53789,46 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
53522
53789
  return { ...base, payloadDigest: digest(base), anchorDigests };
53523
53790
  }
53524
53791
 
53792
+ // src/backfill-edges.ts
53793
+ init_src();
53794
+ init_src2();
53795
+ var CAUSAL_LABELS = ["Problem", "Solution", "RootCause"];
53796
+ var CAUSAL_EDGES2 = ["SOLVED_BY", "CAUSED_BY", "FIXED_BY"];
53797
+ function cloudWireId(store, id) {
53798
+ const n = store.getNode(id);
53799
+ if (!n || !CAUSAL_LABELS.includes(n.label)) return null;
53800
+ if (n.label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) return null;
53801
+ const cloudNodeId = n.attrs["cloudNodeId"];
53802
+ if (typeof cloudNodeId === "string" && cloudNodeId.length > 0) return cloudNodeId;
53803
+ if (n.attrs["source"] === "cloud") return n.id;
53804
+ return typeof n.attrs["contributedAtSeq"] === "number" ? n.id : null;
53805
+ }
53806
+ function buildCausalEdgeBackfill(store, profile, daemonVersion) {
53807
+ const edges = [];
53808
+ const seenEdge = /* @__PURE__ */ new Set();
53809
+ for (const label of CAUSAL_LABELS) {
53810
+ for (const n of store.findNodesByLabel(label)) {
53811
+ for (const e of store.outEdges(n.id, [...CAUSAL_EDGES2])) {
53812
+ if (seenEdge.has(e.id)) continue;
53813
+ seenEdge.add(e.id);
53814
+ const from = cloudWireId(store, e.from);
53815
+ const to = cloudWireId(store, e.to);
53816
+ if (!from || !to) continue;
53817
+ edges.push({ ...e, from, to, attrs: {} });
53818
+ }
53819
+ }
53820
+ }
53821
+ if (edges.length === 0) return null;
53822
+ const base = {
53823
+ daemonVersion,
53824
+ profile: { stack: profile.stack, languages: profile.languages, domains: profile.domains },
53825
+ originProject: profile.id,
53826
+ nodes: [],
53827
+ edges
53828
+ };
53829
+ return { ...base, payloadDigest: digest(base) };
53830
+ }
53831
+
53525
53832
  // src/multi.ts
53526
53833
  init_generalize_graph();
53527
53834
  init_symbol_summaries();
@@ -54095,8 +54402,10 @@ async function startMultiDaemon(opts = {}) {
54095
54402
  path: r.root,
54096
54403
  stack: r.entry.stack,
54097
54404
  nodes: r.engine.store.nodeCount(),
54098
- problems: r.engine.store.findNodesByLabel("Problem").length,
54099
- solutions: r.engine.store.findNodesByLabel("Solution").length,
54405
+ // COUNT(*) — the materializing findNodesByLabel(...).length form held
54406
+ // the event loop for seconds per poll once stores grew (HZ-index-hydrate).
54407
+ problems: r.engine.store.countNodesByLabel("Problem"),
54408
+ solutions: r.engine.store.countNodesByLabel("Solution"),
54100
54409
  endpoints: `/ws/${r.id}/`
54101
54410
  })),
54102
54411
  humanView: "run `errata report` \u2014 the dashboard was retired (GRAFT 4e)"
@@ -54152,8 +54461,9 @@ async function startMultiDaemon(opts = {}) {
54152
54461
  name: r.entry.name,
54153
54462
  path: r.root,
54154
54463
  nodes: r.engine.store.nodeCount(),
54155
- problems: r.engine.store.findNodesByLabel("Problem").length,
54156
- solutions: r.engine.store.findNodesByLabel("Solution").length,
54464
+ // COUNT(*) — same hydration hazard as `/` (HZ-index-hydrate).
54465
+ problems: r.engine.store.countNodesByLabel("Problem"),
54466
+ solutions: r.engine.store.countNodesByLabel("Solution"),
54157
54467
  stranded: strandedCount(r)
54158
54468
  }))
54159
54469
  })
@@ -54522,7 +54832,19 @@ async function startMultiDaemon(opts = {}) {
54522
54832
  async syncTriagePublic() {
54523
54833
  if (!loadConfig().consent.sync) return { uploaded: 0, skipped: "consent-off" };
54524
54834
  const ignore = loadClaimIgnorePatterns(globalDir());
54525
- const payload = buildTriageIngest(sharedStore, DAEMON_VERSION, ignore);
54835
+ const langCounts = /* @__PURE__ */ new Map();
54836
+ for (const r of records) {
54837
+ for (const l of r.engine.profile.languages ?? []) {
54838
+ const k = String(l).trim().toLowerCase();
54839
+ if (k) langCounts.set(k, (langCounts.get(k) ?? 0) + 1);
54840
+ }
54841
+ }
54842
+ const primaryLanguage = [...langCounts.entries()].sort(
54843
+ (a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)
54844
+ )[0]?.[0];
54845
+ const payload = buildTriageIngest(sharedStore, DAEMON_VERSION, ignore, 2, {
54846
+ ...primaryLanguage ? { primaryLanguage } : {}
54847
+ });
54526
54848
  if (!payload) return { uploaded: 0 };
54527
54849
  const res = await cloudNow().ingest(payload);
54528
54850
  return { uploaded: res.accepted };
@@ -54533,14 +54855,27 @@ async function startMultiDaemon(opts = {}) {
54533
54855
  const ignore = loadClaimIgnorePatterns(globalDir());
54534
54856
  const client = cloudNow();
54535
54857
  let uploaded = 0;
54858
+ const errors = [];
54859
+ const lane = async (name2, projectName, run3) => {
54860
+ try {
54861
+ await run3();
54862
+ } catch (err2) {
54863
+ const msg = `[${projectName}] ${name2}: ${err2 instanceof Error ? err2.message : String(err2)}`;
54864
+ errors.push(msg);
54865
+ console.error(`[sync\u2192cloud] ${msg}`);
54866
+ }
54867
+ };
54536
54868
  for (const r of records) {
54537
54869
  const store = r.engine.store;
54870
+ const projectName = r.engine.profile.name ?? r.engine.profile.id;
54538
54871
  const context = buildContextIngest(store, r.engine.profile, DAEMON_VERSION, ignore, {
54539
54872
  includePackages: cfg2.consent.contributePackages
54540
54873
  });
54541
54874
  if (context) {
54542
- const res = await client.ingest(context);
54543
- uploaded += res.accepted;
54875
+ await lane("context", projectName, async () => {
54876
+ const res = await client.ingest(context);
54877
+ uploaded += res.accepted;
54878
+ });
54544
54879
  }
54545
54880
  let lexicon;
54546
54881
  try {
@@ -54563,34 +54898,58 @@ async function startMultiDaemon(opts = {}) {
54563
54898
  });
54564
54899
  if (instances) {
54565
54900
  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])
54901
+ await lane("instances", projectName, async () => {
54902
+ const res = await client.ingest(instances);
54903
+ uploaded += res.accepted;
54904
+ const seq = store.currentIngestSeq();
54905
+ const cloudIdByLocal = new Map(
54906
+ res.result.nodes.filter((rn) => rn.nodeId).map((rn) => [rn.canonicalId, rn.nodeId])
54907
+ );
54908
+ for (const n of instances.nodes) {
54909
+ const local = store.getNode(n.id);
54910
+ if (!local || !["Problem", "Solution", "RootCause"].includes(local.label)) continue;
54911
+ const cloudNodeId = cloudIdByLocal.get(n.id);
54912
+ store.updateNode(n.id, {
54913
+ attrs: {
54914
+ ...local.attrs,
54915
+ contributedAtSeq: seq,
54916
+ ...cloudNodeId && cloudNodeId !== n.id ? { cloudNodeId } : {}
54917
+ }
54918
+ });
54919
+ }
54920
+ for (const [localId, dg] of Object.entries(instances.anchorDigests)) {
54921
+ const local = store.getNode(localId);
54922
+ if (!local) continue;
54923
+ store.updateNode(localId, {
54924
+ attrs: { ...local.attrs, anchorsContributedDigest: dg }
54925
+ });
54926
+ }
54927
+ });
54928
+ }
54929
+ }
54930
+ return { uploaded, ...errors.length > 0 ? { errors } : {} };
54931
+ },
54932
+ async backfillCausalEdges() {
54933
+ if (!loadConfig().consent.sync) return { accepted: 0, rejected: 0, skipped: "consent-off" };
54934
+ const client = cloudNow();
54935
+ let accepted = 0;
54936
+ let rejected = 0;
54937
+ const errors = [];
54938
+ for (const r of records) {
54939
+ const payload = buildCausalEdgeBackfill(r.engine.store, r.engine.profile, DAEMON_VERSION);
54940
+ if (!payload) continue;
54941
+ try {
54942
+ const res = await client.ingest(payload);
54943
+ accepted += res.accepted;
54944
+ rejected += res.rejected;
54945
+ if (res.rejected > 0) errors.push(...res.violations.slice(0, 5));
54946
+ } catch (err2) {
54947
+ errors.push(
54948
+ `[${r.engine.profile.name ?? r.engine.profile.id}] backfill: ${err2 instanceof Error ? err2.message : String(err2)}`
54571
54949
  );
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
54950
  }
54592
54951
  }
54593
- return { uploaded };
54952
+ return { accepted, rejected, ...errors.length > 0 ? { errors } : {} };
54594
54953
  },
54595
54954
  async pullTriagePublic() {
54596
54955
  if (!loadConfig().consent.sync) return { merged: 0, skipped: "consent-off" };
@@ -54640,7 +54999,12 @@ async function startMultiDaemon(opts = {}) {
54640
54999
  }
54641
55000
  const inst = await daemon.syncInstancesPublic();
54642
55001
  const skills = await daemon.syncSkillsAll();
54643
- return { uploaded: inst.uploaded, written: skills.written, pruned: skills.pruned };
55002
+ return {
55003
+ uploaded: inst.uploaded,
55004
+ written: skills.written,
55005
+ pruned: skills.pruned,
55006
+ ...inst.errors ? { errors: inst.errors } : {}
55007
+ };
54644
55008
  } finally {
54645
55009
  boundaryFlushing = false;
54646
55010
  }
@@ -54667,13 +55031,18 @@ async function startMultiDaemon(opts = {}) {
54667
55031
  }
54668
55032
  };
54669
55033
  app.post("/api/sync", async (c) => {
55034
+ if (boundaryFlushing) return c.json({ skipped: "in-flight" }, 409);
55035
+ boundaryFlushing = true;
54670
55036
  try {
54671
55037
  const principles = await daemon.syncPrinciplesPublic();
54672
55038
  const triage = await daemon.syncTriagePublic();
54673
55039
  const instances = await daemon.syncInstancesPublic();
54674
- return c.json({ principles, triage, instances });
55040
+ const edgeBackfill = await daemon.backfillCausalEdges();
55041
+ return c.json({ principles, triage, instances, edgeBackfill });
54675
55042
  } catch (err2) {
54676
55043
  return c.json({ error: err2 instanceof Error ? err2.message : String(err2) }, 500);
55044
+ } finally {
55045
+ boundaryFlushing = false;
54677
55046
  }
54678
55047
  });
54679
55048
  app.post("/api/tick", async (c) => {
@@ -57497,8 +57866,12 @@ async function cmdSync(arg) {
57497
57866
  try {
57498
57867
  const res = await fetch(`${lock.webUiUrl}/api/sync`, {
57499
57868
  method: "POST",
57500
- signal: AbortSignal.timeout(3e4)
57869
+ signal: AbortSignal.timeout(15 * 6e4)
57501
57870
  });
57871
+ if (res.status === 409) {
57872
+ console.log("sync already running: the daemon is mid-drain \u2014 it will finish on its own. Nothing new was started.");
57873
+ return;
57874
+ }
57502
57875
  if (!res.ok) {
57503
57876
  const detail = await res.text().catch(() => "");
57504
57877
  console.error(`sync failed: daemon at ${lock.webUiUrl} returned ${res.status}${detail ? ` \u2014 ${detail}` : ""}`);
@@ -57509,7 +57882,7 @@ async function cmdSync(arg) {
57509
57882
  } catch (err2) {
57510
57883
  const timedOut = err2 instanceof Error && err2.name === "TimeoutError";
57511
57884
  console.error(
57512
- timedOut ? `sync failed: the running daemon at ${lock.webUiUrl} didn't respond within 30s \u2014 it may be mid-reindex. Retry shortly.` : `sync failed: could not reach the running daemon at ${lock.webUiUrl} (${err2 instanceof Error ? err2.message : err2})`
57885
+ timedOut ? `sync gave up waiting after 15 min \u2014 the daemon's pass may STILL be running (aborting this request does not cancel it). Check daemon.log before retrying; a retry while it runs answers 409.` : `sync failed: could not reach the running daemon at ${lock.webUiUrl} (${err2 instanceof Error ? err2.message : err2})`
57513
57886
  );
57514
57887
  process.exit(1);
57515
57888
  }
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.135",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -144,7 +144,14 @@ var init_castalia = __esm({
144
144
  "SOLVED_BY",
145
145
  "MITIGATES",
146
146
  "REPORTED_FAILURE",
147
- "CONTRADICTS"
147
+ "CONTRADICTS",
148
+ // Motif twin-faces (KN-twin-faces): failure-face motif → remedy-face motif
149
+ // across layers (AntiPattern/Weakness ↔ Technique/Pattern). Same
150
+ // problem→fix direction as FIXED_BY/SOLVED_BY. Minted by the nightly LLM
151
+ // twin-face pass — these are the near-identical cross-layer pairs the
152
+ // polarity gate (finding 5) correctly refuses to FUSE; the link carries
153
+ // what fusion can't.
154
+ "REMEDIED_BY"
148
155
  ];
149
156
  CONCEPTUAL_EDGES = [
150
157
  "INSTANCE_OF",
@@ -274,6 +281,10 @@ var init_castalia = __esm({
274
281
  CAUSED_BY: 3,
275
282
  FIXED_BY: 3,
276
283
  SOLVED_BY: 3,
284
+ // Twin-face link (failure motif → remedy motif, KN-twin-faces) — causal-grade
285
+ // but a notch below witnessed FIXED_BY/SOLVED_BY: the pairing is an LLM
286
+ // judgment over descriptions, not an agent-witnessed resolution.
287
+ REMEDIED_BY: 2.5,
277
288
  MANIFESTS_AS: 2,
278
289
  ESCALATES_TO: 1.5,
279
290
  AFFECTS: 1.2,
@@ -15049,7 +15060,10 @@ var init_edge_rules = __esm({
15049
15060
  // NOT ruled here — the type pre-exists with broader extractor senses, and a
15050
15061
  // new rule on an old type would reject legitimate live flows (reject-never-flip
15051
15062
  // cuts both ways: only rule types you introduce or senses that are documented).
15052
- CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool"] },
15063
+ // `Component` joined the target set with OM-agent-anchors: an agent-named
15064
+ // component ("React Router") is the same knowledge→named-unit anchor shape as
15065
+ // a Tool — the knowledge is ABOUT it, not dependent on it.
15066
+ CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool", "Component"] },
15053
15067
  OPERATES_ON: { from: ["Algorithm"], to: ["DataStructure"] },
15054
15068
  INVOLVES: { from: ["Problem", "Solution"], to: ["DataStructure"] },
15055
15069
  // ── Taxonomy (Track 4 Stream C: MITRE CWE `ChildOf` → `Weakness —IS_A→ Weakness`) ──
@@ -15162,7 +15176,12 @@ var init_wire = __esm({
15162
15176
  * knows which packages are private. The door does NOT trust a `public` claim
15163
15177
  * blindly (it re-confirms on the spine); absent ⇒ unknown ⇒ fail-closed to
15164
15178
  * org-private. Accepted-but-ignored until ORG_MEMBRANE_ENABLED flips. */
15165
- anchorVisibility: external_exports.enum(["public", "private"]).optional()
15179
+ anchorVisibility: external_exports.enum(["public", "private"]).optional(),
15180
+ /** The spine-resolvable anchor id backing a `public` tag (OM-anchor-tag): a
15181
+ * Package purl or `languageCanonicalId`. The door validates THIS on the
15182
+ * public spine (falling back to `canonicalId` when absent — context stubs
15183
+ * are self-anchored). Never trusted without spine confirmation. */
15184
+ anchor: external_exports.string().min(1).max(300).optional()
15166
15185
  });
15167
15186
  RouteContextCountWireSchema = external_exports.object({
15168
15187
  confirmed: external_exports.number().int().min(0),
@@ -24330,6 +24349,13 @@ var SqliteGraphStore = class {
24330
24349
  findByLabel: this.db.prepare(
24331
24350
  "SELECT * FROM nodes WHERE label = ? AND valid_to IS NULL"
24332
24351
  ),
24352
+ // Scalar count twin of findByLabel — same live-row semantics, no row
24353
+ // hydration. Exists because status surfaces (daemon `/` + `/health`) used
24354
+ // findNodesByLabel(...).length, materializing every row's attrs JSON and
24355
+ // embedding blob per request — seconds of synchronous loop-hold per poll.
24356
+ countByLabel: this.db.prepare(
24357
+ "SELECT COUNT(*) AS n FROM nodes WHERE label = ? AND valid_to IS NULL"
24358
+ ),
24333
24359
  // ALL versions of a label — incl frozen/closed (valid_to set). Used by the
24334
24360
  // clean-reindex purge so a true wipe removes history too, not just live rows.
24335
24361
  findByLabelAll: this.db.prepare("SELECT * FROM nodes WHERE label = ?"),
@@ -24583,6 +24609,13 @@ var SqliteGraphStore = class {
24583
24609
  const rows = this.stmts.findByLabel.all(label);
24584
24610
  return rows.map(rowToNode);
24585
24611
  }
24612
+ /** Live-row count for a label — `findNodesByLabel(label).length` without the
24613
+ * per-row hydration (attrs JSON + embedding blob). Status surfaces poll this
24614
+ * per project per request; the materializing form held the daemon's event
24615
+ * loop for seconds at scale. */
24616
+ countNodesByLabel(label) {
24617
+ return Number(this.stmts.countByLabel.get(label).n);
24618
+ }
24586
24619
  findAllVersionsByLabel(label) {
24587
24620
  const rows = this.stmts.findByLabelAll.all(label);
24588
24621
  return rows.map(rowToNode);