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

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.3126" : "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)) {
@@ -84685,12 +84825,12 @@ function startUpdatePoller(opts) {
84685
84825
  const check2 = opts.check ?? (() => checkForUpdate(opts.channel));
84686
84826
  const base = opts.intervalMs ?? POLL_INTERVAL_MS[opts.channel];
84687
84827
  let pending = null;
84688
- let inFlight = false;
84828
+ let inFlight2 = false;
84689
84829
  let timer = null;
84690
84830
  let stopped = false;
84691
84831
  const runOnce = async () => {
84692
- if (inFlight) return;
84693
- inFlight = true;
84832
+ if (inFlight2) return;
84833
+ inFlight2 = true;
84694
84834
  try {
84695
84835
  const status = await check2();
84696
84836
  if (status.latest === null) return;
@@ -84700,7 +84840,7 @@ function startUpdatePoller(opts) {
84700
84840
  if (changed) opts.onChange?.(pending);
84701
84841
  } catch {
84702
84842
  } finally {
84703
- inFlight = false;
84843
+ inFlight2 = false;
84704
84844
  }
84705
84845
  };
84706
84846
  const schedule = () => {
@@ -85269,7 +85409,31 @@ import { extname as extname3 } from "node:path";
85269
85409
  init_profile2();
85270
85410
  var exec = promisify2(execFile4);
85271
85411
  var KINDS = { function: "Function", class: "Class", method: "Method", interface: "Interface", const: "Const", enum: "Enum", typealias: "TypeAlias", namespace: "Namespace", property: "Property" };
85412
+ var inFlight = /* @__PURE__ */ new Map();
85272
85413
  async function syncHistoryRevision(opts) {
85414
+ const current = inFlight.get(opts.root);
85415
+ if (current) return current;
85416
+ const work = (async () => {
85417
+ const started2 = Date.now();
85418
+ let changed = 0, remaining = false;
85419
+ const pages = Math.min(20, Math.max(1, opts.maxPages ?? 10));
85420
+ for (let i2 = 0; i2 < pages; i2++) {
85421
+ const result = await syncHistoryRevisionPage(opts);
85422
+ changed += result.changed;
85423
+ remaining = result.remaining;
85424
+ if (!remaining || Date.now() - started2 >= (opts.budgetMs ?? 45e3)) break;
85425
+ await new Promise((resolve7) => setImmediate(resolve7));
85426
+ }
85427
+ return { changed, remaining };
85428
+ })();
85429
+ inFlight.set(opts.root, work);
85430
+ try {
85431
+ return await work;
85432
+ } finally {
85433
+ if (inFlight.get(opts.root) === work) inFlight.delete(opts.root);
85434
+ }
85435
+ }
85436
+ async function syncHistoryRevisionPage(opts) {
85273
85437
  const { root, profile, store, cloud, salt } = opts;
85274
85438
  if (!profile.projectId) return { changed: 0, remaining: false };
85275
85439
  const projectId = profile.projectId;
@@ -85279,7 +85443,8 @@ async function syncHistoryRevision(opts) {
85279
85443
  const oldCursor = profile.historyRevisionCursor;
85280
85444
  const same = oldCursor?.atCommit === atCommit;
85281
85445
  if (same && oldCursor.complete && Date.now() - oldCursor.updatedAt < 60 * 6e4) return { changed: 0, remaining: false };
85282
- const after = same && !oldCursor.complete ? oldCursor.after : void 0;
85446
+ const after = oldCursor && !oldCursor.complete ? oldCursor.after : void 0;
85447
+ const sweepCommit = after ? oldCursor.atCommit : atCommit;
85283
85448
  const page = await cloud.getHistoryOccurrences(projectId, atCommit, after);
85284
85449
  if (!Array.isArray(page.occurrences) || page.occurrences.length > 50 || page.occurrences.some((o, i2) => o.occurrenceId <= (page.occurrences[i2 - 1]?.occurrenceId ?? after ?? "")) || page.cursor !== null && page.cursor !== page.occurrences.at(-1)?.occurrenceId) throw new Error("invalid history occurrence page");
85285
85450
  const parents = (await git3(["rev-list", "--parents", "--max-count=8192", atCommit])).trim().split("\n").filter(Boolean).map((row) => {
@@ -85335,7 +85500,7 @@ async function syncHistoryRevision(opts) {
85335
85500
  }
85336
85501
  const response = page.occurrences.length ? await cloud.reconcileHistory(projectId, { atCommit, parents, observations }) : { atCommit, receipts: [] };
85337
85502
  if (response.atCommit !== atCommit || response.receipts.length !== page.occurrences.length || new Set(response.receipts.map((r) => r.occurrenceId)).size !== response.receipts.length || response.receipts.some((r) => r.atCommit !== atCommit || !observations.some((o) => o.occurrenceId === r.occurrenceId))) throw new Error("incomplete history reconciliation response");
85338
- if ((await git3(["rev-parse", "--verify", "HEAD"])).trim() !== atCommit) return { changed: 0, remaining: true };
85503
+ const headChanged = (await git3(["rev-parse", "--verify", "HEAD"])).trim() !== atCommit;
85339
85504
  let changed = 0;
85340
85505
  for (const occurrence of page.occurrences) {
85341
85506
  const node2 = store.getNode(occurrence.canonicalId) ?? (occurrence.nodeId ? store.getNode(occurrence.nodeId) : null);
@@ -85351,9 +85516,9 @@ async function syncHistoryRevision(opts) {
85351
85516
  } } });
85352
85517
  changed++;
85353
85518
  }
85354
- profile.historyRevisionCursor = { atCommit, ...page.cursor ? { after: page.cursor } : {}, complete: page.cursor === null, updatedAt: Date.now() };
85519
+ profile.historyRevisionCursor = { atCommit: sweepCommit, ...page.cursor ? { after: page.cursor } : {}, complete: page.cursor === null, updatedAt: Date.now() };
85355
85520
  (opts.save ?? ((p) => saveProfile(root, p)))(profile);
85356
- return { changed, remaining: page.cursor !== null };
85521
+ return { changed, remaining: page.cursor !== null || headChanged || sweepCommit !== atCommit };
85357
85522
  }
85358
85523
 
85359
85524
  // src/project-history.ts
@@ -85395,12 +85560,12 @@ var GENERATED_PATH_RES = [
85395
85560
  ];
85396
85561
  var RELEASE_SUBJECT_RE = /^(release|bump|chore\(release\)|v?\d+\.\d+\.\d+)/i;
85397
85562
  var RELEASE_TAG_RE = /^v?\d+\.\d+/;
85398
- function basename7(path2) {
85563
+ function basename8(path2) {
85399
85564
  const i2 = path2.lastIndexOf("/");
85400
85565
  return i2 < 0 ? path2 : path2.slice(i2 + 1);
85401
85566
  }
85402
85567
  function isLockfilePath(path2) {
85403
- const name2 = basename7(path2);
85568
+ const name2 = basename8(path2);
85404
85569
  return LOCKFILE_NAMES.has(name2) || LOCKFILE_SUFFIX_RE.test(name2);
85405
85570
  }
85406
85571
  function isGeneratedPath(path2) {
@@ -87914,6 +88079,8 @@ async function startMultiDaemon(opts = {}) {
87914
88079
  // eslint-disable-next-line no-console
87915
88080
  log: (line) => console.log(line)
87916
88081
  });
88082
+ const revisionBacklog = /* @__PURE__ */ new Set();
88083
+ const lastAnchorPull = /* @__PURE__ */ new Map();
87917
88084
  const pullProjectAnchors = async (r, client, salt) => {
87918
88085
  const donePass = markPass(`anchor-pull:${r.engine.profile.name}`);
87919
88086
  const beforeCorrections = r.engine.profile.projectHistoryCorrectionCursor ?? 0;
@@ -87930,6 +88097,8 @@ async function startMultiDaemon(opts = {}) {
87930
88097
  }
87931
88098
  try {
87932
88099
  const revision = await syncHistoryRevision({ root: r.root, profile: r.engine.profile, store: r.engine.store, cloud: client, salt });
88100
+ if (revision.remaining) revisionBacklog.add(r.root);
88101
+ else revisionBacklog.delete(r.root);
87933
88102
  if (revision.changed) r.engine.markContextDirty();
87934
88103
  } catch (err2) {
87935
88104
  console.warn(`[errata] history revision reconciliation failed for ${r.engine.profile.name}: ${err2 instanceof Error ? err2.message : String(err2)}`);
@@ -87946,30 +88115,39 @@ async function startMultiDaemon(opts = {}) {
87946
88115
  }
87947
88116
  };
87948
88117
  const ANCHOR_PULL_INTERVAL_MS = 10 * 60 * 1e3;
88118
+ let anchorPullRunning = false;
87949
88119
  const anchorPullAll = async () => {
87950
- const c = loadConfig();
87951
- if (registryCorrupt()) return;
87952
- for (const r of records) {
87953
- const projectId = r.engine.profile.projectId;
87954
- if (!projectId || !effectiveConsent(c, r.engine.profile).sync) continue;
87955
- const identity = identityFor(liveWorkspaceEntry(r.entry));
87956
- if (identity.invalid) {
87957
- noteIdentityBlock(r.id, r.entry.name, identity);
87958
- continue;
87959
- }
87960
- if (identity.rule !== "workspace-binding" && !hasCloudCredential(c)) continue;
87961
- const client = cloudForIdentity(identity);
87962
- try {
87963
- const { salt } = await resolveProjectSymbolSalt(client, projectId);
87964
- await pullProjectAnchors(r, client, salt);
87965
- } catch (err2) {
87966
- console.warn(`[errata] anchor pull skipped for ${r.engine.profile.name}: ${err2 instanceof Error ? err2.message : String(err2)}`);
88120
+ if (anchorPullRunning) return;
88121
+ anchorPullRunning = true;
88122
+ try {
88123
+ const c = loadConfig();
88124
+ if (registryCorrupt()) return;
88125
+ for (const r of records) {
88126
+ if (!revisionBacklog.has(r.root) && Date.now() - (lastAnchorPull.get(r.root) ?? 0) < ANCHOR_PULL_INTERVAL_MS) continue;
88127
+ const projectId = r.engine.profile.projectId;
88128
+ if (!projectId || !effectiveConsent(c, r.engine.profile).sync) continue;
88129
+ const identity = identityFor(liveWorkspaceEntry(r.entry));
88130
+ if (identity.invalid) {
88131
+ noteIdentityBlock(r.id, r.entry.name, identity);
88132
+ continue;
88133
+ }
88134
+ if (identity.rule !== "workspace-binding" && !hasCloudCredential(c)) continue;
88135
+ const client = cloudForIdentity(identity);
88136
+ try {
88137
+ const { salt } = await resolveProjectSymbolSalt(client, projectId);
88138
+ await pullProjectAnchors(r, client, salt);
88139
+ lastAnchorPull.set(r.root, Date.now());
88140
+ } catch (err2) {
88141
+ console.warn(`[errata] anchor pull skipped for ${r.engine.profile.name}: ${err2 instanceof Error ? err2.message : String(err2)}`);
88142
+ }
87967
88143
  }
88144
+ } finally {
88145
+ anchorPullRunning = false;
87968
88146
  }
87969
88147
  };
87970
88148
  const anchorPull = setInterval(() => {
87971
88149
  void anchorPullAll();
87972
- }, ANCHOR_PULL_INTERVAL_MS);
88150
+ }, 6e4);
87973
88151
  const ambientLinkAll = async () => {
87974
88152
  const c = loadConfig();
87975
88153
  if (registryCorrupt()) return;
@@ -89419,7 +89597,7 @@ function buildCodexHooksToml(port, options) {
89419
89597
  const quote = (value) => {
89420
89598
  if (platform === "win32") {
89421
89599
  if (/["\r\n]/.test(value)) throw new Error("Unsupported character in Codex hook path");
89422
- return "'" + value.replace(/'/g, "''") + "'";
89600
+ return "'" + value.replace(/['\u2018-\u201B]/g, (quote2) => quote2 + quote2) + "'";
89423
89601
  }
89424
89602
  return "'" + value.replace(/'/g, "'\\''") + "'";
89425
89603
  };
@@ -89437,6 +89615,9 @@ import { readFileSync } from 'node:fs';
89437
89615
 
89438
89616
  try {
89439
89617
  const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
89618
+ if (typeof payload.session_id === 'string' && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(payload.session_id)) {
89619
+ payload.session_id = 'codex:' + payload.session_id.toLowerCase();
89620
+ }
89440
89621
  const port = Number(process.argv[2]);
89441
89622
  const event = payload.hook_event_name;
89442
89623
  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.3126",
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
  }