@inerrata-corporation/errata 2.0.22-dev.4665 → 2.0.22-dev.4676
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.
- package/consolidate-worker.mjs +212 -31
- package/errata.mjs +500 -141
- package/generalize-worker.mjs +212 -31
- package/package.json +1 -1
- package/pass-worker.mjs +400 -109
- package/resources/context-plugin/.codex-plugin/plugin.json +1 -1
- package/resources/context-plugin/SHA256SUMS +1 -1
package/consolidate-worker.mjs
CHANGED
|
@@ -16825,6 +16825,48 @@ import { parentPort } from "node:worker_threads";
|
|
|
16825
16825
|
init_src2();
|
|
16826
16826
|
init_src();
|
|
16827
16827
|
|
|
16828
|
+
// ../../packages/local-graph/src/wm-calibration.ts
|
|
16829
|
+
var WM_CALIBRATION_KEYS = [
|
|
16830
|
+
"shownCount",
|
|
16831
|
+
"evictedCount",
|
|
16832
|
+
"reinforceShownCount",
|
|
16833
|
+
"reinforceRecentCount",
|
|
16834
|
+
"reinforceEvictedCount",
|
|
16835
|
+
"reinforceUnshownCount",
|
|
16836
|
+
"reinforceUnshownQueryOnlyCount"
|
|
16837
|
+
];
|
|
16838
|
+
var WM_COUNTED_PREDICATE = WM_CALIBRATION_KEYS.map((k) => `json_type(attrs_json, '$.${k}') IS NOT NULL`).join(" OR ");
|
|
16839
|
+
var wmCounterSql = (k) => `CASE WHEN json_type(attrs_json, '$.${k}') IN ('integer', 'real') THEN json_extract(attrs_json, '$.${k}') END`;
|
|
16840
|
+
|
|
16841
|
+
// ../../packages/local-graph/src/exposure-attrs.ts
|
|
16842
|
+
var EXPOSURE_ATTRS = [
|
|
16843
|
+
"shownCount",
|
|
16844
|
+
"evictedCount",
|
|
16845
|
+
"firstShownAt",
|
|
16846
|
+
"firstEvictedAt",
|
|
16847
|
+
"firstShownAtSeq",
|
|
16848
|
+
"lastShownAtSeq",
|
|
16849
|
+
"lastEvictedAtSeq",
|
|
16850
|
+
"shownSinceTouch",
|
|
16851
|
+
"shownSinceTouchAt",
|
|
16852
|
+
"exposureLog",
|
|
16853
|
+
"exposureLogStart",
|
|
16854
|
+
"exposureLogSince",
|
|
16855
|
+
"exposureFirst",
|
|
16856
|
+
"exposureTrimmed"
|
|
16857
|
+
];
|
|
16858
|
+
var EXPOSURE_SCOPE = "exposure";
|
|
16859
|
+
var EXPOSURE_KEYS = new Set(EXPOSURE_ATTRS);
|
|
16860
|
+
var isExposureAttr = (key) => EXPOSURE_KEYS.has(key);
|
|
16861
|
+
function pickExposure(attrs) {
|
|
16862
|
+
const out = {};
|
|
16863
|
+
for (const k of EXPOSURE_ATTRS) if (attrs[k] !== void 0) out[k] = attrs[k];
|
|
16864
|
+
return out;
|
|
16865
|
+
}
|
|
16866
|
+
function exposureDocSql(key) {
|
|
16867
|
+
return `coalesce((SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id AND json_type(x.attrs_json, '$.${key}') IS NOT NULL), attrs_json)`;
|
|
16868
|
+
}
|
|
16869
|
+
|
|
16828
16870
|
// ../../packages/local-graph/src/history-cache-floor.ts
|
|
16829
16871
|
var HISTORY_CACHE_FLOOR_KEY = "history-cache-withdrawal-floor-3";
|
|
16830
16872
|
function installHistoryCacheFloor(db) {
|
|
@@ -17109,25 +17151,6 @@ function textIndexReady(db) {
|
|
|
17109
17151
|
return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
|
|
17110
17152
|
}
|
|
17111
17153
|
|
|
17112
|
-
// ../../packages/local-graph/src/exposure-attrs.ts
|
|
17113
|
-
var EXPOSURE_ATTRS = [
|
|
17114
|
-
"shownCount",
|
|
17115
|
-
"evictedCount",
|
|
17116
|
-
"firstShownAt",
|
|
17117
|
-
"firstEvictedAt",
|
|
17118
|
-
"firstShownAtSeq",
|
|
17119
|
-
"lastShownAtSeq",
|
|
17120
|
-
"lastEvictedAtSeq",
|
|
17121
|
-
"shownSinceTouch",
|
|
17122
|
-
"shownSinceTouchAt",
|
|
17123
|
-
"exposureLog",
|
|
17124
|
-
"exposureLogStart",
|
|
17125
|
-
"exposureLogSince",
|
|
17126
|
-
"exposureFirst",
|
|
17127
|
-
"exposureTrimmed"
|
|
17128
|
-
];
|
|
17129
|
-
var EXPOSURE_SCOPE = "exposure";
|
|
17130
|
-
|
|
17131
17154
|
// ../../packages/local-graph/src/change-feed.ts
|
|
17132
17155
|
var CHANGE_FEED_KEY = "change-feed";
|
|
17133
17156
|
var ANY_CHANGE = "*";
|
|
@@ -17191,6 +17214,16 @@ function readChangedNodeIds(db, sinceSeq, labels) {
|
|
|
17191
17214
|
const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND label IN (${labels.map(() => "?").join(",")})`).all(sinceSeq, ...labels);
|
|
17192
17215
|
return rows.map((r) => r.id);
|
|
17193
17216
|
}
|
|
17217
|
+
function readChangedAmong(db, sinceSeq, ids) {
|
|
17218
|
+
if (!changeLogCovers(db, sinceSeq)) return null;
|
|
17219
|
+
const out = [];
|
|
17220
|
+
for (let i = 0; i < ids.length; i += 500) {
|
|
17221
|
+
const chunk = ids.slice(i, i + 500);
|
|
17222
|
+
const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND id IN (${chunk.map(() => "?").join(",")})`).all(sinceSeq, ...chunk);
|
|
17223
|
+
for (const r of rows) out.push(r.id);
|
|
17224
|
+
}
|
|
17225
|
+
return out;
|
|
17226
|
+
}
|
|
17194
17227
|
function readLineageChangedFiles(db, sinceSeq) {
|
|
17195
17228
|
if (!changeLogCovers(db, sinceSeq)) return null;
|
|
17196
17229
|
const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
|
|
@@ -17665,8 +17698,11 @@ function compileNodeWhere(q) {
|
|
|
17665
17698
|
}
|
|
17666
17699
|
return { where: conds.join(" AND "), params };
|
|
17667
17700
|
}
|
|
17668
|
-
function attrValueSql(p) {
|
|
17669
|
-
return `CASE json_type(
|
|
17701
|
+
function attrValueSql(p, doc = "attrs_json") {
|
|
17702
|
+
return `CASE json_type(${doc}, ${p}) WHEN 'integer' THEN CAST(json_extract(${doc}, ${p}) AS REAL) ELSE json_extract(${doc}, ${p}) END`;
|
|
17703
|
+
}
|
|
17704
|
+
function attrDocSql(key) {
|
|
17705
|
+
return isExposureAttr(key) ? exposureDocSql(key) : "attrs_json";
|
|
17670
17706
|
}
|
|
17671
17707
|
function nodeSelectFrom(q) {
|
|
17672
17708
|
if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
|
|
@@ -17679,7 +17715,7 @@ function nodeSelectFrom(q) {
|
|
|
17679
17715
|
function compileNodeSelect(q) {
|
|
17680
17716
|
const { where, params } = compileNodeWhere(q);
|
|
17681
17717
|
const attrs = q.attrs ?? [];
|
|
17682
|
-
const attrCols = attrs.map((k, i) => `, ${attrValueSql(path(k))} AS a${i}, json_type(
|
|
17718
|
+
const attrCols = attrs.map((k, i) => `, ${attrValueSql(path(k), attrDocSql(k))} AS a${i}, json_type(${attrDocSql(k)}, ${path(k)}) AS t${i}`).join("");
|
|
17683
17719
|
let order;
|
|
17684
17720
|
if (q.orderBy && "column" in q.orderBy) {
|
|
17685
17721
|
order = `${column(q.orderBy.column)} ${q.orderBy.desc ? "DESC" : "ASC"}, rowid`;
|
|
@@ -17796,6 +17832,7 @@ function localEdgeViolation(fromLabel, type, toLabel) {
|
|
|
17796
17832
|
}
|
|
17797
17833
|
var SCHEMA_VERSION = 6;
|
|
17798
17834
|
var SESSION_REPAIR_KEY = "session-evidence-v1";
|
|
17835
|
+
var EXPOSURE_SIDE_KEY = "exposure-side-rows-1";
|
|
17799
17836
|
var SESSION_EVIDENCE_PREDICATE = SESSION_EVIDENCE_FIELDS.map((field) => `instr(attrs_json, '${JSON.stringify(field)}') > 0`).join(" OR ");
|
|
17800
17837
|
var SCHEMA_SQL = `
|
|
17801
17838
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
@@ -17859,8 +17896,13 @@ CREATE TABLE IF NOT EXISTS nodes (
|
|
|
17859
17896
|
);
|
|
17860
17897
|
|
|
17861
17898
|
CREATE INDEX IF NOT EXISTS nodes_label ON nodes(label);
|
|
17862
|
-
|
|
17863
|
-
|
|
17899
|
+
-- Dropped: no query plans through them (every prepared statement across the
|
|
17900
|
+
-- local-graph, indexer, context-writer, generalizer and daemon suites was
|
|
17901
|
+
-- planned with and without them, 2026-10-21), and each write that touches
|
|
17902
|
+
-- memory_tier / page_rank (every node upsert, every rescore) paid to maintain
|
|
17903
|
+
-- them. nodes_live_page_rank serves the live page-rank orderings.
|
|
17904
|
+
DROP INDEX IF EXISTS nodes_memory_tier;
|
|
17905
|
+
DROP INDEX IF EXISTS nodes_page_rank;
|
|
17864
17906
|
|
|
17865
17907
|
CREATE TABLE IF NOT EXISTS edges (
|
|
17866
17908
|
id TEXT PRIMARY KEY,
|
|
@@ -17882,7 +17924,10 @@ CREATE TABLE IF NOT EXISTS edges (
|
|
|
17882
17924
|
FOREIGN KEY (to_id) REFERENCES nodes(id) ON DELETE CASCADE
|
|
17883
17925
|
);
|
|
17884
17926
|
|
|
17885
|
-
|
|
17927
|
+
-- Dropped: a prefix of edges_triple (from_id, type, to_id), which every
|
|
17928
|
+
-- from-side lookup and foreign-key check already plans through; it cost 47 MB
|
|
17929
|
+
-- and an index write on every edge write.
|
|
17930
|
+
DROP INDEX IF EXISTS edges_from;
|
|
17886
17931
|
CREATE INDEX IF NOT EXISTS edges_to ON edges(to_id, type);
|
|
17887
17932
|
CREATE INDEX IF NOT EXISTS edges_type ON edges(type);
|
|
17888
17933
|
CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
|
|
@@ -17904,6 +17949,12 @@ CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to,
|
|
|
17904
17949
|
-- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
|
|
17905
17950
|
-- Problems; without it every live Problem was read to find them (56 MB).
|
|
17906
17951
|
CREATE INDEX IF NOT EXISTS nodes_live_fold_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.foldCandidate') IS NOT NULL;
|
|
17952
|
+
-- Render impressions, one row per node id (exposure-attrs.ts): a render bumps
|
|
17953
|
+
-- ~40 of these, which in the node row rewrote each whole row and its indexes.
|
|
17954
|
+
CREATE TABLE IF NOT EXISTS node_exposure (
|
|
17955
|
+
id TEXT PRIMARY KEY,
|
|
17956
|
+
attrs_json TEXT NOT NULL DEFAULT '{}'
|
|
17957
|
+
) WITHOUT ROWID;
|
|
17907
17958
|
-- The semantic-maintenance sweeps' own filters (closed, asked, hinted): each read
|
|
17908
17959
|
-- every Problem row (~30 MB on a 1.1 GB store) for the few hundred that match,
|
|
17909
17960
|
-- several times a run, and a run follows most writes (2026-10-20).
|
|
@@ -17911,6 +17962,9 @@ CREATE INDEX IF NOT EXISTS nodes_live_resolved ON nodes(label, valid_to) WHERE v
|
|
|
17911
17962
|
CREATE INDEX IF NOT EXISTS nodes_live_fix_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.fixCandidateAt') IS NOT NULL;
|
|
17912
17963
|
CREATE INDEX IF NOT EXISTS nodes_live_anchor_hint ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.anchorHint') IS NOT NULL;
|
|
17913
17964
|
CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
|
|
17965
|
+
-- Rows carrying a calibration counter, any version: the logical nodes wm
|
|
17966
|
+
-- calibration can count (~3k), found without reading every citable row.
|
|
17967
|
+
CREATE INDEX IF NOT EXISTS nodes_wm_counted ON nodes(logical_id, version, valid_to, label, ${WM_CALIBRATION_KEYS.map(wmCounterSql).join(", ")}) WHERE ${WM_COUNTED_PREDICATE};
|
|
17914
17968
|
CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
|
|
17915
17969
|
-- Covering + partial index for labelFingerprint over the instance labels: the
|
|
17916
17970
|
-- live-push probe runs it per workspace on a timer, and each hit used to read
|
|
@@ -18064,6 +18118,7 @@ var SqliteGraphStore = class {
|
|
|
18064
18118
|
constructor(opts) {
|
|
18065
18119
|
this.db = openDatabase(opts.path);
|
|
18066
18120
|
this.db.exec("PRAGMA cache_size = -32768");
|
|
18121
|
+
this.db.exec("PRAGMA mmap_size = 2147483648");
|
|
18067
18122
|
this.db.exec(SCHEMA_SQL);
|
|
18068
18123
|
const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
|
|
18069
18124
|
if (!versionRow) {
|
|
@@ -18127,8 +18182,17 @@ var SqliteGraphStore = class {
|
|
|
18127
18182
|
last_reinforced_at_seq = excluded.last_reinforced_at_seq,
|
|
18128
18183
|
attrs_json = excluded.attrs_json
|
|
18129
18184
|
`),
|
|
18130
|
-
getNode: this.db.prepare("SELECT
|
|
18185
|
+
getNode: this.db.prepare("SELECT nodes.*, (SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id) AS exposure_json FROM nodes WHERE id = ?"),
|
|
18186
|
+
exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
|
|
18187
|
+
writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
|
|
18188
|
+
lastUpdatedOf: this.db.prepare("SELECT last_updated_at AS t, attrs_json FROM nodes WHERE id = ?"),
|
|
18131
18189
|
labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
|
|
18190
|
+
nodeContent: this.db.prepare(
|
|
18191
|
+
"SELECT label, description, extraction_confidence, extraction_source, stability, valid_to, attrs_json FROM nodes WHERE id = ?"
|
|
18192
|
+
),
|
|
18193
|
+
edgeContent: this.db.prepare(
|
|
18194
|
+
"SELECT confidence, valid_to, attrs_json FROM edges WHERE from_id = ? AND type = ? AND to_id = ?"
|
|
18195
|
+
),
|
|
18132
18196
|
// Single-column fast paths for the nightly pass — writing 100k pageRank
|
|
18133
18197
|
// values through the 18-column COALESCE updateNode took ~30s; these are ~10x
|
|
18134
18198
|
// cheaper (one bound column, no COALESCE evaluation per row).
|
|
@@ -18285,6 +18349,7 @@ var SqliteGraphStore = class {
|
|
|
18285
18349
|
"INSERT INTO store_meta (key, value) VALUES ('ingest_seq', :v) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
18286
18350
|
)
|
|
18287
18351
|
};
|
|
18352
|
+
this.moveExposureToSideRows();
|
|
18288
18353
|
const seqRow = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
|
|
18289
18354
|
this.ingestSeq = seqRow ? Number(seqRow.value) : 0;
|
|
18290
18355
|
}
|
|
@@ -18340,6 +18405,41 @@ var SqliteGraphStore = class {
|
|
|
18340
18405
|
ORDER BY label, created_at, rowid`
|
|
18341
18406
|
).all(label);
|
|
18342
18407
|
}
|
|
18408
|
+
wmCalibrationRows(labels) {
|
|
18409
|
+
const counted = this.db.prepare(
|
|
18410
|
+
`SELECT rowid AS rid, logical_id, version, valid_to, label, ${WM_CALIBRATION_KEYS.map((k, i) => `${wmCounterSql(k)} AS c${i}`).join(", ")}
|
|
18411
|
+
FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`
|
|
18412
|
+
).all();
|
|
18413
|
+
const exposed = this.db.prepare(
|
|
18414
|
+
`SELECT n.rowid AS rid, n.logical_id, n.version, n.valid_to, n.label, x.attrs_json AS exposure
|
|
18415
|
+
FROM node_exposure x JOIN nodes n ON n.id = x.id`
|
|
18416
|
+
).all();
|
|
18417
|
+
const others = this.db.prepare(
|
|
18418
|
+
`SELECT rowid AS rid, logical_id, version, valid_to, label FROM nodes WHERE rowid IN (
|
|
18419
|
+
SELECT n.rowid FROM nodes AS n INDEXED BY nodes_logical
|
|
18420
|
+
WHERE n.logical_id IN (SELECT logical_id FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
|
|
18421
|
+
UNION SELECT n2.logical_id FROM node_exposure x2 JOIN nodes n2 ON n2.id = x2.id)
|
|
18422
|
+
EXCEPT SELECT rowid FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
|
|
18423
|
+
EXCEPT SELECT n3.rowid FROM node_exposure x3 JOIN nodes n3 ON n3.id = x3.id)`
|
|
18424
|
+
).all();
|
|
18425
|
+
const byRid = /* @__PURE__ */ new Map();
|
|
18426
|
+
for (const r of [...counted, ...others]) {
|
|
18427
|
+
const attrs = {};
|
|
18428
|
+
WM_CALIBRATION_KEYS.forEach((k, i) => {
|
|
18429
|
+
const v = r[`c${i}`];
|
|
18430
|
+
if (typeof v === "number") attrs[k] = v;
|
|
18431
|
+
});
|
|
18432
|
+
byRid.set(r.rid, { row: r, attrs });
|
|
18433
|
+
}
|
|
18434
|
+
for (const r of exposed) {
|
|
18435
|
+
const side = JSON.parse(r.exposure);
|
|
18436
|
+
const entry = byRid.get(r.rid) ?? { row: r, attrs: {} };
|
|
18437
|
+
for (const k of WM_CALIBRATION_KEYS) if (typeof side[k] === "number" && Number.isFinite(side[k])) entry.attrs[k] = side[k];
|
|
18438
|
+
byRid.set(r.rid, entry);
|
|
18439
|
+
}
|
|
18440
|
+
const order = new Map(labels.map((l, i) => [l, i]));
|
|
18441
|
+
return [...byRid.values()].filter(({ row }) => order.has(row.label)).sort((a, b) => order.get(a.row.label) - order.get(b.row.label) || a.row.rid - b.row.rid).map(({ row, attrs }) => ({ logicalId: row.logical_id, version: Number(row.version), validTo: row.valid_to ?? null, attrs }));
|
|
18442
|
+
}
|
|
18343
18443
|
liveCommentsAttachedTo(name) {
|
|
18344
18444
|
return this.db.prepare(
|
|
18345
18445
|
`SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
|
|
@@ -18476,9 +18576,22 @@ var SqliteGraphStore = class {
|
|
|
18476
18576
|
logical_id: node.logicalId ?? node.id,
|
|
18477
18577
|
created_at_seq: node.createdAtSeq ?? null,
|
|
18478
18578
|
last_reinforced_at_seq: node.lastReinforcedAtSeq ?? null,
|
|
18479
|
-
attrs_json:
|
|
18579
|
+
attrs_json: this.nodeAttrsJson(node.id, node.attrs ?? {})
|
|
18480
18580
|
});
|
|
18481
18581
|
}
|
|
18582
|
+
/** mergeNode, unless the live row already holds this content. Content is what
|
|
18583
|
+
* the columns a merge rewrites from the producer (label, description, attrs,
|
|
18584
|
+
* confidence, stability);
|
|
18585
|
+
* rank, community, landmark, surprise, hits, tier and the vector belong to
|
|
18586
|
+
* the passes that compute them, and a skipped write leaves them as they are
|
|
18587
|
+
* where a merge would have reset them to the producer's defaults. Returns
|
|
18588
|
+
* whether it wrote. */
|
|
18589
|
+
mergeNodeIfChanged(node) {
|
|
18590
|
+
const row = this.stmts.nodeContent.get(node.id);
|
|
18591
|
+
if (row && row.valid_to === null && row.label === node.label && row.description === node.description && row.extraction_confidence === node.extractionConfidence && row.stability === node.stability && !EXPOSURE_ATTRS.some((k) => node.attrs?.[k] !== void 0) && row.attrs_json === JSON.stringify(node.attrs ?? {})) return false;
|
|
18592
|
+
this.mergeNode(node);
|
|
18593
|
+
return true;
|
|
18594
|
+
}
|
|
18482
18595
|
updateNode(id, patch) {
|
|
18483
18596
|
const hasEmbedding = patch.embedding !== void 0 && patch.embedding.length > 0;
|
|
18484
18597
|
this.stmts.updateNode.run({
|
|
@@ -18502,7 +18615,37 @@ var SqliteGraphStore = class {
|
|
|
18502
18615
|
logical_id: patch.logicalId ?? null,
|
|
18503
18616
|
created_at_seq: patch.createdAtSeq ?? null,
|
|
18504
18617
|
last_reinforced_at_seq: patch.lastReinforcedAtSeq ?? null,
|
|
18505
|
-
attrs_json: patch.attrs ?
|
|
18618
|
+
attrs_json: patch.attrs ? this.nodeAttrsJson(id, patch.attrs) : null
|
|
18619
|
+
});
|
|
18620
|
+
}
|
|
18621
|
+
/**
|
|
18622
|
+
* The node-row attrs of a write, after handing any exposure keys to the side
|
|
18623
|
+
* row (exposure-attrs.ts). `getNode` returns exposure merged into attrs, so a
|
|
18624
|
+
* read-modify-write carries it; kept in the row it would re-bloat the row and
|
|
18625
|
+
* drift from the side row. Unchanged exposure costs no write.
|
|
18626
|
+
*/
|
|
18627
|
+
nodeAttrsJson(id, attrs) {
|
|
18628
|
+
if (!EXPOSURE_ATTRS.some((k) => attrs[k] !== void 0)) return JSON.stringify(attrs);
|
|
18629
|
+
const current = this.stmts.exposureOf.get(id);
|
|
18630
|
+
const next = JSON.stringify({ ...current ? JSON.parse(current.attrs_json) : {}, ...pickExposure(attrs) });
|
|
18631
|
+
if (current?.attrs_json !== next) this.stmts.writeExposure.run(id, next);
|
|
18632
|
+
const rest = { ...attrs };
|
|
18633
|
+
for (const k of EXPOSURE_ATTRS) delete rest[k];
|
|
18634
|
+
return JSON.stringify(rest);
|
|
18635
|
+
}
|
|
18636
|
+
/** One-time: exposure keys written to node rows before the side table existed
|
|
18637
|
+
* move to it. Only rows carrying a counter can carry exposure, and those are
|
|
18638
|
+
* what nodes_wm_counted holds, so this reads the index, not the table. */
|
|
18639
|
+
moveExposureToSideRows() {
|
|
18640
|
+
if (this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(EXPOSURE_SIDE_KEY)) return;
|
|
18641
|
+
const rows = this.db.prepare(`SELECT id, attrs_json FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`).all();
|
|
18642
|
+
const move = this.db.prepare("UPDATE nodes SET attrs_json = ? WHERE id = ?");
|
|
18643
|
+
this.db.transaction(() => {
|
|
18644
|
+
for (const r of rows) {
|
|
18645
|
+
const stripped = this.nodeAttrsJson(r.id, JSON.parse(r.attrs_json));
|
|
18646
|
+
if (stripped !== r.attrs_json) move.run(stripped, r.id);
|
|
18647
|
+
}
|
|
18648
|
+
this.db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, 'done')").run(EXPOSURE_SIDE_KEY);
|
|
18506
18649
|
});
|
|
18507
18650
|
}
|
|
18508
18651
|
/** Fast single-column writes for the nightly pass (avoid the 18-column COALESCE
|
|
@@ -18584,6 +18727,21 @@ var SqliteGraphStore = class {
|
|
|
18584
18727
|
attrs_json: JSON.stringify(edge.attrs ?? {})
|
|
18585
18728
|
});
|
|
18586
18729
|
}
|
|
18730
|
+
/** mergeEdge, unless the live edge already holds this content: same attrs, and
|
|
18731
|
+
* a confidence the upsert's MAX would keep. Its navigation posteriors and
|
|
18732
|
+
* last-seen stamp stay as they are where a merge would have reset them. The
|
|
18733
|
+
* ontology gate runs first either way, so refusals are recorded as before.
|
|
18734
|
+
* Returns whether it wrote (or refused). */
|
|
18735
|
+
mergeEdgeIfChanged(edge) {
|
|
18736
|
+
if (!this.edgeRuleViolation(edge)) {
|
|
18737
|
+
const row = this.stmts.edgeContent.get(edge.from, edge.type, edge.to);
|
|
18738
|
+
if (row && row.valid_to === null && row.confidence >= edge.confidence && row.attrs_json === JSON.stringify(edge.attrs ?? {})) {
|
|
18739
|
+
return false;
|
|
18740
|
+
}
|
|
18741
|
+
}
|
|
18742
|
+
this.mergeEdge(edge);
|
|
18743
|
+
return true;
|
|
18744
|
+
}
|
|
18587
18745
|
/** EDGE_RULES check against the stored endpoints' labels, with the LOCAL
|
|
18588
18746
|
* overlay consulted first. Returns the reason string on a documented-
|
|
18589
18747
|
* forbidden combination, else null. Point lookups on the id PK — negligible
|
|
@@ -18650,7 +18808,27 @@ var SqliteGraphStore = class {
|
|
|
18650
18808
|
}
|
|
18651
18809
|
getNode(id) {
|
|
18652
18810
|
const row = this.stmts.getNode.get(id);
|
|
18653
|
-
|
|
18811
|
+
if (!row) return null;
|
|
18812
|
+
const node = rowToNode(row);
|
|
18813
|
+
if (row.exposure_json) Object.assign(node.attrs, JSON.parse(row.exposure_json));
|
|
18814
|
+
return node;
|
|
18815
|
+
}
|
|
18816
|
+
/** A node's render exposure: the side row's keys over the node row's own
|
|
18817
|
+
* (exposure-attrs.ts), plus the node's `lastUpdatedAt` (the "shown since
|
|
18818
|
+
* touch" counter is relative to it) — without reading the node's attrs. */
|
|
18819
|
+
exposureOf(id) {
|
|
18820
|
+
const row = this.stmts.lastUpdatedOf.get(id);
|
|
18821
|
+
if (!row) return null;
|
|
18822
|
+
const side = this.stmts.exposureOf.get(id);
|
|
18823
|
+
return {
|
|
18824
|
+
attrs: { ...pickExposure(JSON.parse(row.attrs_json)), ...side ? JSON.parse(side.attrs_json) : {} },
|
|
18825
|
+
lastUpdatedAt: row.t
|
|
18826
|
+
};
|
|
18827
|
+
}
|
|
18828
|
+
/** Replace a node's exposure (only EXPOSURE_ATTRS keys belong here). Touches
|
|
18829
|
+
* neither the node row nor its indexes, nor the change feed. */
|
|
18830
|
+
writeExposure(id, attrs) {
|
|
18831
|
+
this.stmts.writeExposure.run(id, JSON.stringify(attrs));
|
|
18654
18832
|
}
|
|
18655
18833
|
nodeLabels(ids) {
|
|
18656
18834
|
const width = NODE_LABELS_CHUNK;
|
|
@@ -18701,7 +18879,7 @@ var SqliteGraphStore = class {
|
|
|
18701
18879
|
findNodeStampsByLabel(label, attrKey) {
|
|
18702
18880
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attrKey)) throw new Error(`findNodeStampsByLabel: bad attr key ${attrKey}`);
|
|
18703
18881
|
const rows = this.db.prepare(
|
|
18704
|
-
`SELECT id, created_at, created_at_seq, json_type(
|
|
18882
|
+
`SELECT id, created_at, created_at_seq, json_type(${attrDocSql(attrKey)}, '$.${attrKey}') IS NOT NULL AS has,
|
|
18705
18883
|
COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
|
|
18706
18884
|
FROM nodes WHERE label = ? AND valid_to IS NULL`
|
|
18707
18885
|
).all(label);
|
|
@@ -18718,7 +18896,7 @@ var SqliteGraphStore = class {
|
|
|
18718
18896
|
const keys = [...new Set(attrKeys)];
|
|
18719
18897
|
const rows = this.db.prepare(
|
|
18720
18898
|
`SELECT id, created_at, created_at_seq,
|
|
18721
|
-
${keys.map((k, i) => `json_type(
|
|
18899
|
+
${keys.map((k, i) => `json_type(${attrDocSql(k)}, '$.${k}') IS NOT NULL AS h${i}`).join(", ")}${keys.length ? "," : ""}
|
|
18722
18900
|
COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
|
|
18723
18901
|
FROM nodes WHERE label = ? AND valid_to IS NULL`
|
|
18724
18902
|
).all(label);
|
|
@@ -18978,6 +19156,9 @@ var SqliteGraphStore = class {
|
|
|
18978
19156
|
changedNodeIds(sinceSeq, labels) {
|
|
18979
19157
|
return readChangedNodeIds(this.db, sinceSeq, labels);
|
|
18980
19158
|
}
|
|
19159
|
+
changedAmong(sinceSeq, ids) {
|
|
19160
|
+
return readChangedAmong(this.db, sinceSeq, ids);
|
|
19161
|
+
}
|
|
18981
19162
|
pruneChangeLog() {
|
|
18982
19163
|
return pruneChangeLog(this.db);
|
|
18983
19164
|
}
|