@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/generalize-worker.mjs
CHANGED
|
@@ -15402,6 +15402,48 @@ var LEADING_VERB = new RegExp(String.raw`^\s*(${VERB_WORDS_SOURCE})\s*:`, "i");
|
|
|
15402
15402
|
var ANY_VERB_WORD = new RegExp(String.raw`\b(?:${VERB_WORDS_SOURCE})\s*:`, "gi");
|
|
15403
15403
|
var QUOTED_TAG_OPENER = new RegExp(String.raw`\(\s*(?:(${VERB_WORDS_SOURCE})\s*:|(?=\[))`, "gi");
|
|
15404
15404
|
|
|
15405
|
+
// ../../packages/local-graph/src/wm-calibration.ts
|
|
15406
|
+
var WM_CALIBRATION_KEYS = [
|
|
15407
|
+
"shownCount",
|
|
15408
|
+
"evictedCount",
|
|
15409
|
+
"reinforceShownCount",
|
|
15410
|
+
"reinforceRecentCount",
|
|
15411
|
+
"reinforceEvictedCount",
|
|
15412
|
+
"reinforceUnshownCount",
|
|
15413
|
+
"reinforceUnshownQueryOnlyCount"
|
|
15414
|
+
];
|
|
15415
|
+
var WM_COUNTED_PREDICATE = WM_CALIBRATION_KEYS.map((k) => `json_type(attrs_json, '$.${k}') IS NOT NULL`).join(" OR ");
|
|
15416
|
+
var wmCounterSql = (k) => `CASE WHEN json_type(attrs_json, '$.${k}') IN ('integer', 'real') THEN json_extract(attrs_json, '$.${k}') END`;
|
|
15417
|
+
|
|
15418
|
+
// ../../packages/local-graph/src/exposure-attrs.ts
|
|
15419
|
+
var EXPOSURE_ATTRS = [
|
|
15420
|
+
"shownCount",
|
|
15421
|
+
"evictedCount",
|
|
15422
|
+
"firstShownAt",
|
|
15423
|
+
"firstEvictedAt",
|
|
15424
|
+
"firstShownAtSeq",
|
|
15425
|
+
"lastShownAtSeq",
|
|
15426
|
+
"lastEvictedAtSeq",
|
|
15427
|
+
"shownSinceTouch",
|
|
15428
|
+
"shownSinceTouchAt",
|
|
15429
|
+
"exposureLog",
|
|
15430
|
+
"exposureLogStart",
|
|
15431
|
+
"exposureLogSince",
|
|
15432
|
+
"exposureFirst",
|
|
15433
|
+
"exposureTrimmed"
|
|
15434
|
+
];
|
|
15435
|
+
var EXPOSURE_SCOPE = "exposure";
|
|
15436
|
+
var EXPOSURE_KEYS = new Set(EXPOSURE_ATTRS);
|
|
15437
|
+
var isExposureAttr = (key) => EXPOSURE_KEYS.has(key);
|
|
15438
|
+
function pickExposure(attrs) {
|
|
15439
|
+
const out = {};
|
|
15440
|
+
for (const k of EXPOSURE_ATTRS) if (attrs[k] !== void 0) out[k] = attrs[k];
|
|
15441
|
+
return out;
|
|
15442
|
+
}
|
|
15443
|
+
function exposureDocSql(key) {
|
|
15444
|
+
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)`;
|
|
15445
|
+
}
|
|
15446
|
+
|
|
15405
15447
|
// ../../packages/local-graph/src/history-cache-floor.ts
|
|
15406
15448
|
var HISTORY_CACHE_FLOOR_KEY = "history-cache-withdrawal-floor-3";
|
|
15407
15449
|
function installHistoryCacheFloor(db) {
|
|
@@ -15683,25 +15725,6 @@ function textIndexReady(db) {
|
|
|
15683
15725
|
return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
|
|
15684
15726
|
}
|
|
15685
15727
|
|
|
15686
|
-
// ../../packages/local-graph/src/exposure-attrs.ts
|
|
15687
|
-
var EXPOSURE_ATTRS = [
|
|
15688
|
-
"shownCount",
|
|
15689
|
-
"evictedCount",
|
|
15690
|
-
"firstShownAt",
|
|
15691
|
-
"firstEvictedAt",
|
|
15692
|
-
"firstShownAtSeq",
|
|
15693
|
-
"lastShownAtSeq",
|
|
15694
|
-
"lastEvictedAtSeq",
|
|
15695
|
-
"shownSinceTouch",
|
|
15696
|
-
"shownSinceTouchAt",
|
|
15697
|
-
"exposureLog",
|
|
15698
|
-
"exposureLogStart",
|
|
15699
|
-
"exposureLogSince",
|
|
15700
|
-
"exposureFirst",
|
|
15701
|
-
"exposureTrimmed"
|
|
15702
|
-
];
|
|
15703
|
-
var EXPOSURE_SCOPE = "exposure";
|
|
15704
|
-
|
|
15705
15728
|
// ../../packages/local-graph/src/change-feed.ts
|
|
15706
15729
|
var CHANGE_FEED_KEY = "change-feed";
|
|
15707
15730
|
var ANY_CHANGE = "*";
|
|
@@ -15765,6 +15788,16 @@ function readChangedNodeIds(db, sinceSeq, labels) {
|
|
|
15765
15788
|
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);
|
|
15766
15789
|
return rows.map((r) => r.id);
|
|
15767
15790
|
}
|
|
15791
|
+
function readChangedAmong(db, sinceSeq, ids) {
|
|
15792
|
+
if (!changeLogCovers(db, sinceSeq)) return null;
|
|
15793
|
+
const out = [];
|
|
15794
|
+
for (let i = 0; i < ids.length; i += 500) {
|
|
15795
|
+
const chunk = ids.slice(i, i + 500);
|
|
15796
|
+
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);
|
|
15797
|
+
for (const r of rows) out.push(r.id);
|
|
15798
|
+
}
|
|
15799
|
+
return out;
|
|
15800
|
+
}
|
|
15768
15801
|
function readLineageChangedFiles(db, sinceSeq) {
|
|
15769
15802
|
if (!changeLogCovers(db, sinceSeq)) return null;
|
|
15770
15803
|
const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
|
|
@@ -16062,8 +16095,11 @@ function compileNodeWhere(q) {
|
|
|
16062
16095
|
}
|
|
16063
16096
|
return { where: conds.join(" AND "), params };
|
|
16064
16097
|
}
|
|
16065
|
-
function attrValueSql(p) {
|
|
16066
|
-
return `CASE json_type(
|
|
16098
|
+
function attrValueSql(p, doc = "attrs_json") {
|
|
16099
|
+
return `CASE json_type(${doc}, ${p}) WHEN 'integer' THEN CAST(json_extract(${doc}, ${p}) AS REAL) ELSE json_extract(${doc}, ${p}) END`;
|
|
16100
|
+
}
|
|
16101
|
+
function attrDocSql(key) {
|
|
16102
|
+
return isExposureAttr(key) ? exposureDocSql(key) : "attrs_json";
|
|
16067
16103
|
}
|
|
16068
16104
|
function nodeSelectFrom(q) {
|
|
16069
16105
|
if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
|
|
@@ -16076,7 +16112,7 @@ function nodeSelectFrom(q) {
|
|
|
16076
16112
|
function compileNodeSelect(q) {
|
|
16077
16113
|
const { where, params } = compileNodeWhere(q);
|
|
16078
16114
|
const attrs = q.attrs ?? [];
|
|
16079
|
-
const attrCols = attrs.map((k, i) => `, ${attrValueSql(path(k))} AS a${i}, json_type(
|
|
16115
|
+
const attrCols = attrs.map((k, i) => `, ${attrValueSql(path(k), attrDocSql(k))} AS a${i}, json_type(${attrDocSql(k)}, ${path(k)}) AS t${i}`).join("");
|
|
16080
16116
|
let order;
|
|
16081
16117
|
if (q.orderBy && "column" in q.orderBy) {
|
|
16082
16118
|
order = `${column(q.orderBy.column)} ${q.orderBy.desc ? "DESC" : "ASC"}, rowid`;
|
|
@@ -16193,6 +16229,7 @@ function localEdgeViolation(fromLabel, type, toLabel) {
|
|
|
16193
16229
|
}
|
|
16194
16230
|
var SCHEMA_VERSION = 6;
|
|
16195
16231
|
var SESSION_REPAIR_KEY = "session-evidence-v1";
|
|
16232
|
+
var EXPOSURE_SIDE_KEY = "exposure-side-rows-1";
|
|
16196
16233
|
var SESSION_EVIDENCE_PREDICATE = SESSION_EVIDENCE_FIELDS.map((field) => `instr(attrs_json, '${JSON.stringify(field)}') > 0`).join(" OR ");
|
|
16197
16234
|
var SCHEMA_SQL = `
|
|
16198
16235
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
@@ -16256,8 +16293,13 @@ CREATE TABLE IF NOT EXISTS nodes (
|
|
|
16256
16293
|
);
|
|
16257
16294
|
|
|
16258
16295
|
CREATE INDEX IF NOT EXISTS nodes_label ON nodes(label);
|
|
16259
|
-
|
|
16260
|
-
|
|
16296
|
+
-- Dropped: no query plans through them (every prepared statement across the
|
|
16297
|
+
-- local-graph, indexer, context-writer, generalizer and daemon suites was
|
|
16298
|
+
-- planned with and without them, 2026-10-21), and each write that touches
|
|
16299
|
+
-- memory_tier / page_rank (every node upsert, every rescore) paid to maintain
|
|
16300
|
+
-- them. nodes_live_page_rank serves the live page-rank orderings.
|
|
16301
|
+
DROP INDEX IF EXISTS nodes_memory_tier;
|
|
16302
|
+
DROP INDEX IF EXISTS nodes_page_rank;
|
|
16261
16303
|
|
|
16262
16304
|
CREATE TABLE IF NOT EXISTS edges (
|
|
16263
16305
|
id TEXT PRIMARY KEY,
|
|
@@ -16279,7 +16321,10 @@ CREATE TABLE IF NOT EXISTS edges (
|
|
|
16279
16321
|
FOREIGN KEY (to_id) REFERENCES nodes(id) ON DELETE CASCADE
|
|
16280
16322
|
);
|
|
16281
16323
|
|
|
16282
|
-
|
|
16324
|
+
-- Dropped: a prefix of edges_triple (from_id, type, to_id), which every
|
|
16325
|
+
-- from-side lookup and foreign-key check already plans through; it cost 47 MB
|
|
16326
|
+
-- and an index write on every edge write.
|
|
16327
|
+
DROP INDEX IF EXISTS edges_from;
|
|
16283
16328
|
CREATE INDEX IF NOT EXISTS edges_to ON edges(to_id, type);
|
|
16284
16329
|
CREATE INDEX IF NOT EXISTS edges_type ON edges(type);
|
|
16285
16330
|
CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
|
|
@@ -16301,6 +16346,12 @@ CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to,
|
|
|
16301
16346
|
-- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
|
|
16302
16347
|
-- Problems; without it every live Problem was read to find them (56 MB).
|
|
16303
16348
|
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;
|
|
16349
|
+
-- Render impressions, one row per node id (exposure-attrs.ts): a render bumps
|
|
16350
|
+
-- ~40 of these, which in the node row rewrote each whole row and its indexes.
|
|
16351
|
+
CREATE TABLE IF NOT EXISTS node_exposure (
|
|
16352
|
+
id TEXT PRIMARY KEY,
|
|
16353
|
+
attrs_json TEXT NOT NULL DEFAULT '{}'
|
|
16354
|
+
) WITHOUT ROWID;
|
|
16304
16355
|
-- The semantic-maintenance sweeps' own filters (closed, asked, hinted): each read
|
|
16305
16356
|
-- every Problem row (~30 MB on a 1.1 GB store) for the few hundred that match,
|
|
16306
16357
|
-- several times a run, and a run follows most writes (2026-10-20).
|
|
@@ -16308,6 +16359,9 @@ CREATE INDEX IF NOT EXISTS nodes_live_resolved ON nodes(label, valid_to) WHERE v
|
|
|
16308
16359
|
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;
|
|
16309
16360
|
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;
|
|
16310
16361
|
CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
|
|
16362
|
+
-- Rows carrying a calibration counter, any version: the logical nodes wm
|
|
16363
|
+
-- calibration can count (~3k), found without reading every citable row.
|
|
16364
|
+
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};
|
|
16311
16365
|
CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
|
|
16312
16366
|
-- Covering + partial index for labelFingerprint over the instance labels: the
|
|
16313
16367
|
-- live-push probe runs it per workspace on a timer, and each hit used to read
|
|
@@ -16461,6 +16515,7 @@ var SqliteGraphStore = class {
|
|
|
16461
16515
|
constructor(opts) {
|
|
16462
16516
|
this.db = openDatabase(opts.path);
|
|
16463
16517
|
this.db.exec("PRAGMA cache_size = -32768");
|
|
16518
|
+
this.db.exec("PRAGMA mmap_size = 2147483648");
|
|
16464
16519
|
this.db.exec(SCHEMA_SQL);
|
|
16465
16520
|
const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
|
|
16466
16521
|
if (!versionRow) {
|
|
@@ -16524,8 +16579,17 @@ var SqliteGraphStore = class {
|
|
|
16524
16579
|
last_reinforced_at_seq = excluded.last_reinforced_at_seq,
|
|
16525
16580
|
attrs_json = excluded.attrs_json
|
|
16526
16581
|
`),
|
|
16527
|
-
getNode: this.db.prepare("SELECT
|
|
16582
|
+
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 = ?"),
|
|
16583
|
+
exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
|
|
16584
|
+
writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
|
|
16585
|
+
lastUpdatedOf: this.db.prepare("SELECT last_updated_at AS t, attrs_json FROM nodes WHERE id = ?"),
|
|
16528
16586
|
labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
|
|
16587
|
+
nodeContent: this.db.prepare(
|
|
16588
|
+
"SELECT label, description, extraction_confidence, extraction_source, stability, valid_to, attrs_json FROM nodes WHERE id = ?"
|
|
16589
|
+
),
|
|
16590
|
+
edgeContent: this.db.prepare(
|
|
16591
|
+
"SELECT confidence, valid_to, attrs_json FROM edges WHERE from_id = ? AND type = ? AND to_id = ?"
|
|
16592
|
+
),
|
|
16529
16593
|
// Single-column fast paths for the nightly pass — writing 100k pageRank
|
|
16530
16594
|
// values through the 18-column COALESCE updateNode took ~30s; these are ~10x
|
|
16531
16595
|
// cheaper (one bound column, no COALESCE evaluation per row).
|
|
@@ -16682,6 +16746,7 @@ var SqliteGraphStore = class {
|
|
|
16682
16746
|
"INSERT INTO store_meta (key, value) VALUES ('ingest_seq', :v) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
16683
16747
|
)
|
|
16684
16748
|
};
|
|
16749
|
+
this.moveExposureToSideRows();
|
|
16685
16750
|
const seqRow = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
|
|
16686
16751
|
this.ingestSeq = seqRow ? Number(seqRow.value) : 0;
|
|
16687
16752
|
}
|
|
@@ -16737,6 +16802,41 @@ var SqliteGraphStore = class {
|
|
|
16737
16802
|
ORDER BY label, created_at, rowid`
|
|
16738
16803
|
).all(label);
|
|
16739
16804
|
}
|
|
16805
|
+
wmCalibrationRows(labels) {
|
|
16806
|
+
const counted = this.db.prepare(
|
|
16807
|
+
`SELECT rowid AS rid, logical_id, version, valid_to, label, ${WM_CALIBRATION_KEYS.map((k, i) => `${wmCounterSql(k)} AS c${i}`).join(", ")}
|
|
16808
|
+
FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`
|
|
16809
|
+
).all();
|
|
16810
|
+
const exposed = this.db.prepare(
|
|
16811
|
+
`SELECT n.rowid AS rid, n.logical_id, n.version, n.valid_to, n.label, x.attrs_json AS exposure
|
|
16812
|
+
FROM node_exposure x JOIN nodes n ON n.id = x.id`
|
|
16813
|
+
).all();
|
|
16814
|
+
const others = this.db.prepare(
|
|
16815
|
+
`SELECT rowid AS rid, logical_id, version, valid_to, label FROM nodes WHERE rowid IN (
|
|
16816
|
+
SELECT n.rowid FROM nodes AS n INDEXED BY nodes_logical
|
|
16817
|
+
WHERE n.logical_id IN (SELECT logical_id FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
|
|
16818
|
+
UNION SELECT n2.logical_id FROM node_exposure x2 JOIN nodes n2 ON n2.id = x2.id)
|
|
16819
|
+
EXCEPT SELECT rowid FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
|
|
16820
|
+
EXCEPT SELECT n3.rowid FROM node_exposure x3 JOIN nodes n3 ON n3.id = x3.id)`
|
|
16821
|
+
).all();
|
|
16822
|
+
const byRid = /* @__PURE__ */ new Map();
|
|
16823
|
+
for (const r of [...counted, ...others]) {
|
|
16824
|
+
const attrs = {};
|
|
16825
|
+
WM_CALIBRATION_KEYS.forEach((k, i) => {
|
|
16826
|
+
const v = r[`c${i}`];
|
|
16827
|
+
if (typeof v === "number") attrs[k] = v;
|
|
16828
|
+
});
|
|
16829
|
+
byRid.set(r.rid, { row: r, attrs });
|
|
16830
|
+
}
|
|
16831
|
+
for (const r of exposed) {
|
|
16832
|
+
const side = JSON.parse(r.exposure);
|
|
16833
|
+
const entry = byRid.get(r.rid) ?? { row: r, attrs: {} };
|
|
16834
|
+
for (const k of WM_CALIBRATION_KEYS) if (typeof side[k] === "number" && Number.isFinite(side[k])) entry.attrs[k] = side[k];
|
|
16835
|
+
byRid.set(r.rid, entry);
|
|
16836
|
+
}
|
|
16837
|
+
const order = new Map(labels.map((l, i) => [l, i]));
|
|
16838
|
+
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 }));
|
|
16839
|
+
}
|
|
16740
16840
|
liveCommentsAttachedTo(name) {
|
|
16741
16841
|
return this.db.prepare(
|
|
16742
16842
|
`SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
|
|
@@ -16873,9 +16973,22 @@ var SqliteGraphStore = class {
|
|
|
16873
16973
|
logical_id: node.logicalId ?? node.id,
|
|
16874
16974
|
created_at_seq: node.createdAtSeq ?? null,
|
|
16875
16975
|
last_reinforced_at_seq: node.lastReinforcedAtSeq ?? null,
|
|
16876
|
-
attrs_json:
|
|
16976
|
+
attrs_json: this.nodeAttrsJson(node.id, node.attrs ?? {})
|
|
16877
16977
|
});
|
|
16878
16978
|
}
|
|
16979
|
+
/** mergeNode, unless the live row already holds this content. Content is what
|
|
16980
|
+
* the columns a merge rewrites from the producer (label, description, attrs,
|
|
16981
|
+
* confidence, stability);
|
|
16982
|
+
* rank, community, landmark, surprise, hits, tier and the vector belong to
|
|
16983
|
+
* the passes that compute them, and a skipped write leaves them as they are
|
|
16984
|
+
* where a merge would have reset them to the producer's defaults. Returns
|
|
16985
|
+
* whether it wrote. */
|
|
16986
|
+
mergeNodeIfChanged(node) {
|
|
16987
|
+
const row = this.stmts.nodeContent.get(node.id);
|
|
16988
|
+
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;
|
|
16989
|
+
this.mergeNode(node);
|
|
16990
|
+
return true;
|
|
16991
|
+
}
|
|
16879
16992
|
updateNode(id, patch) {
|
|
16880
16993
|
const hasEmbedding = patch.embedding !== void 0 && patch.embedding.length > 0;
|
|
16881
16994
|
this.stmts.updateNode.run({
|
|
@@ -16899,7 +17012,37 @@ var SqliteGraphStore = class {
|
|
|
16899
17012
|
logical_id: patch.logicalId ?? null,
|
|
16900
17013
|
created_at_seq: patch.createdAtSeq ?? null,
|
|
16901
17014
|
last_reinforced_at_seq: patch.lastReinforcedAtSeq ?? null,
|
|
16902
|
-
attrs_json: patch.attrs ?
|
|
17015
|
+
attrs_json: patch.attrs ? this.nodeAttrsJson(id, patch.attrs) : null
|
|
17016
|
+
});
|
|
17017
|
+
}
|
|
17018
|
+
/**
|
|
17019
|
+
* The node-row attrs of a write, after handing any exposure keys to the side
|
|
17020
|
+
* row (exposure-attrs.ts). `getNode` returns exposure merged into attrs, so a
|
|
17021
|
+
* read-modify-write carries it; kept in the row it would re-bloat the row and
|
|
17022
|
+
* drift from the side row. Unchanged exposure costs no write.
|
|
17023
|
+
*/
|
|
17024
|
+
nodeAttrsJson(id, attrs) {
|
|
17025
|
+
if (!EXPOSURE_ATTRS.some((k) => attrs[k] !== void 0)) return JSON.stringify(attrs);
|
|
17026
|
+
const current = this.stmts.exposureOf.get(id);
|
|
17027
|
+
const next = JSON.stringify({ ...current ? JSON.parse(current.attrs_json) : {}, ...pickExposure(attrs) });
|
|
17028
|
+
if (current?.attrs_json !== next) this.stmts.writeExposure.run(id, next);
|
|
17029
|
+
const rest = { ...attrs };
|
|
17030
|
+
for (const k of EXPOSURE_ATTRS) delete rest[k];
|
|
17031
|
+
return JSON.stringify(rest);
|
|
17032
|
+
}
|
|
17033
|
+
/** One-time: exposure keys written to node rows before the side table existed
|
|
17034
|
+
* move to it. Only rows carrying a counter can carry exposure, and those are
|
|
17035
|
+
* what nodes_wm_counted holds, so this reads the index, not the table. */
|
|
17036
|
+
moveExposureToSideRows() {
|
|
17037
|
+
if (this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(EXPOSURE_SIDE_KEY)) return;
|
|
17038
|
+
const rows = this.db.prepare(`SELECT id, attrs_json FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`).all();
|
|
17039
|
+
const move = this.db.prepare("UPDATE nodes SET attrs_json = ? WHERE id = ?");
|
|
17040
|
+
this.db.transaction(() => {
|
|
17041
|
+
for (const r of rows) {
|
|
17042
|
+
const stripped = this.nodeAttrsJson(r.id, JSON.parse(r.attrs_json));
|
|
17043
|
+
if (stripped !== r.attrs_json) move.run(stripped, r.id);
|
|
17044
|
+
}
|
|
17045
|
+
this.db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, 'done')").run(EXPOSURE_SIDE_KEY);
|
|
16903
17046
|
});
|
|
16904
17047
|
}
|
|
16905
17048
|
/** Fast single-column writes for the nightly pass (avoid the 18-column COALESCE
|
|
@@ -16981,6 +17124,21 @@ var SqliteGraphStore = class {
|
|
|
16981
17124
|
attrs_json: JSON.stringify(edge.attrs ?? {})
|
|
16982
17125
|
});
|
|
16983
17126
|
}
|
|
17127
|
+
/** mergeEdge, unless the live edge already holds this content: same attrs, and
|
|
17128
|
+
* a confidence the upsert's MAX would keep. Its navigation posteriors and
|
|
17129
|
+
* last-seen stamp stay as they are where a merge would have reset them. The
|
|
17130
|
+
* ontology gate runs first either way, so refusals are recorded as before.
|
|
17131
|
+
* Returns whether it wrote (or refused). */
|
|
17132
|
+
mergeEdgeIfChanged(edge) {
|
|
17133
|
+
if (!this.edgeRuleViolation(edge)) {
|
|
17134
|
+
const row = this.stmts.edgeContent.get(edge.from, edge.type, edge.to);
|
|
17135
|
+
if (row && row.valid_to === null && row.confidence >= edge.confidence && row.attrs_json === JSON.stringify(edge.attrs ?? {})) {
|
|
17136
|
+
return false;
|
|
17137
|
+
}
|
|
17138
|
+
}
|
|
17139
|
+
this.mergeEdge(edge);
|
|
17140
|
+
return true;
|
|
17141
|
+
}
|
|
16984
17142
|
/** EDGE_RULES check against the stored endpoints' labels, with the LOCAL
|
|
16985
17143
|
* overlay consulted first. Returns the reason string on a documented-
|
|
16986
17144
|
* forbidden combination, else null. Point lookups on the id PK — negligible
|
|
@@ -17047,7 +17205,27 @@ var SqliteGraphStore = class {
|
|
|
17047
17205
|
}
|
|
17048
17206
|
getNode(id) {
|
|
17049
17207
|
const row = this.stmts.getNode.get(id);
|
|
17050
|
-
|
|
17208
|
+
if (!row) return null;
|
|
17209
|
+
const node = rowToNode(row);
|
|
17210
|
+
if (row.exposure_json) Object.assign(node.attrs, JSON.parse(row.exposure_json));
|
|
17211
|
+
return node;
|
|
17212
|
+
}
|
|
17213
|
+
/** A node's render exposure: the side row's keys over the node row's own
|
|
17214
|
+
* (exposure-attrs.ts), plus the node's `lastUpdatedAt` (the "shown since
|
|
17215
|
+
* touch" counter is relative to it) — without reading the node's attrs. */
|
|
17216
|
+
exposureOf(id) {
|
|
17217
|
+
const row = this.stmts.lastUpdatedOf.get(id);
|
|
17218
|
+
if (!row) return null;
|
|
17219
|
+
const side = this.stmts.exposureOf.get(id);
|
|
17220
|
+
return {
|
|
17221
|
+
attrs: { ...pickExposure(JSON.parse(row.attrs_json)), ...side ? JSON.parse(side.attrs_json) : {} },
|
|
17222
|
+
lastUpdatedAt: row.t
|
|
17223
|
+
};
|
|
17224
|
+
}
|
|
17225
|
+
/** Replace a node's exposure (only EXPOSURE_ATTRS keys belong here). Touches
|
|
17226
|
+
* neither the node row nor its indexes, nor the change feed. */
|
|
17227
|
+
writeExposure(id, attrs) {
|
|
17228
|
+
this.stmts.writeExposure.run(id, JSON.stringify(attrs));
|
|
17051
17229
|
}
|
|
17052
17230
|
nodeLabels(ids) {
|
|
17053
17231
|
const width = NODE_LABELS_CHUNK;
|
|
@@ -17098,7 +17276,7 @@ var SqliteGraphStore = class {
|
|
|
17098
17276
|
findNodeStampsByLabel(label, attrKey) {
|
|
17099
17277
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attrKey)) throw new Error(`findNodeStampsByLabel: bad attr key ${attrKey}`);
|
|
17100
17278
|
const rows = this.db.prepare(
|
|
17101
|
-
`SELECT id, created_at, created_at_seq, json_type(
|
|
17279
|
+
`SELECT id, created_at, created_at_seq, json_type(${attrDocSql(attrKey)}, '$.${attrKey}') IS NOT NULL AS has,
|
|
17102
17280
|
COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
|
|
17103
17281
|
FROM nodes WHERE label = ? AND valid_to IS NULL`
|
|
17104
17282
|
).all(label);
|
|
@@ -17115,7 +17293,7 @@ var SqliteGraphStore = class {
|
|
|
17115
17293
|
const keys = [...new Set(attrKeys)];
|
|
17116
17294
|
const rows = this.db.prepare(
|
|
17117
17295
|
`SELECT id, created_at, created_at_seq,
|
|
17118
|
-
${keys.map((k, i) => `json_type(
|
|
17296
|
+
${keys.map((k, i) => `json_type(${attrDocSql(k)}, '$.${k}') IS NOT NULL AS h${i}`).join(", ")}${keys.length ? "," : ""}
|
|
17119
17297
|
COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
|
|
17120
17298
|
FROM nodes WHERE label = ? AND valid_to IS NULL`
|
|
17121
17299
|
).all(label);
|
|
@@ -17375,6 +17553,9 @@ var SqliteGraphStore = class {
|
|
|
17375
17553
|
changedNodeIds(sinceSeq, labels) {
|
|
17376
17554
|
return readChangedNodeIds(this.db, sinceSeq, labels);
|
|
17377
17555
|
}
|
|
17556
|
+
changedAmong(sinceSeq, ids) {
|
|
17557
|
+
return readChangedAmong(this.db, sinceSeq, ids);
|
|
17558
|
+
}
|
|
17378
17559
|
pruneChangeLog() {
|
|
17379
17560
|
return pruneChangeLog(this.db);
|
|
17380
17561
|
}
|