@inerrata-corporation/errata 2.0.2-dev.226 → 2.0.2-dev.245

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.
@@ -15315,6 +15315,15 @@ var init_wire = __esm({
15315
15315
  /** Canonical human-readable description (no raw paths — daemon scrubs; server rechecks). */
15316
15316
  description: external_exports.string().min(1).max(4e3),
15317
15317
  attrs: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
15318
+ /** One-way origin key of the SESSION that minted this node (`ws_…`, a
15319
+ * truncated digest — never the raw session id). Stamped onto the created
15320
+ * node as `authoringSession`, which is the independence unit for the
15321
+ * evidence channels: the session that authored a claim may not corroborate
15322
+ * or refute it, while a DIFFERENT session on the same checkout may (alyssa,
15323
+ * 2026-07-31 — the workspace key made every witness on a single-checkout
15324
+ * deployment a self-corroboration). Optional + additive: absent leaves the
15325
+ * gate fail-open for that node, exactly today's behaviour. */
15326
+ originSession: external_exports.string().min(3).max(64).optional(),
15318
15327
  extractionSource: external_exports.enum(INGEST_EXTRACTION_SOURCES),
15319
15328
  validationSource: external_exports.enum(VALIDATION_SOURCES).optional(),
15320
15329
  /** Org-membrane (M2): the daemon's anchor tag — it owns the lockfile, so it
package/errata.mjs CHANGED
@@ -15616,6 +15616,15 @@ var init_wire = __esm({
15616
15616
  /** Canonical human-readable description (no raw paths — daemon scrubs; server rechecks). */
15617
15617
  description: external_exports.string().min(1).max(4e3),
15618
15618
  attrs: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
15619
+ /** One-way origin key of the SESSION that minted this node (`ws_…`, a
15620
+ * truncated digest — never the raw session id). Stamped onto the created
15621
+ * node as `authoringSession`, which is the independence unit for the
15622
+ * evidence channels: the session that authored a claim may not corroborate
15623
+ * or refute it, while a DIFFERENT session on the same checkout may (alyssa,
15624
+ * 2026-07-31 — the workspace key made every witness on a single-checkout
15625
+ * deployment a self-corroboration). Optional + additive: absent leaves the
15626
+ * gate fail-open for that node, exactly today's behaviour. */
15627
+ originSession: external_exports.string().min(3).max(64).optional(),
15619
15628
  extractionSource: external_exports.enum(INGEST_EXTRACTION_SOURCES),
15620
15629
  validationSource: external_exports.enum(VALIDATION_SOURCES).optional(),
15621
15630
  /** Org-membrane (M2): the daemon's anchor tag — it owns the lockfile, so it
@@ -16279,6 +16288,9 @@ var init_profile = __esm({
16279
16288
  });
16280
16289
 
16281
16290
  // ../../packages/local-shared/src/daemon-wire.ts
16291
+ function formatDisposition(d) {
16292
+ return `${d.recorded} recorded \xB7 ${d.unmatched} unmatched (queued) \xB7 ${d.duplicate} dup \xB7 ${d.selfGated} self`;
16293
+ }
16282
16294
  var init_daemon_wire = __esm({
16283
16295
  "../../packages/local-shared/src/daemon-wire.ts"() {
16284
16296
  "use strict";
@@ -21631,6 +21643,7 @@ var init_oauth = __esm({
21631
21643
 
21632
21644
  // ../../packages/cloud-client/src/client.ts
21633
21645
  import { randomUUID } from "node:crypto";
21646
+ import { gzipSync } from "node:zlib";
21634
21647
  function wirePerContext(value) {
21635
21648
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
21636
21649
  const out2 = {};
@@ -21828,6 +21841,11 @@ function normalizeSolution(input) {
21828
21841
  validationSource: input.validationSource
21829
21842
  };
21830
21843
  }
21844
+ function isIntermediaryRejection(err2) {
21845
+ if (!(err2 instanceof CloudError)) return false;
21846
+ if (err2.status < 400 || err2.status >= 500) return false;
21847
+ return /<!DOCTYPE html|<html/i.test(err2.message);
21848
+ }
21831
21849
  function chunkArray(items, size) {
21832
21850
  if (items.length <= size) return items.length ? [[...items]] : [];
21833
21851
  const out2 = [];
@@ -21845,7 +21863,7 @@ function provenanceHeaders(provenance) {
21845
21863
  ...provenance.agentModel ? { "x-inerrata-agent-model": provenance.agentModel } : {}
21846
21864
  };
21847
21865
  }
21848
- var asWireCount, INGEST_NODE_CHUNK, CloudClient, CloudError;
21866
+ var asWireCount, INGEST_NODE_CHUNK, COMPRESS_MIN_BYTES, CloudClient, CloudError;
21849
21867
  var init_client = __esm({
21850
21868
  "../../packages/cloud-client/src/client.ts"() {
21851
21869
  "use strict";
@@ -21853,6 +21871,7 @@ var init_client = __esm({
21853
21871
  init_src();
21854
21872
  asWireCount = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
21855
21873
  INGEST_NODE_CHUNK = 8;
21874
+ COMPRESS_MIN_BYTES = 4096;
21856
21875
  CloudClient = class {
21857
21876
  baseUrl;
21858
21877
  apiKey;
@@ -21867,6 +21886,8 @@ var init_client = __esm({
21867
21886
  daemonVersion;
21868
21887
  daemonChannel;
21869
21888
  provenance;
21889
+ /** Cleared for the process once a server proves it cannot inflate (see `json`). */
21890
+ compressRequests;
21870
21891
  constructor(opts) {
21871
21892
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
21872
21893
  if (opts.apiKey !== void 0) this.apiKey = opts.apiKey;
@@ -21880,6 +21901,7 @@ var init_client = __esm({
21880
21901
  this.timeoutMs = opts.timeoutMs ?? 15e3;
21881
21902
  this.daemonVersion = opts.daemonVersion;
21882
21903
  this.daemonChannel = opts.daemonChannel;
21904
+ this.compressRequests = opts.compressRequests ?? true;
21883
21905
  this.provenance = opts.provenance ?? {
21884
21906
  clientProduct: "inerrata_cloud_client",
21885
21907
  clientKind: "sdk",
@@ -22017,18 +22039,24 @@ var init_client = __esm({
22017
22039
  const startedAt = Date.now();
22018
22040
  const totalChunks = Math.max(1, Math.ceil(batch.nodes.length / INGEST_NODE_CHUNK)) + Math.ceil(Math.max(0, batch.edges.length - MAX_EDGES_PER_PAYLOAD) / MAX_EDGES_PER_PAYLOAD);
22019
22041
  let chunkIndex = 0;
22042
+ const blocked = [];
22020
22043
  const emit = (nodeIds, accepted) => {
22021
22044
  chunkIndex++;
22045
+ const refused = blocked.splice(0, blocked.length);
22046
+ const refusedSet = new Set(refused);
22022
22047
  opts.onChunk?.({
22023
22048
  chunkIndex,
22024
22049
  totalChunks,
22025
- nodeIds,
22050
+ // Watermark only what actually landed — a blocked node never reached the
22051
+ // door, and marking it contributed would lose it permanently and silently.
22052
+ nodeIds: refusedSet.size ? nodeIds.filter((id) => !refusedSet.has(id)) : nodeIds,
22026
22053
  accepted,
22027
- elapsedMs: Date.now() - startedAt
22054
+ elapsedMs: Date.now() - startedAt,
22055
+ ...refused.length ? { blockedNodeIds: refused } : {}
22028
22056
  });
22029
22057
  };
22030
22058
  if (batch.nodes.length <= INGEST_NODE_CHUNK && batch.edges.length <= MAX_EDGES_PER_PAYLOAD) {
22031
- const result = await this.ingestWithSplit(batch, batch.nodes, batch.edges);
22059
+ const result = await this.ingestWithSplit(batch, batch.nodes, batch.edges, blocked);
22032
22060
  const summary = summarizeIngestResult(result);
22033
22061
  emit(batch.nodes.map((n) => n.id), summary.accepted);
22034
22062
  return { ...summary, result };
@@ -22043,7 +22071,7 @@ var init_client = __esm({
22043
22071
  const shipped = new Set(inChunk);
22044
22072
  pendingEdges = pendingEdges.filter((e) => !shipped.has(e));
22045
22073
  }
22046
- const r = await this.ingestWithSplit(batch, nodeChunk, inChunk);
22074
+ const r = await this.ingestWithSplit(batch, nodeChunk, inChunk, blocked);
22047
22075
  emit(nodeChunk.map((n) => n.id), summarizeIngestResult(r).accepted);
22048
22076
  merged.nodes.push(...r.nodes);
22049
22077
  merged.edges.push(...r.edges);
@@ -22086,16 +22114,21 @@ var init_client = __esm({
22086
22114
  * a verdict about the payload — splitting it would just re-send a rejected batch
22087
22115
  * N more times — and a 409/413 has its own handling upstream.
22088
22116
  */
22089
- async ingestWithSplit(batch, nodes, edges) {
22117
+ async ingestWithSplit(batch, nodes, edges, blocked) {
22090
22118
  try {
22091
22119
  return await this.ingestWire(toWirePayload({ ...batch, nodes, edges }, randomUUID()));
22092
22120
  } catch (err2) {
22093
22121
  const status = err2 instanceof CloudError ? err2.status : 0;
22094
22122
  const timedOut = status === 502 || status === 504 || status === 408 || status === 0;
22095
- if (!timedOut || nodes.length <= 1) throw err2;
22123
+ const refusedAtEdge = blocked !== void 0 && isIntermediaryRejection(err2);
22124
+ if (refusedAtEdge && nodes.length === 1 && nodes[0]) {
22125
+ blocked.push(nodes[0].id);
22126
+ return { runId: randomUUID(), nodes: [], edges: [] };
22127
+ }
22128
+ if (!timedOut && !refusedAtEdge || nodes.length <= 1) throw err2;
22096
22129
  const mid = Math.ceil(nodes.length / 2);
22097
- const a = await this.ingestWithSplit(batch, nodes.slice(0, mid), edges);
22098
- const b = await this.ingestWithSplit(batch, nodes.slice(mid), []);
22130
+ const a = await this.ingestWithSplit(batch, nodes.slice(0, mid), edges, blocked);
22131
+ const b = await this.ingestWithSplit(batch, nodes.slice(mid), [], blocked);
22099
22132
  return {
22100
22133
  runId: a.runId,
22101
22134
  nodes: [...a.nodes, ...b.nodes],
@@ -22397,15 +22430,43 @@ var init_client = __esm({
22397
22430
  const token = opts?.bearerToken ?? await this.authToken();
22398
22431
  if (token) headers["authorization"] = `Bearer ${token}`;
22399
22432
  }
22433
+ let payload;
22434
+ if (body2 !== void 0) {
22435
+ const raw2 = JSON.stringify(body2);
22436
+ if (this.compressRequests && Buffer.byteLength(raw2) >= COMPRESS_MIN_BYTES) {
22437
+ payload = gzipSync(Buffer.from(raw2));
22438
+ headers["content-encoding"] = "gzip";
22439
+ } else {
22440
+ payload = raw2;
22441
+ }
22442
+ }
22400
22443
  const ac = new AbortController();
22401
22444
  const tid = setTimeout(() => ac.abort(), this.timeoutMs);
22402
22445
  try {
22403
22446
  const res = await this.fetchFn(url2, {
22404
22447
  method,
22405
22448
  headers,
22406
- body: body2 === void 0 ? void 0 : JSON.stringify(body2),
22449
+ body: payload,
22407
22450
  signal: ac.signal
22408
22451
  });
22452
+ if (res.status === 400 && headers["content-encoding"] === "gzip") {
22453
+ delete headers["content-encoding"];
22454
+ const retry = await this.fetchFn(url2, {
22455
+ method,
22456
+ headers,
22457
+ body: JSON.stringify(body2),
22458
+ signal: ac.signal
22459
+ });
22460
+ if (!retry.ok) {
22461
+ const text = await retry.text();
22462
+ throw new CloudError(
22463
+ `${method} ${path2} failed: HTTP ${retry.status} ${text.slice(0, 200)}`,
22464
+ retry.status
22465
+ );
22466
+ }
22467
+ this.compressRequests = false;
22468
+ return await retry.json();
22469
+ }
22409
22470
  if (!res.ok) {
22410
22471
  const text = await res.text();
22411
22472
  throw new CloudError(
@@ -47250,12 +47311,12 @@ var init_report_render = __esm({
47250
47311
 
47251
47312
  // src/cli.ts
47252
47313
  init_src5();
47253
- import { closeSync as closeSync2, existsSync as existsSync25, openSync as openSync2, readFileSync as readFileSync23, renameSync as renameSync3, statSync as statSync6 } from "node:fs";
47254
- import { join as join27 } from "node:path";
47314
+ import { closeSync as closeSync2, existsSync as existsSync25, openSync as openSync2, readFileSync as readFileSync24, renameSync as renameSync4, statSync as statSync6 } from "node:fs";
47315
+ import { join as join28 } from "node:path";
47255
47316
  import { spawn as spawn3 } from "node:child_process";
47256
47317
 
47257
47318
  // src/daemon.ts
47258
- import { existsSync as existsSync20, writeFileSync as writeFileSync16 } from "node:fs";
47319
+ import { existsSync as existsSync20, writeFileSync as writeFileSync17 } from "node:fs";
47259
47320
 
47260
47321
  // ../../node_modules/.pnpm/@hono+node-server@1.19.11_hono@4.12.8/node_modules/@hono/node-server/dist/index.mjs
47261
47322
  import { createServer as createServerHTTP } from "http";
@@ -47835,8 +47896,8 @@ init_config();
47835
47896
 
47836
47897
  // src/engine.ts
47837
47898
  import { execFileSync as execFileSync3 } from "node:child_process";
47838
- import { existsSync as existsSync19, statSync as statSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync9, renameSync as renameSync2, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "node:fs";
47839
- import { join as join23, relative as relative6, sep as sep4 } from "node:path";
47899
+ import { existsSync as existsSync19, statSync as statSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync9, renameSync as renameSync3, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "node:fs";
47900
+ import { join as join24, relative as relative6, sep as sep4 } from "node:path";
47840
47901
 
47841
47902
  // ../../node_modules/.pnpm/chokidar@4.0.3/node_modules/chokidar/esm/index.js
47842
47903
  import { stat as statcb } from "fs";
@@ -48567,9 +48628,9 @@ var NodeFsHandler = class {
48567
48628
  if (this.fsw.closed) {
48568
48629
  return;
48569
48630
  }
48570
- const dirname10 = sysPath.dirname(file2);
48631
+ const dirname11 = sysPath.dirname(file2);
48571
48632
  const basename5 = sysPath.basename(file2);
48572
- const parent = this.fsw._getWatchedDir(dirname10);
48633
+ const parent = this.fsw._getWatchedDir(dirname11);
48573
48634
  let prevStats = stats;
48574
48635
  if (parent.has(basename5))
48575
48636
  return;
@@ -48596,7 +48657,7 @@ var NodeFsHandler = class {
48596
48657
  prevStats = newStats2;
48597
48658
  }
48598
48659
  } catch (error48) {
48599
- this.fsw._remove(dirname10, basename5);
48660
+ this.fsw._remove(dirname11, basename5);
48600
48661
  }
48601
48662
  } else if (parent.has(basename5)) {
48602
48663
  const at = newStats.atimeMs;
@@ -51997,109 +52058,6 @@ function createToolRunState() {
51997
52058
  // src/engine.ts
51998
52059
  init_paths();
51999
52060
 
52000
- // src/causal.ts
52001
- var SUPPRESSORS = [
52002
- { kind: "ts-nocheck", re: /@ts-nocheck\b/g },
52003
- { kind: "ts-ignore", re: /@ts-(?:ignore|expect-error)\b/g },
52004
- { kind: "eslint-disable", re: /eslint-disable(?:-next-line|-line)?\b/g },
52005
- { kind: "py-ignore", re: /#\s*(?:noqa|type:\s*ignore|pyright:\s*ignore|mypy:\s*ignore|nosec|pragma:\s*no\s*cover)\b/gi },
52006
- { kind: "test-skip", re: /\bx(?:it|describe|test)\s*\(|\.(?:skip|todo)\s*\(|@(?:pytest\.mark\.skip|unittest\.skip)|\bxfail\b|\bt\.Skip\s*\(/g },
52007
- { kind: "lint-allow", re: /\/\/\s*nolint\b|#\[allow\(|istanbul\s+ignore|c8\s+ignore/g },
52008
- { kind: "config-loosen", re: /"strict"\s*:\s*false|"skipLibCheck"\s*:\s*true|ignore_errors\s*=\s*True/gi }
52009
- ];
52010
- function detectSuppression(oldStr, newStr) {
52011
- if (!newStr) return null;
52012
- const old = oldStr ?? "";
52013
- for (const { kind, re } of SUPPRESSORS) {
52014
- const inNew = (newStr.match(re) ?? []).length;
52015
- const inOld = (old.match(re) ?? []).length;
52016
- if (inNew > inOld) return kind;
52017
- }
52018
- return null;
52019
- }
52020
- var DECL_RE = /\b(?:function|class|const|let|var|def|interface|type|enum)\s+([A-Za-z_$][\w$]*)/g;
52021
- function deriveIntentHints(tool, oldStr, newStr) {
52022
- const hints = /* @__PURE__ */ new Set();
52023
- if (tool === "Write" && (oldStr == null || oldStr === "")) hints.add("add");
52024
- if (detectSuppression(oldStr, newStr)) hints.add("suppress");
52025
- if (oldStr != null && newStr != null) {
52026
- const oldNames = new Set([...oldStr.matchAll(DECL_RE)].map((m) => m[1]));
52027
- const newNames = new Set([...newStr.matchAll(DECL_RE)].map((m) => m[1]));
52028
- const removed = [...oldNames].filter((n) => !newNames.has(n));
52029
- const added = [...newNames].filter((n) => !oldNames.has(n));
52030
- if (removed.length > 0 && added.length > 0) hints.add("rename");
52031
- else if (added.length > 0) hints.add("add");
52032
- else if (removed.length > 0) hints.add("delete");
52033
- }
52034
- return [...hints];
52035
- }
52036
- function deriveDeclaredRenames(oldStr, newStr) {
52037
- if (oldStr == null || newStr == null) return [];
52038
- const oldNames = [...oldStr.matchAll(DECL_RE)].map((m) => m[1]);
52039
- const newNames = [...newStr.matchAll(DECL_RE)].map((m) => m[1]);
52040
- const oldSet = new Set(oldNames);
52041
- const newSet = new Set(newNames);
52042
- const removed = [...new Set(oldNames.filter((n) => !newSet.has(n)))];
52043
- const added = [...new Set(newNames.filter((n) => !oldSet.has(n)))];
52044
- if (removed.length === 1 && added.length === 1) {
52045
- return [{ from: removed[0], to: added[0] }];
52046
- }
52047
- return [];
52048
- }
52049
- var CausalBuffer = class {
52050
- constructor(windowMs = 5e3, maxAgeMs = 3e4) {
52051
- this.windowMs = windowMs;
52052
- this.maxAgeMs = maxAgeMs;
52053
- }
52054
- events = [];
52055
- record(e) {
52056
- this.events.push(e);
52057
- this.prune(e.ts);
52058
- }
52059
- prune(now) {
52060
- const cutoff = now - this.maxAgeMs;
52061
- if (this.events.length > 0 && this.events[0].ts < cutoff) {
52062
- this.events = this.events.filter((e) => e.ts >= cutoff);
52063
- }
52064
- }
52065
- /**
52066
- * Correlate a reindex flush (the changed relPaths at instant `t`) to a causal
52067
- * context, if any buffered tool event touched one of those paths within the
52068
- * window. The most recent match wins for session/tool; hints are unioned.
52069
- */
52070
- correlate(relPaths, t) {
52071
- const set2 = new Set(relPaths);
52072
- const matches = this.events.filter(
52073
- (e) => set2.has(e.relPath) && Math.abs(e.ts - t) <= this.windowMs
52074
- );
52075
- if (matches.length === 0) return null;
52076
- matches.sort((a, b) => b.ts - a.ts);
52077
- const top = matches[0];
52078
- const hints = /* @__PURE__ */ new Set();
52079
- const renames = [];
52080
- const seen = /* @__PURE__ */ new Set();
52081
- for (const m of matches) {
52082
- for (const h of m.intentHints) hints.add(h);
52083
- for (const r of m.declaredRenames) {
52084
- const key = `${r.from}->${r.to}`;
52085
- if (!seen.has(key)) {
52086
- seen.add(key);
52087
- renames.push(r);
52088
- }
52089
- }
52090
- }
52091
- return {
52092
- sessionId: top.sessionId,
52093
- tool: top.tool,
52094
- intentHints: [...hints],
52095
- declaredRenames: renames
52096
- };
52097
- }
52098
- size() {
52099
- return this.events.length;
52100
- }
52101
- };
52102
-
52103
52061
  // src/profile.ts
52104
52062
  init_src2();
52105
52063
  init_paths();
@@ -52163,6 +52121,10 @@ function detectRepoLocator(root, remote) {
52163
52121
  function workspaceId(root) {
52164
52122
  return "wp_" + createHash12("sha256").update(root).digest("hex").slice(0, 12);
52165
52123
  }
52124
+ function sessionOriginKey(sessionId) {
52125
+ if (!sessionId) return void 0;
52126
+ return "ws_" + createHash12("sha256").update(sessionId).digest("hex").slice(0, 12);
52127
+ }
52166
52128
  function refreshRepoLocator(root, profile) {
52167
52129
  const detected = detectRepoLocator(root, profile.repoRemote);
52168
52130
  if (!detected || detected === profile.repoLocator) return false;
@@ -52242,6 +52204,160 @@ function autodetectProfile(root) {
52242
52204
  return p;
52243
52205
  }
52244
52206
 
52207
+ // src/witness-queue.ts
52208
+ import { readFileSync as readFileSync18, renameSync as renameSync2, writeFileSync as writeFileSync15 } from "node:fs";
52209
+ import { dirname as dirname9, join as join23 } from "node:path";
52210
+ var WITNESS_QUEUE_CAP = 500;
52211
+ var WITNESS_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
52212
+ var WITNESS_MAX_ATTEMPTS = 5;
52213
+ function witnessQueuePath(workspaceConfigDir) {
52214
+ return join23(workspaceConfigDir, "witness-queue.json");
52215
+ }
52216
+ function loadWitnessQueue(path2) {
52217
+ try {
52218
+ const raw2 = JSON.parse(readFileSync18(path2, "utf8"));
52219
+ if (!Array.isArray(raw2)) return [];
52220
+ return raw2.filter(
52221
+ (w) => !!w && typeof w === "object" && typeof w.nodeId === "string" && typeof w.witnessKey === "string"
52222
+ );
52223
+ } catch {
52224
+ return [];
52225
+ }
52226
+ }
52227
+ function saveWitnessQueue(path2, queue) {
52228
+ try {
52229
+ const tmp = join23(dirname9(path2), `.${Date.now()}.witness-queue.tmp`);
52230
+ writeFileSync15(tmp, JSON.stringify(queue), "utf8");
52231
+ renameSync2(tmp, path2);
52232
+ } catch {
52233
+ }
52234
+ }
52235
+ function pruneWitnessQueue(queue, now) {
52236
+ const live = queue.filter(
52237
+ (w) => now - w.ts < WITNESS_TTL_MS && w.attempts < WITNESS_MAX_ATTEMPTS
52238
+ );
52239
+ return live.length > WITNESS_QUEUE_CAP ? live.slice(live.length - WITNESS_QUEUE_CAP) : live;
52240
+ }
52241
+ function enqueueWitnesses(queue, fresh, now) {
52242
+ const byKey = new Map(queue.map((w) => [`${w.channel}:${w.witnessKey}`, w]));
52243
+ for (const f of fresh) {
52244
+ const k = `${f.channel}:${f.witnessKey}`;
52245
+ const existing = byKey.get(k);
52246
+ if (existing) existing.attempts += 1;
52247
+ else byKey.set(k, { ...f, ts: now, attempts: 1 });
52248
+ }
52249
+ return pruneWitnessQueue([...byKey.values()], now);
52250
+ }
52251
+ function pendingFor(queue, channel) {
52252
+ return queue.filter((w) => w.channel === channel);
52253
+ }
52254
+ function retireWitnesses(queue, channel, settledKeys) {
52255
+ return queue.filter((w) => !(w.channel === channel && settledKeys.has(w.witnessKey)));
52256
+ }
52257
+
52258
+ // src/causal.ts
52259
+ var SUPPRESSORS = [
52260
+ { kind: "ts-nocheck", re: /@ts-nocheck\b/g },
52261
+ { kind: "ts-ignore", re: /@ts-(?:ignore|expect-error)\b/g },
52262
+ { kind: "eslint-disable", re: /eslint-disable(?:-next-line|-line)?\b/g },
52263
+ { kind: "py-ignore", re: /#\s*(?:noqa|type:\s*ignore|pyright:\s*ignore|mypy:\s*ignore|nosec|pragma:\s*no\s*cover)\b/gi },
52264
+ { kind: "test-skip", re: /\bx(?:it|describe|test)\s*\(|\.(?:skip|todo)\s*\(|@(?:pytest\.mark\.skip|unittest\.skip)|\bxfail\b|\bt\.Skip\s*\(/g },
52265
+ { kind: "lint-allow", re: /\/\/\s*nolint\b|#\[allow\(|istanbul\s+ignore|c8\s+ignore/g },
52266
+ { kind: "config-loosen", re: /"strict"\s*:\s*false|"skipLibCheck"\s*:\s*true|ignore_errors\s*=\s*True/gi }
52267
+ ];
52268
+ function detectSuppression(oldStr, newStr) {
52269
+ if (!newStr) return null;
52270
+ const old = oldStr ?? "";
52271
+ for (const { kind, re } of SUPPRESSORS) {
52272
+ const inNew = (newStr.match(re) ?? []).length;
52273
+ const inOld = (old.match(re) ?? []).length;
52274
+ if (inNew > inOld) return kind;
52275
+ }
52276
+ return null;
52277
+ }
52278
+ var DECL_RE = /\b(?:function|class|const|let|var|def|interface|type|enum)\s+([A-Za-z_$][\w$]*)/g;
52279
+ function deriveIntentHints(tool, oldStr, newStr) {
52280
+ const hints = /* @__PURE__ */ new Set();
52281
+ if (tool === "Write" && (oldStr == null || oldStr === "")) hints.add("add");
52282
+ if (detectSuppression(oldStr, newStr)) hints.add("suppress");
52283
+ if (oldStr != null && newStr != null) {
52284
+ const oldNames = new Set([...oldStr.matchAll(DECL_RE)].map((m) => m[1]));
52285
+ const newNames = new Set([...newStr.matchAll(DECL_RE)].map((m) => m[1]));
52286
+ const removed = [...oldNames].filter((n) => !newNames.has(n));
52287
+ const added = [...newNames].filter((n) => !oldNames.has(n));
52288
+ if (removed.length > 0 && added.length > 0) hints.add("rename");
52289
+ else if (added.length > 0) hints.add("add");
52290
+ else if (removed.length > 0) hints.add("delete");
52291
+ }
52292
+ return [...hints];
52293
+ }
52294
+ function deriveDeclaredRenames(oldStr, newStr) {
52295
+ if (oldStr == null || newStr == null) return [];
52296
+ const oldNames = [...oldStr.matchAll(DECL_RE)].map((m) => m[1]);
52297
+ const newNames = [...newStr.matchAll(DECL_RE)].map((m) => m[1]);
52298
+ const oldSet = new Set(oldNames);
52299
+ const newSet = new Set(newNames);
52300
+ const removed = [...new Set(oldNames.filter((n) => !newSet.has(n)))];
52301
+ const added = [...new Set(newNames.filter((n) => !oldSet.has(n)))];
52302
+ if (removed.length === 1 && added.length === 1) {
52303
+ return [{ from: removed[0], to: added[0] }];
52304
+ }
52305
+ return [];
52306
+ }
52307
+ var CausalBuffer = class {
52308
+ constructor(windowMs = 5e3, maxAgeMs = 3e4) {
52309
+ this.windowMs = windowMs;
52310
+ this.maxAgeMs = maxAgeMs;
52311
+ }
52312
+ events = [];
52313
+ record(e) {
52314
+ this.events.push(e);
52315
+ this.prune(e.ts);
52316
+ }
52317
+ prune(now) {
52318
+ const cutoff = now - this.maxAgeMs;
52319
+ if (this.events.length > 0 && this.events[0].ts < cutoff) {
52320
+ this.events = this.events.filter((e) => e.ts >= cutoff);
52321
+ }
52322
+ }
52323
+ /**
52324
+ * Correlate a reindex flush (the changed relPaths at instant `t`) to a causal
52325
+ * context, if any buffered tool event touched one of those paths within the
52326
+ * window. The most recent match wins for session/tool; hints are unioned.
52327
+ */
52328
+ correlate(relPaths, t) {
52329
+ const set2 = new Set(relPaths);
52330
+ const matches = this.events.filter(
52331
+ (e) => set2.has(e.relPath) && Math.abs(e.ts - t) <= this.windowMs
52332
+ );
52333
+ if (matches.length === 0) return null;
52334
+ matches.sort((a, b) => b.ts - a.ts);
52335
+ const top = matches[0];
52336
+ const hints = /* @__PURE__ */ new Set();
52337
+ const renames = [];
52338
+ const seen = /* @__PURE__ */ new Set();
52339
+ for (const m of matches) {
52340
+ for (const h of m.intentHints) hints.add(h);
52341
+ for (const r of m.declaredRenames) {
52342
+ const key = `${r.from}->${r.to}`;
52343
+ if (!seen.has(key)) {
52344
+ seen.add(key);
52345
+ renames.push(r);
52346
+ }
52347
+ }
52348
+ }
52349
+ return {
52350
+ sessionId: top.sessionId,
52351
+ tool: top.tool,
52352
+ intentHints: [...hints],
52353
+ declaredRenames: renames
52354
+ };
52355
+ }
52356
+ size() {
52357
+ return this.events.length;
52358
+ }
52359
+ };
52360
+
52245
52361
  // src/engine.ts
52246
52362
  init_config();
52247
52363
  init_cloud_auth();
@@ -52391,7 +52507,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
52391
52507
  }
52392
52508
 
52393
52509
  // src/engine.ts
52394
- var DAEMON_VERSION = true ? "2.0.2-dev.226" : "2.0.0-alpha.0";
52510
+ var DAEMON_VERSION = true ? "2.0.2-dev.245" : "2.0.0-alpha.0";
52395
52511
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
52396
52512
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
52397
52513
  var GIT_OP_MUTE_MS = 4e3;
@@ -52402,7 +52518,7 @@ function appendIdentityAudit(path2, record2, line) {
52402
52518
  if (!record2.accepted && record2.score <= 0) return;
52403
52519
  try {
52404
52520
  if (existsSync19(path2) && statSync5(path2).size >= IDENTITY_AUDIT_MAX_BYTES) {
52405
- renameSync2(path2, `${path2}.1`);
52521
+ renameSync3(path2, `${path2}.1`);
52406
52522
  }
52407
52523
  appendFileSync2(path2, line);
52408
52524
  } catch {
@@ -52411,7 +52527,7 @@ function appendIdentityAudit(path2, record2, line) {
52411
52527
  var yieldToLoop = () => new Promise((r) => setImmediate(r));
52412
52528
  function loadTurnCursors(path2) {
52413
52529
  try {
52414
- const raw2 = JSON.parse(readFileSync18(path2, "utf8"));
52530
+ const raw2 = JSON.parse(readFileSync19(path2, "utf8"));
52415
52531
  return new Map(
52416
52532
  Object.entries(raw2).map(([k, v]) => [k, typeof v === "string" ? v : String(v?.uuid ?? "")])
52417
52533
  );
@@ -52421,7 +52537,7 @@ function loadTurnCursors(path2) {
52421
52537
  }
52422
52538
  function loadTurnOffsets(path2) {
52423
52539
  try {
52424
- const raw2 = JSON.parse(readFileSync18(path2, "utf8"));
52540
+ const raw2 = JSON.parse(readFileSync19(path2, "utf8"));
52425
52541
  const out2 = /* @__PURE__ */ new Map();
52426
52542
  for (const [k, v] of Object.entries(raw2)) {
52427
52543
  const off = typeof v === "object" && v !== null ? v.offset : void 0;
@@ -52437,7 +52553,7 @@ function saveTurnCursors(path2, cursors, offsets) {
52437
52553
  const merged = {};
52438
52554
  for (const [k, uuid3] of cursors) merged[k] = { uuid: uuid3, offset: offsets.get(k) ?? 0 };
52439
52555
  for (const [k, offset] of offsets) if (!merged[k]) merged[k] = { uuid: "", offset };
52440
- writeFileSync15(path2, JSON.stringify(merged), "utf8");
52556
+ writeFileSync16(path2, JSON.stringify(merged), "utf8");
52441
52557
  } catch {
52442
52558
  }
52443
52559
  }
@@ -52459,7 +52575,7 @@ function gitSourceWatchTargets(root) {
52459
52575
  ["-C", root, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory", "-z"],
52460
52576
  { encoding: "utf8", maxBuffer: 256 * 1024 * 1024, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }
52461
52577
  );
52462
- ignoredDirs = igOut.split("\0").filter(Boolean).map((d) => d.replace(/\/+$/, "")).filter((d) => !IGNORED_PATH.test(join23(root, d) + sep4));
52578
+ ignoredDirs = igOut.split("\0").filter(Boolean).map((d) => d.replace(/\/+$/, "")).filter((d) => !IGNORED_PATH.test(join24(root, d) + sep4));
52463
52579
  } catch {
52464
52580
  }
52465
52581
  const hasIgnoredChild = (dir) => ignoredDirs.some((ig) => ig.startsWith(dir + "/"));
@@ -52471,19 +52587,19 @@ function gitSourceWatchTargets(root) {
52471
52587
  if (!f.startsWith(prefix)) continue;
52472
52588
  const rest2 = f.slice(prefix.length);
52473
52589
  if (rest2.includes("/")) children.add(dir === "" ? rest2.slice(0, rest2.indexOf("/")) : dir + "/" + rest2.slice(0, rest2.indexOf("/")));
52474
- else targets.add(join23(root, f));
52590
+ else targets.add(join24(root, f));
52475
52591
  }
52476
52592
  for (const c of children) {
52477
- if (IGNORED_PATH.test(join23(root, c) + sep4)) continue;
52593
+ if (IGNORED_PATH.test(join24(root, c) + sep4)) continue;
52478
52594
  if (hasIgnoredChild(c)) addUnder(c);
52479
- else targets.add(join23(root, c));
52595
+ else targets.add(join24(root, c));
52480
52596
  }
52481
52597
  };
52482
52598
  addUnder("");
52483
52599
  if (targets.size > 0) return [...targets];
52484
52600
  } catch {
52485
52601
  }
52486
- return readdirSync9(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_PATH.test(join23(root, String(e.name)) + sep4)).map((e) => join23(root, String(e.name)));
52602
+ return readdirSync9(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_PATH.test(join24(root, String(e.name)) + sep4)).map((e) => join24(root, String(e.name)));
52487
52603
  }
52488
52604
  function createWorkspaceEngine(opts) {
52489
52605
  const paths = workspacePaths(opts.workspaceRoot);
@@ -52498,6 +52614,10 @@ function createWorkspaceEngine(opts) {
52498
52614
  refreshRepoLocator(opts.workspaceRoot, profile);
52499
52615
  }
52500
52616
  const store = openGraphStore({ path: paths.castalia });
52617
+ let witnessQueue = pruneWitnessQueue(
52618
+ loadWitnessQueue(witnessQueuePath(paths.configDir)),
52619
+ Date.now()
52620
+ );
52501
52621
  const log = openEventLog({ path: paths.eventLog });
52502
52622
  const bundled = import.meta.url.endsWith(".mjs");
52503
52623
  const passWorker = bundled && paths.castalia !== ":memory:" ? new PassWorker({
@@ -52637,7 +52757,7 @@ function createWorkspaceEngine(opts) {
52637
52757
  const srcPaths = diff.changedPaths.filter((p) => /\.(ts|tsx|js|jsx|mjs|cjs|py)$/i.test(p));
52638
52758
  let episodeId2;
52639
52759
  if (srcPaths.length > 0) {
52640
- const abs = srcPaths.map((p) => join23(opts.workspaceRoot, p));
52760
+ const abs = srcPaths.map((p) => join24(opts.workspaceRoot, p));
52641
52761
  try {
52642
52762
  const r = await runReindexPass(
52643
52763
  `git-reindex:${profile.name} (${abs.length} files)`,
@@ -52673,7 +52793,7 @@ function createWorkspaceEngine(opts) {
52673
52793
  `[errata] git: ${ev.kind} ${ev.newSha.slice(0, 7)} by ${meta3.authorName} \u2014 ${diff.changedPaths.length} file(s), ${diff.renames.length} rename(s)`
52674
52794
  );
52675
52795
  };
52676
- const gitDir = join23(opts.workspaceRoot, ".git");
52796
+ const gitDir = join24(opts.workspaceRoot, ".git");
52677
52797
  if (existsSync19(gitDir)) {
52678
52798
  stopGit = startGitSensor(gitDir, (ev) => {
52679
52799
  void handleGitEvent(ev).catch((err2) => {
@@ -52744,10 +52864,10 @@ function createWorkspaceEngine(opts) {
52744
52864
  });
52745
52865
  doneRender?.();
52746
52866
  writeContextFile(opts.workspaceRoot, body2);
52747
- const target = join23(opts.workspaceRoot, "AGENTS.md");
52867
+ const target = join24(opts.workspaceRoot, "AGENTS.md");
52748
52868
  writeManagedBlock(target, { body: AGENTS_POINTER_BODY, stable: true, force: true });
52749
52869
  if (elicit) {
52750
- writePrimingHandles(join23(paths.configDir, "priming-handles.json"), [
52870
+ writePrimingHandles(join24(paths.configDir, "priming-handles.json"), [
52751
52871
  ...snapshot.recentProblems.map((r) => r.node),
52752
52872
  // Resolved-band handles: the ✓ problem AND its Solution are citable
52753
52873
  // (a fix tag on an already-resolved problem no-ops idempotently; the
@@ -52952,7 +53072,7 @@ function createWorkspaceEngine(opts) {
52952
53072
  resultSummary: { exitCode: e.exitCode ?? 1, errorTokens: e.errorTokens }
52953
53073
  });
52954
53074
  };
52955
- const turnCursorPath = join23(paths.configDir, "turn-cursors.json");
53075
+ const turnCursorPath = join24(paths.configDir, "turn-cursors.json");
52956
53076
  const lastTurnUuid = loadTurnCursors(turnCursorPath);
52957
53077
  const turnOffset = loadTurnOffsets(turnCursorPath);
52958
53078
  const sessionLastProblem = /* @__PURE__ */ new Map();
@@ -52976,7 +53096,7 @@ function createWorkspaceEngine(opts) {
52976
53096
  const t = Date.now();
52977
53097
  let processedTurns = 0;
52978
53098
  const elicit = isEdgeElicitationEnabled();
52979
- const handleMap = elicit ? readPrimingHandles(join23(paths.configDir, "priming-handles.json")) : {};
53099
+ const handleMap = elicit ? readPrimingHandles(join24(paths.configDir, "priming-handles.json")) : {};
52980
53100
  const wsRoot = (opts.workspaceRoot ?? "").replace(/\\/g, "/");
52981
53101
  const toRel = (abs) => {
52982
53102
  const p = abs.replace(/\\/g, "/");
@@ -53318,18 +53438,83 @@ function createWorkspaceEngine(opts) {
53318
53438
  mintCiteEdge(solId, targetId, sl.relation, 0.4, { solutionStructure: true, hypothesis: true });
53319
53439
  }
53320
53440
  }
53321
- if (plan.refutes.length > 0 && typeof cloud.reportContradictions === "function") {
53322
- void cloud.reportContradictions({ daemonVersion: DAEMON_VERSION, projectId: profile.id, items: stampWitnessOrigin(plan.refutes, profile.id) }).then((r) => {
53323
- if (r?.recorded) console.log(`[errata] refute: ${r.recorded} contradiction(s) recorded`);
53324
- }).catch(
53325
- (err2) => console.warn("[errata] refute transport failed (continuing):", err2 instanceof Error ? err2.message : err2)
53441
+ const citingSession = sessionOriginKey(sessionId);
53442
+ const mintedHere = (nodeId) => store.getNode(nodeId)?.attrs["sources"]?.[0] === sessionId;
53443
+ const sendWitnesses = async (channel, fresh, send) => {
53444
+ const queued = pendingFor(witnessQueue, channel);
53445
+ if (fresh.length === 0 && queued.length === 0) return;
53446
+ const groups = /* @__PURE__ */ new Map();
53447
+ for (const q of queued) {
53448
+ const g = groups.get(q.sessionKey) ?? [];
53449
+ g.push({ nodeId: q.nodeId, witnessKey: q.witnessKey });
53450
+ groups.set(q.sessionKey, g);
53451
+ }
53452
+ const freshGroup = groups.get(citingSession) ?? [];
53453
+ for (const f of fresh) {
53454
+ if (freshGroup.some((x) => x.witnessKey === f.witnessKey)) continue;
53455
+ freshGroup.push(f);
53456
+ }
53457
+ groups.set(citingSession, freshGroup);
53458
+ const settled = /* @__PURE__ */ new Set();
53459
+ const stillUnmatched = [];
53460
+ let recorded = 0, unmatched = 0, duplicate = 0, selfGated = 0;
53461
+ for (const [session, items2] of groups) {
53462
+ if (items2.length === 0) continue;
53463
+ try {
53464
+ const raw2 = await send(items2, session);
53465
+ const d = {
53466
+ recorded: raw2?.recorded ?? 0,
53467
+ unmatched: raw2?.unmatched ?? 0,
53468
+ duplicate: raw2?.duplicate ?? 0,
53469
+ selfGated: raw2?.selfGated ?? 0,
53470
+ unmatchedIds: raw2?.unmatchedIds ?? []
53471
+ };
53472
+ recorded += d.recorded;
53473
+ unmatched += d.unmatched;
53474
+ duplicate += d.duplicate;
53475
+ selfGated += d.selfGated;
53476
+ const missing = new Set(d.unmatchedIds);
53477
+ for (const it of items2) {
53478
+ if (missing.has(it.nodeId)) stillUnmatched.push({ channel, ...it, sessionKey: session });
53479
+ else settled.add(it.witnessKey);
53480
+ }
53481
+ } catch (err2) {
53482
+ console.warn(`[errata] ${channel} transport failed (witnesses kept for retry):`, err2 instanceof Error ? err2.message : err2);
53483
+ for (const it of items2) stillUnmatched.push({ channel, ...it, sessionKey: session });
53484
+ }
53485
+ }
53486
+ witnessQueue = retireWitnesses(witnessQueue, channel, settled);
53487
+ witnessQueue = enqueueWitnesses(witnessQueue, stillUnmatched, Date.now());
53488
+ saveWitnessQueue(witnessQueuePath(paths.configDir), witnessQueue);
53489
+ console.log(
53490
+ `[errata] ${channel}: ${formatDisposition({ recorded, unmatched, duplicate, selfGated })}` + (queued.length > 0 ? ` (incl. ${queued.length} replayed)` : "")
53491
+ );
53492
+ };
53493
+ if (typeof cloud.reportContradictions === "function") {
53494
+ await sendWitnesses(
53495
+ "contradict",
53496
+ plan.refutes,
53497
+ (items2, session) => cloud.reportContradictions({
53498
+ daemonVersion: DAEMON_VERSION,
53499
+ projectId: profile.id,
53500
+ ...session ? { sessionId: session } : {},
53501
+ items: stampWitnessOrigin(items2, profile.id)
53502
+ })
53326
53503
  );
53327
53504
  }
53328
- if (plan.corroborations.length > 0 && typeof cloud.reportCorroborations === "function") {
53329
- void cloud.reportCorroborations({ daemonVersion: DAEMON_VERSION, projectId: profile.id, items: stampWitnessOrigin(plan.corroborations, profile.id) }).then((r) => {
53330
- if (r?.recorded) console.log(`[errata] corroborate: ${r.recorded} corroboration(s) recorded`);
53331
- }).catch(
53332
- (err2) => console.warn("[errata] corroboration transport failed (continuing):", err2 instanceof Error ? err2.message : err2)
53505
+ if (typeof cloud.reportCorroborations === "function") {
53506
+ const emit = plan.corroborations.filter((c) => !mintedHere(c.nodeId));
53507
+ const skipped = plan.corroborations.length - emit.length;
53508
+ if (skipped > 0) console.log(`[errata] corroborate: ${skipped} skipped (this session authored the node)`);
53509
+ await sendWitnesses(
53510
+ "corroborate",
53511
+ emit,
53512
+ (items2, session) => cloud.reportCorroborations({
53513
+ daemonVersion: DAEMON_VERSION,
53514
+ projectId: profile.id,
53515
+ ...session ? { sessionId: session } : {},
53516
+ items: stampWitnessOrigin(items2, profile.id)
53517
+ })
53333
53518
  );
53334
53519
  }
53335
53520
  } catch (err2) {
@@ -53522,7 +53707,7 @@ function createWorkspaceEngine(opts) {
53522
53707
  try {
53523
53708
  const inputs = emitInputsFromManifest(paths.configDir, paths.skillsManifest);
53524
53709
  emitAndProjectSkills(opts.workspaceRoot, inputs);
53525
- writePrimingHandles(join23(paths.configDir, "priming-handles.json"), skillHandleNodes(inputs));
53710
+ writePrimingHandles(join24(paths.configDir, "priming-handles.json"), skillHandleNodes(inputs));
53526
53711
  } catch (err2) {
53527
53712
  console.warn("[skills] agent-skills projection failed (non-fatal):", err2 instanceof Error ? err2.message : err2);
53528
53713
  }
@@ -53796,7 +53981,7 @@ async function startDaemon(opts) {
53796
53981
  reviewUrl: () => webUiUrl + "/review"
53797
53982
  });
53798
53983
  const writeLockFile = (url2) => {
53799
- writeFileSync16(
53984
+ writeFileSync17(
53800
53985
  engine.paths.daemonLock,
53801
53986
  JSON.stringify({ pid: process.pid, webUiUrl: url2, startedAt: Date.now() }),
53802
53987
  "utf8"
@@ -53856,16 +54041,16 @@ async function listenServer(fetchFn, port) {
53856
54041
 
53857
54042
  // src/registry.ts
53858
54043
  init_paths();
53859
- import { existsSync as existsSync21, readFileSync as readFileSync19, writeFileSync as writeFileSync17 } from "node:fs";
53860
- import { join as join24 } from "node:path";
54044
+ import { existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync18 } from "node:fs";
54045
+ import { join as join25 } from "node:path";
53861
54046
  function registryPath() {
53862
- return process.env["ERRATA_REGISTRY_PATH"] ?? join24(globalDir(), "workspaces.json");
54047
+ return process.env["ERRATA_REGISTRY_PATH"] ?? join25(globalDir(), "workspaces.json");
53863
54048
  }
53864
54049
  function read() {
53865
54050
  const p = registryPath();
53866
54051
  if (!existsSync21(p)) return { version: 1, workspaces: {} };
53867
54052
  try {
53868
- const parsed = JSON.parse(readFileSync19(p, "utf8"));
54053
+ const parsed = JSON.parse(readFileSync20(p, "utf8"));
53869
54054
  return { version: 1, workspaces: parsed.workspaces ?? {} };
53870
54055
  } catch {
53871
54056
  return { version: 1, workspaces: {} };
@@ -53873,7 +54058,7 @@ function read() {
53873
54058
  }
53874
54059
  function write(reg) {
53875
54060
  ensureDir(globalDir());
53876
- writeFileSync17(registryPath(), JSON.stringify(reg, null, 2), "utf8");
54061
+ writeFileSync18(registryPath(), JSON.stringify(reg, null, 2), "utf8");
53877
54062
  }
53878
54063
  function registerWorkspace(profile, root, now = Date.now()) {
53879
54064
  const reg = read();
@@ -53905,7 +54090,7 @@ function workspaceStatus(entry) {
53905
54090
  let webUiUrl = null;
53906
54091
  if (existsSync21(lockPath)) {
53907
54092
  try {
53908
- const lock = JSON.parse(readFileSync19(lockPath, "utf8"));
54093
+ const lock = JSON.parse(readFileSync20(lockPath, "utf8"));
53909
54094
  if (lock.pid && lock.webUiUrl && pidAlive(lock.pid)) {
53910
54095
  running = true;
53911
54096
  webUiUrl = lock.webUiUrl;
@@ -53933,7 +54118,7 @@ function pidAlive(pid) {
53933
54118
  // src/multi.ts
53934
54119
  init_dist();
53935
54120
  init_src4();
53936
- import { readFileSync as readFileSync22, unlinkSync as unlinkSync3, writeFileSync as writeFileSync18 } from "node:fs";
54121
+ import { readFileSync as readFileSync23, unlinkSync as unlinkSync3, writeFileSync as writeFileSync19 } from "node:fs";
53937
54122
 
53938
54123
  // src/principle-sync.ts
53939
54124
  init_src4();
@@ -53961,8 +54146,8 @@ init_reconcile();
53961
54146
 
53962
54147
  // src/lockfile-auto.ts
53963
54148
  init_src();
53964
- import { existsSync as existsSync22, readFileSync as readFileSync20 } from "node:fs";
53965
- import { join as join25 } from "node:path";
54149
+ import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
54150
+ import { join as join26 } from "node:path";
53966
54151
 
53967
54152
  // src/package-index.ts
53968
54153
  init_src();
@@ -54111,11 +54296,11 @@ function runLockfilePass(opts) {
54111
54296
  { file: "package-lock.json", parse: parsePackageLockJson }
54112
54297
  ];
54113
54298
  for (const c of candidates) {
54114
- const p = join25(opts.root, c.file);
54299
+ const p = join26(opts.root, c.file);
54115
54300
  if (!existsSync22(p)) continue;
54116
54301
  let sbom;
54117
54302
  try {
54118
- sbom = c.parse(readFileSync20(p, "utf8"));
54303
+ sbom = c.parse(readFileSync21(p, "utf8"));
54119
54304
  } catch {
54120
54305
  continue;
54121
54306
  }
@@ -54225,10 +54410,15 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
54225
54410
  const level = opts.level ?? 1;
54226
54411
  const noveltyOpts = opts.dedupCosine != null ? { dedupCosine: opts.dedupCosine } : {};
54227
54412
  const ignored = (text) => ignorePatterns.length > 0 && ignorePatterns.some((p) => text.toLowerCase().includes(p));
54413
+ const originSessionOf = (n) => {
54414
+ const key = sessionOriginKey(n.attrs["sources"]?.[0]);
54415
+ return key ? { originSession: key } : {};
54416
+ };
54228
54417
  const shareable = (n) => ({
54229
54418
  ...n,
54230
54419
  description: generalize(n.description, { level }).text,
54231
54420
  embedding: [],
54421
+ ...originSessionOf(n),
54232
54422
  attrs: {
54233
54423
  scope: stripCodebaseScope(n.attrs["scope"]),
54234
54424
  ...n.attrs["kind"] ? { kind: n.attrs["kind"] } : {},
@@ -54558,7 +54748,7 @@ var ConsolidateWorker = class {
54558
54748
  init_paths();
54559
54749
 
54560
54750
  // src/lock.ts
54561
- import { existsSync as existsSync23, readFileSync as readFileSync21 } from "node:fs";
54751
+ import { existsSync as existsSync23, readFileSync as readFileSync22 } from "node:fs";
54562
54752
  function isProcessAlive(pid) {
54563
54753
  if (!pid || pid <= 0) return false;
54564
54754
  try {
@@ -54571,7 +54761,7 @@ function isProcessAlive(pid) {
54571
54761
  function readDaemonLock(lockPath) {
54572
54762
  if (!existsSync23(lockPath)) return null;
54573
54763
  try {
54574
- const lock = JSON.parse(readFileSync21(lockPath, "utf8"));
54764
+ const lock = JSON.parse(readFileSync22(lockPath, "utf8"));
54575
54765
  return typeof lock.pid === "number" ? lock : null;
54576
54766
  } catch {
54577
54767
  return null;
@@ -54588,6 +54778,39 @@ init_config();
54588
54778
  // src/update.ts
54589
54779
  import { spawn as spawn2 } from "node:child_process";
54590
54780
  var PACKAGE_NAME = "@inerrata-corporation/errata";
54781
+ function parse3(v) {
54782
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z][0-9A-Za-z.-]*))?/.exec(v.trim());
54783
+ if (!m) return null;
54784
+ return {
54785
+ base: [Number(m[1]), Number(m[2]), Number(m[3])],
54786
+ pre: m[4] === void 0 ? null : m[4].split(/[.-]/).map((p) => /^\d+$/.test(p) ? Number(p) : p)
54787
+ };
54788
+ }
54789
+ function compareVersions(a, b) {
54790
+ const pa = parse3(a);
54791
+ const pb = parse3(b);
54792
+ if (!pa || !pb) return 0;
54793
+ const [aMaj, aMin, aPatch] = pa.base;
54794
+ const [bMaj, bMin, bPatch] = pb.base;
54795
+ if (aMaj !== bMaj) return aMaj - bMaj;
54796
+ if (aMin !== bMin) return aMin - bMin;
54797
+ if (aPatch !== bPatch) return aPatch - bPatch;
54798
+ if (pa.pre === null && pb.pre === null) return 0;
54799
+ if (pa.pre === null) return 1;
54800
+ if (pb.pre === null) return -1;
54801
+ for (let i2 = 0; i2 < Math.max(pa.pre.length, pb.pre.length); i2++) {
54802
+ const x = pa.pre[i2];
54803
+ const y = pb.pre[i2];
54804
+ if (x === void 0) return -1;
54805
+ if (y === void 0) return 1;
54806
+ if (x === y) continue;
54807
+ if (typeof x === "number" && typeof y === "number") return x - y;
54808
+ if (typeof x === "number") return -1;
54809
+ if (typeof y === "number") return 1;
54810
+ return x < y ? -1 : 1;
54811
+ }
54812
+ return 0;
54813
+ }
54591
54814
  async function latestPublished(channel, timeoutMs = 4e3) {
54592
54815
  const ctrl = new AbortController();
54593
54816
  const timer = setTimeout(() => ctrl.abort(), timeoutMs);
@@ -54607,10 +54830,14 @@ async function latestPublished(channel, timeoutMs = 4e3) {
54607
54830
  }
54608
54831
  async function checkForUpdate(channel) {
54609
54832
  const latest = await latestPublished(channel);
54833
+ const direction = latest === null ? 0 : compareVersions(latest, DAEMON_VERSION);
54610
54834
  return {
54611
54835
  current: DAEMON_VERSION,
54612
54836
  latest,
54613
- updateAvailable: latest !== null && latest !== DAEMON_VERSION
54837
+ // `compareVersions` returns 0 for unparseable input, so an unrecognizable tag
54838
+ // is treated as "nothing to do" rather than gambling on a direction.
54839
+ updateAvailable: latest !== null && direction > 0,
54840
+ downgradeAvailable: latest !== null && direction < 0
54614
54841
  };
54615
54842
  }
54616
54843
  var POLL_INTERVAL_MS = {
@@ -54814,12 +55041,12 @@ async function reanchorProject(opts) {
54814
55041
 
54815
55042
  // src/adopt.ts
54816
55043
  import { existsSync as existsSync24 } from "node:fs";
54817
- import { dirname as dirname9, join as join26 } from "node:path";
55044
+ import { dirname as dirname10, join as join27 } from "node:path";
54818
55045
  function findGitRoot(absPath) {
54819
55046
  let dir = absPath;
54820
55047
  for (let depth = 0; depth < 64; depth++) {
54821
- if (existsSync24(join26(dir, ".git"))) return dir;
54822
- const parent = dirname9(dir);
55048
+ if (existsSync24(join27(dir, ".git"))) return dir;
55049
+ const parent = dirname10(dir);
54823
55050
  if (parent === dir) return null;
54824
55051
  dir = parent;
54825
55052
  }
@@ -55067,7 +55294,7 @@ async function startMultiDaemon(opts = {}) {
55067
55294
  void ambientLinkAll();
55068
55295
  app.route(`/ws/${rec.id}`, rec.webApp);
55069
55296
  try {
55070
- writeFileSync18(
55297
+ writeFileSync19(
55071
55298
  rec.engine.paths.daemonLock,
55072
55299
  JSON.stringify({ pid: process.pid, webUiUrl: `${baseUrl}/ws/${rec.id}`, startedAt: Date.now() }),
55073
55300
  "utf8"
@@ -55256,7 +55483,7 @@ async function startMultiDaemon(opts = {}) {
55256
55483
  baseUrl = `http://127.0.0.1:${port}`;
55257
55484
  try {
55258
55485
  ensureDir(globalDir());
55259
- writeFileSync18(
55486
+ writeFileSync19(
55260
55487
  lockPath,
55261
55488
  JSON.stringify({ pid: process.pid, webUiUrl: baseUrl, startedAt: Date.now() }),
55262
55489
  "utf8"
@@ -55265,7 +55492,7 @@ async function startMultiDaemon(opts = {}) {
55265
55492
  }
55266
55493
  for (const r of records) {
55267
55494
  try {
55268
- writeFileSync18(
55495
+ writeFileSync19(
55269
55496
  r.engine.paths.daemonLock,
55270
55497
  JSON.stringify({ pid: process.pid, webUiUrl: `${baseUrl}/ws/${r.id}`, startedAt: Date.now() }),
55271
55498
  "utf8"
@@ -55592,6 +55819,11 @@ async function startMultiDaemon(opts = {}) {
55592
55819
  if (!local) continue;
55593
55820
  store.updateNode(localId, { attrs: { ...local.attrs, contributedAtSeq: seq } });
55594
55821
  }
55822
+ if (p.blockedNodeIds?.length) {
55823
+ console.log(
55824
+ `[sync\u2192cloud] [${projectName}] context: ${p.blockedNodeIds.length} node(s) refused by an edge/CDN filter on content \u2014 left pending, will retry`
55825
+ );
55826
+ }
55595
55827
  noteFlushProgress(p.accepted, p.nodeIds.length, `context ${p.chunkIndex}/${p.totalChunks}`);
55596
55828
  }
55597
55829
  });
@@ -55621,9 +55853,18 @@ async function startMultiDaemon(opts = {}) {
55621
55853
  if (instances) {
55622
55854
  if (project) instances.projectId = project.projectId;
55623
55855
  await lane("instances", projectName, async () => {
55856
+ const blocked = /* @__PURE__ */ new Set();
55624
55857
  const res = await client.ingest(instances, {
55625
- onChunk: (p) => noteFlushProgress(p.accepted, p.nodeIds.length, `instances ${p.chunkIndex}/${p.totalChunks}`)
55858
+ onChunk: (p) => {
55859
+ for (const id of p.blockedNodeIds ?? []) blocked.add(id);
55860
+ noteFlushProgress(p.accepted, p.nodeIds.length, `instances ${p.chunkIndex}/${p.totalChunks}`);
55861
+ }
55626
55862
  });
55863
+ if (blocked.size > 0) {
55864
+ console.log(
55865
+ `[sync\u2192cloud] [${projectName}] instances: ${blocked.size} node(s) refused by an edge/CDN filter on content \u2014 left pending, will retry. First: ${store.getNode([...blocked][0])?.description.slice(0, 120) ?? [...blocked][0]}`
55866
+ );
55867
+ }
55627
55868
  uploaded += res.accepted;
55628
55869
  const seq = store.currentIngestSeq();
55629
55870
  const cloudIdByLocal = new Map(
@@ -55632,6 +55873,7 @@ async function startMultiDaemon(opts = {}) {
55632
55873
  for (const n of instances.nodes) {
55633
55874
  const local = store.getNode(n.id);
55634
55875
  if (!local || !["Problem", "Solution", "RootCause"].includes(local.label)) continue;
55876
+ if (blocked.has(n.id)) continue;
55635
55877
  const cloudNodeId = cloudIdByLocal.get(n.id);
55636
55878
  store.updateNode(n.id, {
55637
55879
  attrs: {
@@ -55748,7 +55990,7 @@ async function startMultiDaemon(opts = {}) {
55748
55990
  },
55749
55991
  async stop() {
55750
55992
  try {
55751
- const cur = readFileSync22(lockPath, "utf8");
55993
+ const cur = readFileSync23(lockPath, "utf8");
55752
55994
  if (JSON.parse(cur).pid === process.pid) unlinkSync3(lockPath);
55753
55995
  } catch {
55754
55996
  }
@@ -56766,20 +57008,20 @@ async function cmdInit() {
56766
57008
  console.log("");
56767
57009
  console.log("installing harness hooks...");
56768
57010
  const { existsSync: existsSync26 } = await import("node:fs");
56769
- const { join: join28 } = await import("node:path");
57011
+ const { join: join29 } = await import("node:path");
56770
57012
  try {
56771
57013
  await installClaudeHooks(port);
56772
57014
  } catch (err2) {
56773
57015
  console.warn(` \u26A0\uFE0F Claude hook install failed: ${err2 instanceof Error ? err2.message : err2}`);
56774
57016
  }
56775
- if (existsSync26(join28(ROOT, ".cursor"))) {
57017
+ if (existsSync26(join29(ROOT, ".cursor"))) {
56776
57018
  try {
56777
57019
  await installCursorMcpConfig();
56778
57020
  } catch (err2) {
56779
57021
  console.warn(` \u26A0\uFE0F Cursor MCP config failed: ${err2 instanceof Error ? err2.message : err2}`);
56780
57022
  }
56781
57023
  }
56782
- if (existsSync26(join28(ROOT, ".codex"))) {
57024
+ if (existsSync26(join29(ROOT, ".codex"))) {
56783
57025
  try {
56784
57026
  await installCodexHooks(port);
56785
57027
  } catch (err2) {
@@ -57073,9 +57315,11 @@ async function cmdUpdate(args2) {
57073
57315
  const cfg = loadConfig();
57074
57316
  let channel = cfg.updateChannel;
57075
57317
  let checkOnly = false;
57318
+ let allowDowngrade = false;
57076
57319
  for (let i2 = 0; i2 < args2.length; i2++) {
57077
57320
  const a = args2[i2];
57078
57321
  if (a === "--check") checkOnly = true;
57322
+ else if (a === "--allow-downgrade") allowDowngrade = true;
57079
57323
  else if (a === "--channel") {
57080
57324
  const v = args2[++i2];
57081
57325
  if (v === "dev" || v === "latest") channel = v;
@@ -57096,8 +57340,19 @@ async function cmdUpdate(args2) {
57096
57340
  return;
57097
57341
  }
57098
57342
  if (!status.updateAvailable) {
57099
- console.log(`errata is up to date \u2014 ${status.current} (channel: ${channel})`);
57100
- return;
57343
+ if (status.downgradeAvailable) {
57344
+ console.log(
57345
+ `channel '${channel}' points at ${status.latest}, which is OLDER than the running ${status.current}.`
57346
+ );
57347
+ console.log(` not installing \u2014 a downgrade past a store migration cannot open its own graph.`);
57348
+ console.log(` if this is a deliberate rollback: errata update --allow-downgrade`);
57349
+ if (!allowDowngrade) return;
57350
+ console.log(`
57351
+ --allow-downgrade given; proceeding.`);
57352
+ } else {
57353
+ console.log(`errata is up to date \u2014 ${status.current} (channel: ${channel})`);
57354
+ return;
57355
+ }
57101
57356
  }
57102
57357
  console.log(`update available: ${status.current} \u2192 ${status.latest} (channel: ${channel})`);
57103
57358
  if (checkOnly) {
@@ -57593,7 +57848,7 @@ async function cmdReview() {
57593
57848
  console.log("(review queue empty)");
57594
57849
  return;
57595
57850
  }
57596
- const queue = JSON.parse(readFileSync23(paths.reviewQueue, "utf8"));
57851
+ const queue = JSON.parse(readFileSync24(paths.reviewQueue, "utf8"));
57597
57852
  if (queue.length === 0) {
57598
57853
  console.log("(review queue empty)");
57599
57854
  return;
@@ -58303,7 +58558,7 @@ async function gatherReportData(generatedAt) {
58303
58558
  };
58304
58559
  }
58305
58560
  async function cmdReport(args2) {
58306
- const { mkdirSync: mkdirSync8, writeFileSync: writeFileSync19 } = await import("node:fs");
58561
+ const { mkdirSync: mkdirSync8, writeFileSync: writeFileSync20 } = await import("node:fs");
58307
58562
  const { renderReport: renderReport2 } = await Promise.resolve().then(() => (init_report_render(), report_render_exports));
58308
58563
  const includeFutureVerbs = args2.includes("--future-verbs");
58309
58564
  const now = /* @__PURE__ */ new Date();
@@ -58316,8 +58571,8 @@ async function cmdReport(args2) {
58316
58571
  const outDir = workspacePaths(ROOT).configDir;
58317
58572
  mkdirSync8(outDir, { recursive: true });
58318
58573
  const files = renderReport2(data, { includeFutureVerbs });
58319
- for (const f of files) writeFileSync19(join27(outDir, f.name), f.html, "utf8");
58320
- const indexPath = join27(outDir, "report.html");
58574
+ for (const f of files) writeFileSync20(join28(outDir, f.name), f.html, "utf8");
58575
+ const indexPath = join28(outDir, "report.html");
58321
58576
  console.log(`report \u2192 ${indexPath}`);
58322
58577
  console.log(
58323
58578
  ` ${data.repos.length} repo(s) \xB7 ${files.length} file(s) \xB7 ${data.rollup.nodes.toLocaleString("en-US")} nodes`
@@ -58369,7 +58624,7 @@ function spawnDaemonDetached() {
58369
58624
  try {
58370
58625
  const logPath = daemonLogPath();
58371
58626
  try {
58372
- if (statSync6(logPath).size > 5 * 1024 * 1024) renameSync3(logPath, `${logPath}.1`);
58627
+ if (statSync6(logPath).size > 5 * 1024 * 1024) renameSync4(logPath, `${logPath}.1`);
58373
58628
  } catch {
58374
58629
  }
58375
58630
  out2 = openSync2(logPath, "a");
@@ -58435,15 +58690,15 @@ function hookRelayCommand(port, path2) {
58435
58690
  return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 2 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} 2>NUL || echo {}"` : `curl -s --connect-timeout 1 --max-time 2 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} 2>/dev/null || echo '{}'`;
58436
58691
  }
58437
58692
  async function installClaudeHooks(port) {
58438
- const { mkdirSync: mkdirSync8, existsSync: existsSync26, readFileSync: readFileSync24, writeFileSync: writeFileSync19 } = await import("node:fs");
58439
- const { join: join28 } = await import("node:path");
58440
- const dir = join28(ROOT, ".claude");
58693
+ const { mkdirSync: mkdirSync8, existsSync: existsSync26, readFileSync: readFileSync25, writeFileSync: writeFileSync20 } = await import("node:fs");
58694
+ const { join: join29 } = await import("node:path");
58695
+ const dir = join29(ROOT, ".claude");
58441
58696
  if (!existsSync26(dir)) mkdirSync8(dir, { recursive: true });
58442
- const file2 = join28(dir, "settings.json");
58697
+ const file2 = join29(dir, "settings.json");
58443
58698
  let settings = {};
58444
58699
  if (existsSync26(file2)) {
58445
58700
  try {
58446
- settings = JSON.parse(readFileSync24(file2, "utf8"));
58701
+ settings = JSON.parse(readFileSync25(file2, "utf8"));
58447
58702
  } catch {
58448
58703
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
58449
58704
  process.exit(2);
@@ -58489,10 +58744,10 @@ async function installClaudeHooks(port) {
58489
58744
  dropErrata(list);
58490
58745
  list.push({ hooks: [{ type: "command", command: injectCmd }] });
58491
58746
  }
58492
- writeFileSync19(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
58747
+ writeFileSync20(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
58493
58748
  console.log(`installed Claude Code hooks \u2192 ${file2}`);
58494
58749
  await installClaudeMcpConfig();
58495
- const claudeMd = join28(ROOT, "CLAUDE.md");
58750
+ const claudeMd = join29(ROOT, "CLAUDE.md");
58496
58751
  const recall = writeManagedBlock(claudeMd, { body: RECALL_FIRST_BLOCK });
58497
58752
  if (recall.kind === "collision") {
58498
58753
  console.warn(
@@ -58504,15 +58759,15 @@ async function installClaudeHooks(port) {
58504
58759
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
58505
58760
  }
58506
58761
  async function installClaudeMcpConfig() {
58507
- const { mkdirSync: mkdirSync8, existsSync: existsSync26, readFileSync: readFileSync24, writeFileSync: writeFileSync19 } = await import("node:fs");
58508
- const { join: join28, dirname: dirname10 } = await import("node:path");
58509
- const file2 = join28(ROOT, ".mcp.json");
58510
- const dir = dirname10(file2);
58762
+ const { mkdirSync: mkdirSync8, existsSync: existsSync26, readFileSync: readFileSync25, writeFileSync: writeFileSync20 } = await import("node:fs");
58763
+ const { join: join29, dirname: dirname11 } = await import("node:path");
58764
+ const file2 = join29(ROOT, ".mcp.json");
58765
+ const dir = dirname11(file2);
58511
58766
  if (!existsSync26(dir)) mkdirSync8(dir, { recursive: true });
58512
58767
  let cfg = {};
58513
58768
  if (existsSync26(file2)) {
58514
58769
  try {
58515
- cfg = JSON.parse(readFileSync24(file2, "utf8"));
58770
+ cfg = JSON.parse(readFileSync25(file2, "utf8"));
58516
58771
  } catch {
58517
58772
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
58518
58773
  process.exit(2);
@@ -58520,21 +58775,21 @@ async function installClaudeMcpConfig() {
58520
58775
  }
58521
58776
  cfg.mcpServers ??= {};
58522
58777
  cfg.mcpServers["errata"] = errataMcpInvocation();
58523
- writeFileSync19(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
58778
+ writeFileSync20(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
58524
58779
  console.log(`installed Claude Code MCP server config \u2192 ${file2}`);
58525
58780
  console.log(` Claude Code will spawn \`errata mcp\` on workspace open.`);
58526
58781
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
58527
58782
  }
58528
58783
  async function installCursorMcpConfig() {
58529
- const { mkdirSync: mkdirSync8, existsSync: existsSync26, readFileSync: readFileSync24, writeFileSync: writeFileSync19 } = await import("node:fs");
58530
- const { join: join28 } = await import("node:path");
58531
- const dir = join28(ROOT, ".cursor");
58784
+ const { mkdirSync: mkdirSync8, existsSync: existsSync26, readFileSync: readFileSync25, writeFileSync: writeFileSync20 } = await import("node:fs");
58785
+ const { join: join29 } = await import("node:path");
58786
+ const dir = join29(ROOT, ".cursor");
58532
58787
  if (!existsSync26(dir)) mkdirSync8(dir, { recursive: true });
58533
- const file2 = join28(dir, "mcp.json");
58788
+ const file2 = join29(dir, "mcp.json");
58534
58789
  let cfg = {};
58535
58790
  if (existsSync26(file2)) {
58536
58791
  try {
58537
- cfg = JSON.parse(readFileSync24(file2, "utf8"));
58792
+ cfg = JSON.parse(readFileSync25(file2, "utf8"));
58538
58793
  } catch {
58539
58794
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
58540
58795
  process.exit(2);
@@ -58542,7 +58797,7 @@ async function installCursorMcpConfig() {
58542
58797
  }
58543
58798
  cfg.mcpServers ??= {};
58544
58799
  cfg.mcpServers["errata"] = errataMcpInvocation();
58545
- writeFileSync19(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
58800
+ writeFileSync20(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
58546
58801
  console.log(`installed Cursor MCP server config \u2192 ${file2}`);
58547
58802
  console.log(` Cursor will spawn \`errata mcp\` on workspace open.`);
58548
58803
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses`);
@@ -58550,16 +58805,16 @@ async function installCursorMcpConfig() {
58550
58805
  console.log(` \u26A0\uFE0F reload Cursor (Cmd/Ctrl-Shift-P \u2192 "Reload Window") to pick it up.`);
58551
58806
  }
58552
58807
  async function installCodexHooks(port) {
58553
- const { mkdirSync: mkdirSync8, existsSync: existsSync26, readFileSync: readFileSync24, writeFileSync: writeFileSync19 } = await import("node:fs");
58554
- const { join: join28 } = await import("node:path");
58555
- const dir = join28(ROOT, ".codex");
58808
+ const { mkdirSync: mkdirSync8, existsSync: existsSync26, readFileSync: readFileSync25, writeFileSync: writeFileSync20 } = await import("node:fs");
58809
+ const { join: join29 } = await import("node:path");
58810
+ const dir = join29(ROOT, ".codex");
58556
58811
  if (!existsSync26(dir)) mkdirSync8(dir, { recursive: true });
58557
- const file2 = join28(dir, "config.toml");
58812
+ const file2 = join29(dir, "config.toml");
58558
58813
  const BEGIN = `# >>> errata hooks (errata-managed)`;
58559
58814
  const END = `# <<< errata hooks`;
58560
58815
  let existing = "";
58561
58816
  if (existsSync26(file2)) {
58562
- existing = readFileSync24(file2, "utf8");
58817
+ existing = readFileSync25(file2, "utf8");
58563
58818
  const beginIdx = existing.indexOf(BEGIN);
58564
58819
  const endIdx = existing.indexOf(END);
58565
58820
  if (beginIdx >= 0 && endIdx > beginIdx) {
@@ -58588,7 +58843,7 @@ ${END}
58588
58843
  const final = existing.length > 0 && !existing.endsWith("\n") ? `${existing}
58589
58844
 
58590
58845
  ${block}` : existing + (existing.endsWith("\n\n") ? "" : "\n") + block;
58591
- writeFileSync19(file2, final, "utf8");
58846
+ writeFileSync20(file2, final, "utf8");
58592
58847
  console.log(`installed Codex hooks \u2192 ${file2}`);
58593
58848
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
58594
58849
  console.log("");
@@ -58902,7 +59157,7 @@ async function cmdDash(args2) {
58902
59157
  await yieldToLoop2();
58903
59158
  try {
58904
59159
  const items = selectDurableMemory(handle2.sharedStore, r.engine.profile);
58905
- const res = bleedRules(join27(r.root, ".claude", "rules"), items);
59160
+ const res = bleedRules(join28(r.root, ".claude", "rules"), items);
58906
59161
  if (res.written || res.pruned) {
58907
59162
  console.log(
58908
59163
  `[rules:${r.entry.name}] ${res.written} collective principle(s) \u2192 .claude/rules` + (res.pruned ? `, ${res.pruned} pruned` : "")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.226",
3
+ "version": "2.0.2-dev.245",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -15170,6 +15170,15 @@ var init_wire = __esm({
15170
15170
  /** Canonical human-readable description (no raw paths — daemon scrubs; server rechecks). */
15171
15171
  description: external_exports.string().min(1).max(4e3),
15172
15172
  attrs: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
15173
+ /** One-way origin key of the SESSION that minted this node (`ws_…`, a
15174
+ * truncated digest — never the raw session id). Stamped onto the created
15175
+ * node as `authoringSession`, which is the independence unit for the
15176
+ * evidence channels: the session that authored a claim may not corroborate
15177
+ * or refute it, while a DIFFERENT session on the same checkout may (alyssa,
15178
+ * 2026-07-31 — the workspace key made every witness on a single-checkout
15179
+ * deployment a self-corroboration). Optional + additive: absent leaves the
15180
+ * gate fail-open for that node, exactly today's behaviour. */
15181
+ originSession: external_exports.string().min(3).max(64).optional(),
15173
15182
  extractionSource: external_exports.enum(INGEST_EXTRACTION_SOURCES),
15174
15183
  validationSource: external_exports.enum(VALIDATION_SOURCES).optional(),
15175
15184
  /** Org-membrane (M2): the daemon's anchor tag — it owns the lockfile, so it