@inerrata-corporation/errata 2.0.14-dev.3118 → 2.0.14-dev.3125

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.
@@ -16188,6 +16188,71 @@ function installHistoryCacheFloor(db) {
16188
16188
  });
16189
16189
  }
16190
16190
 
16191
+ // ../../packages/local-graph/src/session-evidence.ts
16192
+ function repairSessionEvidence(input, aliases = /* @__PURE__ */ new Map()) {
16193
+ const mapping = new Map(aliases);
16194
+ const uuid3 = "[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}";
16195
+ const codex = new RegExp(`^codex:(?:rollout-.*?)?(${uuid3})$`, "i");
16196
+ const arrays = /* @__PURE__ */ new Set(["sources", "recurrenceSources", "contributors", "inferredContributors", "citeSessions"]);
16197
+ const scalars = /* @__PURE__ */ new Set(["discriminatorSource", "originSession", "sessionId", "sourceSession"]);
16198
+ const discover = (value, key = "") => {
16199
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
16200
+ const match = value.match(codex);
16201
+ if (match) {
16202
+ const canonical = `codex:${match[1].toLowerCase()}`;
16203
+ mapping.set(value, canonical);
16204
+ mapping.set(match[1].toLowerCase(), canonical);
16205
+ }
16206
+ } else if (Array.isArray(value)) value.forEach((v) => discover(v, key));
16207
+ else if (value && typeof value === "object") {
16208
+ for (const [k, v] of Object.entries(value)) discover(v, key === "citeAnchorHints" ? key : k);
16209
+ }
16210
+ };
16211
+ discover(input);
16212
+ const rewrite = (value, key = "") => {
16213
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
16214
+ return mapping.get(value) ?? mapping.get(value.toLowerCase()) ?? value;
16215
+ }
16216
+ if (Array.isArray(value)) {
16217
+ const values = value.map((v) => rewrite(v, key));
16218
+ return arrays.has(key) || key === "citeAnchorHints" ? [...new Set(values)] : values;
16219
+ }
16220
+ if (!value || typeof value !== "object") return value;
16221
+ const before = value;
16222
+ const after = Object.fromEntries(Object.entries(before).map(([k, v]) => [k, rewrite(v, key === "citeAnchorHints" ? key : k)]));
16223
+ for (const field of ["sources", "contributors"]) {
16224
+ const old = before[field], next = after[field];
16225
+ if (!Array.isArray(old) || !Array.isArray(next) || old.length <= next.length) continue;
16226
+ after["sessionAliasRepair"] = { originalCount: old.length, distinctCount: next.length };
16227
+ if (field === "sources" && typeof after["corroborations"] === "number") {
16228
+ after["corroborations"] = Math.min(after["corroborations"], Math.max(0, next.length - 1));
16229
+ if (after["corroborations"] === 0) after["provisional"] = true;
16230
+ }
16231
+ if (field === "contributors" && typeof after["confirmed"] === "number") {
16232
+ after["preAliasRepairConfirmed"] = after["confirmed"];
16233
+ after["confirmed"] = Math.min(after["confirmed"], next.length);
16234
+ }
16235
+ }
16236
+ return after;
16237
+ };
16238
+ const repaired = rewrite(input);
16239
+ const source = repaired["discriminatorSource"];
16240
+ if (typeof source === "string") {
16241
+ const global = repaired["perContext"]?.["*"];
16242
+ const contributors = Array.isArray(global?.["contributors"]) ? global["contributors"] : [];
16243
+ const inferred = new Set(Array.isArray(global?.["inferredContributors"]) ? global["inferredContributors"] : []);
16244
+ if (contributors.includes(source) || contributors.filter((id) => !inferred.has(id)).length < 2) {
16245
+ repaired["withdrawnDiscriminator"] = repaired["discriminator"];
16246
+ repaired["discriminatorWithdrawalReason"] = "insufficient-independent-witnesses";
16247
+ repaired["provisional"] = true;
16248
+ delete repaired["discriminator"];
16249
+ delete repaired["discriminatorSource"];
16250
+ delete repaired["discriminatorAsked"];
16251
+ }
16252
+ }
16253
+ return repaired;
16254
+ }
16255
+
16191
16256
  // ../../packages/local-graph/src/store.ts
16192
16257
  var LOCAL_RULE_OVERRIDES = {
16193
16258
  ANCHORED_AT: {
@@ -16432,6 +16497,12 @@ var SqliteGraphStore = class {
16432
16497
  }
16433
16498
  this.db.exec(TEMPORAL_INDEX_SQL);
16434
16499
  installHistoryCacheFloor(this.db);
16500
+ if (!this.db.prepare("SELECT value FROM store_meta WHERE key = 'session-evidence-v1'").get()) {
16501
+ this.db.transaction(() => {
16502
+ this.repairSessionAliases();
16503
+ this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES ('session-evidence-v1', 'done')").run();
16504
+ });
16505
+ }
16435
16506
  this.stmts = {
16436
16507
  insertNode: this.db.prepare(`
16437
16508
  INSERT INTO nodes (
@@ -16680,6 +16751,23 @@ var SqliteGraphStore = class {
16680
16751
  this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
16681
16752
  });
16682
16753
  }
16754
+ repairSessionAliases(canonical, aliases = []) {
16755
+ const mapping = new Map(aliases.map((alias) => [alias, canonical]));
16756
+ let changed = 0;
16757
+ for (const table of ["nodes", "edges"]) {
16758
+ const predicates = canonical ? aliases.map(() => "instr(attrs_json, ?) > 0") : ["instr(attrs_json, 'codex:') > 0", "instr(attrs_json, 'discriminatorSource') > 0"];
16759
+ if (!predicates.length) continue;
16760
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM ${table} WHERE ${predicates.join(" OR ")}`).all(...canonical ? aliases : []);
16761
+ const update = this.db.prepare(`UPDATE ${table} SET attrs_json = ?${table === "edges" ? ", type = CASE WHEN type = 'CONFIRMS' AND json_extract(?, '$.provisional') = 1 THEN 'INDICATES' ELSE type END" : ""} WHERE id = ?`);
16762
+ for (const row of rows) {
16763
+ const attrs = JSON.stringify(repairSessionEvidence(JSON.parse(row.attrs_json), mapping));
16764
+ if (attrs === row.attrs_json) continue;
16765
+ update.run(...table === "edges" ? [attrs, attrs, row.id] : [attrs, row.id]);
16766
+ changed++;
16767
+ }
16768
+ }
16769
+ return changed;
16770
+ }
16683
16771
  deleteNode(id) {
16684
16772
  this.stmts.deleteNode.run(id);
16685
16773
  }
@@ -17761,6 +17849,7 @@ function recordTriageObservation(l2, obs, ts) {
17761
17849
  }
17762
17850
  const discriminator = obs.discriminator ?? existing?.attrs["discriminator"];
17763
17851
  const attrs = {
17852
+ ...existing?.attrs,
17764
17853
  driftKind: obs.driftKind ?? existing?.attrs["driftKind"] ?? DRIFT_KIND.RELOCATION,
17765
17854
  perContext,
17766
17855
  // A discriminator (now or previously supplied) promotes the route to a router.
package/errata.mjs CHANGED
@@ -18007,6 +18007,76 @@ var init_history_cache_floor = __esm({
18007
18007
  }
18008
18008
  });
18009
18009
 
18010
+ // ../../packages/local-graph/src/session-evidence.ts
18011
+ function repairSessionEvidence(input, aliases = /* @__PURE__ */ new Map()) {
18012
+ const mapping = new Map(aliases);
18013
+ const uuid3 = "[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}";
18014
+ const codex = new RegExp(`^codex:(?:rollout-.*?)?(${uuid3})$`, "i");
18015
+ const arrays = /* @__PURE__ */ new Set(["sources", "recurrenceSources", "contributors", "inferredContributors", "citeSessions"]);
18016
+ const scalars = /* @__PURE__ */ new Set(["discriminatorSource", "originSession", "sessionId", "sourceSession"]);
18017
+ const discover = (value, key = "") => {
18018
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
18019
+ const match2 = value.match(codex);
18020
+ if (match2) {
18021
+ const canonical = `codex:${match2[1].toLowerCase()}`;
18022
+ mapping.set(value, canonical);
18023
+ mapping.set(match2[1].toLowerCase(), canonical);
18024
+ }
18025
+ } else if (Array.isArray(value)) value.forEach((v) => discover(v, key));
18026
+ else if (value && typeof value === "object") {
18027
+ for (const [k, v] of Object.entries(value)) discover(v, key === "citeAnchorHints" ? key : k);
18028
+ }
18029
+ };
18030
+ discover(input);
18031
+ const rewrite = (value, key = "") => {
18032
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
18033
+ return mapping.get(value) ?? mapping.get(value.toLowerCase()) ?? value;
18034
+ }
18035
+ if (Array.isArray(value)) {
18036
+ const values = value.map((v) => rewrite(v, key));
18037
+ return arrays.has(key) || key === "citeAnchorHints" ? [...new Set(values)] : values;
18038
+ }
18039
+ if (!value || typeof value !== "object") return value;
18040
+ const before = value;
18041
+ const after = Object.fromEntries(Object.entries(before).map(([k, v]) => [k, rewrite(v, key === "citeAnchorHints" ? key : k)]));
18042
+ for (const field of ["sources", "contributors"]) {
18043
+ const old = before[field], next = after[field];
18044
+ if (!Array.isArray(old) || !Array.isArray(next) || old.length <= next.length) continue;
18045
+ after["sessionAliasRepair"] = { originalCount: old.length, distinctCount: next.length };
18046
+ if (field === "sources" && typeof after["corroborations"] === "number") {
18047
+ after["corroborations"] = Math.min(after["corroborations"], Math.max(0, next.length - 1));
18048
+ if (after["corroborations"] === 0) after["provisional"] = true;
18049
+ }
18050
+ if (field === "contributors" && typeof after["confirmed"] === "number") {
18051
+ after["preAliasRepairConfirmed"] = after["confirmed"];
18052
+ after["confirmed"] = Math.min(after["confirmed"], next.length);
18053
+ }
18054
+ }
18055
+ return after;
18056
+ };
18057
+ const repaired = rewrite(input);
18058
+ const source = repaired["discriminatorSource"];
18059
+ if (typeof source === "string") {
18060
+ const global2 = repaired["perContext"]?.["*"];
18061
+ const contributors = Array.isArray(global2?.["contributors"]) ? global2["contributors"] : [];
18062
+ const inferred = new Set(Array.isArray(global2?.["inferredContributors"]) ? global2["inferredContributors"] : []);
18063
+ if (contributors.includes(source) || contributors.filter((id) => !inferred.has(id)).length < 2) {
18064
+ repaired["withdrawnDiscriminator"] = repaired["discriminator"];
18065
+ repaired["discriminatorWithdrawalReason"] = "insufficient-independent-witnesses";
18066
+ repaired["provisional"] = true;
18067
+ delete repaired["discriminator"];
18068
+ delete repaired["discriminatorSource"];
18069
+ delete repaired["discriminatorAsked"];
18070
+ }
18071
+ }
18072
+ return repaired;
18073
+ }
18074
+ var init_session_evidence = __esm({
18075
+ "../../packages/local-graph/src/session-evidence.ts"() {
18076
+ "use strict";
18077
+ }
18078
+ });
18079
+
18010
18080
  // ../../packages/local-graph/src/store.ts
18011
18081
  function localEdgeViolation(fromLabel, type, toLabel) {
18012
18082
  if (type in LOCAL_RULE_OVERRIDES) {
@@ -18090,6 +18160,7 @@ var init_store = __esm({
18090
18160
  init_src2();
18091
18161
  init_src();
18092
18162
  init_history_cache_floor();
18163
+ init_session_evidence();
18093
18164
  LOCAL_RULE_OVERRIDES = {
18094
18165
  ANCHORED_AT: {
18095
18166
  from: EDGE_RULES["ANCHORED_AT"]?.from,
@@ -18261,6 +18332,12 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18261
18332
  }
18262
18333
  this.db.exec(TEMPORAL_INDEX_SQL);
18263
18334
  installHistoryCacheFloor(this.db);
18335
+ if (!this.db.prepare("SELECT value FROM store_meta WHERE key = 'session-evidence-v1'").get()) {
18336
+ this.db.transaction(() => {
18337
+ this.repairSessionAliases();
18338
+ this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES ('session-evidence-v1', 'done')").run();
18339
+ });
18340
+ }
18264
18341
  this.stmts = {
18265
18342
  insertNode: this.db.prepare(`
18266
18343
  INSERT INTO nodes (
@@ -18509,6 +18586,23 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18509
18586
  this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
18510
18587
  });
18511
18588
  }
18589
+ repairSessionAliases(canonical, aliases = []) {
18590
+ const mapping = new Map(aliases.map((alias) => [alias, canonical]));
18591
+ let changed = 0;
18592
+ for (const table of ["nodes", "edges"]) {
18593
+ const predicates = canonical ? aliases.map(() => "instr(attrs_json, ?) > 0") : ["instr(attrs_json, 'codex:') > 0", "instr(attrs_json, 'discriminatorSource') > 0"];
18594
+ if (!predicates.length) continue;
18595
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM ${table} WHERE ${predicates.join(" OR ")}`).all(...canonical ? aliases : []);
18596
+ const update = this.db.prepare(`UPDATE ${table} SET attrs_json = ?${table === "edges" ? ", type = CASE WHEN type = 'CONFIRMS' AND json_extract(?, '$.provisional') = 1 THEN 'INDICATES' ELSE type END" : ""} WHERE id = ?`);
18597
+ for (const row of rows) {
18598
+ const attrs = JSON.stringify(repairSessionEvidence(JSON.parse(row.attrs_json), mapping));
18599
+ if (attrs === row.attrs_json) continue;
18600
+ update.run(...table === "edges" ? [attrs, attrs, row.id] : [attrs, row.id]);
18601
+ changed++;
18602
+ }
18603
+ }
18604
+ return changed;
18605
+ }
18512
18606
  deleteNode(id) {
18513
18607
  this.stmts.deleteNode.run(id);
18514
18608
  }
@@ -23027,6 +23121,7 @@ function recordTriageObservation(l2, obs, ts) {
23027
23121
  }
23028
23122
  const discriminator = obs.discriminator ?? existing?.attrs["discriminator"];
23029
23123
  const attrs = {
23124
+ ...existing?.attrs,
23030
23125
  driftKind: obs.driftKind ?? existing?.attrs["driftKind"] ?? DRIFT_KIND.RELOCATION,
23031
23126
  perContext,
23032
23127
  // A discriminator (now or previously supplied) promotes the route to a router.
@@ -23265,7 +23360,7 @@ function induceTriage(l2, opts) {
23265
23360
  return report;
23266
23361
  }
23267
23362
  function pendingDiscriminators(l2, opts = {}) {
23268
- const minSources = opts.minSources ?? 1;
23363
+ const minSources = opts.minSources ?? 2;
23269
23364
  const minObservations = opts.minObservations ?? 2;
23270
23365
  const out2 = [];
23271
23366
  for (const triage of l2.findNodesByLabel("Triage")) {
@@ -23300,6 +23395,12 @@ function promoteRouteWithDiscriminator(l2, routeId, discriminator, ts, source) {
23300
23395
  if (isUnfilledPlaceholder(test)) return false;
23301
23396
  const existing = l2.getEdge(routeId);
23302
23397
  if (!existing || !ROUTE_EDGE_TYPES.includes(existing.type)) return false;
23398
+ const global2 = (existing.attrs["perContext"] ?? {})[TRIAGE_GLOBAL_BUCKET] ?? {};
23399
+ const contributors = new Set(global2.contributors ?? []);
23400
+ const inferred = new Set(global2.inferredContributors ?? []);
23401
+ const witnesses = [...contributors].filter((id) => !inferred.has(id));
23402
+ if (!source || contributors.has(source) || witnesses.length < 2) return false;
23403
+ if (existing.attrs["discriminator"] && existing.attrs["discriminator"] !== test) return false;
23303
23404
  const attrs = {
23304
23405
  ...existing.attrs,
23305
23406
  discriminator: test,
@@ -31164,26 +31265,26 @@ function buildFileLexicon(store) {
31164
31265
  for (const n of store.findNodesByLabel("File")) {
31165
31266
  const relPath = typeof n.attrs["relPath"] === "string" && n.attrs["relPath"].length > 0 ? n.attrs["relPath"] : n.description;
31166
31267
  if (!relPath) continue;
31167
- const basename8 = relPath.split("/").pop();
31168
- if (!basename8.includes(".")) continue;
31169
- entries.push({ relPath, basename: basename8 });
31170
- basenameCount.set(basename8, (basenameCount.get(basename8) ?? 0) + 1);
31171
- const stem = basename8.slice(0, basename8.indexOf("."));
31268
+ const basename9 = relPath.split("/").pop();
31269
+ if (!basename9.includes(".")) continue;
31270
+ entries.push({ relPath, basename: basename9 });
31271
+ basenameCount.set(basename9, (basenameCount.get(basename9) ?? 0) + 1);
31272
+ const stem = basename9.slice(0, basename9.indexOf("."));
31172
31273
  if (isBareMatchableStem(stem)) {
31173
31274
  let s = stemRelPaths.get(stem);
31174
31275
  if (!s) stemRelPaths.set(stem, s = /* @__PURE__ */ new Set());
31175
31276
  s.add(relPath);
31176
31277
  }
31177
31278
  }
31178
- for (const { relPath, basename: basename8 } of entries) {
31279
+ for (const { relPath, basename: basename9 } of entries) {
31179
31280
  const entry = { relPath };
31180
- if (relPath !== basename8) byKey.set(relPath, entry);
31181
- const conventional = CONVENTION_FILENAMES.has(basename8) || (basenameCount.get(basename8) ?? 0) >= CONVENTION_BASENAME_MIN_FILES;
31182
- if (!conventional && !byKey.has(basename8)) byKey.set(basename8, entry);
31183
- const list = byBasename.get(basename8);
31281
+ if (relPath !== basename9) byKey.set(relPath, entry);
31282
+ const conventional = CONVENTION_FILENAMES.has(basename9) || (basenameCount.get(basename9) ?? 0) >= CONVENTION_BASENAME_MIN_FILES;
31283
+ if (!conventional && !byKey.has(basename9)) byKey.set(basename9, entry);
31284
+ const list = byBasename.get(basename9);
31184
31285
  if (list) list.push(entry);
31185
- else byBasename.set(basename8, [entry]);
31186
- const stem = basename8.slice(0, basename8.indexOf("."));
31286
+ else byBasename.set(basename9, [entry]);
31287
+ const stem = basename9.slice(0, basename9.indexOf("."));
31187
31288
  if (isBareMatchableStem(stem) && !CONVENTION_STEMS.has(stem) && !byStem.has(stem)) {
31188
31289
  const dirStems = new Set(
31189
31290
  [...stemRelPaths.get(stem) ?? []].map((p) => {
@@ -31200,8 +31301,8 @@ function resolveFileToken(token, lexicon) {
31200
31301
  const exact = lexicon.byKey.get(token);
31201
31302
  if (exact) return exact;
31202
31303
  if (!token.includes("/")) return null;
31203
- const basename8 = token.split("/").pop();
31204
- for (const entry of lexicon.byBasename.get(basename8) ?? []) {
31304
+ const basename9 = token.split("/").pop();
31305
+ for (const entry of lexicon.byBasename.get(basename9) ?? []) {
31205
31306
  if (entry.relPath === token || entry.relPath.endsWith(`/${token}`)) return entry;
31206
31307
  }
31207
31308
  return null;
@@ -31226,9 +31327,9 @@ function fileRolePhrase(relPath, level = 1) {
31226
31327
  return "a source file";
31227
31328
  }
31228
31329
  function isSourceFileToken(token) {
31229
- const basename8 = token.split(/[/\\]/).pop() ?? token;
31230
- if (CONVENTION_FILENAMES.has(basename8)) return false;
31231
- const ext = basename8.split(".").pop();
31330
+ const basename9 = token.split(/[/\\]/).pop() ?? token;
31331
+ if (CONVENTION_FILENAMES.has(basename9)) return false;
31332
+ const ext = basename9.split(".").pop();
31232
31333
  return ext !== void 0 && SOURCE_EXTENSIONS.has(ext.toLowerCase());
31233
31334
  }
31234
31335
  function generalizeFileRefs(text, fileLexicon, level = 1) {
@@ -72764,7 +72865,7 @@ init_config();
72764
72865
  // src/engine.ts
72765
72866
  import { execFileSync as execFileSync4 } from "node:child_process";
72766
72867
  import { existsSync as existsSync25, statSync as statSync8, appendFileSync as appendFileSync4, readdirSync as readdirSync10, renameSync as renameSync3, readFileSync as readFileSync28, writeFileSync as writeFileSync21 } from "node:fs";
72767
- import { join as join34, relative as relative6, sep as sep4 } from "node:path";
72868
+ import { basename as basename7, join as join34, relative as relative6, sep as sep4 } from "node:path";
72768
72869
 
72769
72870
  // src/source-watcher.ts
72770
72871
  import { EventEmitter as EventEmitter2 } from "node:events";
@@ -72924,10 +73025,10 @@ var ReaddirpStream = class extends Readable2 {
72924
73025
  }
72925
73026
  async _formatEntry(dirent, path2) {
72926
73027
  let entry;
72927
- const basename8 = this._isDirent ? dirent.name : dirent;
73028
+ const basename9 = this._isDirent ? dirent.name : dirent;
72928
73029
  try {
72929
- const fullPath = presolve(pjoin(path2, basename8));
72930
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename8 };
73030
+ const fullPath = presolve(pjoin(path2, basename9));
73031
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
72931
73032
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
72932
73033
  } catch (err2) {
72933
73034
  this._onError(err2);
@@ -73466,9 +73567,9 @@ var NodeFsHandler = class {
73466
73567
  _watchWithNodeFs(path2, listener) {
73467
73568
  const opts = this.fsw.options;
73468
73569
  const directory = sysPath.dirname(path2);
73469
- const basename8 = sysPath.basename(path2);
73570
+ const basename9 = sysPath.basename(path2);
73470
73571
  const parent = this.fsw._getWatchedDir(directory);
73471
- parent.add(basename8);
73572
+ parent.add(basename9);
73472
73573
  const absolutePath = sysPath.resolve(path2);
73473
73574
  const options = {
73474
73575
  persistent: opts.persistent
@@ -73478,7 +73579,7 @@ var NodeFsHandler = class {
73478
73579
  let closer;
73479
73580
  if (opts.usePolling) {
73480
73581
  const enableBin = opts.interval !== opts.binaryInterval;
73481
- options.interval = enableBin && isBinaryPath(basename8) ? opts.binaryInterval : opts.interval;
73582
+ options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
73482
73583
  closer = setFsWatchFileListener(path2, absolutePath, options, {
73483
73584
  listener,
73484
73585
  rawEmitter: this.fsw._emitRaw
@@ -73501,10 +73602,10 @@ var NodeFsHandler = class {
73501
73602
  return;
73502
73603
  }
73503
73604
  const dirname14 = sysPath.dirname(file2);
73504
- const basename8 = sysPath.basename(file2);
73605
+ const basename9 = sysPath.basename(file2);
73505
73606
  const parent = this.fsw._getWatchedDir(dirname14);
73506
73607
  let prevStats = stats;
73507
- if (parent.has(basename8))
73608
+ if (parent.has(basename9))
73508
73609
  return;
73509
73610
  const listener = async (path2, newStats) => {
73510
73611
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
@@ -73529,9 +73630,9 @@ var NodeFsHandler = class {
73529
73630
  prevStats = newStats2;
73530
73631
  }
73531
73632
  } catch (error48) {
73532
- this.fsw._remove(dirname14, basename8);
73633
+ this.fsw._remove(dirname14, basename9);
73533
73634
  }
73534
- } else if (parent.has(basename8)) {
73635
+ } else if (parent.has(basename9)) {
73535
73636
  const at = newStats.atimeMs;
73536
73637
  const mt = newStats.mtimeMs;
73537
73638
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -74811,6 +74912,16 @@ import { closeSync, existsSync as existsSync16, fstatSync, openSync, readdirSync
74811
74912
  import { basename as basename5, delimiter, dirname as dirname10, join as join20, normalize as normalize3 } from "node:path";
74812
74913
  import { homedir as homedir4 } from "node:os";
74813
74914
  import { createHash as createHash16 } from "node:crypto";
74915
+ function completeJsonlPrefix(buf) {
74916
+ const end = buf.lastIndexOf(10) + 1;
74917
+ if (end === buf.length) return end;
74918
+ try {
74919
+ JSON.parse(buf.subarray(end).toString("utf8"));
74920
+ return buf.length;
74921
+ } catch {
74922
+ return end;
74923
+ }
74924
+ }
74814
74925
  function readFrom(path2, fromByte, maxBytes) {
74815
74926
  let fd;
74816
74927
  try {
@@ -74827,11 +74938,12 @@ function readFrom(path2, fromByte, maxBytes) {
74827
74938
  for (; ; ) {
74828
74939
  const len = Math.min(cap, remaining);
74829
74940
  const buf = Buffer.allocUnsafe(len);
74830
- readSync(fd, buf, 0, len, start2);
74831
- if (len >= remaining) {
74832
- return { text: buf.toString("utf8"), nextOffset: size, more: false, ioFailed: false };
74941
+ const read2 = readSync(fd, buf, 0, len, start2);
74942
+ if (len >= remaining || read2 < len) {
74943
+ const end = completeJsonlPrefix(buf.subarray(0, read2));
74944
+ return { text: buf.subarray(0, end).toString("utf8"), nextOffset: start2 + end, more: false, ioFailed: false };
74833
74945
  }
74834
- const nl = buf.lastIndexOf(10);
74946
+ const nl = buf.subarray(0, read2).lastIndexOf(10);
74835
74947
  if (nl >= 0) {
74836
74948
  return {
74837
74949
  text: buf.subarray(0, nl + 1).toString("utf8"),
@@ -74848,41 +74960,30 @@ function readFrom(path2, fromByte, maxBytes) {
74848
74960
  closeSync(fd);
74849
74961
  }
74850
74962
  }
74851
- function transcriptSize(path2) {
74852
- try {
74853
- const fd = openSync(path2, "r");
74854
- try {
74855
- return fstatSync(fd).size;
74856
- } finally {
74857
- closeSync(fd);
74858
- }
74859
- } catch {
74860
- return 0;
74861
- }
74862
- }
74863
74963
  function readTail(path2, maxBytes) {
74864
74964
  return readTailWithLosses(path2, maxBytes).text;
74865
74965
  }
74866
- function readTailWithLosses(path2, maxBytes) {
74966
+ function readTailWithLosses(path2, maxBytes, completeLinesOnly = false) {
74867
74967
  let fd;
74868
74968
  try {
74869
74969
  fd = openSync(path2, "r");
74870
74970
  } catch {
74871
- return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true };
74971
+ return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true, nextOffset: 0 };
74872
74972
  }
74873
74973
  try {
74874
74974
  const size = fstatSync(fd).size;
74875
74975
  const start2 = Math.max(0, size - maxBytes);
74876
74976
  const len = size - start2;
74877
- if (len <= 0) return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: false };
74977
+ if (len <= 0) return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: false, nextOffset: 0 };
74878
74978
  const buf = Buffer.allocUnsafe(len);
74879
- readSync(fd, buf, 0, len, start2);
74880
- const text = buf.toString("utf8");
74979
+ const read2 = readSync(fd, buf, 0, len, start2);
74980
+ const end = completeLinesOnly ? completeJsonlPrefix(buf.subarray(0, read2)) : read2;
74981
+ const text = buf.subarray(0, end).toString("utf8");
74881
74982
  const firstNewline = buf.indexOf(10);
74882
74983
  const parseStartByte = firstNewline < 0 ? start2 : start2 + firstNewline + 1 - Buffer.byteLength(buf.subarray(0, firstNewline + 1).toString("utf8"));
74883
- return { text, bytesUnreachable: start2, parseStartByte, ioFailed: false };
74984
+ return { text, bytesUnreachable: start2, parseStartByte, ioFailed: false, nextOffset: start2 + end };
74884
74985
  } catch {
74885
- return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true };
74986
+ return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true, nextOffset: 0 };
74886
74987
  } finally {
74887
74988
  closeSync(fd);
74888
74989
  }
@@ -74945,10 +75046,11 @@ function isUserTurnBoundary(obj) {
74945
75046
  return blocks.some((b) => b["type"] === "text");
74946
75047
  }
74947
75048
  function readAssistantTurnsWithLosses(transcriptPath, includeThinking = true, maxBytes = 2e6) {
74948
- const tail = readTailWithLosses(transcriptPath, maxBytes);
75049
+ const tail = readTailWithLosses(transcriptPath, maxBytes, true);
74949
75050
  const parsed = parseAssistantTurns(tail.text, includeThinking, false, tail.parseStartByte);
74950
75051
  return {
74951
75052
  turns: parsed.turns,
75053
+ nextOffset: tail.nextOffset,
74952
75054
  losses: {
74953
75055
  parseFailures: parsed.parseFailures,
74954
75056
  ioFailed: tail.ioFailed,
@@ -75259,7 +75361,13 @@ function codexSessionRoots(env2 = process.env, home = homedir4()) {
75259
75361
  roots.add(join20(home, ".codex", "sessions"));
75260
75362
  return [...roots];
75261
75363
  }
75262
- function readCodexRolloutCwd(path2) {
75364
+ function readCodexRolloutIdentity(path2) {
75365
+ const legacy = `codex:${basename5(path2, ".jsonl")}`;
75366
+ const identity = (cwd, id) => {
75367
+ const uuid3 = typeof id === "string" && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(id) ? id.toLowerCase() : basename5(path2, ".jsonl").match(/([a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12})$/i)?.[1]?.toLowerCase();
75368
+ const sessionId = uuid3 ? `codex:${uuid3}` : legacy;
75369
+ return { cwd, sessionId, aliases: [legacy, ...uuid3 ? [uuid3] : []].filter((s) => s !== sessionId) };
75370
+ };
75263
75371
  let fd;
75264
75372
  try {
75265
75373
  fd = openSync(path2, "r");
@@ -75282,11 +75390,11 @@ function readCodexRolloutCwd(path2) {
75282
75390
  const o = JSON.parse(firstLine);
75283
75391
  const meta3 = o?.type === "session_meta" ? o.payload ?? o : o.payload ?? o;
75284
75392
  const cwd = meta3?.cwd ?? o?.cwd;
75285
- if (typeof cwd === "string") return cwd;
75393
+ if (typeof cwd === "string") return identity(cwd, o.type === "session_meta" ? meta3.id : void 0);
75286
75394
  } catch {
75287
75395
  }
75288
75396
  const m = firstLine.match(/"cwd"\s*:\s*"((?:[^"\\]|\\.)*)"/);
75289
- return m ? JSON.parse(`"${m[1]}"`) : null;
75397
+ return m ? identity(JSON.parse(`"${m[1]}"`)) : null;
75290
75398
  } catch {
75291
75399
  return null;
75292
75400
  } finally {
@@ -75344,15 +75452,16 @@ function codexRolloutsForCwd(cwd, opts = {}) {
75344
75452
  );
75345
75453
  activity.set(full, { size: st.size, activeAt: activityAtMs });
75346
75454
  if (activityAtMs < sinceMs) continue;
75347
- const sessionCwd = readCodexRolloutCwd(full);
75348
- if (sessionCwd === null) continue;
75455
+ const identity = readCodexRolloutIdentity(full);
75456
+ if (identity === null) continue;
75457
+ const sessionCwd = identity.cwd;
75349
75458
  let sessionOwner = owners.get(sessionCwd);
75350
75459
  if (sessionOwner === void 0) {
75351
75460
  sessionOwner = checkoutRootOf(sessionCwd);
75352
75461
  owners.set(sessionCwd, sessionOwner);
75353
75462
  }
75354
75463
  if (sessionOwner !== owner) continue;
75355
- out2.push({ path: full, sessionId: `codex:${basename5(name2, ".jsonl")}`, mtimeMs: st.mtimeMs, activityAtMs });
75464
+ out2.push({ path: full, sessionId: identity.sessionId, mtimeMs: st.mtimeMs, activityAtMs });
75356
75465
  }
75357
75466
  }
75358
75467
  for (const path2 of activity.keys()) if (!observed.has(path2)) activity.delete(path2);
@@ -80918,7 +81027,7 @@ function createWatchBreaker(deps) {
80918
81027
  }
80919
81028
 
80920
81029
  // src/engine.ts
80921
- var DAEMON_VERSION = true ? "2.0.14-dev.3118" : "2.0.0-alpha.0";
81030
+ var DAEMON_VERSION = true ? "2.0.14-dev.3125" : "2.0.0-alpha.0";
80922
81031
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
80923
81032
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
80924
81033
  var GIT_OP_MUTE_MS = 4e3;
@@ -82651,7 +82760,7 @@ function createWorkspaceEngine(opts) {
82651
82760
  readLosses.sliceGuardHits = 0;
82652
82761
  }
82653
82762
  };
82654
- const harvestTurns = async (sessionId, transcriptPath) => {
82763
+ const harvestTurnsUnqueued = async (sessionId, transcriptPath) => {
82655
82764
  const known = turnOffset.get(sessionId);
82656
82765
  let turns;
82657
82766
  let nextOffset;
@@ -82659,7 +82768,7 @@ function createWorkspaceEngine(opts) {
82659
82768
  if (known === void 0) {
82660
82769
  const first = readAssistantTurnsWithLosses(transcriptPath);
82661
82770
  turns = first.turns;
82662
- nextOffset = transcriptSize(transcriptPath);
82771
+ nextOffset = first.nextOffset;
82663
82772
  readLosses.bytesUnreachable += first.losses.bytesUnreachable;
82664
82773
  readLosses.parseFailures += first.losses.parseFailures;
82665
82774
  if (first.losses.ioFailed) readLosses.ioFailures++;
@@ -82719,6 +82828,37 @@ function createWorkspaceEngine(opts) {
82719
82828
  }
82720
82829
  setImmediate(() => void harvestTexts(e.sessionId, [{ text: e.text }]));
82721
82830
  };
82831
+ const harvestInFlight = /* @__PURE__ */ new Map();
82832
+ const repairedSessions = /* @__PURE__ */ new Set();
82833
+ const harvestTurns = async (inputSessionId, transcriptPath) => {
82834
+ const identity = basename7(transcriptPath).startsWith("rollout-") ? readCodexRolloutIdentity(transcriptPath) : null;
82835
+ const sessionId = identity?.sessionId ?? inputSessionId;
82836
+ const work = (harvestInFlight.get(sessionId) ?? Promise.resolve()).catch(() => {
82837
+ }).then(async () => {
82838
+ if (identity && !repairedSessions.has(sessionId)) {
82839
+ store.repairSessionAliases(sessionId, identity.aliases);
82840
+ opts.sharedStore?.repairSessionAliases(sessionId, identity.aliases);
82841
+ repairedSessions.add(sessionId);
82842
+ }
82843
+ for (const alias of identity?.aliases ?? []) {
82844
+ const offset = turnOffset.get(alias);
82845
+ if (offset !== void 0 && offset > (turnOffset.get(sessionId) ?? -1)) {
82846
+ turnOffset.set(sessionId, offset);
82847
+ const uuid3 = lastTurnUuid.get(alias);
82848
+ if (uuid3) lastTurnUuid.set(sessionId, uuid3);
82849
+ }
82850
+ turnOffset.delete(alias);
82851
+ lastTurnUuid.delete(alias);
82852
+ }
82853
+ await harvestTurnsUnqueued(sessionId, transcriptPath);
82854
+ });
82855
+ harvestInFlight.set(sessionId, work);
82856
+ try {
82857
+ await work;
82858
+ } finally {
82859
+ if (harvestInFlight.get(sessionId) === work) harvestInFlight.delete(sessionId);
82860
+ }
82861
+ };
82722
82862
  const harvestSession = async (sessionId, transcriptPath) => {
82723
82863
  await harvestTurns(sessionId, transcriptPath);
82724
82864
  for (const sub of subagentTranscripts(transcriptPath, sessionId)) {
@@ -85395,12 +85535,12 @@ var GENERATED_PATH_RES = [
85395
85535
  ];
85396
85536
  var RELEASE_SUBJECT_RE = /^(release|bump|chore\(release\)|v?\d+\.\d+\.\d+)/i;
85397
85537
  var RELEASE_TAG_RE = /^v?\d+\.\d+/;
85398
- function basename7(path2) {
85538
+ function basename8(path2) {
85399
85539
  const i2 = path2.lastIndexOf("/");
85400
85540
  return i2 < 0 ? path2 : path2.slice(i2 + 1);
85401
85541
  }
85402
85542
  function isLockfilePath(path2) {
85403
- const name2 = basename7(path2);
85543
+ const name2 = basename8(path2);
85404
85544
  return LOCKFILE_NAMES.has(name2) || LOCKFILE_SUFFIX_RE.test(name2);
85405
85545
  }
85406
85546
  function isGeneratedPath(path2) {
@@ -89419,7 +89559,7 @@ function buildCodexHooksToml(port, options) {
89419
89559
  const quote = (value) => {
89420
89560
  if (platform === "win32") {
89421
89561
  if (/["\r\n]/.test(value)) throw new Error("Unsupported character in Codex hook path");
89422
- return "'" + value.replace(/'/g, "''") + "'";
89562
+ return "'" + value.replace(/['\u2018-\u201B]/g, (quote2) => quote2 + quote2) + "'";
89423
89563
  }
89424
89564
  return "'" + value.replace(/'/g, "'\\''") + "'";
89425
89565
  };
@@ -89437,6 +89577,9 @@ import { readFileSync } from 'node:fs';
89437
89577
 
89438
89578
  try {
89439
89579
  const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
89580
+ if (typeof payload.session_id === 'string' && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(payload.session_id)) {
89581
+ payload.session_id = 'codex:' + payload.session_id.toLowerCase();
89582
+ }
89440
89583
  const port = Number(process.argv[2]);
89441
89584
  const event = payload.hook_event_name;
89442
89585
  if (!Number.isInteger(port) || port < 1 || port > 65535) process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.14-dev.3118",
3
+ "version": "2.0.14-dev.3125",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -24605,6 +24605,71 @@ function installHistoryCacheFloor(db) {
24605
24605
  });
24606
24606
  }
24607
24607
 
24608
+ // ../../packages/local-graph/src/session-evidence.ts
24609
+ function repairSessionEvidence(input, aliases = /* @__PURE__ */ new Map()) {
24610
+ const mapping = new Map(aliases);
24611
+ const uuid3 = "[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}";
24612
+ const codex = new RegExp(`^codex:(?:rollout-.*?)?(${uuid3})$`, "i");
24613
+ const arrays = /* @__PURE__ */ new Set(["sources", "recurrenceSources", "contributors", "inferredContributors", "citeSessions"]);
24614
+ const scalars = /* @__PURE__ */ new Set(["discriminatorSource", "originSession", "sessionId", "sourceSession"]);
24615
+ const discover = (value, key = "") => {
24616
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
24617
+ const match = value.match(codex);
24618
+ if (match) {
24619
+ const canonical = `codex:${match[1].toLowerCase()}`;
24620
+ mapping.set(value, canonical);
24621
+ mapping.set(match[1].toLowerCase(), canonical);
24622
+ }
24623
+ } else if (Array.isArray(value)) value.forEach((v) => discover(v, key));
24624
+ else if (value && typeof value === "object") {
24625
+ for (const [k, v] of Object.entries(value)) discover(v, key === "citeAnchorHints" ? key : k);
24626
+ }
24627
+ };
24628
+ discover(input);
24629
+ const rewrite = (value, key = "") => {
24630
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
24631
+ return mapping.get(value) ?? mapping.get(value.toLowerCase()) ?? value;
24632
+ }
24633
+ if (Array.isArray(value)) {
24634
+ const values = value.map((v) => rewrite(v, key));
24635
+ return arrays.has(key) || key === "citeAnchorHints" ? [...new Set(values)] : values;
24636
+ }
24637
+ if (!value || typeof value !== "object") return value;
24638
+ const before = value;
24639
+ const after = Object.fromEntries(Object.entries(before).map(([k, v]) => [k, rewrite(v, key === "citeAnchorHints" ? key : k)]));
24640
+ for (const field of ["sources", "contributors"]) {
24641
+ const old = before[field], next = after[field];
24642
+ if (!Array.isArray(old) || !Array.isArray(next) || old.length <= next.length) continue;
24643
+ after["sessionAliasRepair"] = { originalCount: old.length, distinctCount: next.length };
24644
+ if (field === "sources" && typeof after["corroborations"] === "number") {
24645
+ after["corroborations"] = Math.min(after["corroborations"], Math.max(0, next.length - 1));
24646
+ if (after["corroborations"] === 0) after["provisional"] = true;
24647
+ }
24648
+ if (field === "contributors" && typeof after["confirmed"] === "number") {
24649
+ after["preAliasRepairConfirmed"] = after["confirmed"];
24650
+ after["confirmed"] = Math.min(after["confirmed"], next.length);
24651
+ }
24652
+ }
24653
+ return after;
24654
+ };
24655
+ const repaired = rewrite(input);
24656
+ const source = repaired["discriminatorSource"];
24657
+ if (typeof source === "string") {
24658
+ const global = repaired["perContext"]?.["*"];
24659
+ const contributors = Array.isArray(global?.["contributors"]) ? global["contributors"] : [];
24660
+ const inferred = new Set(Array.isArray(global?.["inferredContributors"]) ? global["inferredContributors"] : []);
24661
+ if (contributors.includes(source) || contributors.filter((id) => !inferred.has(id)).length < 2) {
24662
+ repaired["withdrawnDiscriminator"] = repaired["discriminator"];
24663
+ repaired["discriminatorWithdrawalReason"] = "insufficient-independent-witnesses";
24664
+ repaired["provisional"] = true;
24665
+ delete repaired["discriminator"];
24666
+ delete repaired["discriminatorSource"];
24667
+ delete repaired["discriminatorAsked"];
24668
+ }
24669
+ }
24670
+ return repaired;
24671
+ }
24672
+
24608
24673
  // ../../packages/local-graph/src/store.ts
24609
24674
  var LOCAL_RULE_OVERRIDES = {
24610
24675
  ANCHORED_AT: {
@@ -24849,6 +24914,12 @@ var SqliteGraphStore = class {
24849
24914
  }
24850
24915
  this.db.exec(TEMPORAL_INDEX_SQL);
24851
24916
  installHistoryCacheFloor(this.db);
24917
+ if (!this.db.prepare("SELECT value FROM store_meta WHERE key = 'session-evidence-v1'").get()) {
24918
+ this.db.transaction(() => {
24919
+ this.repairSessionAliases();
24920
+ this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES ('session-evidence-v1', 'done')").run();
24921
+ });
24922
+ }
24852
24923
  this.stmts = {
24853
24924
  insertNode: this.db.prepare(`
24854
24925
  INSERT INTO nodes (
@@ -25097,6 +25168,23 @@ var SqliteGraphStore = class {
25097
25168
  this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
25098
25169
  });
25099
25170
  }
25171
+ repairSessionAliases(canonical, aliases = []) {
25172
+ const mapping = new Map(aliases.map((alias) => [alias, canonical]));
25173
+ let changed = 0;
25174
+ for (const table of ["nodes", "edges"]) {
25175
+ const predicates = canonical ? aliases.map(() => "instr(attrs_json, ?) > 0") : ["instr(attrs_json, 'codex:') > 0", "instr(attrs_json, 'discriminatorSource') > 0"];
25176
+ if (!predicates.length) continue;
25177
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM ${table} WHERE ${predicates.join(" OR ")}`).all(...canonical ? aliases : []);
25178
+ const update = this.db.prepare(`UPDATE ${table} SET attrs_json = ?${table === "edges" ? ", type = CASE WHEN type = 'CONFIRMS' AND json_extract(?, '$.provisional') = 1 THEN 'INDICATES' ELSE type END" : ""} WHERE id = ?`);
25179
+ for (const row of rows) {
25180
+ const attrs = JSON.stringify(repairSessionEvidence(JSON.parse(row.attrs_json), mapping));
25181
+ if (attrs === row.attrs_json) continue;
25182
+ update.run(...table === "edges" ? [attrs, attrs, row.id] : [attrs, row.id]);
25183
+ changed++;
25184
+ }
25185
+ }
25186
+ return changed;
25187
+ }
25100
25188
  deleteNode(id) {
25101
25189
  this.stmts.deleteNode.run(id);
25102
25190
  }