@inerrata-corporation/errata 2.0.6-dev.2069 → 2.0.6-dev.2082

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 +172 -19
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -77056,7 +77056,7 @@ function createWatchBreaker(deps) {
77056
77056
  }
77057
77057
 
77058
77058
  // src/engine.ts
77059
- var DAEMON_VERSION = true ? "2.0.6-dev.2069" : "2.0.0-alpha.0";
77059
+ var DAEMON_VERSION = true ? "2.0.6-dev.2082" : "2.0.0-alpha.0";
77060
77060
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
77061
77061
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
77062
77062
  var GIT_OP_MUTE_MS = 4e3;
@@ -79783,6 +79783,13 @@ var SHIP_TERMINAL_REFUSALS = 3;
79783
79783
  function refusalFingerprint(reason, description) {
79784
79784
  return digest(`${reason}\0${description}`);
79785
79785
  }
79786
+ function isRetryableRefusal(reason) {
79787
+ return /\bretry\b/i.test(reason);
79788
+ }
79789
+ function isTerminallyRefused(n) {
79790
+ const reason = String(n.attrs["shipRefusedReason"] ?? "");
79791
+ return typeof n.attrs["shipRefusedCount"] === "number" && n.attrs["shipRefusedCount"] >= SHIP_TERMINAL_REFUSALS && !isRetryableRefusal(reason) && n.attrs["shipRefusedFingerprint"] === refusalFingerprint(reason, n.description);
79792
+ }
79786
79793
  function unshippableReason(canonical, label) {
79787
79794
  void label;
79788
79795
  if (canonical.length < WIRE_CANONICAL_ID_MIN) {
@@ -79935,10 +79942,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
79935
79942
  composition.autoMint++;
79936
79943
  continue;
79937
79944
  }
79938
- if (typeof n.attrs["shipRefusedCount"] === "number" && n.attrs["shipRefusedCount"] >= SHIP_TERMINAL_REFUSALS && n.attrs["shipRefusedFingerprint"] === refusalFingerprint(
79939
- String(n.attrs["shipRefusedReason"] ?? ""),
79940
- n.description
79941
- )) {
79945
+ if (isTerminallyRefused(n)) {
79942
79946
  composition.terminalRefused++;
79943
79947
  continue;
79944
79948
  }
@@ -80022,12 +80026,14 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
80022
80026
  }
80023
80027
  }
80024
80028
  const project = opts.project;
80029
+ const projectAnchorDigests = {};
80025
80030
  if (project) {
80026
80031
  const now = Date.now();
80027
80032
  for (const sourceId of anchorSources) {
80028
80033
  const source = store.getNode(sourceId);
80029
80034
  if (!source) continue;
80030
80035
  const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source);
80036
+ const planned = [];
80031
80037
  for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
80032
80038
  const target = store.getNode(e.to);
80033
80039
  if (!target) continue;
@@ -80035,7 +80041,15 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
80035
80041
  const qname = target.attrs["qname"];
80036
80042
  if (typeof relPath !== "string" || typeof qname !== "string") continue;
80037
80043
  if (ignored(relPath) || ignored(qname)) continue;
80038
- const symId = projectSymbolId(project.salt, project.projectId, relPath, qname, target.label);
80044
+ planned.push({ e, target, symId: projectSymbolId(project.salt, project.projectId, relPath, qname, target.label) });
80045
+ }
80046
+ if (planned.length === 0) continue;
80047
+ const dg = digest(
80048
+ planned.map(({ e, symId }) => `ANCHORED_AT>${symId}>${JSON.stringify(anchorVersionAttrs(e.attrs))}`).sort()
80049
+ );
80050
+ if (source.attrs["projectAnchorsContributedDigest"] === dg) continue;
80051
+ projectAnchorDigests[sourceId] = dg;
80052
+ for (const { e, target, symId } of planned) {
80039
80053
  if (!contextSeen.has(symId)) {
80040
80054
  contextSeen.add(symId);
80041
80055
  contextNodes.push({
@@ -80142,6 +80156,11 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
80142
80156
  }
80143
80157
  const shippableNodes = heldOrphans.size === 0 ? nodes : nodes.filter((n) => !heldOrphans.has(n.id));
80144
80158
  const shippableEdges = heldOrphans.size === 0 ? edges : edges.filter((e) => !heldOrphans.has(e.from) && !heldOrphans.has(e.to));
80159
+ for (const id of heldOrphans) {
80160
+ delete anchorDigests[id];
80161
+ delete semanticEdgeDigests[id];
80162
+ delete projectAnchorDigests[id];
80163
+ }
80145
80164
  if (heldOrphans.size > 0) {
80146
80165
  console.warn(
80147
80166
  `[errata] held ${heldOrphans.size} causal-orphan node(s) \u2014 no shippable binding edge this drain (VS-orphan-gate)`
@@ -80171,7 +80190,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
80171
80190
  ...symbolSummaries ? { symbolSummaries } : {}
80172
80191
  };
80173
80192
  opts.onComposition?.(composition);
80174
- return { ...base, payloadDigest: digest(base), anchorDigests, semanticEdgeDigests, composition };
80193
+ return { ...base, payloadDigest: digest(base), anchorDigests, semanticEdgeDigests, projectAnchorDigests, composition };
80175
80194
  }
80176
80195
 
80177
80196
  // src/backfill-edges.ts
@@ -83140,6 +83159,76 @@ function codexWakeCandidates(input) {
83140
83159
  return out2;
83141
83160
  }
83142
83161
 
83162
+ // src/hook-route.ts
83163
+ function msysToWindowsPath(p, platform = process.platform) {
83164
+ if (platform !== "win32") return p;
83165
+ const m = /^\/([a-zA-Z])(\/.*)?$/.exec(p);
83166
+ return m ? `${m[1].toUpperCase()}:${m[2] ?? "/"}` : p;
83167
+ }
83168
+ var SHELL_BREAK = /[\s"'`;&|()<>=]+/;
83169
+ function absolutePathsInCommand(command, platform = process.platform) {
83170
+ const out2 = [];
83171
+ for (const raw2 of command.split(SHELL_BREAK)) {
83172
+ const token = raw2.replace(/^[:,]+/, "").replace(/[,:]+$/, "");
83173
+ if (!token) continue;
83174
+ if (/^[a-zA-Z]:[\\/]/.test(token)) {
83175
+ out2.push(token);
83176
+ } else if (token.startsWith("/") && !token.startsWith("//")) {
83177
+ if (platform === "win32") {
83178
+ if (/^\/[a-zA-Z](\/|$)/.test(token)) out2.push(msysToWindowsPath(token, platform));
83179
+ } else if (token.length > 1) {
83180
+ out2.push(token);
83181
+ }
83182
+ }
83183
+ }
83184
+ return out2;
83185
+ }
83186
+ function hookPathCandidates(body2, platform = process.platform) {
83187
+ const input = body2["tool_input"] ?? {};
83188
+ const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
83189
+ const out2 = [];
83190
+ for (const key of ["file_path", "notebook_path", "path"]) {
83191
+ const v = str(input[key]);
83192
+ if (v) out2.push(msysToWindowsPath(v, platform));
83193
+ }
83194
+ const command = str(input["command"]);
83195
+ if (command) out2.push(...absolutePathsInCommand(command, platform));
83196
+ const cwd = str(body2["cwd"]);
83197
+ if (cwd) out2.push(msysToWindowsPath(cwd, platform));
83198
+ return [...new Set(out2)];
83199
+ }
83200
+ var SESSION_AFFINITY_TTL_MS = 30 * 6e4;
83201
+ var SessionAffinity = class {
83202
+ constructor(ttlMs = SESSION_AFFINITY_TTL_MS, maxSessions = 500) {
83203
+ this.ttlMs = ttlMs;
83204
+ this.maxSessions = maxSessions;
83205
+ }
83206
+ bySession = /* @__PURE__ */ new Map();
83207
+ note(sessionId, root, now = Date.now()) {
83208
+ if (typeof sessionId !== "string" || sessionId.length === 0) return;
83209
+ this.bySession.delete(sessionId);
83210
+ this.bySession.set(sessionId, { root, at: now });
83211
+ while (this.bySession.size > this.maxSessions) {
83212
+ const oldest = this.bySession.keys().next().value;
83213
+ if (oldest === void 0) break;
83214
+ this.bySession.delete(oldest);
83215
+ }
83216
+ }
83217
+ rootFor(sessionId, now = Date.now()) {
83218
+ if (typeof sessionId !== "string" || sessionId.length === 0) return null;
83219
+ const hit = this.bySession.get(sessionId);
83220
+ if (!hit) return null;
83221
+ if (now - hit.at > this.ttlMs) {
83222
+ this.bySession.delete(sessionId);
83223
+ return null;
83224
+ }
83225
+ return hit.root;
83226
+ }
83227
+ forget(sessionId) {
83228
+ if (typeof sessionId === "string") this.bySession.delete(sessionId);
83229
+ }
83230
+ };
83231
+
83143
83232
  // src/multi.ts
83144
83233
  var projectSymbolSalts = /* @__PURE__ */ new Map();
83145
83234
  async function resolveProjectSymbolSalt(client, projectId) {
@@ -83590,7 +83679,8 @@ async function startMultiDaemon(opts = {}) {
83590
83679
  }))
83591
83680
  })
83592
83681
  );
83593
- const forward = async (c, pathInBody) => {
83682
+ const sessionAffinity = new SessionAffinity();
83683
+ const forward = async (c, pathInBody, opts2 = {}) => {
83594
83684
  const raw2 = await c.req.text();
83595
83685
  let body2 = {};
83596
83686
  try {
@@ -83599,7 +83689,21 @@ async function startMultiDaemon(opts = {}) {
83599
83689
  return c.json({ error: "bad-json" }, 400);
83600
83690
  }
83601
83691
  lastHook = { at: Date.now(), event: typeof body2["hook_event_name"] === "string" ? body2["hook_event_name"] : c.req.path };
83602
- let owner = ownerOf(records, pathInBody(body2));
83692
+ const primary = pathInBody(body2);
83693
+ const others = (opts2.extraPaths?.(body2) ?? []).filter((p) => p !== primary);
83694
+ let owner = ownerOf(records, primary);
83695
+ for (const p of others) {
83696
+ if (owner) break;
83697
+ owner = ownerOf(records, p);
83698
+ }
83699
+ if (!owner && others.length > 0) {
83700
+ const registryRoots = listWorkspaceEntries().map((e) => e.path);
83701
+ const registered = others.map((p) => pickOwnerRoot(registryRoots, p)).find((r) => r != null);
83702
+ if (registered && attachWorkspace(registered).attached) {
83703
+ owner = records.find((r) => r.root === registered) ?? null;
83704
+ if (owner) console.log(`[errata] woke workspace ${owner.entry.name}`);
83705
+ }
83706
+ }
83603
83707
  if (!owner) {
83604
83708
  const missPath = pathInBody(body2);
83605
83709
  const registeredRoot = missPath ? pickOwnerRoot(listWorkspaceEntries().map((e) => e.path), missPath) : null;
@@ -83609,10 +83713,18 @@ async function startMultiDaemon(opts = {}) {
83609
83713
  if (owner) console.log(`[errata] woke workspace ${owner.entry.name}`);
83610
83714
  }
83611
83715
  }
83716
+ if (!owner && opts2.affinity === "follow") {
83717
+ const root = sessionAffinity.rootFor(body2["session_id"]);
83718
+ if (root) {
83719
+ owner = records.find((r) => r.root === root) ?? null;
83720
+ if (!owner && attachWorkspace(root).attached) owner = records.find((r) => r.root === root) ?? null;
83721
+ }
83722
+ }
83612
83723
  if (!owner) {
83613
- rootAdopter.noteMiss(pathInBody(body2));
83724
+ rootAdopter.noteMiss(primary);
83614
83725
  return c.json({ error: "no-matching-project" }, 404);
83615
83726
  }
83727
+ if (opts2.affinity === "note") sessionAffinity.note(body2["session_id"], owner.root);
83616
83728
  owner.lastHookAt = Date.now();
83617
83729
  const sub = c.req.path;
83618
83730
  const done = markPass(`${sub}:${owner.entry.name}`);
@@ -83635,7 +83747,7 @@ async function startMultiDaemon(opts = {}) {
83635
83747
  const fp = input["file_path"];
83636
83748
  const cwd = b["cwd"];
83637
83749
  return (typeof fp === "string" ? fp : void 0) ?? (typeof cwd === "string" ? cwd : void 0);
83638
- })
83750
+ }, { extraPaths: (b) => hookPathCandidates(b), affinity: "note" })
83639
83751
  );
83640
83752
  app.post(
83641
83753
  "/api/diagnostics",
@@ -83645,10 +83757,10 @@ async function startMultiDaemon(opts = {}) {
83645
83757
  })
83646
83758
  );
83647
83759
  const cwdOf = (b) => typeof b["cwd"] === "string" ? b["cwd"] : void 0;
83648
- app.post("/api/turn", (c) => forward(c, cwdOf));
83649
- app.post("/api/message", (c) => forward(c, cwdOf));
83650
- app.post("/api/session-end", (c) => forward(c, cwdOf));
83651
- app.post("/api/context-inject", (c) => forward(c, cwdOf));
83760
+ app.post("/api/turn", (c) => forward(c, cwdOf, { affinity: "follow" }));
83761
+ app.post("/api/message", (c) => forward(c, cwdOf, { affinity: "follow" }));
83762
+ app.post("/api/session-end", (c) => forward(c, cwdOf, { affinity: "follow" }));
83763
+ app.post("/api/context-inject", (c) => forward(c, cwdOf, { affinity: "follow" }));
83652
83764
  for (const r of records) {
83653
83765
  mountedIds.add(r.id);
83654
83766
  app.route(`/ws/${r.id}`, r.webApp);
@@ -84325,7 +84437,8 @@ async function startMultiDaemon(opts = {}) {
84325
84437
  // Same shape as the drained row: a first/last trend reading must not
84326
84438
  // straddle rows where the counter is merely absent.
84327
84439
  blockedRepeat: 0,
84328
- rejectedRepeat: 0
84440
+ rejectedRepeat: 0,
84441
+ edgesRejected: 0
84329
84442
  });
84330
84443
  }
84331
84444
  if (instances) {
@@ -84353,6 +84466,17 @@ async function startMultiDaemon(opts = {}) {
84353
84466
  for (const rn of res.result.nodes) {
84354
84467
  if (rn.decision === "rejected") rejected.set(rn.canonicalId, rn.reason ?? "no reason given");
84355
84468
  }
84469
+ const rejectedEdges = res.result.edges.filter((re) => re.decision === "rejected");
84470
+ if (rejectedEdges.length > 0) {
84471
+ const byReason = /* @__PURE__ */ new Map();
84472
+ for (const re of rejectedEdges) {
84473
+ const why = `${re.type}: ${re.reason ?? "no reason given"}`;
84474
+ byReason.set(why, (byReason.get(why) ?? 0) + 1);
84475
+ }
84476
+ console.warn(
84477
+ `[sync\u2192cloud] [${projectName}] instances: ${rejectedEdges.length} edge(s) REJECTED by the door: ` + [...byReason.entries()].map(([why, n]) => `${n}\xD7 ${why}`).join(" | ")
84478
+ );
84479
+ }
84356
84480
  if (rejected.size > 0) {
84357
84481
  const sample = [...rejected.entries()].slice(0, 3).map(([id, why]) => `${store.getNode(id)?.description.slice(0, 60) ?? id}: ${why}`);
84358
84482
  console.warn(
@@ -84368,6 +84492,7 @@ async function startMultiDaemon(opts = {}) {
84368
84492
  };
84369
84493
  let blockedRepeat = 0;
84370
84494
  let rejectedRepeat = 0;
84495
+ const nowHeld = [];
84371
84496
  const noteRefused = (id, reason, repeat) => {
84372
84497
  const owner = owningStore(id);
84373
84498
  if (!owner) return;
@@ -84384,9 +84509,17 @@ async function startMultiDaemon(opts = {}) {
84384
84509
  shipRefusedFingerprint: fp
84385
84510
  }
84386
84511
  });
84512
+ if (same && count === SHIP_TERMINAL_REFUSALS && !isRetryableRefusal(reason)) {
84513
+ nowHeld.push({ id, reason });
84514
+ }
84387
84515
  };
84388
84516
  for (const id of blocked) noteRefused(id, "blocked", () => blockedRepeat++);
84389
84517
  for (const [id, why] of rejected) noteRefused(id, why, () => rejectedRepeat++);
84518
+ if (nowHeld.length > 0) {
84519
+ console.warn(
84520
+ `[sync\u2192cloud] [${projectName}] instances: HOLDING ${nowHeld.length} node(s) \u2014 refused identically ${SHIP_TERMINAL_REFUSALS}\xD7 on unchanged text; not re-sent until the text changes: ` + nowHeld.map(({ id, reason }) => `${id} "${owningStore(id)?.n.description.slice(0, 60) ?? ""}": ${reason}`).join(" | ")
84521
+ );
84522
+ }
84390
84523
  for (const n of instances.nodes) {
84391
84524
  const owner = owningStore(n.id);
84392
84525
  if (!owner || !["Problem", "Solution", "RootCause"].includes(owner.n.label)) continue;
@@ -84416,6 +84549,14 @@ async function startMultiDaemon(opts = {}) {
84416
84549
  attrs: { ...owner.n.attrs, anchorsContributedDigest: dg }
84417
84550
  });
84418
84551
  }
84552
+ for (const [localId, dg] of Object.entries(instances.projectAnchorDigests)) {
84553
+ const owner = owningStore(localId);
84554
+ if (!owner) continue;
84555
+ if (rejected.has(localId) || blocked.has(localId)) continue;
84556
+ owner.s.updateNode(localId, {
84557
+ attrs: { ...owner.n.attrs, projectAnchorsContributedDigest: dg }
84558
+ });
84559
+ }
84419
84560
  for (const [localId, dg] of Object.entries(instances.semanticEdgeDigests)) {
84420
84561
  const owner = owningStore(localId);
84421
84562
  if (!owner) continue;
@@ -84434,7 +84575,8 @@ async function startMultiDaemon(opts = {}) {
84434
84575
  // node; flat at 1 with `blockedRepeat` 0 is a different node
84435
84576
  // failing every pass. Same total, different bug.
84436
84577
  blockedRepeat,
84437
- rejectedRepeat
84578
+ rejectedRepeat,
84579
+ edgesRejected: rejectedEdges.length
84438
84580
  });
84439
84581
  return res.accepted;
84440
84582
  });
@@ -85771,15 +85913,26 @@ async function cmdStatus() {
85771
85913
  try {
85772
85914
  let pending = 0;
85773
85915
  let total = 0;
85916
+ let heldRefused = 0;
85917
+ let autoMint = 0;
85774
85918
  for (const label of ["Problem", "Solution", "RootCause"]) {
85775
85919
  for (const n of store.findNodesByLabel(label)) {
85776
85920
  if (n.attrs["source"] === "cloud") continue;
85777
85921
  total++;
85778
85922
  const c = n.attrs["contributedAtSeq"];
85779
- if (typeof c !== "number" || (n.lastReinforcedAtSeq ?? 0) > c) pending++;
85923
+ if (typeof c === "number" && (n.lastReinforcedAtSeq ?? 0) <= c) continue;
85924
+ if (label === "Solution" && n.description.startsWith(AUTO_MINT_PREFIX)) autoMint++;
85925
+ else if (isTerminallyRefused(n)) heldRefused++;
85926
+ else pending++;
85780
85927
  }
85781
85928
  }
85782
- console.log(` contribute: ${pending} pending of ${total} instance node(s)`);
85929
+ const held = [
85930
+ ...heldRefused > 0 ? [`${heldRefused} held after identical door refusals`] : [],
85931
+ ...autoMint > 0 ? [`${autoMint} auto-close marker(s) that never ship`] : []
85932
+ ];
85933
+ console.log(
85934
+ ` contribute: ${pending} pending of ${total} instance node(s)${held.length > 0 ? ` \xB7 ${held.join(" \xB7 ")}` : ""}`
85935
+ );
85783
85936
  const rejections = store.edgeRejections();
85784
85937
  if (rejections.length > 0) {
85785
85938
  const refused = rejections.reduce((n, r) => n + r.count, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.6-dev.2069",
3
+ "version": "2.0.6-dev.2082",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {