@inerrata-corporation/errata 2.0.22-dev.4430 → 2.0.22-dev.4530

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.
@@ -14712,6 +14712,17 @@ var HistoryRevisionRequestSchema = external_exports.strictObject({
14712
14712
  dependencyContext: HistoryDependencyContextSchema.optional()
14713
14713
  })).max(50)
14714
14714
  });
14715
+ var HistoryCarryRequestSchema = external_exports.strictObject({
14716
+ fromCommit: Sha,
14717
+ atCommit: Sha,
14718
+ parents: external_exports.array(external_exports.strictObject({ commit: Sha, parents: external_exports.array(Sha).max(32) })).max(8192),
14719
+ /** Every symbol in a file the range changed, both sides; `null` = too many to name, treat all as changed. */
14720
+ changedSymbols: external_exports.array(external_exports.string().regex(/^sym_[a-f0-9]{8,64}$/)).max(5e3).nullable(),
14721
+ /** Manifests the range changed; `null` = a lockfile (or too many manifests) changed, treat all as changed. */
14722
+ changedManifests: external_exports.array(external_exports.string().regex(/^hmanifest_[a-f0-9]{32}$/)).max(512).nullable()
14723
+ });
14724
+ var HISTORY_REVIEW_BUDGET = 48e3;
14725
+ var HISTORY_REVIEW_POLICY = `reviewer-view-${HISTORY_REVIEW_BUDGET}`;
14715
14726
 
14716
14727
  // ../../packages/shared/src/edit-placeholder.ts
14717
14728
  var AUTO_MINT_PREFIX = "addressed by an edit to ";
@@ -15667,6 +15678,114 @@ function textIndexReady(db) {
15667
15678
  return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
15668
15679
  }
15669
15680
 
15681
+ // ../../packages/local-graph/src/exposure-attrs.ts
15682
+ var EXPOSURE_ATTRS = [
15683
+ "shownCount",
15684
+ "evictedCount",
15685
+ "firstShownAt",
15686
+ "firstEvictedAt",
15687
+ "firstShownAtSeq",
15688
+ "lastShownAtSeq",
15689
+ "lastEvictedAtSeq",
15690
+ "shownSinceTouch",
15691
+ "shownSinceTouchAt",
15692
+ "exposureLog",
15693
+ "exposureLogStart",
15694
+ "exposureLogSince",
15695
+ "exposureFirst",
15696
+ "exposureTrimmed"
15697
+ ];
15698
+ var EXPOSURE_SCOPE = "exposure";
15699
+
15700
+ // ../../packages/local-graph/src/change-feed.ts
15701
+ var CHANGE_FEED_KEY = "change-feed";
15702
+ var ANY_CHANGE = "*";
15703
+ var CHANGE_LOG_KEEP = 25e4;
15704
+ var PRUNED_TO_KEY = "change-log-pruned-to";
15705
+ function installChangeFeed(db) {
15706
+ if (changeFeedInstalled(db)) return;
15707
+ db.transaction(() => {
15708
+ db.exec("CREATE TABLE IF NOT EXISTS change_seq (scope TEXT PRIMARY KEY, seq INTEGER NOT NULL) WITHOUT ROWID");
15709
+ db.exec(`CREATE TABLE IF NOT EXISTS change_log (change_seq INTEGER PRIMARY KEY, kind TEXT NOT NULL, id TEXT NOT NULL,
15710
+ label TEXT, from_id TEXT, to_id TEXT)`);
15711
+ for (const name of ["nodes", "edges"].flatMap((t) => ["insert", "update", "delete"].map((op) => `change_feed_${t}_${op}`))) {
15712
+ db.exec(`DROP TRIGGER IF EXISTS ${name}`);
15713
+ }
15714
+ const changed = (table) => columns(db, table).map((c) => `OLD."${c}" IS NOT NEW."${c}"`).join(" OR ");
15715
+ const strip = (row) => `json_remove(${row}.attrs_json, ${EXPOSURE_ATTRS.map((a) => `'$.${a}'`).join(", ")})`;
15716
+ const exposureOnly = `(${columns(db, "nodes").filter((c) => c !== "attrs_json").map((c) => `OLD."${c}" IS NEW."${c}"`).join(" AND ")}
15717
+ AND CASE WHEN json_valid(OLD.attrs_json) AND json_valid(NEW.attrs_json) THEN ${strip("OLD")} IS ${strip("NEW")} ELSE 0 END)`;
15718
+ const bump = (...scopes) => `INSERT INTO change_seq(scope, seq) VALUES ${scopes.map((s) => `(${s}, 1)`).join(", ")}
15719
+ ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;`;
15720
+ const seq = `(SELECT seq FROM change_seq WHERE scope = '*')`;
15721
+ const logNode = (row) => `INSERT INTO change_log(change_seq, kind, id, label) VALUES (${seq}, 'node', ${row}.id, ${row}.label);`;
15722
+ const logEdge = (row) => `INSERT INTO change_log(change_seq, kind, id, label, from_id, to_id) VALUES (${seq}, 'edge', ${row}.id, ${row}.type, ${row}.from_id, ${row}.to_id);`;
15723
+ const node = (row) => `'node:' || ${row}.label`;
15724
+ const edge = (row) => `'edge:' || ${row}.type`;
15725
+ db.exec(`CREATE TRIGGER change_feed_nodes_insert AFTER INSERT ON nodes BEGIN ${bump(`'*'`, node("NEW"))} ${logNode("NEW")} END;`);
15726
+ db.exec(`CREATE TRIGGER change_feed_nodes_update AFTER UPDATE ON nodes WHEN ${changed("nodes")} BEGIN
15727
+ ${bump(`'*'`)}
15728
+ INSERT INTO change_seq(scope, seq) SELECT CASE WHEN ${exposureOnly} THEN '${EXPOSURE_SCOPE}' ELSE ${node("NEW")} END, 1 WHERE 1
15729
+ ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
15730
+ INSERT INTO change_seq(scope, seq) SELECT ${node("OLD")}, 1 WHERE OLD.label IS NOT NEW.label
15731
+ ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
15732
+ ${logNode("NEW")}
15733
+ END;`);
15734
+ db.exec(`CREATE TRIGGER change_feed_nodes_delete AFTER DELETE ON nodes BEGIN ${bump(`'*'`, node("OLD"))} ${logNode("OLD")} END;`);
15735
+ db.exec(`CREATE TRIGGER change_feed_edges_insert AFTER INSERT ON edges BEGIN ${bump(`'*'`, edge("NEW"))} ${logEdge("NEW")} END;`);
15736
+ db.exec(`CREATE TRIGGER change_feed_edges_update AFTER UPDATE ON edges WHEN ${changed("edges")} BEGIN
15737
+ ${bump(`'*'`, edge("NEW"))}
15738
+ INSERT INTO change_seq(scope, seq) SELECT ${edge("OLD")}, 1 WHERE OLD.type IS NOT NEW.type
15739
+ ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
15740
+ ${logEdge("NEW")}
15741
+ END;`);
15742
+ db.exec(`CREATE TRIGGER change_feed_edges_delete AFTER DELETE ON edges BEGIN ${bump(`'*'`, edge("OLD"))} ${logEdge("OLD")} END;`);
15743
+ db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(PRUNED_TO_KEY, String(readChangeSeq(db)));
15744
+ db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(CHANGE_FEED_KEY, columnSignature(db));
15745
+ });
15746
+ }
15747
+ function readChangeSeq(db, scopes = [ANY_CHANGE]) {
15748
+ if (!scopes.length) return 0;
15749
+ const row = db.prepare(`SELECT coalesce(sum(seq), 0) AS n FROM change_seq WHERE scope IN (${scopes.map(() => "?").join(",")})`).get(...scopes);
15750
+ return Number(row?.n ?? 0);
15751
+ }
15752
+ function changeLogCovers(db, sinceSeq) {
15753
+ const prunedTo = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? NaN);
15754
+ return Number.isFinite(prunedTo) && sinceSeq >= prunedTo;
15755
+ }
15756
+ function readLineageChangedFiles(db, sinceSeq) {
15757
+ if (!changeLogCovers(db, sinceSeq)) return null;
15758
+ const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
15759
+ return rows.map((r) => r.id);
15760
+ }
15761
+ var LINEAGE_CHANGED_FILES_SQL = `
15762
+ SELECT id FROM change_log WHERE change_seq > :s AND kind = 'node' AND label = 'File'
15763
+ UNION SELECT from_id FROM change_log WHERE change_seq > :s AND kind = 'edge' AND label IN ('DEFINES','CONTAINS')
15764
+ UNION SELECT e.from_id FROM (SELECT DISTINCT id FROM change_log WHERE change_seq > :s AND kind = 'node') c
15765
+ CROSS JOIN edges e ON e.to_id = c.id AND e.type IN ('DEFINES','CONTAINS')
15766
+ `;
15767
+ function pruneChangeLog(db) {
15768
+ const max = Number(db.prepare("SELECT coalesce(max(change_seq), 0) AS n FROM change_log").get()?.["n"] ?? 0);
15769
+ const cut = max - CHANGE_LOG_KEEP;
15770
+ if (cut <= 0) return 0;
15771
+ let removed = 0;
15772
+ db.transaction(() => {
15773
+ removed = Number(db.prepare("DELETE FROM change_log WHERE change_seq <= ?").run(cut).changes);
15774
+ const prev = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? 0);
15775
+ db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(PRUNED_TO_KEY, String(Math.max(prev, cut)));
15776
+ });
15777
+ return removed;
15778
+ }
15779
+ function changeFeedInstalled(db) {
15780
+ return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(CHANGE_FEED_KEY)?.["value"] === columnSignature(db);
15781
+ }
15782
+ function columns(db, table) {
15783
+ return db.prepare(`SELECT name FROM pragma_table_info('${table}') ORDER BY cid`).all().map((r) => r.name);
15784
+ }
15785
+ function columnSignature(db) {
15786
+ return `5|${columns(db, "nodes").join(",")}|${columns(db, "edges").join(",")}|${EXPOSURE_ATTRS.join(",")}`;
15787
+ }
15788
+
15670
15789
  // ../../packages/local-graph/src/lineage-integrity.ts
15671
15790
  var statements = /* @__PURE__ */ new WeakMap();
15672
15791
  function stmt(db, sql) {
@@ -16277,6 +16396,7 @@ var SqliteGraphStore = class {
16277
16396
  });
16278
16397
  }
16279
16398
  installLineageIntegrityRepair(this.db);
16399
+ installChangeFeed(this.db);
16280
16400
  this.stmts = {
16281
16401
  insertNode: this.db.prepare(`
16282
16402
  INSERT INTO nodes (
@@ -17064,12 +17184,39 @@ var SqliteGraphStore = class {
17064
17184
  mutationCount() {
17065
17185
  return this.mutations;
17066
17186
  }
17187
+ changeStamp() {
17188
+ const { data_version } = this.db.pragma("data_version");
17189
+ const { n } = this.db.prepare("SELECT total_changes() AS n").get();
17190
+ return `${data_version}:${n}`;
17191
+ }
17192
+ changeSeq(scopes) {
17193
+ return readChangeSeq(this.db, scopes);
17194
+ }
17195
+ lineageChangedFiles(sinceSeq) {
17196
+ return readLineageChangedFiles(this.db, sinceSeq);
17197
+ }
17198
+ pruneChangeLog() {
17199
+ return pruneChangeLog(this.db);
17200
+ }
17201
+ fileRowStates(ids) {
17202
+ if (ids.length === 0) return [];
17203
+ if (ids.length > 500) throw new RangeError("fileRowStates accepts at most 500 ids per read");
17204
+ return this.db.prepare(`SELECT id, last_updated_at AS lastUpdatedAt, valid_to AS validTo FROM nodes WHERE id IN (${ids.map(() => "?").join(",")})`).all(...ids);
17205
+ }
17206
+ // Read and advanced in SQL, never from a per-connection copy: pass workers and
17207
+ // MCP processes open their own connections and advance the same row, so a
17208
+ // copy loaded at open went stale, and `copy + 1` wrote a duplicate or a
17209
+ // backwards seq from whichever connection had fallen behind.
17067
17210
  currentIngestSeq() {
17211
+ const row = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
17212
+ this.ingestSeq = row ? Number(row.value) : 0;
17068
17213
  return this.ingestSeq;
17069
17214
  }
17070
17215
  nextIngestSeq() {
17071
- this.ingestSeq += 1;
17072
- this.stmts.setIngestSeq.run({ v: String(this.ingestSeq) });
17216
+ const row = this.db.prepare(
17217
+ "INSERT INTO store_meta (key, value) VALUES ('ingest_seq', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(value AS INTEGER) + 1 RETURNING value"
17218
+ ).get();
17219
+ this.ingestSeq = Number(row.value);
17073
17220
  return this.ingestSeq;
17074
17221
  }
17075
17222
  edgeCount() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.22-dev.4430",
3
+ "version": "2.0.22-dev.4530",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -16184,7 +16184,7 @@ var init_history_temporal = __esm({
16184
16184
  });
16185
16185
 
16186
16186
  // ../../packages/shared/src/history-revision.ts
16187
- var Sha, HistoryRevisionRequestSchema;
16187
+ var Sha, HistoryRevisionRequestSchema, HistoryCarryRequestSchema, HISTORY_REVIEW_BUDGET, HISTORY_REVIEW_POLICY;
16188
16188
  var init_history_revision = __esm({
16189
16189
  "../../packages/shared/src/history-revision.ts"() {
16190
16190
  "use strict";
@@ -16204,6 +16204,17 @@ var init_history_revision = __esm({
16204
16204
  dependencyContext: HistoryDependencyContextSchema.optional()
16205
16205
  })).max(50)
16206
16206
  });
16207
+ HistoryCarryRequestSchema = external_exports.strictObject({
16208
+ fromCommit: Sha,
16209
+ atCommit: Sha,
16210
+ parents: external_exports.array(external_exports.strictObject({ commit: Sha, parents: external_exports.array(Sha).max(32) })).max(8192),
16211
+ /** Every symbol in a file the range changed, both sides; `null` = too many to name, treat all as changed. */
16212
+ changedSymbols: external_exports.array(external_exports.string().regex(/^sym_[a-f0-9]{8,64}$/)).max(5e3).nullable(),
16213
+ /** Manifests the range changed; `null` = a lockfile (or too many manifests) changed, treat all as changed. */
16214
+ changedManifests: external_exports.array(external_exports.string().regex(/^hmanifest_[a-f0-9]{32}$/)).max(512).nullable()
16215
+ });
16216
+ HISTORY_REVIEW_BUDGET = 48e3;
16217
+ HISTORY_REVIEW_POLICY = `reviewer-view-${HISTORY_REVIEW_BUDGET}`;
16207
16218
  }
16208
16219
  });
16209
16220
 
@@ -26099,6 +26110,114 @@ function textIndexReady(db) {
26099
26110
  return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
26100
26111
  }
26101
26112
 
26113
+ // ../../packages/local-graph/src/exposure-attrs.ts
26114
+ var EXPOSURE_ATTRS = [
26115
+ "shownCount",
26116
+ "evictedCount",
26117
+ "firstShownAt",
26118
+ "firstEvictedAt",
26119
+ "firstShownAtSeq",
26120
+ "lastShownAtSeq",
26121
+ "lastEvictedAtSeq",
26122
+ "shownSinceTouch",
26123
+ "shownSinceTouchAt",
26124
+ "exposureLog",
26125
+ "exposureLogStart",
26126
+ "exposureLogSince",
26127
+ "exposureFirst",
26128
+ "exposureTrimmed"
26129
+ ];
26130
+ var EXPOSURE_SCOPE = "exposure";
26131
+
26132
+ // ../../packages/local-graph/src/change-feed.ts
26133
+ var CHANGE_FEED_KEY = "change-feed";
26134
+ var ANY_CHANGE = "*";
26135
+ var CHANGE_LOG_KEEP = 25e4;
26136
+ var PRUNED_TO_KEY = "change-log-pruned-to";
26137
+ function installChangeFeed(db) {
26138
+ if (changeFeedInstalled(db)) return;
26139
+ db.transaction(() => {
26140
+ db.exec("CREATE TABLE IF NOT EXISTS change_seq (scope TEXT PRIMARY KEY, seq INTEGER NOT NULL) WITHOUT ROWID");
26141
+ db.exec(`CREATE TABLE IF NOT EXISTS change_log (change_seq INTEGER PRIMARY KEY, kind TEXT NOT NULL, id TEXT NOT NULL,
26142
+ label TEXT, from_id TEXT, to_id TEXT)`);
26143
+ for (const name2 of ["nodes", "edges"].flatMap((t) => ["insert", "update", "delete"].map((op) => `change_feed_${t}_${op}`))) {
26144
+ db.exec(`DROP TRIGGER IF EXISTS ${name2}`);
26145
+ }
26146
+ const changed = (table) => columns(db, table).map((c) => `OLD."${c}" IS NOT NEW."${c}"`).join(" OR ");
26147
+ const strip = (row) => `json_remove(${row}.attrs_json, ${EXPOSURE_ATTRS.map((a) => `'$.${a}'`).join(", ")})`;
26148
+ const exposureOnly = `(${columns(db, "nodes").filter((c) => c !== "attrs_json").map((c) => `OLD."${c}" IS NEW."${c}"`).join(" AND ")}
26149
+ AND CASE WHEN json_valid(OLD.attrs_json) AND json_valid(NEW.attrs_json) THEN ${strip("OLD")} IS ${strip("NEW")} ELSE 0 END)`;
26150
+ const bump = (...scopes) => `INSERT INTO change_seq(scope, seq) VALUES ${scopes.map((s) => `(${s}, 1)`).join(", ")}
26151
+ ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;`;
26152
+ const seq = `(SELECT seq FROM change_seq WHERE scope = '*')`;
26153
+ const logNode = (row) => `INSERT INTO change_log(change_seq, kind, id, label) VALUES (${seq}, 'node', ${row}.id, ${row}.label);`;
26154
+ const logEdge = (row) => `INSERT INTO change_log(change_seq, kind, id, label, from_id, to_id) VALUES (${seq}, 'edge', ${row}.id, ${row}.type, ${row}.from_id, ${row}.to_id);`;
26155
+ const node = (row) => `'node:' || ${row}.label`;
26156
+ const edge = (row) => `'edge:' || ${row}.type`;
26157
+ db.exec(`CREATE TRIGGER change_feed_nodes_insert AFTER INSERT ON nodes BEGIN ${bump(`'*'`, node("NEW"))} ${logNode("NEW")} END;`);
26158
+ db.exec(`CREATE TRIGGER change_feed_nodes_update AFTER UPDATE ON nodes WHEN ${changed("nodes")} BEGIN
26159
+ ${bump(`'*'`)}
26160
+ INSERT INTO change_seq(scope, seq) SELECT CASE WHEN ${exposureOnly} THEN '${EXPOSURE_SCOPE}' ELSE ${node("NEW")} END, 1 WHERE 1
26161
+ ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
26162
+ INSERT INTO change_seq(scope, seq) SELECT ${node("OLD")}, 1 WHERE OLD.label IS NOT NEW.label
26163
+ ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
26164
+ ${logNode("NEW")}
26165
+ END;`);
26166
+ db.exec(`CREATE TRIGGER change_feed_nodes_delete AFTER DELETE ON nodes BEGIN ${bump(`'*'`, node("OLD"))} ${logNode("OLD")} END;`);
26167
+ db.exec(`CREATE TRIGGER change_feed_edges_insert AFTER INSERT ON edges BEGIN ${bump(`'*'`, edge("NEW"))} ${logEdge("NEW")} END;`);
26168
+ db.exec(`CREATE TRIGGER change_feed_edges_update AFTER UPDATE ON edges WHEN ${changed("edges")} BEGIN
26169
+ ${bump(`'*'`, edge("NEW"))}
26170
+ INSERT INTO change_seq(scope, seq) SELECT ${edge("OLD")}, 1 WHERE OLD.type IS NOT NEW.type
26171
+ ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
26172
+ ${logEdge("NEW")}
26173
+ END;`);
26174
+ db.exec(`CREATE TRIGGER change_feed_edges_delete AFTER DELETE ON edges BEGIN ${bump(`'*'`, edge("OLD"))} ${logEdge("OLD")} END;`);
26175
+ db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(PRUNED_TO_KEY, String(readChangeSeq(db)));
26176
+ db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(CHANGE_FEED_KEY, columnSignature(db));
26177
+ });
26178
+ }
26179
+ function readChangeSeq(db, scopes = [ANY_CHANGE]) {
26180
+ if (!scopes.length) return 0;
26181
+ const row = db.prepare(`SELECT coalesce(sum(seq), 0) AS n FROM change_seq WHERE scope IN (${scopes.map(() => "?").join(",")})`).get(...scopes);
26182
+ return Number(row?.n ?? 0);
26183
+ }
26184
+ function changeLogCovers(db, sinceSeq) {
26185
+ const prunedTo = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? NaN);
26186
+ return Number.isFinite(prunedTo) && sinceSeq >= prunedTo;
26187
+ }
26188
+ function readLineageChangedFiles(db, sinceSeq) {
26189
+ if (!changeLogCovers(db, sinceSeq)) return null;
26190
+ const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
26191
+ return rows.map((r) => r.id);
26192
+ }
26193
+ var LINEAGE_CHANGED_FILES_SQL = `
26194
+ SELECT id FROM change_log WHERE change_seq > :s AND kind = 'node' AND label = 'File'
26195
+ UNION SELECT from_id FROM change_log WHERE change_seq > :s AND kind = 'edge' AND label IN ('DEFINES','CONTAINS')
26196
+ UNION SELECT e.from_id FROM (SELECT DISTINCT id FROM change_log WHERE change_seq > :s AND kind = 'node') c
26197
+ CROSS JOIN edges e ON e.to_id = c.id AND e.type IN ('DEFINES','CONTAINS')
26198
+ `;
26199
+ function pruneChangeLog(db) {
26200
+ const max = Number(db.prepare("SELECT coalesce(max(change_seq), 0) AS n FROM change_log").get()?.["n"] ?? 0);
26201
+ const cut = max - CHANGE_LOG_KEEP;
26202
+ if (cut <= 0) return 0;
26203
+ let removed = 0;
26204
+ db.transaction(() => {
26205
+ removed = Number(db.prepare("DELETE FROM change_log WHERE change_seq <= ?").run(cut).changes);
26206
+ const prev = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? 0);
26207
+ db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(PRUNED_TO_KEY, String(Math.max(prev, cut)));
26208
+ });
26209
+ return removed;
26210
+ }
26211
+ function changeFeedInstalled(db) {
26212
+ return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(CHANGE_FEED_KEY)?.["value"] === columnSignature(db);
26213
+ }
26214
+ function columns(db, table) {
26215
+ return db.prepare(`SELECT name FROM pragma_table_info('${table}') ORDER BY cid`).all().map((r) => r.name);
26216
+ }
26217
+ function columnSignature(db) {
26218
+ return `5|${columns(db, "nodes").join(",")}|${columns(db, "edges").join(",")}|${EXPOSURE_ATTRS.join(",")}`;
26219
+ }
26220
+
26102
26221
  // ../../packages/local-graph/src/lineage-integrity.ts
26103
26222
  var statements = /* @__PURE__ */ new WeakMap();
26104
26223
  function stmt(db, sql) {
@@ -27145,6 +27264,7 @@ var SqliteGraphStore = class {
27145
27264
  });
27146
27265
  }
27147
27266
  installLineageIntegrityRepair(this.db);
27267
+ installChangeFeed(this.db);
27148
27268
  this.stmts = {
27149
27269
  insertNode: this.db.prepare(`
27150
27270
  INSERT INTO nodes (
@@ -27932,12 +28052,39 @@ var SqliteGraphStore = class {
27932
28052
  mutationCount() {
27933
28053
  return this.mutations;
27934
28054
  }
28055
+ changeStamp() {
28056
+ const { data_version } = this.db.pragma("data_version");
28057
+ const { n } = this.db.prepare("SELECT total_changes() AS n").get();
28058
+ return `${data_version}:${n}`;
28059
+ }
28060
+ changeSeq(scopes) {
28061
+ return readChangeSeq(this.db, scopes);
28062
+ }
28063
+ lineageChangedFiles(sinceSeq) {
28064
+ return readLineageChangedFiles(this.db, sinceSeq);
28065
+ }
28066
+ pruneChangeLog() {
28067
+ return pruneChangeLog(this.db);
28068
+ }
28069
+ fileRowStates(ids) {
28070
+ if (ids.length === 0) return [];
28071
+ if (ids.length > 500) throw new RangeError("fileRowStates accepts at most 500 ids per read");
28072
+ return this.db.prepare(`SELECT id, last_updated_at AS lastUpdatedAt, valid_to AS validTo FROM nodes WHERE id IN (${ids.map(() => "?").join(",")})`).all(...ids);
28073
+ }
28074
+ // Read and advanced in SQL, never from a per-connection copy: pass workers and
28075
+ // MCP processes open their own connections and advance the same row, so a
28076
+ // copy loaded at open went stale, and `copy + 1` wrote a duplicate or a
28077
+ // backwards seq from whichever connection had fallen behind.
27935
28078
  currentIngestSeq() {
28079
+ const row = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
28080
+ this.ingestSeq = row ? Number(row.value) : 0;
27936
28081
  return this.ingestSeq;
27937
28082
  }
27938
28083
  nextIngestSeq() {
27939
- this.ingestSeq += 1;
27940
- this.stmts.setIngestSeq.run({ v: String(this.ingestSeq) });
28084
+ const row = this.db.prepare(
28085
+ "INSERT INTO store_meta (key, value) VALUES ('ingest_seq', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(value AS INTEGER) + 1 RETURNING value"
28086
+ ).get();
28087
+ this.ingestSeq = Number(row.value);
27941
28088
  return this.ingestSeq;
27942
28089
  }
27943
28090
  edgeCount() {
@@ -30669,12 +30816,12 @@ function buildSnapshot(opts) {
30669
30816
  const problems = opts.store.selectNodes({ labels: ["Problem", "Vulnerability"], attrs: PROBLEM_BAND_ATTRS }).filter((p) => p.attrs["mergedInto"] === void 0 && passiveAllowed(p, opts.atCommit));
30670
30817
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
30671
30818
  const allProblems = rankRecentBand(stillOpen.filter((p) => !isConstraintProblem(p)));
30672
- const openPool = allProblems.slice(0, RECENT_OPEN * SCOPE_POOL_FACTOR).map((p) => ({
30819
+ const openPool = allProblems.slice(0, Math.max(RECENT_OPEN * SCOPE_POOL_FACTOR, RECENT_WINDOW_BANDS * RECENT_OPEN)).map((p) => ({
30673
30820
  node: hydrate(p.id),
30674
30821
  anchors: opts.store.outEdges(p.id, ["MANIFESTED_IN", "EVIDENCED_BY"])
30675
30822
  }));
30676
30823
  const recent = openPool.slice(0, RECENT_OPEN);
30677
- const constraintPool = rankRecentBand(stillOpen.filter((p) => isConstraintProblem(p)), { window: RECENT_WINDOW_BANDS * RECENT_CONSTRAINTS }).slice(0, RECENT_CONSTRAINTS * SCOPE_POOL_FACTOR).map((p) => hydrate(p.id));
30824
+ const constraintPool = rankRecentBand(stillOpen.filter((p) => isConstraintProblem(p)), { window: RECENT_WINDOW_BANDS * RECENT_CONSTRAINTS }).slice(0, Math.max(RECENT_CONSTRAINTS * SCOPE_POOL_FACTOR, RECENT_WINDOW_BANDS * RECENT_CONSTRAINTS)).map((p) => hydrate(p.id));
30678
30825
  const recentConstraints = constraintPool.slice(0, RECENT_CONSTRAINTS);
30679
30826
  const regressionSources = /* @__PURE__ */ new Set();
30680
30827
  for (const target of opts.store.inDegreeByType(["REGRESSION_OF"]).keys()) {
@@ -30970,11 +31117,20 @@ function listSourceFiles(root) {
30970
31117
  return gitSourceFiles(root) ?? walkSource(root);
30971
31118
  }
30972
31119
  var SCAN_YIELD_EVERY = 100;
30973
- async function findStaleFiles(store2, rootPath, workspaceId) {
31120
+ async function findStaleFiles(store2, rootPath, workspaceId, lineageVerifiedAt) {
30974
31121
  const stale = [];
31122
+ const changed = lineageVerifiedAt !== void 0 && store2.fileRowStates ? store2.lineageChangedFiles?.(lineageVerifiedAt) : null;
31123
+ const relineage = changed ? new Set(changed) : null;
30975
31124
  let pending = [];
30976
31125
  const check2 = () => {
30977
- const states = new Map(store2.fileIndexStates(pending.map((f) => f.id)).map((s) => [s.id, s]));
31126
+ const full = relineage ? pending.filter((f) => relineage.has(f.id)) : pending;
31127
+ const states = new Map(
31128
+ store2.fileIndexStates(full.map((f) => f.id)).map((s) => [s.id, s])
31129
+ );
31130
+ if (relineage) {
31131
+ const rowOnly = pending.filter((f) => !relineage.has(f.id)).map((f) => f.id);
31132
+ for (const s of store2.fileRowStates(rowOnly)) states.set(s.id, { ...s, complete: true });
31133
+ }
30978
31134
  for (const f of pending) {
30979
31135
  const state = states.get(f.id);
30980
31136
  if (!state || state.validTo !== null || state.lastUpdatedAt < f.mtimeMs || !state.complete) stale.push(f.abs);
@@ -30996,9 +31152,18 @@ async function findStaleFiles(store2, rootPath, workspaceId) {
30996
31152
  check2();
30997
31153
  return stale;
30998
31154
  }
31155
+ var LINEAGE_VERIFIED_KEY = "pass-inputs:reconcile-lineage";
30999
31156
  async function reconcileStaleFiles(store2, rootPath, workspaceId) {
31000
- const stale = await findStaleFiles(store2, rootPath, workspaceId);
31001
- if (stale.length === 0) return 0;
31157
+ const startedAt = store2.changeSeq?.();
31158
+ const verified = Number(store2.getMeta?.(LINEAGE_VERIFIED_KEY) ?? NaN);
31159
+ const stale = await findStaleFiles(store2, rootPath, workspaceId, Number.isFinite(verified) ? verified : void 0);
31160
+ const verify = () => {
31161
+ if (startedAt !== void 0) store2.setMeta?.(LINEAGE_VERIFIED_KEY, String(startedAt));
31162
+ };
31163
+ if (stale.length === 0) {
31164
+ verify();
31165
+ return 0;
31166
+ }
31002
31167
  let total = 0;
31003
31168
  let failedBatches = 0;
31004
31169
  const BATCH = 20;
@@ -31016,6 +31181,8 @@ async function reconcileStaleFiles(store2, rootPath, workspaceId) {
31016
31181
  }
31017
31182
  if (failedBatches > 0) {
31018
31183
  console.warn(`[errata] reconcile: ${failedBatches} batch(es) failed; ${total} file(s) reindexed`);
31184
+ } else {
31185
+ verify();
31019
31186
  }
31020
31187
  return total;
31021
31188
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.22-dev.4430",
3
+ "version": "2.0.22-dev.4530",
4
4
  "description": "A minimal 3D view of loaded Errata priors and recent graph bursts.",
5
5
  "author": {
6
6
  "name": "inErrata"
@@ -1,4 +1,4 @@
1
- c8d7a2f0df9c257eb8438735d11bb97cf7ac5f2a47de52a555203b42778a3b73 .codex-plugin/plugin.json
1
+ 070354c29f752597560a79b2f85cda3b3988bc46099b17b49f95e8ce37fdad47 .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js