@monoes/monomindcli 2.16.12 → 2.16.13
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/.claude/agents/core/coder.md +1 -1
- package/.claude/agents/engineering/engineering-devops-automator.md +1 -1
- package/.claude/agents/github/repo-architect.md +1 -1
- package/.claude/agents/testing/production-validator.md +1 -1
- package/.claude/agents/testing/testing-tool-evaluator.md +1 -1
- package/.claude/commands/github/pr-manager.md +1 -1
- package/.claude/helpers/pick-rank.cjs +34 -4
- package/.claude/skills/monolean-audit/SKILL.md +1 -1
- package/dist/src/commands/cleanup-data.d.ts +30 -0
- package/dist/src/commands/cleanup-data.d.ts.map +1 -0
- package/dist/src/commands/cleanup-data.js +138 -0
- package/dist/src/commands/cleanup-data.js.map +1 -0
- package/dist/src/commands/cleanup-scratch.d.ts +43 -0
- package/dist/src/commands/cleanup-scratch.d.ts.map +1 -0
- package/dist/src/commands/cleanup-scratch.js +125 -0
- package/dist/src/commands/cleanup-scratch.js.map +1 -0
- package/dist/src/commands/cleanup.d.ts +2 -39
- package/dist/src/commands/cleanup.d.ts.map +1 -1
- package/dist/src/commands/cleanup.js +5 -247
- package/dist/src/commands/cleanup.js.map +1 -1
- package/dist/src/commands/doctor-gitignore-checks.d.ts +10 -0
- package/dist/src/commands/doctor-gitignore-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-gitignore-checks.js +141 -0
- package/dist/src/commands/doctor-gitignore-checks.js.map +1 -0
- package/dist/src/commands/doctor-helpers-checks.d.ts +10 -0
- package/dist/src/commands/doctor-helpers-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-helpers-checks.js +292 -0
- package/dist/src/commands/doctor-helpers-checks.js.map +1 -0
- package/dist/src/commands/doctor-memory-checks.d.ts +47 -0
- package/dist/src/commands/doctor-memory-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-memory-checks.js +298 -0
- package/dist/src/commands/doctor-memory-checks.js.map +1 -0
- package/dist/src/commands/doctor-monograph-checks.d.ts +9 -0
- package/dist/src/commands/doctor-monograph-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-monograph-checks.js +331 -0
- package/dist/src/commands/doctor-monograph-checks.js.map +1 -0
- package/dist/src/commands/doctor-project-checks.d.ts +7 -66
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +10 -1331
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/commands/doctor-routing-checks.d.ts +10 -0
- package/dist/src/commands/doctor-routing-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-routing-checks.js +194 -0
- package/dist/src/commands/doctor-routing-checks.js.map +1 -0
- package/dist/src/commands/doctor-worker-checks.d.ts +17 -0
- package/dist/src/commands/doctor-worker-checks.d.ts.map +1 -0
- package/dist/src/commands/doctor-worker-checks.js +122 -0
- package/dist/src/commands/doctor-worker-checks.js.map +1 -0
- package/dist/src/commands/hooks-routing-commands.js +1 -1
- package/dist/src/commands/hooks-routing-commands.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +6 -15
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/init/never-commit.d.ts +3 -3
- package/dist/src/init/types.d.ts +1 -1
- package/dist/src/init/types.js +1 -1
- package/dist/src/knowledge/document-store.js +1 -1
- package/dist/src/knowledge/document-store.js.map +1 -1
- package/dist/src/mcp-tools/hooks-edit-command.d.ts +10 -0
- package/dist/src/mcp-tools/hooks-edit-command.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-edit-command.js +247 -0
- package/dist/src/mcp-tools/hooks-edit-command.js.map +1 -0
- package/dist/src/mcp-tools/hooks-learning.d.ts +9 -0
- package/dist/src/mcp-tools/hooks-learning.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-learning.js +408 -0
- package/dist/src/mcp-tools/hooks-learning.js.map +1 -0
- package/dist/src/mcp-tools/hooks-metrics-list.d.ts +8 -0
- package/dist/src/mcp-tools/hooks-metrics-list.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-metrics-list.js +205 -0
- package/dist/src/mcp-tools/hooks-metrics-list.js.map +1 -0
- package/dist/src/mcp-tools/hooks-route.d.ts +12 -0
- package/dist/src/mcp-tools/hooks-route.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-route.js +251 -0
- package/dist/src/mcp-tools/hooks-route.js.map +1 -0
- package/dist/src/mcp-tools/hooks-routing.d.ts +6 -27
- package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-routing.js +6 -1727
- package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
- package/dist/src/mcp-tools/hooks-session.d.ts +9 -0
- package/dist/src/mcp-tools/hooks-session.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-session.js +243 -0
- package/dist/src/mcp-tools/hooks-session.js.map +1 -0
- package/dist/src/mcp-tools/hooks-task.d.ts +17 -0
- package/dist/src/mcp-tools/hooks-task.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-task.js +427 -0
- package/dist/src/mcp-tools/hooks-task.js.map +1 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts +2 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +2 -1
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +1 -1
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/memory/bm25-index.d.ts +2 -2
- package/dist/src/memory/bm25-index.js +2 -2
- package/dist/src/memory/embedding-operations.js +2 -2
- package/dist/src/memory/embedding-operations.js.map +1 -1
- package/dist/src/memory/entity-name-key.d.ts +1 -1
- package/dist/src/memory/entity-name-key.js +1 -1
- package/dist/src/memory/hnsw-operations.d.ts +1 -1
- package/dist/src/memory/hnsw-operations.js +1 -1
- package/dist/src/memory/memory-bridge-backend.d.ts +43 -0
- package/dist/src/memory/memory-bridge-backend.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-backend.js +363 -0
- package/dist/src/memory/memory-bridge-backend.js.map +1 -0
- package/dist/src/memory/memory-bridge-core.d.ts +42 -0
- package/dist/src/memory/memory-bridge-core.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-core.js +126 -0
- package/dist/src/memory/memory-bridge-core.js.map +1 -0
- package/dist/src/memory/memory-bridge-entries.d.ts +131 -0
- package/dist/src/memory/memory-bridge-entries.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-entries.js +227 -0
- package/dist/src/memory/memory-bridge-entries.js.map +1 -0
- package/dist/src/memory/memory-bridge-feedback.d.ts +60 -0
- package/dist/src/memory/memory-bridge-feedback.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-feedback.js +118 -0
- package/dist/src/memory/memory-bridge-feedback.js.map +1 -0
- package/dist/src/memory/memory-bridge-paths.d.ts +123 -0
- package/dist/src/memory/memory-bridge-paths.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-paths.js +335 -0
- package/dist/src/memory/memory-bridge-paths.js.map +1 -0
- package/dist/src/memory/memory-bridge-search.d.ts +48 -0
- package/dist/src/memory/memory-bridge-search.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-search.js +383 -0
- package/dist/src/memory/memory-bridge-search.js.map +1 -0
- package/dist/src/memory/memory-bridge-sessions.d.ts +38 -0
- package/dist/src/memory/memory-bridge-sessions.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-sessions.js +109 -0
- package/dist/src/memory/memory-bridge-sessions.js.map +1 -0
- package/dist/src/memory/memory-bridge-store.d.ts +58 -0
- package/dist/src/memory/memory-bridge-store.d.ts.map +1 -0
- package/dist/src/memory/memory-bridge-store.js +201 -0
- package/dist/src/memory/memory-bridge-store.js.map +1 -0
- package/dist/src/memory/memory-bridge.d.ts +10 -438
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +14 -1787
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-kg-claims.d.ts +52 -0
- package/dist/src/memory/memory-kg-claims.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-claims.js +99 -0
- package/dist/src/memory/memory-kg-claims.js.map +1 -0
- package/dist/src/memory/memory-kg-index.d.ts +86 -0
- package/dist/src/memory/memory-kg-index.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-index.js +256 -0
- package/dist/src/memory/memory-kg-index.js.map +1 -0
- package/dist/src/memory/memory-kg-ingest.d.ts +44 -0
- package/dist/src/memory/memory-kg-ingest.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-ingest.js +361 -0
- package/dist/src/memory/memory-kg-ingest.js.map +1 -0
- package/dist/src/memory/memory-kg-model.d.ts +168 -0
- package/dist/src/memory/memory-kg-model.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-model.js +158 -0
- package/dist/src/memory/memory-kg-model.js.map +1 -0
- package/dist/src/memory/memory-kg-names.d.ts +64 -0
- package/dist/src/memory/memory-kg-names.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-names.js +187 -0
- package/dist/src/memory/memory-kg-names.js.map +1 -0
- package/dist/src/memory/memory-kg-promote.d.ts +79 -0
- package/dist/src/memory/memory-kg-promote.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-promote.js +248 -0
- package/dist/src/memory/memory-kg-promote.js.map +1 -0
- package/dist/src/memory/memory-kg-rebuild.d.ts +77 -0
- package/dist/src/memory/memory-kg-rebuild.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rebuild.js +299 -0
- package/dist/src/memory/memory-kg-rebuild.js.map +1 -0
- package/dist/src/memory/memory-kg-rollback.d.ts +47 -0
- package/dist/src/memory/memory-kg-rollback.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rollback.js +193 -0
- package/dist/src/memory/memory-kg-rollback.js.map +1 -0
- package/dist/src/memory/memory-kg-rules.d.ts +64 -0
- package/dist/src/memory/memory-kg-rules.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-rules.js +251 -0
- package/dist/src/memory/memory-kg-rules.js.map +1 -0
- package/dist/src/memory/memory-kg-scan.d.ts +118 -0
- package/dist/src/memory/memory-kg-scan.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-scan.js +209 -0
- package/dist/src/memory/memory-kg-scan.js.map +1 -0
- package/dist/src/memory/memory-kg-search.d.ts +80 -0
- package/dist/src/memory/memory-kg-search.d.ts.map +1 -0
- package/dist/src/memory/memory-kg-search.js +239 -0
- package/dist/src/memory/memory-kg-search.js.map +1 -0
- package/dist/src/memory/memory-kg.d.ts +19 -547
- package/dist/src/memory/memory-kg.d.ts.map +1 -1
- package/dist/src/memory/memory-kg.js +10 -2389
- package/dist/src/memory/memory-kg.js.map +1 -1
- package/dist/src/ui/org-hil.mjs +1 -1
- package/dist/src/ui/routes-org-agents.mjs +318 -0
- package/dist/src/ui/routes-org-approvals.mjs +133 -0
- package/dist/src/ui/routes-org-config.mjs +459 -0
- package/dist/src/ui/routes-org-control.mjs +300 -0
- package/dist/src/ui/routes-org-files.mjs +172 -0
- package/dist/src/ui/routes-org-helpers.mjs +84 -0
- package/dist/src/ui/routes-org-knowledge.mjs +262 -0
- package/dist/src/ui/routes-org-lifecycle.mjs +224 -0
- package/dist/src/ui/routes-org-live.mjs +278 -0
- package/dist/src/ui/routes-org-mastermind.mjs +346 -0
- package/dist/src/ui/routes-org-planning.mjs +398 -0
- package/dist/src/ui/routes-org-runs.mjs +430 -0
- package/dist/src/ui/routes-org-status.mjs +342 -0
- package/dist/src/ui/routes-org-workflows.mjs +212 -0
- package/dist/src/ui/routes-org.mjs +26 -3847
- package/dist/src/ui/server.mjs +1 -1
- package/dist/src/utils/native-error.d.ts +1 -1
- package/dist/src/utils/native-error.js +1 -1
- package/dist/src/utils/paths.d.ts +2 -1
- package/dist/src/utils/paths.d.ts.map +1 -1
- package/dist/src/utils/paths.js +2 -1
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/org-skills/adversarial-reviewer/SKILL.md +1 -1
- package/org-skills/board-deck-builder/SKILL.md +1 -1
- package/org-skills/code-reviewer/SKILL.md +1 -1
- package/org-skills/debugging-wizard/SKILL.md +1 -1
- package/org-skills/scientific-thinking-literature-review/SKILL.md +1 -1
- package/org-skills/scientific-thinking-scholar-evaluation/SKILL.md +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Knowledge Graph — graph maintenance reads and moves: kgPromote
|
|
3
|
+
* (org-owned → project-shared, explicit), consolidation candidates, and
|
|
4
|
+
* kgStats. Split out of memory-kg.ts, which re-exports the public symbols.
|
|
5
|
+
*/
|
|
6
|
+
import { bridgeCountEntries } from './memory-bridge.js';
|
|
7
|
+
import { kgIngest } from './memory-kg-ingest.js';
|
|
8
|
+
import { kgNamespaces, kgQualifyOrigin, MAX_DESC_LEN, MAX_EDGES_PER_CALL, MAX_NODES_PER_CALL, MAX_RULES_PER_CALL, } from './memory-kg-model.js';
|
|
9
|
+
import { kgIngestRules } from './memory-kg-rules.js';
|
|
10
|
+
import { collectByOrigin, FailureLog, scanNamespace } from './memory-kg-scan.js';
|
|
11
|
+
/** Copy one origin's claims out of an org's scope into project-shared
|
|
12
|
+
* knowledge.
|
|
13
|
+
*
|
|
14
|
+
* Sharing is deliberate, never a side effect of learning: `kgIngest` under a
|
|
15
|
+
* scope only ever writes that org's namespaces, and this is the one path a
|
|
16
|
+
* claim takes across the boundary. The org keeps its own copy untouched — the
|
|
17
|
+
* shared copy is an INDEPENDENT assertion under `promoted:<org-ref>`, so
|
|
18
|
+
* rolling back either side leaves the other standing, and a shared claim
|
|
19
|
+
* always names the org that vouched for it.
|
|
20
|
+
*
|
|
21
|
+
* Like ingest, NOT atomic: the counters report what actually landed. */
|
|
22
|
+
export async function kgPromote(options) {
|
|
23
|
+
const empty = { nodes: 0, edges: 0, rules: 0 };
|
|
24
|
+
if (!options.from?.org?.trim())
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
...empty,
|
|
28
|
+
promotedAs: '',
|
|
29
|
+
error: 'promotion needs an owning org to promote from',
|
|
30
|
+
};
|
|
31
|
+
const ns = kgNamespaces(options.from);
|
|
32
|
+
const sourceRef = kgQualifyOrigin(options.originRef, options.from);
|
|
33
|
+
const promotedAs = `promoted:${sourceRef}`;
|
|
34
|
+
const failures = new FailureLog();
|
|
35
|
+
try {
|
|
36
|
+
const [nodeHits, edgeHits, ruleHits] = await Promise.all([
|
|
37
|
+
collectByOrigin(ns.nodes, sourceRef, options.dbPath),
|
|
38
|
+
collectByOrigin(ns.edges, sourceRef, options.dbPath),
|
|
39
|
+
collectByOrigin(ns.rules, sourceRef, options.dbPath),
|
|
40
|
+
]);
|
|
41
|
+
// A partial read would promote a partial claim set while reporting the
|
|
42
|
+
// whole origin as shared.
|
|
43
|
+
for (const [name, hit] of [
|
|
44
|
+
['nodes', nodeHits],
|
|
45
|
+
['edges', edgeHits],
|
|
46
|
+
['rules', ruleHits],
|
|
47
|
+
])
|
|
48
|
+
if (!hit.covered)
|
|
49
|
+
failures.note(`${name}: memory backend unavailable`);
|
|
50
|
+
if (failures.failed)
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
...empty,
|
|
54
|
+
promotedAs,
|
|
55
|
+
failures: failures.messages,
|
|
56
|
+
error: failures.summary(),
|
|
57
|
+
};
|
|
58
|
+
// Rule NODES are re-created by kgIngestRules; promoting them again through
|
|
59
|
+
// kgIngest would double-count and strip their rules-namespace entry.
|
|
60
|
+
const nodes = nodeHits.entries
|
|
61
|
+
.filter((e) => {
|
|
62
|
+
const md = (e.metadata ?? {});
|
|
63
|
+
return md.node_set !== 'rules' && String(md.type ?? '').toLowerCase() !== 'rule';
|
|
64
|
+
})
|
|
65
|
+
.map((e) => {
|
|
66
|
+
const md = (e.metadata ?? {});
|
|
67
|
+
return {
|
|
68
|
+
name: String(md.name ?? e.key),
|
|
69
|
+
type: String(md.type ?? 'entity'),
|
|
70
|
+
description: String(md.description ?? ''),
|
|
71
|
+
nodeSet: typeof md.node_set === 'string' ? md.node_set : undefined,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
const edges = edgeHits.entries.map((e) => {
|
|
75
|
+
const md = (e.metadata ?? {});
|
|
76
|
+
return {
|
|
77
|
+
source: String(md.source_name ?? md.src ?? ''),
|
|
78
|
+
target: String(md.target_name ?? md.dst ?? ''),
|
|
79
|
+
relation: String(md.relation ?? 'related_to'),
|
|
80
|
+
description: String(md.description ?? ''),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
const rules = ruleHits.entries.map((e) => {
|
|
84
|
+
const md = (e.metadata ?? {});
|
|
85
|
+
return { rule: String(md.rule ?? e.content.split('\n')[0]) };
|
|
86
|
+
});
|
|
87
|
+
let promotedNodes = 0, promotedEdges = 0, promotedRules = 0;
|
|
88
|
+
/** A batch that REJECTED part of what it was handed did not promote the
|
|
89
|
+
* whole origin, so it cannot be reported as a clean share. Rejections are
|
|
90
|
+
* not in `failures` — they are the caller's payload being refused, not the
|
|
91
|
+
* bridge failing — so they have to be carried across explicitly. */
|
|
92
|
+
const carry = (res, what) => {
|
|
93
|
+
if (res.failures?.length)
|
|
94
|
+
for (const m of res.failures)
|
|
95
|
+
failures.note(m);
|
|
96
|
+
const rejected = (res.nodesRejected ?? 0) + (res.edgesRejected ?? 0);
|
|
97
|
+
if (rejected)
|
|
98
|
+
failures.note(`${what}: ${rejected} item(s) rejected — ${res.rejections?.[0]}`);
|
|
99
|
+
};
|
|
100
|
+
// kgIngest caps a call at 500 nodes / 1000 edges, so a large origin has to
|
|
101
|
+
// be promoted in batches rather than silently truncated. Nodes go first so
|
|
102
|
+
// every edge endpoint already exists when the edges land.
|
|
103
|
+
for (let i = 0; i < nodes.length; i += MAX_NODES_PER_CALL) {
|
|
104
|
+
const res = await kgIngest({
|
|
105
|
+
nodes: nodes.slice(i, i + MAX_NODES_PER_CALL),
|
|
106
|
+
originRef: promotedAs,
|
|
107
|
+
dbPath: options.dbPath,
|
|
108
|
+
});
|
|
109
|
+
promotedNodes += res.nodesAdded + res.nodesMerged;
|
|
110
|
+
carry(res, 'promoted nodes');
|
|
111
|
+
}
|
|
112
|
+
for (let i = 0; i < edges.length; i += MAX_EDGES_PER_CALL) {
|
|
113
|
+
const res = await kgIngest({
|
|
114
|
+
nodes: [],
|
|
115
|
+
edges: edges.slice(i, i + MAX_EDGES_PER_CALL),
|
|
116
|
+
originRef: promotedAs,
|
|
117
|
+
dbPath: options.dbPath,
|
|
118
|
+
});
|
|
119
|
+
promotedEdges += res.edgesAdded + res.edgesMerged;
|
|
120
|
+
carry(res, 'promoted edges');
|
|
121
|
+
}
|
|
122
|
+
for (let i = 0; i < rules.length; i += MAX_RULES_PER_CALL) {
|
|
123
|
+
const res = await kgIngestRules({
|
|
124
|
+
rules: rules.slice(i, i + MAX_RULES_PER_CALL),
|
|
125
|
+
originRef: promotedAs,
|
|
126
|
+
dbPath: options.dbPath,
|
|
127
|
+
});
|
|
128
|
+
promotedRules += res.accepted;
|
|
129
|
+
if (res.failures?.length)
|
|
130
|
+
for (const m of res.failures)
|
|
131
|
+
failures.note(m);
|
|
132
|
+
const invalid = res.verdicts.filter((v) => v.verdict === 'invalid').length;
|
|
133
|
+
if (invalid)
|
|
134
|
+
failures.note(`promoted rules: ${invalid} rejected as invalid`);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
success: !failures.failed,
|
|
138
|
+
nodes: promotedNodes,
|
|
139
|
+
edges: promotedEdges,
|
|
140
|
+
rules: promotedRules,
|
|
141
|
+
promotedAs,
|
|
142
|
+
...(failures.failed ? { failures: failures.messages, error: failures.summary() } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
...empty,
|
|
149
|
+
promotedAs,
|
|
150
|
+
...(failures.messages.length ? { failures: failures.messages } : {}),
|
|
151
|
+
error: err instanceof Error ? err.message : String(err),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Entities whose descriptions are stale relative to their connectivity —
|
|
156
|
+
* the LLM half runs in the LIVE agent: it rewrites each candidate's
|
|
157
|
+
* description from the neighborhood facts and resubmits via memory_kg_ingest.
|
|
158
|
+
* A resubmission is a NEW contribution and therefore the current one, so a
|
|
159
|
+
* shorter but better-supported summary now wins; under "longest description
|
|
160
|
+
* wins" a consolidation that tightened the prose was silently discarded.
|
|
161
|
+
* No LLM here (fully local constraint). */
|
|
162
|
+
export async function kgConsolidateCandidates(options) {
|
|
163
|
+
const minEdges = options?.minEdges ?? 3;
|
|
164
|
+
const limit = options?.limit ?? 10;
|
|
165
|
+
const ns = kgNamespaces(options?.scope);
|
|
166
|
+
// Degree index over the FULL edge namespace. Only the first 12 facts per node
|
|
167
|
+
// are kept (that is all the result exposes), so the index costs a bounded
|
|
168
|
+
// amount per node rather than one string per edge.
|
|
169
|
+
const degree = new Map();
|
|
170
|
+
await scanNamespace(ns.edges, options?.dbPath, (page) => {
|
|
171
|
+
for (const e of page) {
|
|
172
|
+
const md = e.metadata;
|
|
173
|
+
for (const end of [String(md.src ?? ''), String(md.dst ?? '')]) {
|
|
174
|
+
if (!end)
|
|
175
|
+
continue;
|
|
176
|
+
const slot = degree.get(end) ?? { count: 0, facts: [] };
|
|
177
|
+
slot.count++;
|
|
178
|
+
if (slot.facts.length < 12)
|
|
179
|
+
slot.facts.push(e.content);
|
|
180
|
+
degree.set(end, slot);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
let candidates = [];
|
|
185
|
+
await scanNamespace(ns.nodes, options?.dbPath, (page) => {
|
|
186
|
+
for (const n of page) {
|
|
187
|
+
const md = n.metadata;
|
|
188
|
+
const slot = degree.get(n.key);
|
|
189
|
+
if (!slot || slot.count < minEdges)
|
|
190
|
+
continue;
|
|
191
|
+
const description = String(md.description ?? '');
|
|
192
|
+
// Cap the growth target at MAX_DESC_LEN — a very-high-degree node whose
|
|
193
|
+
// description is already at the cap can never "grow out" of candidacy and
|
|
194
|
+
// would otherwise permanently occupy a slot.
|
|
195
|
+
if (description.length >= Math.min(40 * slot.count, MAX_DESC_LEN))
|
|
196
|
+
continue;
|
|
197
|
+
candidates.push({
|
|
198
|
+
name: String(md.name ?? n.key),
|
|
199
|
+
type: String(md.type ?? 'entity'),
|
|
200
|
+
description,
|
|
201
|
+
edgeCount: slot.count,
|
|
202
|
+
neighborhood: slot.facts,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// Ranking is per-node, so keeping only the running top-`limit` after each
|
|
206
|
+
// page gives the same answer as ranking every node at the end.
|
|
207
|
+
candidates.sort((a, b) => b.edgeCount - a.edgeCount);
|
|
208
|
+
candidates = candidates.slice(0, limit);
|
|
209
|
+
});
|
|
210
|
+
return candidates;
|
|
211
|
+
}
|
|
212
|
+
// ── Stats ───────────────────────────────────────────────────────────
|
|
213
|
+
/** Real counts, not page lengths. `bridgeListEntries.total` reports how many
|
|
214
|
+
* rows that one call returned, so the old capped list made a 10,001-node graph
|
|
215
|
+
* report exactly 10,000 forever.
|
|
216
|
+
*
|
|
217
|
+
* Tries a real indexed `SELECT COUNT(*) WHERE namespace = ?` first
|
|
218
|
+
* (`bridgeCountEntries`, K7) — cheap and exact, since every row in a KG
|
|
219
|
+
* namespace is exactly one node/edge/rule (the name index lives in its own
|
|
220
|
+
* namespace). Falls back to the paginated scan — one query per 1,000 rows,
|
|
221
|
+
* still exact — when the loaded bridge predates `bridgeCountEntries`, or a
|
|
222
|
+
* test double doesn't stub it. */
|
|
223
|
+
export async function kgStats(options) {
|
|
224
|
+
const ns = kgNamespaces(options?.scope);
|
|
225
|
+
const count = async (namespace) => {
|
|
226
|
+
try {
|
|
227
|
+
const real = await bridgeCountEntries(namespace, options?.dbPath);
|
|
228
|
+
if (real !== null)
|
|
229
|
+
return real;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
/* bridgeCountEntries unavailable on this loaded bridge (or a test
|
|
233
|
+
double that doesn't stub it) — fall back to the exhaustive scan. */
|
|
234
|
+
}
|
|
235
|
+
let n = 0;
|
|
236
|
+
await scanNamespace(namespace, options?.dbPath, (page) => {
|
|
237
|
+
n += page.length;
|
|
238
|
+
});
|
|
239
|
+
return n;
|
|
240
|
+
};
|
|
241
|
+
const [nodes, edges, rules] = await Promise.all([
|
|
242
|
+
count(ns.nodes),
|
|
243
|
+
count(ns.edges),
|
|
244
|
+
count(ns.rules),
|
|
245
|
+
]);
|
|
246
|
+
return { nodes, edges, rules };
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=memory-kg-promote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-kg-promote.js","sourceRoot":"","sources":["../../../src/memory/memory-kg-promote.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Knowledge Graph — K7 index rebuild/repair (kgRebuildIndex, with
|
|
3
|
+
* resume and validation against the reference reads) and the graph
|
|
4
|
+
* integrity check. Split out of memory-kg.ts, which re-exports the public
|
|
5
|
+
* symbols.
|
|
6
|
+
*/
|
|
7
|
+
import type { KgIndexStatus } from './memory-kg-index.js';
|
|
8
|
+
import type { KgScope } from './memory-kg-model.js';
|
|
9
|
+
export interface KgRebuildResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
status: KgIndexStatus;
|
|
12
|
+
validation?: {
|
|
13
|
+
sampledEntities: number;
|
|
14
|
+
sampledOrigins: number;
|
|
15
|
+
full: boolean;
|
|
16
|
+
};
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
/** (Re)build a scope's derived index from canonical data — nodes, then
|
|
20
|
+
* edges, then rules, that fixed order, resuming from the last checkpointed
|
|
21
|
+
* `{phase, offset}` rather than restarting when a prior call was
|
|
22
|
+
* interrupted mid-build. Every write here (`addToAdj`/`addToOriginIndex`)
|
|
23
|
+
* is idempotent, so a page reprocessed after an interruption cannot
|
|
24
|
+
* duplicate an entry.
|
|
25
|
+
*
|
|
26
|
+
* A concurrent ingest/rollback during the build is safe, not just tolerated:
|
|
27
|
+
* the dual-write hooks run whenever state is not `absent`/`failed`, so a
|
|
28
|
+
* write made mid-build is captured whether or not the scan has reached that
|
|
29
|
+
* row yet — at worst twice, which idempotency absorbs for free. The one
|
|
30
|
+
* residual race (a row deleted between the scan reading it and the scan's
|
|
31
|
+
* own write landing) can leave a dangling ref in the index; it is never
|
|
32
|
+
* observable as wrong data, because every indexed READ
|
|
33
|
+
* (`kgIndexedEdgesByEndpoint`/`kgIndexedByOrigin`) returns `null` — and the
|
|
34
|
+
* caller falls back to the exhaustive scan — the instant it cannot resolve
|
|
35
|
+
* a ref it holds. This is the backend's real capability (single-row CAS,
|
|
36
|
+
* no cross-row transaction), used honestly rather than claiming atomicity
|
|
37
|
+
* it cannot provide.
|
|
38
|
+
*
|
|
39
|
+
* Ends in `validating`: samples up to `VALIDATE_SAMPLE` of the entities and
|
|
40
|
+
* origins the scan actually saw, and re-reads them through the just-built
|
|
41
|
+
* index, comparing against a FRESH, independent reference read
|
|
42
|
+
* (`kgReferenceEdges`/`collectByOrigin`) — not the in-memory data the build
|
|
43
|
+
* itself computed, which would only prove the build agrees with itself. A
|
|
44
|
+
* write that silently failed, or a concurrent change the dual-write hooks
|
|
45
|
+
* missed, is exactly what this catches before the index is ever trusted. */
|
|
46
|
+
export declare function kgRebuildIndex(options?: {
|
|
47
|
+
scope?: KgScope;
|
|
48
|
+
dbPath?: string;
|
|
49
|
+
}): Promise<KgRebuildResult>;
|
|
50
|
+
export interface KgIntegrityResult {
|
|
51
|
+
success: boolean;
|
|
52
|
+
edges: number;
|
|
53
|
+
/** Edges naming an endpoint that does not exist. Retrieval is node-seeded,
|
|
54
|
+
* so such an edge is a fact unreachable through either of the things it is
|
|
55
|
+
* about — it can only be found by a check like this one. */
|
|
56
|
+
dangling: {
|
|
57
|
+
key: string;
|
|
58
|
+
missing: string[];
|
|
59
|
+
}[];
|
|
60
|
+
/** True when the edge scan did not cover the namespace: absence of a dangling
|
|
61
|
+
* edge here is then not evidence that there is none. */
|
|
62
|
+
truncated?: boolean;
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Verify that every edge's endpoints exist.
|
|
66
|
+
*
|
|
67
|
+
* `kgIngest` now creates missing endpoints before writing an edge and
|
|
68
|
+
* `kgRollback` removes edges whose endpoints it deleted, so a healthy graph
|
|
69
|
+
* reports nothing. This exists for graphs written before either rule, and as
|
|
70
|
+
* the check that says so rather than assuming it. */
|
|
71
|
+
export declare function kgIntegrityCheck(options?: {
|
|
72
|
+
dbPath?: string;
|
|
73
|
+
scope?: KgScope;
|
|
74
|
+
/** Dangling edges to report before stopping (default 100). */
|
|
75
|
+
limit?: number;
|
|
76
|
+
}): Promise<KgIntegrityResult>;
|
|
77
|
+
//# sourceMappingURL=memory-kg-rebuild.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-kg-rebuild.d.ts","sourceRoot":"","sources":["../../../src/memory/memory-kg-rebuild.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAU1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAoBpD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,CAAC,EAAE;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;6EA0B6E;AAC7E,wBAAsB,cAAc,CAAC,OAAO,CAAC,EAAE;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,eAAe,CAAC,CAqL3B;AAID,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd;;iEAE6D;IAC7D,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAC/C;6DACyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;sDAKsD;AACtD,wBAAsB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmD7B"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Knowledge Graph — K7 index rebuild/repair (kgRebuildIndex, with
|
|
3
|
+
* resume and validation against the reference reads) and the graph
|
|
4
|
+
* integrity check. Split out of memory-kg.ts, which re-exports the public
|
|
5
|
+
* symbols.
|
|
6
|
+
*/
|
|
7
|
+
import { bridgeGetEntry, bridgeListEntries } from './memory-bridge.js';
|
|
8
|
+
import { addToAdj, addToOriginIndex, KG_INDEX_SCHEMA_VERSION, kgIndexedByOrigin, kgIndexedEdgesByEndpoint, readIndexStatus, writeIndexStatus, } from './memory-kg-index.js';
|
|
9
|
+
import { kgNamespaces } from './memory-kg-model.js';
|
|
10
|
+
import { clearNamespace, collectByOrigin, kgReferenceEdges, originsOf, SCAN_PAGE, scanNamespace, } from './memory-kg-scan.js';
|
|
11
|
+
// ── Rebuild (K7): the one function that builds/repairs the derived index ──
|
|
12
|
+
/** Entities/origins sampled for post-build validation. A scan that saw fewer
|
|
13
|
+
* than this many distinct entities AND origins gets FULL validation, not a
|
|
14
|
+
* sample — most real scopes will. `KgRebuildResult.status.validation` says
|
|
15
|
+
* which happened, honestly, rather than letting "validated" imply "all". */
|
|
16
|
+
const VALIDATE_SAMPLE = 200;
|
|
17
|
+
function phaseOrder(phase) {
|
|
18
|
+
return phase === 'edges' ? 1 : phase === 'rules' ? 2 : 0;
|
|
19
|
+
}
|
|
20
|
+
function sameEdgeKeySet(a, b) {
|
|
21
|
+
const ak = new Set(a.map((e) => e.key));
|
|
22
|
+
const bk = new Set(b.map((e) => e.key));
|
|
23
|
+
if (ak.size !== bk.size)
|
|
24
|
+
return false;
|
|
25
|
+
for (const k of ak)
|
|
26
|
+
if (!bk.has(k))
|
|
27
|
+
return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
/** (Re)build a scope's derived index from canonical data — nodes, then
|
|
31
|
+
* edges, then rules, that fixed order, resuming from the last checkpointed
|
|
32
|
+
* `{phase, offset}` rather than restarting when a prior call was
|
|
33
|
+
* interrupted mid-build. Every write here (`addToAdj`/`addToOriginIndex`)
|
|
34
|
+
* is idempotent, so a page reprocessed after an interruption cannot
|
|
35
|
+
* duplicate an entry.
|
|
36
|
+
*
|
|
37
|
+
* A concurrent ingest/rollback during the build is safe, not just tolerated:
|
|
38
|
+
* the dual-write hooks run whenever state is not `absent`/`failed`, so a
|
|
39
|
+
* write made mid-build is captured whether or not the scan has reached that
|
|
40
|
+
* row yet — at worst twice, which idempotency absorbs for free. The one
|
|
41
|
+
* residual race (a row deleted between the scan reading it and the scan's
|
|
42
|
+
* own write landing) can leave a dangling ref in the index; it is never
|
|
43
|
+
* observable as wrong data, because every indexed READ
|
|
44
|
+
* (`kgIndexedEdgesByEndpoint`/`kgIndexedByOrigin`) returns `null` — and the
|
|
45
|
+
* caller falls back to the exhaustive scan — the instant it cannot resolve
|
|
46
|
+
* a ref it holds. This is the backend's real capability (single-row CAS,
|
|
47
|
+
* no cross-row transaction), used honestly rather than claiming atomicity
|
|
48
|
+
* it cannot provide.
|
|
49
|
+
*
|
|
50
|
+
* Ends in `validating`: samples up to `VALIDATE_SAMPLE` of the entities and
|
|
51
|
+
* origins the scan actually saw, and re-reads them through the just-built
|
|
52
|
+
* index, comparing against a FRESH, independent reference read
|
|
53
|
+
* (`kgReferenceEdges`/`collectByOrigin`) — not the in-memory data the build
|
|
54
|
+
* itself computed, which would only prove the build agrees with itself. A
|
|
55
|
+
* write that silently failed, or a concurrent change the dual-write hooks
|
|
56
|
+
* missed, is exactly what this catches before the index is ever trusted. */
|
|
57
|
+
export async function kgRebuildIndex(options) {
|
|
58
|
+
const ns = kgNamespaces(options?.scope);
|
|
59
|
+
const dbPath = options?.dbPath;
|
|
60
|
+
let status = await readIndexStatus(ns, dbPath);
|
|
61
|
+
// Resume from the checkpointed cursor for an interrupted build or a
|
|
62
|
+
// resumable (build-phase) failure; a fresh scan for everything else —
|
|
63
|
+
// `absent`, `ready` (this call IS the deliberate re-verify), and a
|
|
64
|
+
// validation-phase failure, where something already written was wrong.
|
|
65
|
+
const resume = status.state === 'building' || (status.state === 'failed' && status.resumable);
|
|
66
|
+
if (!resume) {
|
|
67
|
+
status = {
|
|
68
|
+
state: 'building',
|
|
69
|
+
schemaVersion: KG_INDEX_SCHEMA_VERSION,
|
|
70
|
+
cursor: { phase: 'nodes', offset: 0 },
|
|
71
|
+
counts: { nodes: 0, edges: 0, rules: 0 },
|
|
72
|
+
startedAt: Date.now(),
|
|
73
|
+
};
|
|
74
|
+
if (!(await writeIndexStatus(ns, status, dbPath))) {
|
|
75
|
+
return { success: false, status, error: 'could not persist initial build status' };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const seenEntities = [];
|
|
79
|
+
const seenOrigins = [];
|
|
80
|
+
const noteEntity = (id) => {
|
|
81
|
+
if (id && !seenEntities.includes(id) && seenEntities.length < VALIDATE_SAMPLE) {
|
|
82
|
+
seenEntities.push(id);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const noteOrigin = (ref) => {
|
|
86
|
+
if (!seenOrigins.includes(ref) && seenOrigins.length < VALIDATE_SAMPLE)
|
|
87
|
+
seenOrigins.push(ref);
|
|
88
|
+
};
|
|
89
|
+
// `addToAdj`/`addToOriginIndex` only APPEND onto whatever is already
|
|
90
|
+
// there. That's exactly right for a RESUME (everything present was
|
|
91
|
+
// written earlier in this same build attempt), but wrong for a FRESH
|
|
92
|
+
// build: a stale or corrupted entry left over from a PRIOR build (e.g.
|
|
93
|
+
// the exact thing a failed validation just caught) would never be
|
|
94
|
+
// cleared, only added to. So a fresh build clears both derived-index
|
|
95
|
+
// namespaces up front, once, before touching any phase — every write for
|
|
96
|
+
// the rest of THIS build attempt, in this call or a later one resuming
|
|
97
|
+
// it, can then safely append, because the namespace is known to hold only
|
|
98
|
+
// rows this attempt wrote.
|
|
99
|
+
if (!resume) {
|
|
100
|
+
const adjCleared = await clearNamespace(ns.adj, dbPath);
|
|
101
|
+
const originCleared = await clearNamespace(ns.originIdx, dbPath);
|
|
102
|
+
if (!adjCleared || !originCleared) {
|
|
103
|
+
status = { ...status, state: 'failed', error: 'could not clear prior index before rebuild' };
|
|
104
|
+
await writeIndexStatus(ns, status, dbPath);
|
|
105
|
+
return { success: false, status, error: status.error };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const phases = [
|
|
109
|
+
{ phase: 'nodes', namespace: ns.nodes },
|
|
110
|
+
{ phase: 'edges', namespace: ns.edges },
|
|
111
|
+
{ phase: 'rules', namespace: ns.rules },
|
|
112
|
+
];
|
|
113
|
+
try {
|
|
114
|
+
for (const { phase, namespace } of phases) {
|
|
115
|
+
if (phaseOrder(status.cursor?.phase) > phaseOrder(phase))
|
|
116
|
+
continue; // already scanned
|
|
117
|
+
let offset = status.cursor?.phase === phase ? (status.cursor.offset ?? 0) : 0;
|
|
118
|
+
let count = status.counts?.[phase] ?? 0;
|
|
119
|
+
for (;;) {
|
|
120
|
+
const page = await bridgeListEntries({ namespace, limit: SCAN_PAGE, offset, dbPath });
|
|
121
|
+
if (!page) {
|
|
122
|
+
// The cursor stays exactly where it was: everything indexed before
|
|
123
|
+
// this page is still correct, only incomplete, so a retry resumes.
|
|
124
|
+
status = {
|
|
125
|
+
...status,
|
|
126
|
+
state: 'failed',
|
|
127
|
+
resumable: true,
|
|
128
|
+
error: `${namespace}: backend unavailable during rebuild`,
|
|
129
|
+
};
|
|
130
|
+
await writeIndexStatus(ns, status, dbPath);
|
|
131
|
+
return { success: false, status, error: status.error };
|
|
132
|
+
}
|
|
133
|
+
for (const e of page.entries) {
|
|
134
|
+
const md = (e.metadata ?? {});
|
|
135
|
+
count++;
|
|
136
|
+
if (phase === 'edges' && md.kg === 'edge') {
|
|
137
|
+
const src = String(md.src ?? '');
|
|
138
|
+
const dst = String(md.dst ?? '');
|
|
139
|
+
if (src) {
|
|
140
|
+
await addToAdj(ns, src, e.key, dbPath);
|
|
141
|
+
noteEntity(src);
|
|
142
|
+
}
|
|
143
|
+
if (dst && dst !== src) {
|
|
144
|
+
await addToAdj(ns, dst, e.key, dbPath);
|
|
145
|
+
noteEntity(dst);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (phase === 'nodes')
|
|
149
|
+
noteEntity(e.key);
|
|
150
|
+
for (const originRef of originsOf(e)) {
|
|
151
|
+
await addToOriginIndex(ns, originRef, { ns: namespace, key: e.key }, dbPath);
|
|
152
|
+
noteOrigin(originRef);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
offset += page.entries.length;
|
|
156
|
+
status = {
|
|
157
|
+
...status,
|
|
158
|
+
cursor: { phase, offset },
|
|
159
|
+
counts: { ...status.counts, [phase]: count },
|
|
160
|
+
};
|
|
161
|
+
await writeIndexStatus(ns, status, dbPath);
|
|
162
|
+
if (page.entries.length < SCAN_PAGE)
|
|
163
|
+
break; // last page of this namespace
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
status = { ...status, state: 'validating' };
|
|
167
|
+
await writeIndexStatus(ns, status, dbPath);
|
|
168
|
+
const mismatches = [];
|
|
169
|
+
for (const id of seenEntities) {
|
|
170
|
+
const indexed = await kgIndexedEdgesByEndpoint(ns, id, dbPath);
|
|
171
|
+
const reference = await kgReferenceEdges({ endpointId: id, scope: options?.scope, dbPath });
|
|
172
|
+
if (indexed === null || !reference.success || reference.truncated) {
|
|
173
|
+
mismatches.push(`entity ${id}: reference read incomplete`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!sameEdgeKeySet(indexed, reference.edges))
|
|
177
|
+
mismatches.push(`entity ${id}: adjacency mismatch`);
|
|
178
|
+
}
|
|
179
|
+
for (const originRef of seenOrigins) {
|
|
180
|
+
const indexed = await kgIndexedByOrigin(ns, originRef, dbPath);
|
|
181
|
+
if (indexed === null) {
|
|
182
|
+
mismatches.push(`origin ${originRef}: index unreadable`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const refA = await collectByOrigin(ns.nodes, originRef, dbPath);
|
|
186
|
+
const refB = await collectByOrigin(ns.edges, originRef, dbPath);
|
|
187
|
+
const refC = await collectByOrigin(ns.rules, originRef, dbPath);
|
|
188
|
+
if (!refA.covered || !refB.covered || !refC.covered) {
|
|
189
|
+
mismatches.push(`origin ${originRef}: reference scan incomplete`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const expected = new Set([
|
|
193
|
+
...refA.entries.map((e) => `${ns.nodes}|${e.key}`),
|
|
194
|
+
...refB.entries.map((e) => `${ns.edges}|${e.key}`),
|
|
195
|
+
...refC.entries.map((e) => `${ns.rules}|${e.key}`),
|
|
196
|
+
]);
|
|
197
|
+
const indexedKeys = new Set(indexed.map((e) => `${e.ns}|${e.entry.key}`));
|
|
198
|
+
if (expected.size !== indexedKeys.size || [...expected].some((k) => !indexedKeys.has(k))) {
|
|
199
|
+
mismatches.push(`origin ${originRef}: support-index mismatch`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const full = seenEntities.length < VALIDATE_SAMPLE && seenOrigins.length < VALIDATE_SAMPLE;
|
|
203
|
+
const validation = {
|
|
204
|
+
sampledEntities: seenEntities.length,
|
|
205
|
+
sampledOrigins: seenOrigins.length,
|
|
206
|
+
full,
|
|
207
|
+
};
|
|
208
|
+
if (mismatches.length) {
|
|
209
|
+
const summary = mismatches.slice(0, 5).join('; ');
|
|
210
|
+
status = {
|
|
211
|
+
...status,
|
|
212
|
+
state: 'failed',
|
|
213
|
+
error: `validation failed: ${summary}${mismatches.length > 5 ? ` (+${mismatches.length - 5} more)` : ''}`,
|
|
214
|
+
};
|
|
215
|
+
await writeIndexStatus(ns, status, dbPath);
|
|
216
|
+
return { success: false, status, validation, error: status.error };
|
|
217
|
+
}
|
|
218
|
+
status = { ...status, state: 'ready', error: undefined };
|
|
219
|
+
await writeIndexStatus(ns, status, dbPath);
|
|
220
|
+
return { success: true, status, validation };
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
status = {
|
|
224
|
+
...status,
|
|
225
|
+
state: 'failed',
|
|
226
|
+
error: err instanceof Error ? err.message : String(err),
|
|
227
|
+
};
|
|
228
|
+
try {
|
|
229
|
+
await writeIndexStatus(ns, status, dbPath);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
/* best-effort — see markIndexFailed */
|
|
233
|
+
}
|
|
234
|
+
return { success: false, status, error: status.error };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Verify that every edge's endpoints exist.
|
|
238
|
+
*
|
|
239
|
+
* `kgIngest` now creates missing endpoints before writing an edge and
|
|
240
|
+
* `kgRollback` removes edges whose endpoints it deleted, so a healthy graph
|
|
241
|
+
* reports nothing. This exists for graphs written before either rule, and as
|
|
242
|
+
* the check that says so rather than assuming it. */
|
|
243
|
+
export async function kgIntegrityCheck(options) {
|
|
244
|
+
const ns = kgNamespaces(options?.scope);
|
|
245
|
+
const limit = options?.limit ?? 100;
|
|
246
|
+
const dangling = [];
|
|
247
|
+
let edges = 0;
|
|
248
|
+
try {
|
|
249
|
+
/** Endpoint id → exists. One keyed lookup per DISTINCT endpoint. */
|
|
250
|
+
const seen = new Map();
|
|
251
|
+
const exists = async (id) => {
|
|
252
|
+
const hit = seen.get(id);
|
|
253
|
+
if (hit !== undefined)
|
|
254
|
+
return hit;
|
|
255
|
+
const res = await bridgeGetEntry({ key: id, namespace: ns.nodes, dbPath: options?.dbPath });
|
|
256
|
+
const found = Boolean(res?.found && res.entry);
|
|
257
|
+
seen.set(id, found);
|
|
258
|
+
return found;
|
|
259
|
+
};
|
|
260
|
+
// Collect first, then probe: the probes are reads, but interleaving many
|
|
261
|
+
// per page would hold the page open far longer than the scan needs.
|
|
262
|
+
const rows = [];
|
|
263
|
+
const covered = await scanNamespace(ns.edges, options?.dbPath, (page) => {
|
|
264
|
+
for (const e of page) {
|
|
265
|
+
const md = (e.metadata ?? {});
|
|
266
|
+
if (md.kg !== 'edge')
|
|
267
|
+
continue;
|
|
268
|
+
edges++;
|
|
269
|
+
rows.push({ key: e.key, src: String(md.src ?? ''), dst: String(md.dst ?? '') });
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
for (const row of rows) {
|
|
273
|
+
if (dangling.length >= limit)
|
|
274
|
+
break;
|
|
275
|
+
const missing = [];
|
|
276
|
+
if (!row.src || !(await exists(row.src)))
|
|
277
|
+
missing.push(row.src || '<no src>');
|
|
278
|
+
if (!row.dst || !(await exists(row.dst)))
|
|
279
|
+
missing.push(row.dst || '<no dst>');
|
|
280
|
+
if (missing.length)
|
|
281
|
+
dangling.push({ key: row.key, missing });
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
success: true,
|
|
285
|
+
edges,
|
|
286
|
+
dangling,
|
|
287
|
+
...(covered && dangling.length < limit ? {} : { truncated: true }),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
return {
|
|
292
|
+
success: false,
|
|
293
|
+
edges,
|
|
294
|
+
dangling,
|
|
295
|
+
error: err instanceof Error ? err.message : String(err),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=memory-kg-rebuild.js.map
|